diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 61667546acf..a5bf154dab9 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1452,7 +1452,7 @@ PODS:
- RCT-Folly (= 2022.05.16.00)
- React-Core
- ReactCommon/turbomodule/core
- - RNScreens (3.34.0):
+ - RNScreens (3.35.0):
- glog
- RCT-Folly (= 2022.05.16.00)
- React-Core
@@ -2187,7 +2187,7 @@ SPEC CHECKSUMS:
RNPermissions: 23abdfa77d3cd3700b181a2d47f29939c6878ce6
RNReactNativeHapticFeedback: b83bfb4b537bdd78eb4f6ffe63c6884f7b049ead
RNReanimated: 8a4d86eb951a4a99d8e86266dc71d7735c0c30a9
- RNScreens: 29418ceffb585b8f0ebd363de304288c3dce8323
+ RNScreens: 69850d4519d95b9359cec15a67bb5f9d0bd75262
RNSentry: 3feba366b62cbf306d9f8be629beb516ed811f65
RNShare: 859ff710211285676b0bcedd156c12437ea1d564
RNSVG: ba3e7232f45e34b7b47e74472386cf4e1a676d0a
diff --git a/package.json b/package.json
index b8051ffe851..085f82736dd 100644
--- a/package.json
+++ b/package.json
@@ -183,7 +183,7 @@
"react-native-reanimated-zoom": "0.3.3",
"react-native-render-html": "6.3.4",
"react-native-safe-area-context": "3.4.0",
- "react-native-screens": "3.34.0",
+ "react-native-screens": "3.35.0",
"react-native-shake": "5.5.2",
"react-native-share": "10.0.2",
"react-native-svg": "14.1.0",
diff --git a/src/app/App.tsx b/src/app/App.tsx
index 5c1c89522c7..10e56314b43 100644
--- a/src/app/App.tsx
+++ b/src/app/App.tsx
@@ -1,5 +1,6 @@
import { GoogleSignin } from "@react-native-google-signin/google-signin"
import * as Sentry from "@sentry/react-native"
+import { Navigation } from "app/Navigation/Navigation"
import { GlobalStore, unsafe__getEnvironment, unsafe_getDevToggle } from "app/store/GlobalStore"
import { codePushOptions } from "app/system/codepush"
import { AsyncStorageDevtools } from "app/system/devTools/AsyncStorageDevTools"
@@ -10,6 +11,7 @@ import { setupSentry } from "app/system/errorReporting/setupSentry"
import { ModalStack } from "app/system/navigation/ModalStack"
import { usePurgeCacheOnAppUpdate } from "app/system/relay/usePurgeCacheOnAppUpdate"
import { useDevToggle } from "app/utils/hooks/useDevToggle"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { addTrackingProvider } from "app/utils/track"
import {
SEGMENT_TRACKING_PROVIDER,
@@ -93,6 +95,7 @@ const Main = () => {
)
const fpsCounter = useDevToggle("DTFPSCounter")
+ const useNewNavigation = useFeatureFlag("AREnableNewNavigation")
useStripeConfig()
useSiftConfig()
@@ -127,6 +130,9 @@ const Main = () => {
return
}
+ if (useNewNavigation) {
+ return
+ }
if (!isLoggedIn || onboardingState === "incomplete") {
return
}
@@ -139,17 +145,19 @@ const Main = () => {
)
}
-const InnerApp = () => (
-
-
+const InnerApp = () => {
+ return (
+
+
-
-
-
+
+
+
-
-
-)
+
+
+ )
+}
const SentryApp = !__DEV__ ? Sentry.wrap(InnerApp) : InnerApp
export const App = codePush(codePushOptions)(SentryApp)
diff --git a/src/app/AppRegistry.tsx b/src/app/AppRegistry.tsx
index 203afc1d453..3dc11691d69 100644
--- a/src/app/AppRegistry.tsx
+++ b/src/app/AppRegistry.tsx
@@ -1,3 +1,4 @@
+import { Flex } from "@artsy/palette-mobile"
import { NativeStackNavigationOptions } from "@react-navigation/native-stack"
import { BidFlow } from "app/Components/Containers/BidFlow"
import { InboxQueryRenderer, InboxScreenQuery } from "app/Components/Containers/Inbox"
@@ -39,6 +40,8 @@ import { SearchScreen, SearchScreenQuery } from "app/Scenes/Search/Search"
import { SubmitArtworkForm } from "app/Scenes/SellWithArtsy/ArtworkForm/SubmitArtworkForm"
import { SubmitArtworkFormEditContainer } from "app/Scenes/SellWithArtsy/ArtworkForm/SubmitArtworkFormEdit"
import { SimilarToRecentlyViewedScreen } from "app/Scenes/SimilarToRecentlyViewed/SimilarToRecentlyViewed"
+import { BackButton } from "app/system/navigation/BackButton"
+import { goBack } from "app/system/navigation/navigate"
import { ArtsyKeyboardAvoidingViewContext } from "app/utils/ArtsyKeyboardAvoidingView"
import { SafeAreaInsets, useScreenDimensions } from "app/utils/hooks"
import { useSelectedTab } from "app/utils/hooks/useSelectedTab"
@@ -143,7 +146,7 @@ import {
ViewingRoomsListScreen,
viewingRoomsListScreenQuery,
} from "./Scenes/ViewingRoom/ViewingRoomsList"
-import { GlobalStore } from "./store/GlobalStore"
+import { GlobalStore, unsafe_getFeatureFlag } from "./store/GlobalStore"
import { propsStore } from "./store/PropsStore"
import { DevMenu } from "./system/devTools/DevMenu/DevMenu"
import { Schema, screenTrack } from "./utils/track"
@@ -298,7 +301,7 @@ export interface ViewOptions {
screenOptions?: NativeStackNavigationOptions
}
-type ModuleDescriptor = {
+export type ModuleDescriptor = {
type: "react"
Component: React.ComponentType
Queries?: GraphQLTaggedNode[]
@@ -339,7 +342,11 @@ export const modules = defineModules({
hidesBackButton: true,
hidesBottomTabs: true,
}),
- About: reactModule(About),
+ About: reactModule(About, {
+ screenOptions: {
+ headerTitle: "About",
+ },
+ }),
AddMyCollectionArtist: reactModule(AddMyCollectionArtist, {
hidesBackButton: true,
}),
@@ -392,18 +399,24 @@ export const modules = defineModules({
),
ArtworkMedium: reactModule(ArtworkMediumQueryRenderer, {
fullBleed: true,
- alwaysPresentModally: true,
- modalPresentationStyle: "fullScreen",
+ alwaysPresentModally: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ modalPresentationStyle: !unsafe_getFeatureFlag("AREnableNewNavigation")
+ ? "fullScreen"
+ : undefined,
}),
ArtworkAttributionClassFAQ: reactModule(ArtworkAttributionClassFAQQueryRenderer, {
fullBleed: true,
- alwaysPresentModally: true,
- modalPresentationStyle: "fullScreen",
+ alwaysPresentModally: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ modalPresentationStyle: !unsafe_getFeatureFlag("AREnableNewNavigation")
+ ? "fullScreen"
+ : undefined,
}),
ArtworkCertificateAuthenticity: reactModule(CertificateOfAuthenticity, {
fullBleed: true,
- alwaysPresentModally: true,
- modalPresentationStyle: "fullScreen",
+ alwaysPresentModally: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ modalPresentationStyle: !unsafe_getFeatureFlag("AREnableNewNavigation")
+ ? "fullScreen"
+ : undefined,
}),
ArtworkList: reactModule(ArtworkListScreen, { hidesBackButton: true }),
ArtworkRecommendations: reactModule(ArtworkRecommendationsScreen),
@@ -417,7 +430,9 @@ export const modules = defineModules({
[SalesScreenQuery]
),
AuctionInfo: reactModule(SaleInfoQueryRenderer),
- AuctionResult: reactModule(AuctionResultQueryRenderer, { hidesBackButton: true }),
+ AuctionResult: reactModule(AuctionResultQueryRenderer, {
+ hidesBackButton: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ }),
AuctionResultsForArtistsYouFollow: reactModule(
AuctionResultsForArtistsYouFollowQueryRenderer,
{},
@@ -426,8 +441,8 @@ export const modules = defineModules({
AuctionResultsForArtistsYouCollect: reactModule(AuctionResultsForArtistsYouCollect),
AuctionRegistration: reactModule(RegistrationFlow, {
alwaysPresentModally: true,
+ fullBleed: Platform.OS === "ios" && !unsafe_getFeatureFlag("AREnableNewNavigation"),
hasOwnModalCloseButton: true,
- fullBleed: Platform.OS === "ios",
screenOptions: {
// Don't allow the screen to be swiped away by mistake
gestureEnabled: false,
@@ -436,12 +451,14 @@ export const modules = defineModules({
AuctionBidArtwork: reactModule(BidFlow, {
alwaysPresentModally: true,
hasOwnModalCloseButton: true,
- fullBleed: true,
+ fullBleed: !unsafe_getFeatureFlag("AREnableNewNavigation"),
}),
AuctionBuyersPremium: reactModule(AuctionBuyersPremiumQueryRenderer, {
fullBleed: true,
- alwaysPresentModally: true,
- modalPresentationStyle: "fullScreen",
+ alwaysPresentModally: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ modalPresentationStyle: !unsafe_getFeatureFlag("AREnableNewNavigation")
+ ? "fullScreen"
+ : undefined,
}),
BottomTabs: reactModule(BottomTabs, { fullBleed: true }),
BrowseSimilarWorks: reactModule(BrowseSimilarWorksQueryRenderer, {
@@ -449,9 +466,10 @@ export const modules = defineModules({
hidesBottomTabs: true,
}),
CareerHighlightsBigCardsSwiper: reactModule(CareerHighlightsBigCardsSwiper, {
- alwaysPresentModally: true,
+ alwaysPresentModally: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ fullBleed: !unsafe_getFeatureFlag("AREnableNewNavigation"),
hidesBackButton: true,
- fullBleed: true,
+ hidesBottomTabs: unsafe_getFeatureFlag("AREnableNewNavigation"),
}),
City: reactModule(CityView, { fullBleed: true, ignoreTabs: true }),
CityFairList: reactModule(CityFairListQueryRenderer, { fullBleed: true }),
@@ -464,15 +482,35 @@ export const modules = defineModules({
hidesBackButton: true,
}),
ConsignmentInquiry: reactModule(ConsignmentInquiryScreen, {
- hidesBottomTabs: true,
+ hidesBottomTabs: !unsafe_getFeatureFlag("AREnableNewNavigation"),
screenOptions: {
gestureEnabled: false,
},
}),
- Conversation: reactModule(Conversation, { onlyShowInTabName: "inbox" }),
- ConversationDetails: reactModule(ConversationDetailsQueryRenderer),
+ Conversation: reactModule(Conversation, {
+ onlyShowInTabName: "inbox",
+ hidesBackButton: true,
+ }),
+ ConversationDetails: reactModule(ConversationDetailsQueryRenderer, {
+ screenOptions: {
+ headerTitle: "Details",
+ },
+ }),
DarkModeSettings: reactModule(DarkModeSettings),
- DevMenu: reactModule(DevMenu, { hidesBottomTabs: true, hidesBackButton: true }),
+ DevMenu: reactModule(DevMenu, {
+ // No need to hide bottom tabs if it's a modal because they will be hidden by default
+ hidesBottomTabs: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ hidesBackButton: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ alwaysPresentModally: !!unsafe_getFeatureFlag("AREnableNewNavigation"),
+ fullBleed: !!unsafe_getFeatureFlag("AREnableNewNavigation"),
+ screenOptions: {
+ headerTitle: "Dev Settings",
+ headerLargeTitle: true,
+ headerLeft: () => {
+ return
+ },
+ },
+ }),
EditSavedSearchAlert: reactModule(EditSavedSearchAlertQueryRenderer, {
hidesBackButton: true,
hidesBottomTabs: true,
@@ -500,6 +538,7 @@ export const modules = defineModules({
HomeContainer,
{
isRootViewForTabName: "home",
+ hidesBackButton: true,
fullBleed: true,
},
[homeViewScreenQuery]
@@ -509,26 +548,76 @@ export const modules = defineModules({
hidesBackButton: true,
fullBleed: true,
}),
- Inbox: reactModule(InboxQueryRenderer, { isRootViewForTabName: "inbox" }, [InboxScreenQuery]),
+ Inbox: reactModule(
+ InboxQueryRenderer,
+ {
+ isRootViewForTabName: "inbox",
+ hidesBackButton: true,
+ fullBleed: true,
+ },
+ [InboxScreenQuery]
+ ),
Inquiry: reactModule(Inquiry, { alwaysPresentModally: true, hasOwnModalCloseButton: true }),
LiveAuction: reactModule(LiveAuctionView, {
alwaysPresentModally: true,
hasOwnModalCloseButton: true,
modalPresentationStyle: "fullScreen",
}),
- LocalDiscovery: reactModule(CityGuideView, { fullBleed: true }),
+ LocalDiscovery: reactModule(CityGuideView, {
+ fullBleed: true,
+ screenOptions: unsafe_getFeatureFlag("AREnableNewNavigation")
+ ? {
+ headerTransparent: true,
+ headerLeft: () => {
+ return (
+ {
+ goBack()
+ }}
+ />
+ )
+ },
+ }
+ : undefined,
+ }),
MakeOfferModal: reactModule(MakeOfferModalQueryRenderer, {
hasOwnModalCloseButton: true,
}),
MedianSalePriceAtAuction: reactModule(MedianSalePriceAtAuction),
Map: reactModule(MapContainer, { fullBleed: true, ignoreTabs: true }),
- MyAccount: reactModule(MyAccountQueryRenderer),
- MyAccountEditEmail: reactModule(MyAccountEditEmailQueryRenderer, { hidesBackButton: true }),
+ MyAccount: reactModule(MyAccountQueryRenderer, {
+ screenOptions: {
+ headerTitle: "Account Settings",
+ },
+ }),
+ MyAccountEditEmail: reactModule(MyAccountEditEmailQueryRenderer, {
+ hidesBackButton: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ screenOptions: {
+ headerTitle: "Email",
+ },
+ }),
MyAccountEditPriceRange: reactModule(MyAccountEditPriceRangeQueryRenderer, {
- hidesBackButton: true,
+ hidesBackButton: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ screenOptions: {
+ headerTitle: "Price Range",
+ },
+ }),
+ MyAccountEditPassword: reactModule(MyAccountEditPassword, {
+ hidesBackButton: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ screenOptions: {
+ headerTitle: "Password",
+ },
+ }),
+ MyAccountEditPhone: reactModule(MyAccountEditPhoneQueryRenderer, {
+ hidesBackButton: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ screenOptions: {
+ headerTitle: "Phone Number",
+ },
}),
- MyAccountEditPassword: reactModule(MyAccountEditPassword, { hidesBackButton: true }),
- MyAccountEditPhone: reactModule(MyAccountEditPhoneQueryRenderer, { hidesBackButton: true }),
MyAccountDeleteAccount: reactModule(MyAccountDeleteAccountQueryRenderer),
MyBids: reactModule(MyBidsQueryRenderer),
MyCollection: reactModule(MyCollectionQueryRenderer),
@@ -539,7 +628,7 @@ export const modules = defineModules({
),
MyCollectionArtworkAdd: reactModule(MyCollectionArtworkAdd, {
hidesBackButton: true,
- hidesBottomTabs: true,
+ hidesBottomTabs: !unsafe_getFeatureFlag("AREnableNewNavigation"),
alwaysPresentModally: true,
modalPresentationStyle: "fullScreen",
screenOptions: {
@@ -548,7 +637,7 @@ export const modules = defineModules({
}),
MyCollectionArtworkEdit: reactModule(MyCollectionArtworkEditQueryRenderer, {
hidesBackButton: true,
- hidesBottomTabs: true,
+ hidesBottomTabs: !unsafe_getFeatureFlag("AREnableNewNavigation"),
alwaysPresentModally: true,
modalPresentationStyle: "fullScreen",
screenOptions: {
@@ -556,10 +645,12 @@ export const modules = defineModules({
},
}),
MyCollectionAddCollectedArtists: reactModule(MyCollectionAddCollectedArtistsScreen, {
+ hidesBackButton: !unsafe_getFeatureFlag("AREnableNewNavigation"),
+ hidesBottomTabs: true,
screenOptions: {
+ headerTitle: "Add Artists You Collect",
gestureEnabled: false,
},
- hidesBottomTabs: true,
}),
MyCollectionSellingWithartsyFAQ: reactModule(MyCollectionSellingWithArtsyFAQ),
MyCollectionCollectedArtistsPrivacy: reactModule(
@@ -578,6 +669,7 @@ export const modules = defineModules({
{
isRootViewForTabName: "profile",
fullBleed: true,
+ hidesBackButton: true,
},
[MyCollectionScreenQuery]
),
@@ -586,9 +678,21 @@ export const modules = defineModules({
hidesBackButton: true,
hidesBottomTabs: true,
}),
- MyProfileEditForm: reactModule(MyProfileEditFormScreen),
- MyProfilePayment: reactModule(MyProfilePaymentQueryRenderer),
- MyProfileSettings: reactModule(MyProfileSettings),
+ MyProfileEditForm: reactModule(MyProfileEditFormScreen, {
+ screenOptions: {
+ headerTitle: "Edit Profile",
+ },
+ }),
+ MyProfilePayment: reactModule(MyProfilePaymentQueryRenderer, {
+ screenOptions: {
+ headerTitle: "Payment",
+ },
+ }),
+ MyProfileSettings: reactModule(MyProfileSettings, {
+ screenOptions: {
+ headerTitle: "Account",
+ },
+ }),
MySellingProfile: reactModule(View),
NewWorksForYou: reactModule(NewWorksForYouQueryRenderer, {
hidesBottomTabs: true,
@@ -596,9 +700,15 @@ export const modules = defineModules({
fullBleed: true,
}),
MyProfilePaymentNewCreditCard: reactModule(MyProfilePaymentNewCreditCard, {
- hidesBackButton: true,
+ screenOptions: {
+ headerTitle: "Add new card",
+ },
+ }),
+ MyProfilePushNotifications: reactModule(MyProfilePushNotificationsQueryRenderer, {
+ screenOptions: {
+ headerTitle: "Push Notifications",
+ },
}),
- MyProfilePushNotifications: reactModule(MyProfilePushNotificationsQueryRenderer),
NewWorksFromGalleriesYouFollow: reactModule(NewWorksFromGalleriesYouFollowScreen, {
hidesBackButton: true,
fullBleed: true,
@@ -611,8 +721,16 @@ export const modules = defineModules({
},
[NewsScreenQuery]
),
- OrderHistory: reactModule(OrderHistoryQueryRender),
- OrderDetails: reactModule(OrderDetailsQueryRender),
+ OrderHistory: reactModule(OrderHistoryQueryRender, {
+ screenOptions: {
+ headerTitle: "Order History",
+ },
+ }),
+ OrderDetails: reactModule(OrderDetailsQueryRender, {
+ screenOptions: {
+ headerTitle: "Order Details",
+ },
+ }),
Partner: reactModule(PartnerQueryRenderer, {
fullBleed: true,
hidesBackButton: true,
@@ -623,22 +741,27 @@ export const modules = defineModules({
hidesBackButton: true,
}),
PriceDatabase: reactModule(PriceDatabase, { hidesBackButton: true }),
- PrivacyRequest: reactModule(PrivacyRequest),
+ PrivacyRequest: reactModule(PrivacyRequest, {
+ screenOptions: {
+ headerTitle: "Personal Data Request",
+ },
+ }),
PurchaseModal: reactModule(PurchaseModalQueryRenderer, {
hasOwnModalCloseButton: true,
}),
ModalWebView: reactModule(ArtsyWebViewPage, {
- fullBleed: false,
hasOwnModalCloseButton: true,
hidesBackButton: true,
alwaysPresentModally: true,
- modalPresentationStyle: "fullScreen",
+ modalPresentationStyle: !unsafe_getFeatureFlag("AREnableNewNavigation")
+ ? "fullScreen"
+ : undefined,
screenOptions: {
gestureEnabled: false,
},
}),
ReactWebView: reactModule(ArtsyWebViewPage, {
- fullBleed: true,
+ fullBleed: !unsafe_getFeatureFlag("AREnableNewNavigation"),
hasOwnModalCloseButton: true,
hidesBackButton: true,
}),
@@ -660,9 +783,11 @@ export const modules = defineModules({
hidesBackButton: true,
fullBleed: true,
}),
- Sell: reactModule(SellWithArtsy, { isRootViewForTabName: "sell", fullBleed: true }, [
- SellWithArtsyHomeScreenQuery,
- ]),
+ Sell: reactModule(
+ SellWithArtsy,
+ { isRootViewForTabName: "sell", fullBleed: true, hidesBackButton: true },
+ [SellWithArtsyHomeScreenQuery]
+ ),
SellNotRootTabView: reactModule(SellWithArtsy),
SavedArtworks: reactModule(SavedArtworks, {
fullBleed: true,
@@ -672,7 +797,11 @@ export const modules = defineModules({
fullBleed: true,
hidesBackButton: true,
}),
- Search: reactModule(SearchScreen, { isRootViewForTabName: "search" }, [SearchScreenQuery]),
+ Search: reactModule(
+ SearchScreen,
+ { isRootViewForTabName: "search", hidesBackButton: true, fullBleed: true },
+ [SearchScreenQuery]
+ ),
Show: reactModule(ShowQueryRenderer, { fullBleed: true }),
ShowMoreInfo: reactModule(ShowMoreInfoQueryRenderer),
SimilarToRecentlyViewed: reactModule(SimilarToRecentlyViewedScreen, {
@@ -682,8 +811,9 @@ export const modules = defineModules({
SubmitArtwork: reactModule(SubmitArtworkForm, {
hidesBackButton: true,
alwaysPresentModally: true,
- modalPresentationStyle: "fullScreen",
- hidesBottomTabs: true,
+ modalPresentationStyle: !unsafe_getFeatureFlag("AREnableNewNavigation")
+ ? "fullScreen"
+ : undefined,
screenOptions: {
gestureEnabled: false,
},
@@ -691,7 +821,6 @@ export const modules = defineModules({
SubmitArtworkEdit: reactModule(SubmitArtworkFormEditContainer, {
hidesBackButton: true,
alwaysPresentModally: true,
- modalPresentationStyle: "fullScreen",
hidesBottomTabs: true,
screenOptions: {
gestureEnabled: false,
@@ -710,7 +839,7 @@ export const modules = defineModules({
// Register react modules with the app registry
for (const moduleName of Object.keys(modules)) {
const descriptor = modules[moduleName as AppModule]
- if (Platform.OS === "ios") {
+ if (Platform.OS === "ios" && !unsafe_getFeatureFlag("AREnableNewNavigation")) {
// TODO: this should not be needed. right?
register(moduleName, descriptor.Component, {
fullBleed: descriptor.options.fullBleed,
diff --git a/src/app/Components/ArtsyWebView.tsx b/src/app/Components/ArtsyWebView.tsx
index 9700f48a536..ecc1e15325a 100644
--- a/src/app/Components/ArtsyWebView.tsx
+++ b/src/app/Components/ArtsyWebView.tsx
@@ -1,7 +1,7 @@
import { OwnerType } from "@artsy/cohesion"
import { Flex, Text } from "@artsy/palette-mobile"
-import { addBreadcrumb } from "@sentry/react-native"
import * as Sentry from "@sentry/react-native"
+import { addBreadcrumb } from "@sentry/react-native"
import { BottomTabRoutes } from "app/Scenes/BottomTabs/bottomTabsConfig"
import { matchRoute } from "app/routes"
import { GlobalStore, getCurrentEmissionState } from "app/store/GlobalStore"
@@ -10,6 +10,7 @@ import { ArtsyKeyboardAvoidingView } from "app/utils/ArtsyKeyboardAvoidingView"
import { useBackHandler } from "app/utils/hooks/useBackHandler"
import { useDevToggle } from "app/utils/hooks/useDevToggle"
import { useEnvironment } from "app/utils/hooks/useEnvironment"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { Schema } from "app/utils/track"
import { useWebViewCallback } from "app/utils/useWebViewEvent"
import { debounce } from "lodash"
@@ -69,6 +70,7 @@ export const ArtsyWebViewPage = ({
backAction?: () => void
} & ArtsyWebViewConfig) => {
const saInsets = useSafeAreaInsets()
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
const [canGoBack, setCanGoBack] = useState(false)
const webURL = useEnvironment().webURL
@@ -131,7 +133,11 @@ export const ArtsyWebViewPage = ({
const leftButton = useRightCloseButton && !canGoBack ? undefined : handleBackButtonPress
return (
-
+
{
@@ -180,6 +186,8 @@ export const ArtsyWebView = forwardRef<
},
ref
) => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
const innerRef = useRef(null)
useImperativeHandle(ref, () => innerRef.current as WebViewWithShareTitleUrl)
const userAgent = getCurrentEmissionState().userAgent
@@ -259,7 +267,7 @@ export const ArtsyWebView = forwardRef<
}
}
- const WebViewWrapper = isPresentedModally ? SafeAreaView : View
+ const WebViewWrapper = isPresentedModally && !enableNewNavigation ? SafeAreaView : View
return (
diff --git a/src/app/Components/Containers/Inbox.tsx b/src/app/Components/Containers/Inbox.tsx
index a3fb8072741..70c9b37a14b 100644
--- a/src/app/Components/Containers/Inbox.tsx
+++ b/src/app/Components/Containers/Inbox.tsx
@@ -1,6 +1,15 @@
import { ActionType } from "@artsy/cohesion"
-import { Spacer, Flex, Separator, Tabs, Skeleton, SkeletonText } from "@artsy/palette-mobile"
+import {
+ Spacer,
+ Flex,
+ Separator,
+ Tabs,
+ Skeleton,
+ SkeletonText,
+ Screen,
+} from "@artsy/palette-mobile"
import { TabsContainer } from "@artsy/palette-mobile/dist/elements/Tabs/TabsContainer"
+import { StackScreenProps } from "@react-navigation/stack"
import { InboxQuery } from "__generated__/InboxQuery.graphql"
import { Inbox_me$data } from "__generated__/Inbox_me.graphql"
import { ConversationsContainer } from "app/Scenes/Inbox/Components/Conversations/Conversations"
@@ -137,20 +146,26 @@ export const InboxScreenQuery = graphql`
}
`
-export const InboxQueryRenderer: React.FC<{ isVisible: boolean }> = (props) => {
+interface InboxQueryRendererProps extends StackScreenProps {
+ isVisible?: boolean
+}
+
+export const InboxQueryRenderer: React.FC = (props) => {
return (
-
- environment={getRelayEnvironment()}
- query={InboxScreenQuery}
- variables={{}}
- render={(...args) =>
- renderWithPlaceholder({
- Container: InboxContainer,
- initialProps: props,
- renderPlaceholder: () => ,
- })(...args)
- }
- />
+
+
+ environment={getRelayEnvironment()}
+ query={InboxScreenQuery}
+ variables={{}}
+ render={(...args) =>
+ renderWithPlaceholder({
+ Container: InboxContainer,
+ initialProps: props,
+ renderPlaceholder: () => ,
+ })(...args)
+ }
+ />
+
)
}
diff --git a/src/app/Components/FancyModal/FancyModalContext.tsx b/src/app/Components/FancyModal/FancyModalContext.tsx
index d8ff2e94826..5009be41c02 100644
--- a/src/app/Components/FancyModal/FancyModalContext.tsx
+++ b/src/app/Components/FancyModal/FancyModalContext.tsx
@@ -1,8 +1,8 @@
import { ExecutionQueue } from "app/utils/ExecutionQueue"
+import { useScreenDimensions } from "app/utils/hooks"
import { compact, flatten } from "lodash"
import React, { RefObject, useEffect, useRef, useState } from "react"
import { Animated, View } from "react-native"
-import { useScreenDimensions } from "app/utils/hooks"
import {
AnimationCreator,
ease,
diff --git a/src/app/Components/PageWithSimpleHeader.tsx b/src/app/Components/PageWithSimpleHeader.tsx
index ff38f89584f..6433b75c196 100644
--- a/src/app/Components/PageWithSimpleHeader.tsx
+++ b/src/app/Components/PageWithSimpleHeader.tsx
@@ -1,4 +1,4 @@
-import { Flex, Box, Text, Separator, TextProps } from "@artsy/palette-mobile"
+import { Flex, Box, Text, Separator, TextProps, NAVBAR_HEIGHT } from "@artsy/palette-mobile"
import { View } from "react-native"
export const PageWithSimpleHeader: React.FC<{
@@ -10,17 +10,29 @@ export const PageWithSimpleHeader: React.FC<{
}> = ({ title, titleWeight, left, right, children, noSeparator }) => {
return (
-
-
+
+
{left}
- {/* TODO: figure out how to make this stretch dynamically */}
-
-
+
+
{title}
-
+
{right}
diff --git a/src/app/NativeModules/ArtsyNativeModule.tsx b/src/app/NativeModules/ArtsyNativeModule.tsx
index e6814f1c085..1fc42f90df0 100644
--- a/src/app/NativeModules/ArtsyNativeModule.tsx
+++ b/src/app/NativeModules/ArtsyNativeModule.tsx
@@ -13,7 +13,7 @@ export const DEFAULT_NAVIGATION_BAR_COLOR = "#FFFFFF"
export const ArtsyNativeModule = {
launchCount:
Platform.OS === "ios"
- ? LegacyNativeModules.ARNotificationsManager.nativeState.launchCount
+ ? LegacyNativeModules.ARNotificationsManager.nativeState.launchCount || 0
: (NativeModules.ArtsyNativeModule.getConstants().launchCount as number),
setAppStyling:
Platform.OS === "ios"
diff --git a/src/app/Navigation/AuthenticatedRoutes/HomeTab.tsx b/src/app/Navigation/AuthenticatedRoutes/HomeTab.tsx
new file mode 100644
index 00000000000..18f7fc9eb62
--- /dev/null
+++ b/src/app/Navigation/AuthenticatedRoutes/HomeTab.tsx
@@ -0,0 +1,15 @@
+import { modules } from "app/AppRegistry"
+import { registerSharedRoutes } from "app/Navigation/AuthenticatedRoutes/SharedRoutes"
+import { registerScreen, StackNavigator } from "app/Navigation/AuthenticatedRoutes/StackNavigator"
+
+export const HomeTab: React.FC = () => {
+ return (
+
+ {registerScreen({
+ name: "Home",
+ module: modules["Home"],
+ })}
+ {registerSharedRoutes()}
+
+ )
+}
diff --git a/src/app/Navigation/AuthenticatedRoutes/InboxTab.tsx b/src/app/Navigation/AuthenticatedRoutes/InboxTab.tsx
new file mode 100644
index 00000000000..67dcc1b1b79
--- /dev/null
+++ b/src/app/Navigation/AuthenticatedRoutes/InboxTab.tsx
@@ -0,0 +1,20 @@
+import { modules } from "app/AppRegistry"
+import { registerSharedRoutes } from "app/Navigation/AuthenticatedRoutes/SharedRoutes"
+import { registerScreen, StackNavigator } from "app/Navigation/AuthenticatedRoutes/StackNavigator"
+
+export const InboxTab: React.FC = () => {
+ return (
+
+ {registerScreen({
+ name: "Inbox",
+ module: modules["Inbox"],
+ })}
+ {registerScreen({
+ name: "Conversation",
+ module: modules["Conversation"],
+ })}
+
+ {registerSharedRoutes()}
+
+ )
+}
diff --git a/src/app/Navigation/AuthenticatedRoutes/NativeScreens.tsx b/src/app/Navigation/AuthenticatedRoutes/NativeScreens.tsx
new file mode 100644
index 00000000000..5ed760cdf06
--- /dev/null
+++ b/src/app/Navigation/AuthenticatedRoutes/NativeScreens.tsx
@@ -0,0 +1,45 @@
+import { CityGuideView } from "app/NativeModules/CityGuideView"
+import { LiveAuctionView } from "app/NativeModules/LiveAuctionView"
+import { Providers } from "app/Providers"
+import { CityView } from "app/Scenes/City/City"
+import { CityPicker } from "app/Scenes/City/CityPicker"
+import { MapContainer } from "app/Scenes/Map/MapContainer"
+import { AppRegistry, Platform } from "react-native"
+
+function register(moduleName: string, Component: React.ComponentType) {
+ const WrappedComponent = (props: any) => (
+
+
+
+ )
+ AppRegistry.registerComponent(moduleName, () => WrappedComponent)
+}
+
+const modules: { moduleName: string; module: React.FC }[] = [
+ {
+ moduleName: "LocalDiscovery",
+ module: CityGuideView,
+ },
+ {
+ moduleName: "LiveAuction",
+ module: LiveAuctionView,
+ },
+ {
+ moduleName: "CityPicker",
+ module: CityPicker,
+ },
+ {
+ moduleName: "Map",
+ module: MapContainer,
+ },
+ {
+ moduleName: "City",
+ module: CityView as any,
+ },
+]
+
+if (Platform.OS === "ios") {
+ modules.map(({ moduleName, module }) => {
+ register(moduleName, module)
+ })
+}
diff --git a/src/app/Navigation/AuthenticatedRoutes/ProfileTab.tsx b/src/app/Navigation/AuthenticatedRoutes/ProfileTab.tsx
new file mode 100644
index 00000000000..afc9ea2bb6d
--- /dev/null
+++ b/src/app/Navigation/AuthenticatedRoutes/ProfileTab.tsx
@@ -0,0 +1,16 @@
+import { modules } from "app/AppRegistry"
+import { registerSharedRoutes } from "app/Navigation/AuthenticatedRoutes/SharedRoutes"
+import { registerScreen, StackNavigator } from "app/Navigation/AuthenticatedRoutes/StackNavigator"
+
+export const ProfileTab: React.FC = () => {
+ return (
+
+ {registerScreen({
+ name: "MyProfile",
+ module: modules["MyProfile"],
+ })}
+
+ {registerSharedRoutes()}
+
+ )
+}
diff --git a/src/app/Navigation/AuthenticatedRoutes/SearchTab.tsx b/src/app/Navigation/AuthenticatedRoutes/SearchTab.tsx
new file mode 100644
index 00000000000..932ea5d8dd0
--- /dev/null
+++ b/src/app/Navigation/AuthenticatedRoutes/SearchTab.tsx
@@ -0,0 +1,16 @@
+import { modules } from "app/AppRegistry"
+import { registerSharedRoutes } from "app/Navigation/AuthenticatedRoutes/SharedRoutes"
+import { registerScreen, StackNavigator } from "app/Navigation/AuthenticatedRoutes/StackNavigator"
+
+export const SearchTab: React.FC = () => {
+ return (
+
+ {registerScreen({
+ name: "Search",
+ module: modules["Search"],
+ })}
+
+ {registerSharedRoutes()}
+
+ )
+}
diff --git a/src/app/Navigation/AuthenticatedRoutes/SellTab.tsx b/src/app/Navigation/AuthenticatedRoutes/SellTab.tsx
new file mode 100644
index 00000000000..99149b7a902
--- /dev/null
+++ b/src/app/Navigation/AuthenticatedRoutes/SellTab.tsx
@@ -0,0 +1,16 @@
+import { modules } from "app/AppRegistry"
+import { registerSharedRoutes } from "app/Navigation/AuthenticatedRoutes/SharedRoutes"
+import { registerScreen, StackNavigator } from "app/Navigation/AuthenticatedRoutes/StackNavigator"
+
+export const SellTab: React.FC = () => {
+ return (
+
+ {registerScreen({
+ name: "Sell",
+ module: modules["Sell"],
+ })}
+
+ {registerSharedRoutes()}
+
+ )
+}
diff --git a/src/app/Navigation/AuthenticatedRoutes/SharedRoutes.tsx b/src/app/Navigation/AuthenticatedRoutes/SharedRoutes.tsx
new file mode 100644
index 00000000000..ac44faf7545
--- /dev/null
+++ b/src/app/Navigation/AuthenticatedRoutes/SharedRoutes.tsx
@@ -0,0 +1,46 @@
+import { AppModule, modules as allModules } from "app/AppRegistry"
+import { registerScreen, StackNavigator } from "app/Navigation/AuthenticatedRoutes/StackNavigator"
+import { AuthenticatedRoutesStack } from "app/Navigation/AuthenticatedRoutes/Tabs"
+import { isModalScreen } from "app/Navigation/Utils/isModalScreen"
+
+const modules = Object.fromEntries(
+ Object.entries(allModules).filter(([_, module]) => {
+ return (
+ module.type === "react" &&
+ module.Component &&
+ // The module should not be a root view for a tab
+ !module.options.isRootViewForTabName &&
+ // The module is not an restricted to a specific tab
+ !module.options.onlyShowInTabName
+ )
+ })
+)
+
+const modalModules = Object.entries(modules).filter(([_, module]) => isModalScreen(module))
+const nonModalModules = Object.entries(modules).filter(([_, module]) => !isModalScreen(module))
+
+export const registerSharedRoutes = () => {
+ return (
+
+ {nonModalModules.map(([moduleName, module]) => {
+ return registerScreen({
+ name: moduleName as AppModule,
+ module: module,
+ })
+ })}
+
+ )
+}
+
+export const registerSharedModalRoutes = () => {
+ return (
+
+ {modalModules.map(([moduleName, module]) => {
+ return registerScreen({
+ name: moduleName as AppModule,
+ module: module,
+ })
+ })}
+
+ )
+}
diff --git a/src/app/Navigation/AuthenticatedRoutes/StackNavigator.tsx b/src/app/Navigation/AuthenticatedRoutes/StackNavigator.tsx
new file mode 100644
index 00000000000..3a7b968e688
--- /dev/null
+++ b/src/app/Navigation/AuthenticatedRoutes/StackNavigator.tsx
@@ -0,0 +1,121 @@
+import {
+ ArrowLeftIcon,
+ CloseIcon,
+ DEFAULT_HIT_SLOP,
+ Flex,
+ THEMES,
+ Touchable,
+} from "@artsy/palette-mobile"
+import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs"
+import { createNativeStackNavigator } from "@react-navigation/native-stack"
+import { ModuleDescriptor } from "app/AppRegistry"
+import { AuthenticatedRoutesParams } from "app/Navigation/AuthenticatedRoutes/Tabs"
+import { isHeaderShown } from "app/Navigation/Utils/isHeaderShown"
+import { isModalScreen } from "app/Navigation/Utils/isModalScreen"
+import { ICON_HEIGHT } from "app/Scenes/BottomTabs/BottomTabsIcon"
+import { goBack } from "app/system/navigation/navigate"
+import { useSafeAreaInsets } from "react-native-safe-area-context"
+
+export const StackNavigator = createNativeStackNavigator()
+
+type StackNavigatorScreenProps = {
+ name: keyof AuthenticatedRoutesParams
+ module: ModuleDescriptor
+} & Omit, "component" | "getComponent">
+
+export const registerScreen: React.FC = ({ name, module, ...props }) => {
+ return (
+ {
+ if (!canGoBack) {
+ return null
+ }
+
+ return (
+ {
+ goBack()
+ }}
+ underlayColor="transparent"
+ hitSlop={DEFAULT_HIT_SLOP}
+ >
+ {isModalScreen(module) ? (
+
+ ) : (
+
+ )}
+
+ )
+ },
+ headerTitle: "",
+ headerTitleAlign: "center",
+ ...module.options.screenOptions,
+ headerTitleStyle: {
+ fontFamily: THEMES.v3.fonts.sans.regular,
+ ...THEMES.v3.textTreatments["sm-display"],
+ ...((module.options.screenOptions?.headerTitleStyle as {} | undefined) ?? {}),
+ },
+ }}
+ children={(screenProps) => {
+ const params = screenProps.route.params || {}
+ const isFullBleed =
+ module.options.fullBleed ??
+ // when no header is visible, we want to make sure we are bound by the insets
+ isHeaderShown(module)
+
+ const hidesBottomTabs = module.options.hidesBottomTabs || isModalScreen(module)
+
+ return (
+
+
+
+ )
+ }}
+ />
+ )
+}
+
+export interface ScreenWrapperProps {
+ fullBleed?: boolean
+ readonly hidesBottomTabs?: boolean
+}
+
+export const ScreenWrapper: React.FC = ({
+ fullBleed = false,
+ hidesBottomTabs = false,
+ children,
+}) => {
+ const safeAreaInsets = useSafeAreaInsets()
+ // We don't have the bottom tabs context on modal screens
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const tabBarHeight = hidesBottomTabs ? 0 : useBottomTabBarHeight()
+
+ const padding = fullBleed
+ ? {
+ paddingBottom: hidesBottomTabs ? 0 : tabBarHeight,
+ }
+ : {
+ // Bottom inset + bottom tabs height - bottom tabs border
+ paddingBottom: hidesBottomTabs ? 0 : safeAreaInsets.bottom + ICON_HEIGHT - 2,
+ paddingTop: safeAreaInsets.top,
+ paddingRight: safeAreaInsets.right,
+ paddingLeft: safeAreaInsets.left,
+ }
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/app/Navigation/AuthenticatedRoutes/Tabs.tsx b/src/app/Navigation/AuthenticatedRoutes/Tabs.tsx
new file mode 100644
index 00000000000..6060246c3e9
--- /dev/null
+++ b/src/app/Navigation/AuthenticatedRoutes/Tabs.tsx
@@ -0,0 +1,80 @@
+import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"
+import { createNativeStackNavigator } from "@react-navigation/native-stack"
+import { AppModule, modules } from "app/AppRegistry"
+import { HomeTab } from "app/Navigation/AuthenticatedRoutes/HomeTab"
+import { InboxTab } from "app/Navigation/AuthenticatedRoutes/InboxTab"
+import { ProfileTab } from "app/Navigation/AuthenticatedRoutes/ProfileTab"
+import { SearchTab } from "app/Navigation/AuthenticatedRoutes/SearchTab"
+import { SellTab } from "app/Navigation/AuthenticatedRoutes/SellTab"
+import { registerSharedModalRoutes } from "app/Navigation/AuthenticatedRoutes/SharedRoutes"
+import { BottomTabsButton } from "app/Scenes/BottomTabs/BottomTabsButton"
+import { internal_navigationRef } from "app/system/navigation/navigate"
+import { Platform } from "react-native"
+
+if (Platform.OS === "ios") {
+ require("app/Navigation/AuthenticatedRoutes/NativeScreens")
+}
+
+export type AuthenticatedRoutesParams = {
+ Home: undefined
+ Search: undefined
+ Profile: undefined
+ Inbox: undefined
+ Sell: undefined
+} & { [key in AppModule]: undefined }
+
+type TabRoutesParams = {
+ home: undefined
+ search: undefined
+ inbox: undefined
+ sell: undefined
+ profile: undefined
+}
+
+const Tab = createBottomTabNavigator()
+
+const AppTabs: React.FC = () => {
+ return (
+ {
+ const currentRoute = internal_navigationRef.current?.getCurrentRoute()?.name
+ return {
+ headerShown: false,
+ tabBarStyle: {
+ animate: true,
+ position: "absolute",
+ display:
+ currentRoute && modules[currentRoute as AppModule]?.options.hidesBottomTabs
+ ? "none"
+ : "flex",
+ },
+ tabBarHideOnKeyboard: true,
+ tabBarButton: (props) => {
+ return
+ },
+ }
+ }}
+ >
+
+
+
+
+
+
+ )
+}
+
+export const AuthenticatedRoutesStack = createNativeStackNavigator()
+
+export const AuthenticatedRoutes: React.FC = () => {
+ return (
+
+
+ {registerSharedModalRoutes()}
+
+ )
+}
diff --git a/src/app/Navigation/Navigation.tsx b/src/app/Navigation/Navigation.tsx
new file mode 100644
index 00000000000..8e3ac31d1ea
--- /dev/null
+++ b/src/app/Navigation/Navigation.tsx
@@ -0,0 +1,118 @@
+import { Flex, Text } from "@artsy/palette-mobile"
+import { DefaultTheme, NavigationContainer } from "@react-navigation/native"
+import { createNativeStackNavigator } from "@react-navigation/native-stack"
+import { addBreadcrumb } from "@sentry/react-native"
+import { LoadingSpinner } from "app/Components/Modals/LoadingModal"
+import {
+ AuthenticatedRoutes,
+ AuthenticatedRoutesParams,
+} from "app/Navigation/AuthenticatedRoutes/Tabs"
+import {
+ UnauthenticatedRoutes,
+ UnauthenticatedRoutesParams,
+} from "app/Navigation/UnauthenticatedRoutes"
+import { GlobalStore } from "app/store/GlobalStore"
+import { routingInstrumentation } from "app/system/errorReporting/setupSentry"
+import { internal_navigationRef } from "app/system/navigation/navigate"
+
+import { useReloadedDevNavigationState } from "app/system/navigation/useReloadedDevNavigationState"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
+import { logNavigation } from "app/utils/loggers"
+import { Platform } from "react-native"
+import SiftReactNative from "sift-react-native"
+
+export type NavigationRoutesParams = UnauthenticatedRoutesParams & AuthenticatedRoutesParams
+
+export const MainStackNavigator = createNativeStackNavigator()
+
+const MODAL_NAVIGATION_STACK_STATE_KEY = "MODAL_NAVIGATION_STACK_STATE_KEY"
+
+export const Navigation = () => {
+ const { isReady, initialState, saveSession } = useReloadedDevNavigationState(
+ MODAL_NAVIGATION_STACK_STATE_KEY
+ )
+
+ const isLoggedIn = GlobalStore.useAppState((state) => state.auth.userID)
+ const { setSessionState: setNavigationReady } = GlobalStore.actions
+
+ // Code for Sift tracking; needs to be manually fired on Android
+ // See https://github.com/SiftScience/sift-react-native/pull/23#issuecomment-1630984250
+ const enableAdditionalSiftAndroidTracking = useFeatureFlag(
+ "AREnableAdditionalSiftAndroidTracking"
+ )
+ const trackSiftAndroid = Platform.OS === "android" && enableAdditionalSiftAndroidTracking
+
+ if (!isReady) {
+ return
+ }
+
+ return (
+ {
+ routingInstrumentation.registerNavigationContainer(internal_navigationRef)
+
+ setNavigationReady({ isNavigationReady: true })
+
+ if (trackSiftAndroid) {
+ const initialRouteName = internal_navigationRef?.current?.getCurrentRoute()?.name
+ SiftReactNative.setPageName(`screen_${initialRouteName}`)
+ SiftReactNative.upload()
+ }
+ }}
+ onStateChange={(state) => {
+ saveSession(state)
+
+ const currentRoute = internal_navigationRef?.current?.getCurrentRoute()
+ const params = currentRoute?.params
+
+ if (__DEV__ && logNavigation) {
+ console.log(
+ `navigated to ${currentRoute?.name} ${
+ currentRoute?.params ? JSON.stringify(currentRoute.params) : ""
+ } `
+ )
+ }
+
+ addBreadcrumb({
+ message: `navigated to ${currentRoute?.name}`,
+ category: "navigation",
+ data: { ...params },
+ level: "info",
+ })
+
+ if (trackSiftAndroid) {
+ SiftReactNative.setPageName(`screen_${currentRoute?.name}`)
+ SiftReactNative.upload()
+ }
+ }}
+ >
+ {!isLoggedIn && }
+ {!!isLoggedIn && }
+
+ )
+}
+
+const theme = {
+ ...DefaultTheme,
+ colors: {
+ ...DefaultTheme.colors,
+ background: "#FFF",
+ },
+}
+
+const NavigationLoadingIndicator = () => {
+ return (
+
+ {!!__DEV__ && (
+
+
+ This spinner is only visible in DEV mode.{"\n"}
+
+
+ )}
+
+ )
+}
diff --git a/src/app/Navigation/UnauthenticatedRoutes.tsx b/src/app/Navigation/UnauthenticatedRoutes.tsx
new file mode 100644
index 00000000000..bdd5d4d2147
--- /dev/null
+++ b/src/app/Navigation/UnauthenticatedRoutes.tsx
@@ -0,0 +1,22 @@
+import { createNativeStackNavigator } from "@react-navigation/native-stack"
+import { MainStackNavigator } from "app/Navigation/Navigation"
+import { LoginScreen } from "app/Navigation/_TO_BE_DELETED_Screens/LoginScreen"
+import { SignUpScreen } from "app/Navigation/_TO_BE_DELETED_Screens/SignUpScreen"
+
+export type UnauthenticatedRoutesParams = {
+ Login: undefined
+ SignUp: undefined
+}
+
+const UnauthenticatedStack = createNativeStackNavigator()
+
+export const UnauthenticatedRoutes = () => {
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/src/app/Navigation/Utils/LargeHeaderView.tsx b/src/app/Navigation/Utils/LargeHeaderView.tsx
new file mode 100644
index 00000000000..4757e10336e
--- /dev/null
+++ b/src/app/Navigation/Utils/LargeHeaderView.tsx
@@ -0,0 +1,12 @@
+import { Flex, NAVBAR_HEIGHT } from "@artsy/palette-mobile"
+import { Platform } from "react-native"
+
+const LARGE_HEADER_HEIGHT = 100
+
+export const LargeHeaderView: React.FC = () => {
+ if (Platform.OS !== "ios") {
+ return null
+ }
+
+ return
+}
diff --git a/src/app/Navigation/Utils/isHeaderShown.tsx b/src/app/Navigation/Utils/isHeaderShown.tsx
new file mode 100644
index 00000000000..b8c6c1697b4
--- /dev/null
+++ b/src/app/Navigation/Utils/isHeaderShown.tsx
@@ -0,0 +1,5 @@
+import { ModuleDescriptor } from "app/AppRegistry"
+
+export const isHeaderShown = (module: ModuleDescriptor) => {
+ return !module.options.hidesBackButton && !module.options.hasOwnModalCloseButton
+}
diff --git a/src/app/Navigation/Utils/isModalScreen.tsx b/src/app/Navigation/Utils/isModalScreen.tsx
new file mode 100644
index 00000000000..981249ea341
--- /dev/null
+++ b/src/app/Navigation/Utils/isModalScreen.tsx
@@ -0,0 +1,5 @@
+import { ModuleDescriptor } from "app/AppRegistry"
+
+export const isModalScreen = (module: ModuleDescriptor) => {
+ return !!module.options.alwaysPresentModally
+}
diff --git a/src/app/Navigation/_TO_BE_DELETED_Screens/LoginScreen.tsx b/src/app/Navigation/_TO_BE_DELETED_Screens/LoginScreen.tsx
new file mode 100644
index 00000000000..b162a11e401
--- /dev/null
+++ b/src/app/Navigation/_TO_BE_DELETED_Screens/LoginScreen.tsx
@@ -0,0 +1,23 @@
+import { Button, Flex, Spacer, Text } from "@artsy/palette-mobile"
+import { NavigationProp, useNavigation } from "@react-navigation/native"
+import { UnauthenticatedRoutesParams } from "app/Navigation/UnauthenticatedRoutes"
+
+export const LoginScreen: React.FC = () => {
+ const navigation = useNavigation>()
+
+ return (
+
+ Login
+
+
+
+
+
+ )
+}
diff --git a/src/app/Navigation/_TO_BE_DELETED_Screens/SignUpScreen.tsx b/src/app/Navigation/_TO_BE_DELETED_Screens/SignUpScreen.tsx
new file mode 100644
index 00000000000..2862aa6d4fd
--- /dev/null
+++ b/src/app/Navigation/_TO_BE_DELETED_Screens/SignUpScreen.tsx
@@ -0,0 +1,22 @@
+import { Button, Flex, Spacer, Text } from "@artsy/palette-mobile"
+import { NavigationProp, useNavigation } from "@react-navigation/native"
+import { UnauthenticatedRoutesParams } from "app/Navigation/UnauthenticatedRoutes"
+
+export const SignUpScreen: React.FC = () => {
+ const navigation = useNavigation>()
+ return (
+
+ Sign Up
+
+
+
+
+
+ )
+}
diff --git a/src/app/Scenes/About/About.tsx b/src/app/Scenes/About/About.tsx
index c32377603e3..af9f4258905 100644
--- a/src/app/Scenes/About/About.tsx
+++ b/src/app/Scenes/About/About.tsx
@@ -5,12 +5,13 @@ import { useToast } from "app/Components/Toast/toastHook"
import { GlobalStore } from "app/store/GlobalStore"
import { navigate } from "app/system/navigation/navigate"
import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
-import React, { useEffect, useState } from "react"
+import React, { Fragment, useEffect, useState } from "react"
import { ScrollView } from "react-native"
import DeviceInfo from "react-native-device-info"
import useDebounce from "react-use/lib/useDebounce"
export const About: React.FC = () => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
const { color } = useTheme()
const appVersion = DeviceInfo.getVersion()
const toast = useToast()
@@ -44,8 +45,14 @@ export const About: React.FC = () => {
[tapCount]
)
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
+
return (
-
+
-
+
)
}
diff --git a/src/app/Scenes/Article/Components/ArticleWebViewScreen.tsx b/src/app/Scenes/Article/Components/ArticleWebViewScreen.tsx
index b774cdd7385..548f5136740 100644
--- a/src/app/Scenes/Article/Components/ArticleWebViewScreen.tsx
+++ b/src/app/Scenes/Article/Components/ArticleWebViewScreen.tsx
@@ -19,12 +19,7 @@ export const ArticleWebViewScreen: React.FC = ({ arti
- {/*
- NOTE: we don't need safeAreaEdges but passing undefined or empty array didn't work,
- so we're passing "left" that doesn't actually add anything to the webview to avoid
- having double paddings from Screen and ArtsyWebView
- */}
-
+
)
diff --git a/src/app/Scenes/Artwork/Components/ArtworkStickyBottomContent.tsx b/src/app/Scenes/Artwork/Components/ArtworkStickyBottomContent.tsx
index fa566d5fc8e..a6cc18d0956 100644
--- a/src/app/Scenes/Artwork/Components/ArtworkStickyBottomContent.tsx
+++ b/src/app/Scenes/Artwork/Components/ArtworkStickyBottomContent.tsx
@@ -11,7 +11,7 @@ import { ArtworkStore } from "app/Scenes/Artwork/ArtworkStore"
import { useScreenDimensions } from "app/utils/hooks"
import { DateTime } from "luxon"
import { useEffect } from "react"
-import { useFragment, graphql } from "react-relay"
+import { graphql, useFragment } from "react-relay"
import { ArtworkCommercialButtons } from "./ArtworkCommercialButtons"
import { ArtworkPrice } from "./ArtworkPrice"
@@ -29,10 +29,10 @@ export const ArtworkStickyBottomContent: React.FC {
- const { safeAreaInsets } = useScreenDimensions()
const artworkData = useFragment(artworkFragment, artwork)
const partnerOfferData = useFragment(partnerOfferFragment, partnerOffer)
const auctionState = ArtworkStore.useStoreState((state) => state.auctionState)
+ const { safeAreaInsets } = useScreenDimensions()
const { bottom: bottomSafeAreaInset } = useScreenDimensions().safeAreaInsets
diff --git a/src/app/Scenes/Artwork/Components/CommercialButtons/InquirySuccessNotification.tsx b/src/app/Scenes/Artwork/Components/CommercialButtons/InquirySuccessNotification.tsx
index 063fba3267b..bf4b82cf176 100644
--- a/src/app/Scenes/Artwork/Components/CommercialButtons/InquirySuccessNotification.tsx
+++ b/src/app/Scenes/Artwork/Components/CommercialButtons/InquirySuccessNotification.tsx
@@ -1,6 +1,6 @@
import { Flex, Text } from "@artsy/palette-mobile"
import { themeGet } from "@styled-system/theme-get"
-import { navigate } from "app/system/navigation/navigate"
+import { switchTab } from "app/system/navigation/navigate"
import { useArtworkInquiryContext } from "app/utils/ArtworkInquiry/ArtworkInquiryStore"
import { useScreenDimensions } from "app/utils/hooks"
import React, { useEffect } from "react"
@@ -28,7 +28,7 @@ export const InquirySuccessNotification: React.FC = () => {
const navigateToConversation = () => {
dispatch({ type: "setSuccessNotificationVisible", payload: false })
- navigate("inbox")
+ switchTab("inbox")
}
const handleRequestClose = () => {
diff --git a/src/app/Scenes/ArtworkAttributionClassFAQ/ArtworkAttributionClassFAQ.tsx b/src/app/Scenes/ArtworkAttributionClassFAQ/ArtworkAttributionClassFAQ.tsx
index 90495c96ab7..0c822dc7b9b 100644
--- a/src/app/Scenes/ArtworkAttributionClassFAQ/ArtworkAttributionClassFAQ.tsx
+++ b/src/app/Scenes/ArtworkAttributionClassFAQ/ArtworkAttributionClassFAQ.tsx
@@ -1,12 +1,12 @@
import {
- Spacer,
Box,
- Text,
- Separator,
- Join,
Button,
- Screen,
CloseIcon,
+ Join,
+ Screen,
+ Separator,
+ Spacer,
+ Text,
Touchable,
useSpace,
} from "@artsy/palette-mobile"
@@ -15,6 +15,7 @@ import { ArtworkAttributionClassFAQ_artworkAttributionClasses$data } from "__gen
import { goBack } from "app/system/navigation/navigate"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import { useAndroidGoBack } from "app/utils/hooks/useBackHandler"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import renderWithLoadProgress from "app/utils/renderWithLoadProgress"
import React from "react"
import { ScrollView } from "react-native"
@@ -25,23 +26,27 @@ interface Props {
}
export const ArtworkAttributionClassFAQ: React.FC = ({ artworkAttributionClasses }) => {
- useAndroidGoBack()
const space = useSpace()
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+ useAndroidGoBack()
return (
- goBack()}
- hitSlop={{ top: space(2), left: space(2), bottom: space(2), right: space(2) }}
- >
-
-
- }
- />
+ {!enableNewNavigation && (
+ goBack()}
+ hitSlop={{ top: space(2), left: space(2), bottom: space(2), right: space(2) }}
+ >
+
+
+ }
+ />
+ )}
+
diff --git a/src/app/Scenes/ArtworkMedium/ArtworkMedium.tsx b/src/app/Scenes/ArtworkMedium/ArtworkMedium.tsx
index 9e5cb152301..2595fb4ac34 100644
--- a/src/app/Scenes/ArtworkMedium/ArtworkMedium.tsx
+++ b/src/app/Scenes/ArtworkMedium/ArtworkMedium.tsx
@@ -1,20 +1,21 @@
import {
- Spacer,
Box,
- Text,
- Separator,
- Join,
Button,
+ CloseIcon,
+ Join,
Screen,
+ Separator,
+ Spacer,
+ Text,
Touchable,
useSpace,
- CloseIcon,
} from "@artsy/palette-mobile"
import { ArtworkMediumQuery } from "__generated__/ArtworkMediumQuery.graphql"
import { ArtworkMedium_artwork$data } from "__generated__/ArtworkMedium_artwork.graphql"
import { goBack } from "app/system/navigation/navigate"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import { useAndroidGoBack } from "app/utils/hooks/useBackHandler"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import renderWithLoadProgress from "app/utils/renderWithLoadProgress"
import { ScrollView } from "react-native"
import { createFragmentContainer, graphql, QueryRenderer } from "react-relay"
@@ -24,23 +25,27 @@ interface Props {
}
export const ArtworkMedium: React.FC = ({ artwork }) => {
- useAndroidGoBack()
const space = useSpace()
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
+ useAndroidGoBack()
return (
- goBack()}
- hitSlop={{ top: space(2), left: space(2), bottom: space(2), right: space(2) }}
- >
-
-
- }
- />
+ {!enableNewNavigation && (
+ goBack()}
+ hitSlop={{ top: space(2), left: space(2), bottom: space(2), right: space(2) }}
+ >
+
+
+ }
+ />
+ )}
diff --git a/src/app/Scenes/AuctionResult/AuctionResult.tests.tsx b/src/app/Scenes/AuctionResult/AuctionResult.tests.tsx
index 0776f578da6..659612058e5 100644
--- a/src/app/Scenes/AuctionResult/AuctionResult.tests.tsx
+++ b/src/app/Scenes/AuctionResult/AuctionResult.tests.tsx
@@ -1,3 +1,4 @@
+import { screen } from "@testing-library/react-native"
import { AuctionResult_artist$data } from "__generated__/AuctionResult_artist.graphql"
import { AuctionResult_auctionResult$data } from "__generated__/AuctionResult_auctionResult.graphql"
import { AuctionResultQueryRenderer } from "app/Scenes/AuctionResult/AuctionResult"
@@ -23,7 +24,7 @@ describe("Activity", () => {
})
it("renders items", async () => {
- const { getAllByText } = renderWithHookWrappersTL(
+ renderWithHookWrappersTL(
{
await flushPromiseQueue()
- expect(getAllByText("Banksy")).toBeTruthy()
- expect(getAllByText("Pulp Fiction")).toBeTruthy()
- expect(getAllByText("Bonhams")).toBeTruthy()
- expect(getAllByText("Pre-sale Estimate")).toBeTruthy()
- expect(getAllByText("£70,000–£100,000")).toBeTruthy()
- expect(getAllByText("London, New Bond Street")).toBeTruthy()
- expect(getAllByText("Lot number")).toBeTruthy()
+ expect(screen.getAllByText("Pulp Fiction")).toBeTruthy()
+ expect(screen.getAllByText("Bonhams")).toBeTruthy()
+ expect(screen.getAllByText("Pre-sale Estimate")).toBeTruthy()
+ expect(screen.getAllByText("£70,000–£100,000")).toBeTruthy()
+ expect(screen.getAllByText("London, New Bond Street")).toBeTruthy()
+ expect(screen.getAllByText("Lot number")).toBeTruthy()
})
})
diff --git a/src/app/Scenes/AuctionResult/AuctionResult.tsx b/src/app/Scenes/AuctionResult/AuctionResult.tsx
index 58f136493c2..72b6ab681d1 100644
--- a/src/app/Scenes/AuctionResult/AuctionResult.tsx
+++ b/src/app/Scenes/AuctionResult/AuctionResult.tsx
@@ -11,6 +11,7 @@ import {
Separator,
BackButton,
} from "@artsy/palette-mobile"
+import { NavigationProp, useNavigation } from "@react-navigation/native"
import { addBreadcrumb } from "@sentry/react-native"
import { AuctionResultQuery } from "__generated__/AuctionResultQuery.graphql"
import { AuctionResult_artist$key } from "__generated__/AuctionResult_artist.graphql"
@@ -20,16 +21,18 @@ import {
} from "__generated__/AuctionResult_auctionResult.graphql"
import { ratioColor } from "app/Components/AuctionResult/AuctionResultMidEstimate"
import { InfoButton } from "app/Components/Buttons/InfoButton"
+import { AuthenticatedRoutesParams } from "app/Navigation/AuthenticatedRoutes/Tabs"
import { goBack, navigate } from "app/system/navigation/navigate"
import { QAInfoPanel } from "app/utils/QAInfo"
import { useScreenDimensions } from "app/utils/hooks"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { PlaceholderBox } from "app/utils/placeholders"
import { getImageSquareDimensions } from "app/utils/resizeImage"
import { ProvideScreenTrackingWithCohesionSchema } from "app/utils/track"
import { screen } from "app/utils/track/helpers"
import { capitalize } from "lodash"
import moment from "moment"
-import React, { Suspense, useEffect, useState } from "react"
+import React, { Suspense, useEffect, useLayoutEffect, useState } from "react"
import { Image, ScrollView, TextInput, TouchableWithoutFeedback } from "react-native"
import FastImage from "react-native-fast-image"
import { graphql, useFragment, useLazyLoadQuery } from "react-relay"
@@ -47,12 +50,20 @@ interface Props {
export const AuctionResult: React.FC = (props) => {
const artist = useFragment(artistFragment, props.artist)
const auctionResult = useFragment(auctionResultFragment, props.auctionResult)
+ const navigation = useNavigation>()
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
const { theme } = useTheme()
const space = useSpace()
const tracking = useTracking()
+ useLayoutEffect(() => {
+ navigation.setOptions({
+ headerTitle: `${auctionResult.title}, ${auctionResult.dateText}`,
+ })
+ }, [navigation])
+
const details = []
const makeRow = (
label: string,
@@ -224,18 +235,21 @@ export const AuctionResult: React.FC = (props) => {
return (
-
-
-
+ {!enableNewNavigation && (
+
+
+
+
+
+ {auctionResult.title}
+
+ {!!auctionResult.dateText && , {auctionResult.dateText}}
-
- {auctionResult.title}
-
- {!!auctionResult.dateText && , {auctionResult.dateText}}
-
+ )}
+
}>
{!!auctionResult?.images?.larger && (
diff --git a/src/app/Scenes/BottomTabs/BottomTabs.tests.tsx b/src/app/Scenes/BottomTabs/BottomTabs.tests.tsx
index ec112bbc052..76f7cec79b1 100644
--- a/src/app/Scenes/BottomTabs/BottomTabs.tests.tsx
+++ b/src/app/Scenes/BottomTabs/BottomTabs.tests.tsx
@@ -62,7 +62,7 @@ describe(BottomTabs, () => {
await flushPromiseQueue()
- expect(screen.queryByLabelText("home visual clue")).toBeTruthy()
+ expect(screen.getByLabelText("home visual clue")).toBeTruthy()
})
it("should NOT be displayed if there are NO unseen notifications", async () => {
@@ -179,8 +179,6 @@ describe(BottomTabs, () => {
// Check badge counters
const currentInboxCounter = await findBadgeCounterForTab("inbox")
expect(currentInboxCounter).toHaveTextContent("3")
-
- jest.useRealTimers()
})
})
diff --git a/src/app/Scenes/BottomTabs/BottomTabsButton.tests.tsx b/src/app/Scenes/BottomTabs/BottomTabsButton.tests.tsx
index 8603b2d7c55..ad61e500ce3 100644
--- a/src/app/Scenes/BottomTabs/BottomTabsButton.tests.tsx
+++ b/src/app/Scenes/BottomTabs/BottomTabsButton.tests.tsx
@@ -1,3 +1,4 @@
+import { Touchable } from "@artsy/palette-mobile"
import { __globalStoreTestUtils__, GlobalStoreProvider } from "app/store/GlobalStore"
import { ModalStack } from "app/system/navigation/ModalStack"
import { switchTab } from "app/system/navigation/navigate"
@@ -5,7 +6,6 @@ import { extractText } from "app/utils/tests/extractText"
import { flushPromiseQueue } from "app/utils/tests/flushPromiseQueue"
import { mockTrackEvent } from "app/utils/tests/globallyMockedStuff"
import { renderWithWrappersLEGACY } from "app/utils/tests/renderWithWrappers"
-import { TouchableWithoutFeedback } from "react-native"
import { BottomTabsButton } from "./BottomTabsButton"
const TestWrapper: React.FC> = (props) => {
@@ -24,7 +24,7 @@ describe(BottomTabsButton, () => {
expect(__globalStoreTestUtils__?.getCurrentState().bottomTabs.sessionState.selectedTab).toBe(
"home"
)
- tree.root.findByType(TouchableWithoutFeedback).props.onPress()
+ tree.root.findByType(Touchable).props.onPress()
await flushPromiseQueue()
expect(switchTab).toHaveBeenCalledWith("search")
})
@@ -32,7 +32,7 @@ describe(BottomTabsButton, () => {
it(`dispatches an analytics action on press`, async () => {
const tree = renderWithWrappersLEGACY()
expect(mockTrackEvent).not.toHaveBeenCalled()
- tree.root.findByType(TouchableWithoutFeedback).props.onPress()
+ tree.root.findByType(Touchable).props.onPress()
await flushPromiseQueue()
expect(switchTab).toHaveBeenCalledWith("sell")
expect(mockTrackEvent).toHaveBeenCalledWith({
diff --git a/src/app/Scenes/BottomTabs/BottomTabsButton.tsx b/src/app/Scenes/BottomTabs/BottomTabsButton.tsx
index ac68f4a6bc6..382af818307 100644
--- a/src/app/Scenes/BottomTabs/BottomTabsButton.tsx
+++ b/src/app/Scenes/BottomTabs/BottomTabsButton.tsx
@@ -1,13 +1,17 @@
import { tappedTabBar } from "@artsy/cohesion"
-import { Flex, PopIn, Text, VisualClueDot, useColor } from "@artsy/palette-mobile"
+import { Flex, PopIn, Text, Touchable, VisualClueDot, useColor } from "@artsy/palette-mobile"
import { ProgressiveOnboardingFindSavedArtwork } from "app/Components/ProgressiveOnboarding/ProgressiveOnboardingFindSavedArtwork"
import { LegacyNativeModules } from "app/NativeModules/LegacyNativeModules"
import { unsafe__getSelectedTab } from "app/store/GlobalStore"
import { VisualClueName } from "app/store/config/visualClues"
import { switchTab } from "app/system/navigation/navigate"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
+import { useIsStaging } from "app/utils/hooks/useIsStaging"
import { useSelectedTab } from "app/utils/hooks/useSelectedTab"
import { useVisualClue } from "app/utils/hooks/useVisualClue"
-import { LayoutAnimation, TouchableWithoutFeedback, View } from "react-native"
+import { useTabBarBadge } from "app/utils/useTabBarBadge"
+import { useMemo } from "react"
+import { GestureResponderEvent, LayoutAnimation, View } from "react-native"
import { useTracking } from "react-tracking"
import styled from "styled-components/native"
import { BottomTabOption, BottomTabType } from "./BottomTabType"
@@ -19,24 +23,72 @@ export interface BottomTabsButtonProps {
badgeCount?: number
visualClue?: VisualClueName
forceDisplayVisualClue?: boolean
+ onPress?: (e: GestureResponderEvent) => void
}
export const BOTTOM_TABS_TEXT_HEIGHT = 15
+// TODO: Improve this component once we remove enableNewNavigation feature flag
+// There are too many rerenders happening in this component
export const BottomTabsButton: React.FC = ({
tab,
- badgeCount = 0,
+ badgeCount: badgeCountProp = 0,
visualClue,
- forceDisplayVisualClue,
+ forceDisplayVisualClue: forceDisplayVisualClueProp,
+ ...buttonProps
}) => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
const selectedTab = useSelectedTab()
+ const color = useColor()
+
const isActive = selectedTab === tab
+ const { unreadConversationsCount, hasUnseenNotifications } = useTabBarBadge()
+
+ const forceDisplayVisualClue = useMemo(() => {
+ if (!enableNewNavigation) {
+ return forceDisplayVisualClueProp
+ }
+
+ if (tab === "home") {
+ return hasUnseenNotifications
+ }
+
+ return false
+ }, [hasUnseenNotifications, forceDisplayVisualClueProp])
+
+ const badgeCount = useMemo(() => {
+ if (!enableNewNavigation) {
+ return badgeCountProp
+ }
+
+ if (tab === "inbox") {
+ return unreadConversationsCount ?? 0
+ }
+
+ return 0
+ }, [unreadConversationsCount, badgeCountProp])
+
const { showVisualClue } = useVisualClue()
const tracking = useTracking()
+ const isStaging = useIsStaging()
+
+ const onPress = (e: GestureResponderEvent) => {
+ if (enableNewNavigation) {
+ buttonProps.onPress?.(e)
+ tracking.trackEvent(
+ tappedTabBar({
+ tab: bottomTabsConfig[tab].analyticsDescription,
+ badge: badgeCount > 0,
+ contextScreenOwnerType: BottomTabOption[selectedTab],
+ })
+ )
+
+ return
+ }
- const onPress = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
if (tab === unsafe__getSelectedTab()) {
LegacyNativeModules.ARScreenPresenterModule.popToRootOrScrollToTop(tab)
@@ -53,17 +105,26 @@ export const BottomTabsButton: React.FC = ({
}
return (
-
-
-
+
+
@@ -127,7 +188,7 @@ export const BottomTabsButton: React.FC = ({
)}
-
+
)
}
diff --git a/src/app/Scenes/City/Components/Event/index.tsx b/src/app/Scenes/City/Components/Event/index.tsx
index 3a6fdd90870..cc7fca2676f 100644
--- a/src/app/Scenes/City/Components/Event/index.tsx
+++ b/src/app/Scenes/City/Components/Event/index.tsx
@@ -1,10 +1,9 @@
-import { Flex, Box, ClassTheme, Text, Button } from "@artsy/palette-mobile"
+import { Box, Button, ClassTheme, Flex, Image, Text } from "@artsy/palette-mobile"
import { EventMutation } from "__generated__/EventMutation.graphql"
-import OpaqueImageView from "app/Components/OpaqueImageView/OpaqueImageView"
import { exhibitionDates } from "app/Scenes/Map/exhibitionPeriodParser"
import { Show } from "app/Scenes/Map/types"
import { navigate } from "app/system/navigation/navigate"
-import { Schema, Track, track as _track } from "app/utils/track"
+import { track as _track, Schema, Track } from "app/utils/track"
import React from "react"
import { TouchableWithoutFeedback } from "react-native"
import { commitMutation, graphql, RelayProp } from "react-relay"
@@ -133,8 +132,8 @@ export class Event extends React.Component {
this.handleTap()}>
{!!url && (
-
-
+
+
)}
diff --git a/src/app/Scenes/CompleteMyProfile/hooks/__tests__/useCompleteProfile.tests.tsx b/src/app/Scenes/CompleteMyProfile/hooks/__tests__/useCompleteProfile.tests.tsx
index ccee8db7172..76a15eb9c30 100644
--- a/src/app/Scenes/CompleteMyProfile/hooks/__tests__/useCompleteProfile.tests.tsx
+++ b/src/app/Scenes/CompleteMyProfile/hooks/__tests__/useCompleteProfile.tests.tsx
@@ -1,9 +1,9 @@
import { useNavigation, useRoute } from "@react-navigation/native"
-import { renderHook, act } from "@testing-library/react-hooks"
+import { act, renderHook } from "@testing-library/react-hooks"
import { useToast } from "app/Components/Toast/toastHook"
import { CompleteMyProfileStore } from "app/Scenes/CompleteMyProfile/CompleteMyProfileProvider"
import { useCompleteProfile } from "app/Scenes/CompleteMyProfile/hooks/useCompleteProfile"
-import { navigate as artsyNavigate } from "app/system/navigation/navigate"
+import { goBack as ArtsyGoBack } from "app/system/navigation/navigate"
import { useUpdateMyProfile } from "app/utils/mutations/useUpdateMyProfile"
jest.mock("@react-navigation/native", () => ({
@@ -17,6 +17,7 @@ jest.mock("app/utils/mutations/useUpdateMyProfile", () => ({
jest.mock("app/system/navigation/navigate", () => ({
navigate: jest.fn(),
+ goBack: jest.fn(),
}))
jest.mock("app/Components/Toast/toastHook", () => ({
@@ -94,7 +95,7 @@ describe("useCompleteProfile", () => {
expect(mockGoBack).toHaveBeenCalled()
})
- it('should navigate to "/my-profile" if cannot go back', () => {
+ it('should navigate back to "/my-profile" if cannot go back', () => {
useNavigationMock.mockReturnValue({
navigate: mockNavigate,
goBack: mockGoBack,
@@ -110,7 +111,7 @@ describe("useCompleteProfile", () => {
result.current.goBack()
})
- expect(artsyNavigate).toHaveBeenCalledWith("/my-profile")
+ expect(ArtsyGoBack).toHaveBeenCalled()
})
it("should save and exit correctly", () => {
diff --git a/src/app/Scenes/CompleteMyProfile/hooks/useCompleteProfile.ts b/src/app/Scenes/CompleteMyProfile/hooks/useCompleteProfile.ts
index c16768080ba..412dec00879 100644
--- a/src/app/Scenes/CompleteMyProfile/hooks/useCompleteProfile.ts
+++ b/src/app/Scenes/CompleteMyProfile/hooks/useCompleteProfile.ts
@@ -11,7 +11,7 @@ import {
CompleteMyProfileStore,
} from "app/Scenes/CompleteMyProfile/CompleteMyProfileProvider"
import { getNextRoute } from "app/Scenes/CompleteMyProfile/hooks/useCompleteMyProfileSteps"
-import { navigate as artsyNavigate } from "app/system/navigation/navigate"
+import { navigate as artsyNavigate, goBack as systemGoBack } from "app/system/navigation/navigate"
import { useUpdateMyProfile } from "app/utils/mutations/useUpdateMyProfile"
import { useMemo } from "react"
@@ -53,7 +53,7 @@ export const useCompleteProfile = () => {
if (canGoBack()) {
_goBack()
} else {
- artsyNavigate("/my-profile")
+ systemGoBack()
}
}
diff --git a/src/app/Scenes/HomeView/HomeView.tsx b/src/app/Scenes/HomeView/HomeView.tsx
index 329b1f02608..1e13e78a4f7 100644
--- a/src/app/Scenes/HomeView/HomeView.tsx
+++ b/src/app/Scenes/HomeView/HomeView.tsx
@@ -22,7 +22,7 @@ import { ProvidePlaceholderContext } from "app/utils/placeholders"
import { usePrefetch } from "app/utils/queryPrefetching"
import { requestPushNotificationsPermission } from "app/utils/requestPushNotificationsPermission"
import { useMaybePromptForReview } from "app/utils/useMaybePromptForReview"
-import { Suspense, useCallback, useEffect, useState } from "react"
+import { RefObject, Suspense, useCallback, useEffect, useState } from "react"
import { FlatList, RefreshControl } from "react-native"
import { fetchQuery, graphql, useLazyLoadQuery, usePaginationFragment } from "react-relay"
@@ -133,7 +133,7 @@ export const HomeView: React.FC = () => {
}
data={sections}
keyExtractor={(item) => item.internalID}
renderItem={({ item, index }) => {
diff --git a/src/app/Scenes/Inbox/Components/Conversations/Composer.tsx b/src/app/Scenes/Inbox/Components/Conversations/Composer.tsx
index 3b67e2747cd..12c826048df 100644
--- a/src/app/Scenes/Inbox/Components/Conversations/Composer.tsx
+++ b/src/app/Scenes/Inbox/Components/Conversations/Composer.tsx
@@ -19,6 +19,8 @@ const Container = styled.View`
align-items: flex-start;
border-top-width: 1px;
border-top-color: ${themeGet("colors.black10")};
+ border-bottom-color: ${themeGet("colors.black10")};
+ border-bottom-width: 1px;
padding: 10px;
background-color: ${(p: ContainerProps) => (p.active ? "white" : themeGet("colors.black5"))};
`
diff --git a/src/app/Scenes/Inbox/Components/Conversations/Messages.tsx b/src/app/Scenes/Inbox/Components/Conversations/Messages.tsx
index 41ab9ed49a2..be12d2a22be 100644
--- a/src/app/Scenes/Inbox/Components/Conversations/Messages.tsx
+++ b/src/app/Scenes/Inbox/Components/Conversations/Messages.tsx
@@ -166,7 +166,7 @@ export const Messages: React.FC = forwardRef((props, ref) => {
inverted
ref={flatList}
keyExtractor={(group) => {
- return group[0].id
+ return group[0].__id
}}
onEndReached={loadMore}
onEndReachedThreshold={0.2}
diff --git a/src/app/Scenes/Inbox/Components/Conversations/OfferSubmittedModal.tests.tsx b/src/app/Scenes/Inbox/Components/Conversations/OfferSubmittedModal.tests.tsx
index 4c93633af17..426ccb8b375 100644
--- a/src/app/Scenes/Inbox/Components/Conversations/OfferSubmittedModal.tests.tsx
+++ b/src/app/Scenes/Inbox/Components/Conversations/OfferSubmittedModal.tests.tsx
@@ -1,11 +1,12 @@
import { act, fireEvent } from "@testing-library/react-native"
-import { goBack, navigate } from "app/system/navigation/navigate"
+import { goBack, switchTab } from "app/system/navigation/navigate"
import { renderWithWrappers } from "app/utils/tests/renderWithWrappers"
import { OfferSubmittedModal } from "./OfferSubmittedModal"
jest.mock("app/system/navigation/navigate", () => ({
navigate: jest.fn(),
goBack: jest.fn(),
+ switchTab: jest.fn(),
}))
let callback: undefined | (([...args]: any) => void)
@@ -37,6 +38,6 @@ describe("OfferSubmittedModal", () => {
act(() => callback?.({ orderCode: "1234", message: "Test message" }))
fireEvent.press(getAllByText("Go to inbox")[0])
- expect(navigate).toHaveBeenCalledWith("inbox")
+ expect(switchTab).toHaveBeenCalledWith("inbox")
})
})
diff --git a/src/app/Scenes/Inbox/Components/Conversations/OfferSubmittedModal.tsx b/src/app/Scenes/Inbox/Components/Conversations/OfferSubmittedModal.tsx
index f2e03449613..2ec9624b912 100644
--- a/src/app/Scenes/Inbox/Components/Conversations/OfferSubmittedModal.tsx
+++ b/src/app/Scenes/Inbox/Components/Conversations/OfferSubmittedModal.tsx
@@ -1,8 +1,8 @@
-import { Box, Text, Button } from "@artsy/palette-mobile"
+import { Box, Button, Text } from "@artsy/palette-mobile"
import { NavigationContainer } from "@react-navigation/native"
import { FancyModal } from "app/Components/FancyModal/FancyModal"
import { FancyModalHeader } from "app/Components/FancyModal/FancyModalHeader"
-import { goBack, navigate } from "app/system/navigation/navigate"
+import { goBack, switchTab } from "app/system/navigation/navigate"
import { useSetWebViewCallback } from "app/utils/useWebViewEvent"
import React, { useState } from "react"
@@ -20,7 +20,7 @@ export const OfferSubmittedModal: React.FC = (props) => {
)
const onGoToInbox = () => {
- navigate("inbox")
+ switchTab("inbox")
setVisible(false)
}
diff --git a/src/app/Scenes/Inbox/Components/Conversations/OpenInquiryModalButton.tsx b/src/app/Scenes/Inbox/Components/Conversations/OpenInquiryModalButton.tsx
index 3596678c4ba..aec2fccde00 100644
--- a/src/app/Scenes/Inbox/Components/Conversations/OpenInquiryModalButton.tsx
+++ b/src/app/Scenes/Inbox/Components/Conversations/OpenInquiryModalButton.tsx
@@ -31,7 +31,13 @@ export const OpenInquiryModalButton: React.FC = ({
-
+
Always complete purchases with our secure checkout in order to be covered by{" "}
{
})
// @ts-ignore
conversation.root.findAllByType(Touchable)[0].props.onPress()
- expect(mockNavigator.push).toHaveBeenCalledWith({
- component: ConversationDetailsQueryRenderer,
- passProps: { conversationID: "123" },
- title: "",
- })
+ expect(navigate).toHaveBeenCalledWith("/conversation/123/details")
})
it("handles a dismissed modal with modalDismiss event", () => {
diff --git a/src/app/Scenes/Inbox/Screens/Conversation.tsx b/src/app/Scenes/Inbox/Screens/Conversation.tsx
index 320f221c059..711e39db0fb 100644
--- a/src/app/Scenes/Inbox/Screens/Conversation.tsx
+++ b/src/app/Scenes/Inbox/Screens/Conversation.tsx
@@ -1,46 +1,29 @@
-import { InfoCircleIcon, Flex, Text, Touchable } from "@artsy/palette-mobile"
+import { BackButton, InfoCircleIcon, Touchable } from "@artsy/palette-mobile"
import NetInfo from "@react-native-community/netinfo"
import { ConversationQuery } from "__generated__/ConversationQuery.graphql"
import { Conversation_me$data } from "__generated__/Conversation_me.graphql"
import ConnectivityBanner from "app/Components/ConnectivityBanner"
import { LoadFailureView } from "app/Components/LoadFailureView"
+import { PageWithSimpleHeader } from "app/Components/PageWithSimpleHeader"
import { ComposerFragmentContainer } from "app/Scenes/Inbox/Components/Conversations/Composer"
import Messages from "app/Scenes/Inbox/Components/Conversations/Messages"
import { sendConversationMessage } from "app/Scenes/Inbox/Components/Conversations/SendConversationMessage"
import { updateConversation } from "app/Scenes/Inbox/Components/Conversations/UpdateConversation"
import { ShadowSeparator } from "app/Scenes/Inbox/Components/ShadowSeparator"
import { GlobalStore } from "app/store/GlobalStore"
-import { navigationEvents } from "app/system/navigation/navigate"
+import { goBack, navigate, navigationEvents } from "app/system/navigation/navigate"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import NavigatorIOS from "app/utils/__legacy_do_not_use__navigator-ios-shim"
import renderWithLoadProgress from "app/utils/renderWithLoadProgress"
-import { Schema, Track, track as _track } from "app/utils/track"
+import { track as _track, Schema, Track } from "app/utils/track"
import React from "react"
-import { View } from "react-native"
import { createRefetchContainer, graphql, QueryRenderer, RelayRefetchProp } from "react-relay"
import styled from "styled-components/native"
-import { ConversationDetailsQueryRenderer } from "./ConversationDetails"
const Container = styled.View`
flex: 1;
flex-direction: column;
`
-const Header = styled.View`
- align-self: stretch;
- margin-top: 22px;
- flex-direction: column;
- margin-bottom: 18px;
-`
-
-// This makes it really easy to style the HeaderTextContainer with space-between
-const PlaceholderView = View
-
-const HeaderTextContainer = styled(Flex)`
- flex-direction: row;
- justify-content: center;
- flex-grow: 1;
- justify-content: space-between;
-`
interface Props {
me: Conversation_me$data
@@ -168,78 +151,70 @@ export class Conversation extends React.Component {
const partnerName = conversation.to.name
return (
- (this.composer = composer)}
- // @ts-expect-error STRICTNESS_MIGRATION --- 🚨 Unsafe legacy code 🚨 Please delete this and fix any type errors if you have time 🙏
- value={this.state.failedMessageText}
- onSubmit={(text) => {
- this.setState({ sendingMessage: true, failedMessageText: null })
- sendConversationMessage(
- this.props.relay.environment,
- // @ts-expect-error STRICTNESS_MIGRATION --- 🚨 Unsafe legacy code 🚨 Please delete this and fix any type errors if you have time 🙏
- conversation,
- text,
- (_response) => {
- this.messageSuccessfullySent(text)
- },
- (error) => {
- this.messageFailedToSend(error, text)
- }
- )
- this.messages.scrollToLastMessage()
- }}
- >
-
-
-
-
-
-
- {partnerName}
-
- {
- this.props.navigator.push({
- component: ConversationDetailsQueryRenderer,
- title: "",
- passProps: {
- conversationID: this.props.me?.conversation?.internalID,
- },
- })
- }}
- hitSlop={{ top: 10, left: 10, right: 10, bottom: 10 }}
- >
-
-
-
-
-
-
- {!this.state.isConnected && }
- (this.messages = messages)}
- conversation={conversation as any}
- onDataFetching={(loading) => {
- this.setState({ fetchingData: loading })
- }}
- onRefresh={() => {
- this.props.relay.refetch(
- { conversationID: conversation?.internalID },
- null,
- (error) => {
- if (error) {
- console.error("Conversation.tsx", error.message)
- }
- },
- { force: true }
- )
+ }
+ noSeparator
+ right={
+ {
+ navigate(`/conversation/${this.props.me?.conversation?.internalID}/details`)
}}
- />
-
-
+ hitSlop={{ top: 10, left: 10, right: 10, bottom: 10 }}
+ >
+
+
+ }
+ >
+ (this.composer = composer)}
+ // @ts-expect-error STRICTNESS_MIGRATION --- 🚨 Unsafe legacy code 🚨 Please delete this and fix any type errors if you have time 🙏
+ value={this.state.failedMessageText}
+ onSubmit={(text) => {
+ this.setState({ sendingMessage: true, failedMessageText: null })
+ sendConversationMessage(
+ this.props.relay.environment,
+ // @ts-expect-error STRICTNESS_MIGRATION --- 🚨 Unsafe legacy code 🚨 Please delete this and fix any type errors if you have time 🙏
+ conversation,
+ text,
+ (_response) => {
+ this.messageSuccessfullySent(text)
+ },
+ (error) => {
+ this.messageFailedToSend(error, text)
+ }
+ )
+ this.messages.scrollToLastMessage()
+ }}
+ >
+
+
+ {!this.state.isConnected && }
+ (this.messages = messages)}
+ conversation={conversation as any}
+ onDataFetching={(loading) => {
+ this.setState({ fetchingData: loading })
+ }}
+ onRefresh={() => {
+ this.props.relay.refetch(
+ { conversationID: conversation?.internalID },
+ null,
+ (error) => {
+ if (error) {
+ console.error("Conversation.tsx", error.message)
+ }
+ },
+ { force: true }
+ )
+ }}
+ />
+
+
+
)
}
}
diff --git a/src/app/Scenes/Inbox/Screens/ConversationDetails.tsx b/src/app/Scenes/Inbox/Screens/ConversationDetails.tsx
index a350270d5c6..433d8fd72f5 100644
--- a/src/app/Scenes/Inbox/Screens/ConversationDetails.tsx
+++ b/src/app/Scenes/Inbox/Screens/ConversationDetails.tsx
@@ -11,7 +11,9 @@ import { ShippingFragmentContainer } from "app/Scenes/Inbox/Components/Conversat
import { Support } from "app/Scenes/Inbox/Components/Conversations/Support"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import { extractNodes } from "app/utils/extractNodes"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import renderWithLoadProgress from "app/utils/renderWithLoadProgress"
+import { Fragment } from "react"
import { ScrollView } from "react-native"
import { createFragmentContainer, graphql, QueryRenderer, RelayProp } from "react-relay"
@@ -21,37 +23,34 @@ interface Props {
}
export const ConversationDetails: React.FC = ({ me }) => {
const conversation = me.conversation
- const partnerName = conversation?.to.name
const item = conversation?.items?.[0]?.item
const order = extractNodes(conversation?.orderConnection)
const orderItem = order[0]
return (
-
-
-
- {!!orderItem && }
+
+
+ {!!orderItem && }
- {!!item && }
+ {!!item && }
- {!!item && !!orderItem && (
-
- )}
+ {!!item && !!orderItem && (
+
+ )}
- {!!orderItem && (
- <>
-
-
- >
- )}
+ {!!orderItem && (
+ <>
+
+
+ >
+ )}
- {!!conversation && }
+ {!!conversation && }
-
-
-
-
+
+
+
)
}
@@ -90,20 +89,30 @@ export const ConversationDetailsFragmentContainer = createFragmentContainer(Conv
export const ConversationDetailsQueryRenderer: React.FC<{
conversationID: string
}> = ({ conversationID }) => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
+
return (
-
- environment={getRelayEnvironment()}
- query={graphql`
- query ConversationDetailsQuery($conversationID: String!) {
- me {
- ...ConversationDetails_me
+
+
+ environment={getRelayEnvironment()}
+ query={graphql`
+ query ConversationDetailsQuery($conversationID: String!) {
+ me {
+ ...ConversationDetails_me
+ }
}
- }
- `}
- variables={{
- conversationID,
- }}
- render={renderWithLoadProgress(ConversationDetailsFragmentContainer)}
- />
+ `}
+ variables={{
+ conversationID,
+ }}
+ render={renderWithLoadProgress(ConversationDetailsFragmentContainer)}
+ />
+
)
}
diff --git a/src/app/Scenes/Map/MapRenderer.tsx b/src/app/Scenes/Map/MapRenderer.tsx
index 71ac22cc4ff..0f70eefe6c1 100644
--- a/src/app/Scenes/Map/MapRenderer.tsx
+++ b/src/app/Scenes/Map/MapRenderer.tsx
@@ -50,7 +50,7 @@ export const MapRenderer: React.FC<{
error,
retry: () => {
isRetrying = true
- retry!()
+ retry?.()
},
isRetrying,
}}
diff --git a/src/app/Scenes/MyAccount/MyAccount.tests.tsx b/src/app/Scenes/MyAccount/MyAccount.tests.tsx
index 5e8a735af13..9982d42f160 100644
--- a/src/app/Scenes/MyAccount/MyAccount.tests.tsx
+++ b/src/app/Scenes/MyAccount/MyAccount.tests.tsx
@@ -1,6 +1,7 @@
import { Text } from "@artsy/palette-mobile"
import { MyAccountTestsQuery } from "__generated__/MyAccountTestsQuery.graphql"
import { MenuItem } from "app/Components/MenuItem"
+import { __globalStoreTestUtils__ } from "app/store/GlobalStore"
import { extractText } from "app/utils/tests/extractText"
import { renderWithWrappersLEGACY } from "app/utils/tests/renderWithWrappers"
import { Platform } from "react-native"
@@ -58,6 +59,9 @@ describe(MyAccountQueryRenderer, () => {
)
beforeEach(() => {
mockEnvironment = createMockEnvironment()
+ __globalStoreTestUtils__?.injectFeatureFlags({
+ AREnableNewNavigation: true,
+ })
})
it("truncated long emails", () => {
@@ -76,7 +80,7 @@ describe(MyAccountQueryRenderer, () => {
return result
})
- expect(tree.findAllByType(Text)[2].props.children).toBe(
+ expect(tree.findAllByType(Text)[1].props.children).toBe(
"myverylongemailmyverylongemailmyverylongemail@averylongdomainaverylongdomainaverylongdomain.com"
)
})
diff --git a/src/app/Scenes/MyAccount/MyAccount.tsx b/src/app/Scenes/MyAccount/MyAccount.tsx
index c5025e0a763..1b495a98d9c 100644
--- a/src/app/Scenes/MyAccount/MyAccount.tsx
+++ b/src/app/Scenes/MyAccount/MyAccount.tsx
@@ -1,4 +1,4 @@
-import { Spacer, Flex, Box, Text, Button } from "@artsy/palette-mobile"
+import { Box, Button, Flex, Spacer, Text } from "@artsy/palette-mobile"
import { MyAccountQuery } from "__generated__/MyAccountQuery.graphql"
import { MyAccount_me$data } from "__generated__/MyAccount_me.graphql"
import { MenuItem } from "app/Components/MenuItem"
@@ -9,15 +9,18 @@ import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import { useAppleLink } from "app/utils/LinkedAccounts/apple"
import { useFacebookLink } from "app/utils/LinkedAccounts/facebook"
import { useGoogleLink } from "app/utils/LinkedAccounts/google"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { PlaceholderText } from "app/utils/placeholders"
import { renderWithPlaceholder } from "app/utils/renderWithPlaceholder"
import { times } from "lodash"
+import { Fragment } from "react"
import { ActivityIndicator, Image, Platform, ScrollView } from "react-native"
import { createFragmentContainer, graphql, QueryRenderer, RelayProp } from "react-relay"
import { PRICE_BUCKETS } from "./MyAccountEditPriceRange"
const MyAccount: React.FC<{ me: MyAccount_me$data; relay: RelayProp }> = ({ me, relay }) => {
const hasOnlyOneAuth = me.authentications.length + (me.hasPassword ? 1 : 0) < 2
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
const onlyExistingAuthFor = (provider: "FACEBOOK" | "GOOGLE" | "APPLE") => {
return (
@@ -69,8 +72,14 @@ const MyAccount: React.FC<{ me: MyAccount_me$data; relay: RelayProp }> = ({ me,
? PRICE_BUCKETS.find((i) => me.priceRange === i.value)?.label ?? "Select a price range"
: "Select a price range"
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
+
return (
-
+
-
+
)
}
const MyAccountPlaceholder: React.FC = () => {
return (
-
-
- {times(5).map((index: number) => (
-
-
-
- ))}
-
-
+
+ {times(5).map((index: number) => (
+
+
+
+ ))}
+
)
}
diff --git a/src/app/Scenes/MyAccount/MyAccountEditEmail.tests.tsx b/src/app/Scenes/MyAccount/MyAccountEditEmail.tests.tsx
index dd4cd1c8aac..0c47b3303e6 100644
--- a/src/app/Scenes/MyAccount/MyAccountEditEmail.tests.tsx
+++ b/src/app/Scenes/MyAccount/MyAccountEditEmail.tests.tsx
@@ -1,7 +1,6 @@
-import { fireEvent } from "@testing-library/react-native"
+import { screen } from "@testing-library/react-native"
import { MyAccountEditEmailTestsQuery } from "__generated__/MyAccountEditEmailTestsQuery.graphql"
import { flushPromiseQueue } from "app/utils/tests/flushPromiseQueue"
-import { resolveMostRecentRelayOperation } from "app/utils/tests/resolveMostRecentRelayOperation"
import { setupTestWrapper } from "app/utils/tests/setupTestWrapper"
import { graphql } from "react-relay"
import { MyAccountEditEmailContainer, MyAccountEditEmailQueryRenderer } from "./MyAccountEditEmail"
@@ -40,8 +39,8 @@ describe(MyAccountEditEmailQueryRenderer, () => {
`,
})
- it("shows confirm email toast when email is changed", async () => {
- const { getByText, getByLabelText, env } = renderWithRelay({
+ it("show email input", async () => {
+ renderWithRelay({
Me: () => ({
email: "old-email@test.com",
}),
@@ -49,62 +48,6 @@ describe(MyAccountEditEmailQueryRenderer, () => {
await flushPromiseQueue()
- expect(getByText("Email")).toBeTruthy()
-
- const input = getByLabelText("email-input")
- expect(input.props.value).toEqual("old-email@test.com")
-
- fireEvent.changeText(input, "new-email@test.com")
- expect(input.props.value).toEqual("new-email@test.com")
-
- const saveButton = getByLabelText("save-button")
- fireEvent.press(saveButton)
-
- resolveMostRecentRelayOperation(env, {
- Me: () => ({
- email: "new-email@test.com",
- }),
- })
-
- await flushPromiseQueue()
-
- expect(mockShow).toHaveBeenCalledWith(
- "Please confirm your new email for this update to take effect",
- "middle",
- {
- duration: "long",
- }
- )
- })
-
- it("does not show confirm email toast when email did not change", async () => {
- const { getByText, getByLabelText, env } = renderWithRelay({
- Me: () => ({
- email: "old-email@test.com",
- }),
- })
-
- await flushPromiseQueue()
-
- expect(getByText("Email")).toBeTruthy()
-
- const input = getByLabelText("email-input")
- expect(input.props.value).toEqual("old-email@test.com")
-
- fireEvent.changeText(input, "old-email@test.com")
- expect(input.props.value).toEqual("old-email@test.com")
-
- const saveButton = getByLabelText("save-button")
- fireEvent.press(saveButton)
-
- resolveMostRecentRelayOperation(env, {
- Me: () => ({
- email: "old-email@test.com",
- }),
- })
-
- await flushPromiseQueue()
-
- expect(mockShow).not.toHaveBeenCalled()
+ expect(screen.getByLabelText("email-input")).toBeTruthy()
})
})
diff --git a/src/app/Scenes/MyAccount/MyAccountEditEmail.tsx b/src/app/Scenes/MyAccount/MyAccountEditEmail.tsx
index f08cf49d165..d60fc4627fa 100644
--- a/src/app/Scenes/MyAccount/MyAccountEditEmail.tsx
+++ b/src/app/Scenes/MyAccount/MyAccountEditEmail.tsx
@@ -1,17 +1,18 @@
-import { Input } from "@artsy/palette-mobile"
+import { Flex, Input, Text, Touchable } from "@artsy/palette-mobile"
+import { useNavigation } from "@react-navigation/native"
import { MyAccountEditEmailQuery } from "__generated__/MyAccountEditEmailQuery.graphql"
import { MyAccountEditEmail_me$data } from "__generated__/MyAccountEditEmail_me.graphql"
+import { PageWithSimpleHeader } from "app/Components/PageWithSimpleHeader"
import { useToast } from "app/Components/Toast/toastHook"
+import { goBack } from "app/system/navigation/navigate"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { PlaceholderBox } from "app/utils/placeholders"
import { renderWithPlaceholder } from "app/utils/renderWithPlaceholder"
-import React, { useEffect, useRef, useState } from "react"
+import React, { Fragment, useEffect, useRef, useState } from "react"
import { createFragmentContainer, graphql, QueryRenderer, RelayProp } from "react-relay"
import { string } from "yup"
-import {
- MyAccountFieldEditScreen,
- MyAccountFieldEditScreenPlaceholder,
-} from "./Components/MyAccountFieldEditScreen"
+import { MyAccountFieldEditScreen } from "./Components/MyAccountFieldEditScreen"
import { updateMyUserProfile } from "./updateMyUserProfile"
const MyAccountEditEmail: React.FC<{ me: MyAccountEditEmail_me$data; relay: RelayProp }> = ({
@@ -19,7 +20,10 @@ const MyAccountEditEmail: React.FC<{ me: MyAccountEditEmail_me$data; relay: Rela
relay,
}) => {
const [email, setEmail] = useState(me.email ?? "")
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
const [receivedError, setReceivedError] = useState(undefined)
+ const navigation = useNavigation()
useEffect(() => {
setReceivedError(undefined)
@@ -27,56 +31,89 @@ const MyAccountEditEmail: React.FC<{ me: MyAccountEditEmail_me$data; relay: Rela
const isEmailValid = Boolean(email && string().email().isValidSync(email))
+ useEffect(() => {
+ navigation.setOptions({
+ headerRight: () => {
+ return (
+
+
+ Save
+
+
+ )
+ },
+ })
+ }, [navigation, email])
+
+ const handleSave = async () => {
+ try {
+ await updateMyUserProfile({ email }, relay.environment)
+
+ if (email !== me.email) {
+ toast.show("Please confirm your new email for this update to take effect", "middle", {
+ duration: "long",
+ })
+ }
+
+ goBack()
+ } catch (e: any) {
+ setReceivedError(e)
+ }
+ }
+
const editScreenRef = useRef(null)
const toast = useToast()
- return (
- {
- try {
- await updateMyUserProfile({ email }, relay.environment)
-
- if (email !== me.email) {
- toast.show("Please confirm your new email for this update to take effect", "middle", {
- duration: "long",
- })
- }
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+ )
- dismiss()
- } catch (e: any) {
- setReceivedError(e)
- }
- }}
- >
- {
- if (isEmailValid) {
- editScreenRef.current?.save()
- }
- }}
- error={receivedError}
- />
-
+ return (
+
+
+ {
+ if (isEmailValid) {
+ editScreenRef.current?.save()
+ }
+ }}
+ error={receivedError}
+ />
+
+
)
}
const MyAccountEditEmailPlaceholder: React.FC<{}> = ({}) => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
+
return (
-
+
-
+
)
}
diff --git a/src/app/Scenes/MyAccount/MyAccountEditPassword.tests.tsx b/src/app/Scenes/MyAccount/MyAccountEditPassword.tests.tsx
index 65489cc56a1..eea1404fe7a 100644
--- a/src/app/Scenes/MyAccount/MyAccountEditPassword.tests.tsx
+++ b/src/app/Scenes/MyAccount/MyAccountEditPassword.tests.tsx
@@ -1,11 +1,18 @@
-import { renderWithWrappersLEGACY } from "app/utils/tests/renderWithWrappers"
-import { MyAccountFieldEditScreen } from "./Components/MyAccountFieldEditScreen"
+import { screen } from "@testing-library/react-native"
+import { __globalStoreTestUtils__ } from "app/store/GlobalStore"
+import { renderWithWrappers } from "app/utils/tests/renderWithWrappers"
import { MyAccountEditPassword } from "./MyAccountEditPassword"
describe(MyAccountEditPassword, () => {
- it("has the right title", () => {
- const tree = renderWithWrappersLEGACY()
+ jest.clearAllMocks()
+ __globalStoreTestUtils__?.injectFeatureFlags({
+ AREnableNewNavigation: true,
+ })
+
+ it("has the right titles", () => {
+ renderWithWrappers()
- expect(tree.root.findByType(MyAccountFieldEditScreen).props.title).toEqual("Password")
+ expect(screen.getByText("Current password")).toBeTruthy()
+ expect(screen.getByText("New password")).toBeTruthy()
})
})
diff --git a/src/app/Scenes/MyAccount/MyAccountEditPassword.tsx b/src/app/Scenes/MyAccount/MyAccountEditPassword.tsx
index e59f37918b8..1f1c1a83437 100644
--- a/src/app/Scenes/MyAccount/MyAccountEditPassword.tsx
+++ b/src/app/Scenes/MyAccount/MyAccountEditPassword.tsx
@@ -1,20 +1,21 @@
-import { Flex, Input, Separator } from "@artsy/palette-mobile"
+import { Flex, Input, Separator, Text, Touchable } from "@artsy/palette-mobile"
+import { useNavigation } from "@react-navigation/native"
import { Stack } from "app/Components/Stack"
+import { MyAccountFieldEditScreen } from "app/Scenes/MyAccount/Components/MyAccountFieldEditScreen"
import { getCurrentEmissionState, GlobalStore, unsafe__getEnvironment } from "app/store/GlobalStore"
-import React, { useEffect, useState } from "react"
-import { Alert, Text } from "react-native"
-import {
- MyAccountFieldEditScreen,
- MyAccountFieldEditScreenProps,
-} from "./Components/MyAccountFieldEditScreen"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
+import React, { Fragment, useEffect, useState } from "react"
+import { Alert } from "react-native"
export const MyAccountEditPassword: React.FC<{}> = ({}) => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
const [currentPassword, setCurrentPassword] = useState("")
const [newPassword, setNewPassword] = useState("")
const [passwordConfirmation, setPasswordConfirmation] = useState("")
const [receivedErrorCurrent, setReceivedErrorCurrent] = useState(undefined)
const [receivedErrorNew, setReceivedErrorNew] = useState(undefined)
const [receivedErrorConfirm, setReceivedErrorConfirm] = useState(undefined)
+ const navigation = useNavigation()
// resetting the errors when user types
useEffect(() => {
@@ -27,7 +28,7 @@ export const MyAccountEditPassword: React.FC<{}> = ({}) => {
setReceivedErrorConfirm(undefined)
}, [passwordConfirmation])
- const onSave: MyAccountFieldEditScreenProps["onSave"] = async () => {
+ const handleSave = async () => {
const { authenticationToken, userAgent } = getCurrentEmissionState()
const { gravityURL } = unsafe__getEnvironment()
if (newPassword !== passwordConfirmation) {
@@ -86,47 +87,74 @@ export const MyAccountEditPassword: React.FC<{}> = ({}) => {
}
}
+ useEffect(() => {
+ const isValid = Boolean(currentPassword && newPassword && passwordConfirmation)
+
+ navigation.setOptions({
+ headerRight: () => {
+ return (
+
+
+ Save
+
+
+ )
+ },
+ })
+ }, [navigation, currentPassword, newPassword, passwordConfirmation])
+
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+ )
+
return (
-
-
-
+
+
+
+
+
+
+
+
+ Password must include at least one uppercase letter, one lowercase letter, and one
+ number.
+
+
+
+
-
-
-
- Password must include at least one uppercase letter, one lowercase letter, and one number.
-
-
-
-
-
+
)
}
diff --git a/src/app/Scenes/MyAccount/MyAccountEditPhone.tsx b/src/app/Scenes/MyAccount/MyAccountEditPhone.tsx
index f4a78973bd2..921c24a5ef0 100644
--- a/src/app/Scenes/MyAccount/MyAccountEditPhone.tsx
+++ b/src/app/Scenes/MyAccount/MyAccountEditPhone.tsx
@@ -1,22 +1,43 @@
+import { Flex, Text, Touchable } from "@artsy/palette-mobile"
+import { useNavigation } from "@react-navigation/native"
import { MyAccountEditPhoneQuery } from "__generated__/MyAccountEditPhoneQuery.graphql"
import { MyAccountEditPhone_me$data } from "__generated__/MyAccountEditPhone_me.graphql"
import { PhoneInput } from "app/Components/Input/PhoneInput/PhoneInput"
+import { PageWithSimpleHeader } from "app/Components/PageWithSimpleHeader"
+import { MyAccountFieldEditScreen } from "app/Scenes/MyAccount/Components/MyAccountFieldEditScreen"
+import { goBack } from "app/system/navigation/navigate"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { PlaceholderBox } from "app/utils/placeholders"
import { renderWithPlaceholder } from "app/utils/renderWithPlaceholder"
-import React, { useEffect, useState } from "react"
+import React, { Fragment, useEffect, useState } from "react"
import { createFragmentContainer, graphql, QueryRenderer } from "react-relay"
-import {
- MyAccountFieldEditScreen,
- MyAccountFieldEditScreenPlaceholder,
-} from "./Components/MyAccountFieldEditScreen"
import { updateMyUserProfile } from "./updateMyUserProfile"
const MyAccountEditPhone: React.FC<{ me: MyAccountEditPhone_me$data }> = ({ me }) => {
+ const navigation = useNavigation()
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
const [phone, setPhone] = useState(me.phone ?? "")
const [receivedError, setReceivedError] = useState(undefined)
const [isValidNumber, setIsValidNumber] = useState(false)
+ useEffect(() => {
+ const isValid = canSave()
+
+ navigation.setOptions({
+ headerRight: () => {
+ return (
+
+
+ Save
+
+
+ )
+ },
+ })
+ }, [navigation, phone, isValidNumber])
+
const canSave = () => {
if (!isValidNumber && !!phone.trim()) {
return false
@@ -29,36 +50,50 @@ const MyAccountEditPhone: React.FC<{ me: MyAccountEditPhone_me$data }> = ({ me }
setReceivedError(undefined)
}, [phone])
+ const handleSave = async () => {
+ try {
+ await updateMyUserProfile({ phone })
+ goBack()
+ } catch (e: any) {
+ setReceivedError(e)
+ }
+ }
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+ )
+
return (
- {
- try {
- await updateMyUserProfile({ phone })
- dismiss()
- } catch (e: any) {
- setReceivedError(e)
- }
- }}
- >
-
-
+
+
+
+
+
)
}
const MyAccountEditPhonePlaceholder: React.FC<{}> = ({}) => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
+
return (
-
+
-
+
)
}
diff --git a/src/app/Scenes/MyAccount/MyAccountEditPriceRange.tests.tsx b/src/app/Scenes/MyAccount/MyAccountEditPriceRange.tests.tsx
index 86a82796a0b..d810fcacda6 100644
--- a/src/app/Scenes/MyAccount/MyAccountEditPriceRange.tests.tsx
+++ b/src/app/Scenes/MyAccount/MyAccountEditPriceRange.tests.tsx
@@ -1,4 +1,6 @@
+import { screen } from "@testing-library/react-native"
import { MyAccountEditPriceRangeTestsQuery } from "__generated__/MyAccountEditPriceRangeTestsQuery.graphql"
+import { __globalStoreTestUtils__ } from "app/store/GlobalStore"
import { flushPromiseQueue } from "app/utils/tests/flushPromiseQueue"
import { setupTestWrapper } from "app/utils/tests/setupTestWrapper"
import { graphql } from "react-relay"
@@ -10,6 +12,9 @@ import {
describe(MyAccountEditPriceRangeQueryRenderer, () => {
beforeEach(() => {
jest.clearAllMocks()
+ __globalStoreTestUtils__?.injectFeatureFlags({
+ AREnableNewNavigation: true,
+ })
})
const { renderWithRelay } = setupTestWrapper({
@@ -29,7 +34,7 @@ describe(MyAccountEditPriceRangeQueryRenderer, () => {
})
it("submits the changes", async () => {
- const { getAllByText, getByText } = renderWithRelay({
+ renderWithRelay({
Me: () => ({
priceRange: "-1:2500",
priceRangeMax: 2500,
@@ -39,8 +44,8 @@ describe(MyAccountEditPriceRangeQueryRenderer, () => {
await flushPromiseQueue()
- expect(getAllByText("Price Range")[0]).toBeTruthy()
+ expect(screen.getAllByText("Price Range")[0]).toBeTruthy()
- expect(getByText("Under $2,500")).toBeTruthy()
+ expect(screen.getByText("Under $2,500")).toBeTruthy()
})
})
diff --git a/src/app/Scenes/MyAccount/MyAccountEditPriceRange.tsx b/src/app/Scenes/MyAccount/MyAccountEditPriceRange.tsx
index a6b49162070..ac911fd44d3 100644
--- a/src/app/Scenes/MyAccount/MyAccountEditPriceRange.tsx
+++ b/src/app/Scenes/MyAccount/MyAccountEditPriceRange.tsx
@@ -1,20 +1,25 @@
+import { Flex, Text, Touchable } from "@artsy/palette-mobile"
+import { useNavigation } from "@react-navigation/native"
import { MyAccountEditPriceRangeQuery } from "__generated__/MyAccountEditPriceRangeQuery.graphql"
import { MyAccountEditPriceRange_me$data } from "__generated__/MyAccountEditPriceRange_me.graphql"
+import { PageWithSimpleHeader } from "app/Components/PageWithSimpleHeader"
import { Select, SelectOption } from "app/Components/Select"
+import { MyAccountFieldEditScreen } from "app/Scenes/MyAccount/Components/MyAccountFieldEditScreen"
+import { goBack } from "app/system/navigation/navigate"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { PlaceholderBox } from "app/utils/placeholders"
import { renderWithPlaceholder } from "app/utils/renderWithPlaceholder"
-import React, { useEffect, useState } from "react"
+import React, { Fragment, useEffect, useState } from "react"
import { createFragmentContainer, graphql, QueryRenderer } from "react-relay"
-import {
- MyAccountFieldEditScreen,
- MyAccountFieldEditScreenPlaceholder,
-} from "./Components/MyAccountFieldEditScreen"
import { updateMyUserProfile } from "./updateMyUserProfile"
const MyAccountEditPriceRange: React.FC<{
me: MyAccountEditPriceRange_me$data
}> = ({ me }) => {
+ const navigation = useNavigation()
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
const [receivedError, setReceivedError] = useState(undefined)
const [priceRange, setPriceRange] = useState(me.priceRange ?? "")
const [priceRangeMax, setPriceRangeMax] = useState(me.priceRangeMax)
@@ -24,45 +29,89 @@ const MyAccountEditPriceRange: React.FC<{
setReceivedError(undefined)
}, [priceRange])
+ useEffect(() => {
+ const isValid = !!priceRange && priceRange !== me.priceRange
+
+ navigation.setOptions({
+ headerRight: () => {
+ return (
+
+
+ Save
+
+
+ )
+ },
+ })
+ }, [navigation, priceRange, me.priceRange])
+
+ const handleSave = async () => {
+ try {
+ await updateMyUserProfile({ priceRangeMin, priceRangeMax })
+ goBack()
+ } catch (e: any) {
+ setReceivedError(e)
+ }
+ }
+
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {
+ try {
+ await updateMyUserProfile({ priceRangeMin, priceRangeMax })
+ dismiss()
+ } catch (e: any) {
+ setReceivedError(e)
+ }
+ }}
+ >
+ {children}
+
+ )
+
return (
- {
- try {
- await updateMyUserProfile({ priceRangeMin, priceRangeMax })
- dismiss()
- } catch (e: any) {
- setReceivedError(e)
- }
- }}
- >
-
+ setPriceRangeMin(priceRangeMinFin)
+ setPriceRangeMax(priceRangeMaxFin)
+ }}
+ hasError={!!receivedError}
+ />
+
+
)
}
const MyAccountEditPriceRangePlaceholder: React.FC<{}> = ({}) => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
+
return (
-
+
-
+
)
}
diff --git a/src/app/Scenes/MyBids/Components/SaleCard.tsx b/src/app/Scenes/MyBids/Components/SaleCard.tsx
index 1f23e8de699..61040b501a9 100644
--- a/src/app/Scenes/MyBids/Components/SaleCard.tsx
+++ b/src/app/Scenes/MyBids/Components/SaleCard.tsx
@@ -4,16 +4,17 @@ import {
ClockFill,
ExclamationMarkCircleFill,
Flex,
- Text,
+ Image,
Separator,
+ Text,
Touchable,
} from "@artsy/palette-mobile"
import { SaleCard_me$data } from "__generated__/SaleCard_me.graphql"
import { SaleCard_sale$data } from "__generated__/SaleCard_sale.graphql"
-import OpaqueImageView from "app/Components/OpaqueImageView/OpaqueImageView"
import { CompleteRegistrationCTAWrapper } from "app/Scenes/MyBids/Components/CompleteRegistrationCTAWrapper"
import { SaleInfo } from "app/Scenes/MyBids/Components/SaleInfo"
import { navigate } from "app/system/navigation/navigate"
+import { Dimensions } from "react-native"
import { createFragmentContainer, graphql } from "react-relay"
import { useTracking } from "react-tracking"
@@ -109,7 +110,14 @@ export const SaleCard: React.FC = ({
}}
>
-
+ {!!sale?.coverImage?.url && (
+
+ )}
+
{!!sale.partner?.name && (
diff --git a/src/app/Scenes/MyCollection/Screens/Artwork/MyCollectionArtwork.tsx b/src/app/Scenes/MyCollection/Screens/Artwork/MyCollectionArtwork.tsx
index 196970d2784..a0e6fb2112b 100644
--- a/src/app/Scenes/MyCollection/Screens/Artwork/MyCollectionArtwork.tsx
+++ b/src/app/Scenes/MyCollection/Screens/Artwork/MyCollectionArtwork.tsx
@@ -6,7 +6,7 @@ import { RetryErrorBoundary } from "app/Components/RetryErrorBoundary"
import { MyCollectionArtworkAboutWork } from "app/Scenes/MyCollection/Screens/Artwork/Components/ArtworkAbout/MyCollectionArtworkAboutWork"
import { MyCollectionArtworkArticles } from "app/Scenes/MyCollection/Screens/Artwork/Components/ArtworkAbout/MyCollectionArtworkArticles"
import { GlobalStore } from "app/store/GlobalStore"
-import { goBack, navigate, popToRoot } from "app/system/navigation/navigate"
+import { goBack, navigate } from "app/system/navigation/navigate"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import { extractNodes } from "app/utils/extractNodes"
import { getVortexMedium } from "app/utils/marketPriceInsightHelpers"
@@ -72,7 +72,6 @@ const MyCollectionArtwork: React.FC = ({
navigate(`my-collection/artworks/${artwork?.internalID}/edit`, {
passProps: {
mode: "edit",
- onDelete: popToRoot,
},
})
}, [artwork])
diff --git a/src/app/Scenes/MyCollection/Screens/ArtworkForm/Components/MyCollectionArtworkFormDeleteArtworkModal.tsx b/src/app/Scenes/MyCollection/Screens/ArtworkForm/Components/MyCollectionArtworkFormDeleteArtworkModal.tsx
index 36efd702829..9f13052d5df 100644
--- a/src/app/Scenes/MyCollection/Screens/ArtworkForm/Components/MyCollectionArtworkFormDeleteArtworkModal.tsx
+++ b/src/app/Scenes/MyCollection/Screens/ArtworkForm/Components/MyCollectionArtworkFormDeleteArtworkModal.tsx
@@ -8,7 +8,8 @@ import {
Text,
} from "@artsy/palette-mobile"
import { MyCollectionArtworkFormDeleteArtworkModalQuery } from "__generated__/MyCollectionArtworkFormDeleteArtworkModalQuery.graphql"
-import { NoFallback, SpinnerFallback, withSuspense } from "app/utils/hooks/withSuspense"
+import LoadingModal from "app/Components/Modals/LoadingModal"
+import { NoFallback, withSuspense } from "app/utils/hooks/withSuspense"
import { useState } from "react"
import { Modal } from "react-native"
import { graphql, useLazyLoadQuery } from "react-relay"
@@ -105,6 +106,6 @@ export const MyCollectionArtworkFormDeleteArtworkModal: React.FC
)
},
- LoadingFallback: SpinnerFallback,
+ LoadingFallback: LoadingModal,
ErrorFallback: NoFallback,
})
diff --git a/src/app/Scenes/MyCollection/Screens/MyCollectionAddCollectedArtists/MyCollectionAddCollectedArtists.tsx b/src/app/Scenes/MyCollection/Screens/MyCollectionAddCollectedArtists/MyCollectionAddCollectedArtists.tsx
index c33e9250c3f..e49a6fed617 100644
--- a/src/app/Scenes/MyCollection/Screens/MyCollectionAddCollectedArtists/MyCollectionAddCollectedArtists.tsx
+++ b/src/app/Scenes/MyCollection/Screens/MyCollectionAddCollectedArtists/MyCollectionAddCollectedArtists.tsx
@@ -6,12 +6,15 @@ import { MyCollectionAddCollectedArtistsAutosuggest } from "app/Scenes/MyCollect
import { MyCollectionAddCollectedArtistsStore } from "app/Scenes/MyCollection/Screens/MyCollectionAddCollectedArtists/MyCollectionAddCollectedArtistsStore"
import { useSubmitMyCollectionArtists } from "app/Scenes/MyCollection/hooks/useSubmitMyCollectionArtists"
import { dismissModal, goBack } from "app/system/navigation/navigate"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { pluralize } from "app/utils/pluralize"
import { refreshMyCollection } from "app/utils/refreshHelpers"
import { Suspense } from "react"
export const MyCollectionAddCollectedArtists: React.FC<{}> = () => {
const { bottom } = useScreenDimensions().safeAreaInsets
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
const toast = useToast()
const { submit, isSubmitting: isLoading } = useSubmitMyCollectionArtists(
"MyCollectionAddCollectedArtists"
@@ -33,9 +36,13 @@ export const MyCollectionAddCollectedArtists: React.FC<{}> = () => {
return (
-
- Add Artists You Collect
-
+ {enableNewNavigation ? (
+
+ ) : (
+
+ Add Artists You Collect
+
+ )}
null}>
diff --git a/src/app/Scenes/MyProfile/MyProfile.tsx b/src/app/Scenes/MyProfile/MyProfile.tsx
index 73d6776588c..5f461358dde 100644
--- a/src/app/Scenes/MyProfile/MyProfile.tsx
+++ b/src/app/Scenes/MyProfile/MyProfile.tsx
@@ -1,12 +1,14 @@
import { NavigationContainer } from "@react-navigation/native"
-import { createStackNavigator } from "@react-navigation/stack"
+import { createStackNavigator, StackScreenProps } from "@react-navigation/stack"
import { MyCollectionArtworkForm } from "app/Scenes/MyCollection/Screens/ArtworkForm/MyCollectionArtworkForm"
import { MyProfileEditFormScreen } from "./MyProfileEditForm"
import { MyProfileHeaderMyCollectionAndSavedWorksQueryRenderer } from "./MyProfileHeaderMyCollectionAndSavedWorks"
const Stack = createStackNavigator()
-export const MyProfile = () => {
+type MyProfileProps = StackScreenProps
+
+export const MyProfile: React.FC = () => {
return (
void
}
-export const MyProfileEditForm: React.FC = ({ onSuccess }) => {
+export const MyProfileEditForm: React.FC = () => {
const { trackEvent } = useTracking()
const data = useLazyLoadQuery(MyProfileEditFormScreenQuery, {})
const { updateProfile, isLoading, setIsLoading } = useEditProfile()
@@ -127,7 +129,9 @@ export const MyProfileEditForm: React.FC = ({ onSuccess
initialValues: {
name: me?.name ?? "",
displayLocation: { display: buildLocationDisplay(me?.location ?? null) },
- location: { ...me?.location },
+ location: {
+ ...me?.location,
+ },
profession: me?.profession ?? "",
otherRelevantPositions: me?.otherRelevantPositions ?? "",
photo: me?.icon?.url || "",
@@ -145,9 +149,7 @@ export const MyProfileEditForm: React.FC = ({ onSuccess
setIsLoading(false)
}
- InteractionManager.runAfterInteractions(() => {
- onSuccess?.()
- })
+ fetchProfileData()
navigation.goBack()
},
validationSchema: editMyProfileSchema,
@@ -184,22 +186,10 @@ export const MyProfileEditForm: React.FC = ({ onSuccess
}
}, [showVerificationBannerForEmail, showVerificationBannerForID])
- const onLeftButtonPressHandler = () => {
- navigation.goBack()
- }
-
const showCompleteYourProfileBanner = !me?.collectorProfile?.isProfileComplete
return (
<>
-
- Edit Profile
-
-
{!!showCompleteYourProfileBanner && (
= (props) => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
+
return (
-
- }>
-
-
-
+
+
+ }>
+
+
+
+
)
}
const LoadingSkeleton = () => {
return (
-
-
- Edit Profile
-
-
-
-
+
diff --git a/src/app/Scenes/MyProfile/MyProfileHeader.tests.tsx b/src/app/Scenes/MyProfile/MyProfileHeader.tests.tsx
index d01818bb91c..d6f4b72db48 100644
--- a/src/app/Scenes/MyProfile/MyProfileHeader.tests.tsx
+++ b/src/app/Scenes/MyProfile/MyProfileHeader.tests.tsx
@@ -24,9 +24,7 @@ describe("MyProfileHeader", () => {
expect(profileImage).toBeTruthy()
fireEvent.press(profileImage)
expect(navigate).toHaveBeenCalledTimes(1)
- expect(navigate).toHaveBeenCalledWith("/my-profile/edit", {
- passProps: { onSuccess: expect.anything() },
- })
+ expect(navigate).toHaveBeenCalledWith("/my-profile/edit")
})
describe("settings screen", () => {
diff --git a/src/app/Scenes/MyProfile/MyProfileHeader.tsx b/src/app/Scenes/MyProfile/MyProfileHeader.tsx
index 5bbcf774501..03cde167f41 100644
--- a/src/app/Scenes/MyProfile/MyProfileHeader.tsx
+++ b/src/app/Scenes/MyProfile/MyProfileHeader.tsx
@@ -1,39 +1,38 @@
import { ActionType, ContextModule, OwnerType, TappedCompleteYourProfile } from "@artsy/cohesion"
import {
+ BellIcon,
+ Button,
Flex,
+ HeartIcon,
+ Image,
MapPinIcon,
+ MultiplePersonsIcon,
+ PersonIcon,
SettingsIcon,
+ ShieldFilledIcon,
+ SimpleMessage,
+ Skeleton,
+ SkeletonBox,
+ SkeletonText,
Spacer,
Text,
Touchable,
useColor,
- PersonIcon,
useSpace,
VerifiedPersonIcon,
- ShieldFilledIcon,
- HeartIcon,
- MultiplePersonsIcon,
- BellIcon,
- SkeletonBox,
- Skeleton,
- SkeletonText,
- Button,
- Image,
- SimpleMessage,
} from "@artsy/palette-mobile"
import { MyProfileHeaderQuery } from "__generated__/MyProfileHeaderQuery.graphql"
import { MyProfileHeader_me$key } from "__generated__/MyProfileHeader_me.graphql"
import { navigate } from "app/system/navigation/navigate"
+import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import { withSuspense } from "app/utils/hooks/withSuspense"
-import { useRefetch } from "app/utils/relayHelpers"
import { TouchableOpacity } from "react-native"
-import { useFragment, useLazyLoadQuery, graphql } from "react-relay"
+import { fetchQuery, graphql, useFragment, useLazyLoadQuery } from "react-relay"
interface MyProfileHeaderProps {
meProp: MyProfileHeader_me$key
}
export const MyProfileHeader: React.FC = ({ meProp }) => {
- const { refetch } = useRefetch()
const me = useFragment(myProfileHeaderFragment, meProp)
const space = useSpace()
@@ -54,15 +53,7 @@ export const MyProfileHeader: React.FC = ({ meProp }) => {
accessibilityRole="button"
haptic
hitSlop={{ top: 10, left: 10, right: 10, bottom: 10 }}
- onPress={() =>
- navigate("/my-profile/settings", {
- passProps: {
- onSuccess: () => {
- refetch()
- },
- },
- })
- }
+ onPress={() => navigate("/my-profile/settings")}
style={{ height: "100%" }}
>
@@ -73,13 +64,7 @@ export const MyProfileHeader: React.FC = ({ meProp }) => {
{
- navigate("/my-profile/edit", {
- passProps: {
- onSuccess: () => {
- refetch()
- },
- },
- })
+ navigate("/my-profile/edit")
}}
testID="profile-image"
style={{
@@ -139,7 +124,7 @@ export const MyProfileHeader: React.FC = ({ meProp }) => {
)}
-
+
{/* Activity */}
@@ -208,7 +193,13 @@ const MyProfileHeaderPlaceholder: React.FC<{}> = () => {
-
+
+
+
+
+
+
+
@@ -285,6 +276,10 @@ const myProfileHeaderQuery = graphql`
}
`
+export const fetchProfileData = async () => {
+ return fetchQuery(getRelayEnvironment(), myProfileHeaderQuery, {})
+}
+
export const MyProfileHeaderQueryRenderer = withSuspense({
Component: (props) => {
const data = useLazyLoadQuery(
diff --git a/src/app/Scenes/MyProfile/MyProfilePayment.tsx b/src/app/Scenes/MyProfile/MyProfilePayment.tsx
index 03696732064..adcd4e9af51 100644
--- a/src/app/Scenes/MyProfile/MyProfilePayment.tsx
+++ b/src/app/Scenes/MyProfile/MyProfilePayment.tsx
@@ -1,4 +1,4 @@
-import { Spacer, Flex, Text } from "@artsy/palette-mobile"
+import { Flex, Spacer, Text } from "@artsy/palette-mobile"
import { MyProfilePaymentDeleteCardMutation } from "__generated__/MyProfilePaymentDeleteCardMutation.graphql"
import { MyProfilePaymentQuery } from "__generated__/MyProfilePaymentQuery.graphql"
import { MyProfilePayment_me$data } from "__generated__/MyProfilePayment_me.graphql"
@@ -8,10 +8,11 @@ import { PageWithSimpleHeader } from "app/Components/PageWithSimpleHeader"
import { navigate } from "app/system/navigation/navigate"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import { extractNodes } from "app/utils/extractNodes"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { PlaceholderText } from "app/utils/placeholders"
import { renderWithPlaceholder } from "app/utils/renderWithPlaceholder"
import { times } from "lodash"
-import React, { useCallback, useEffect, useReducer, useState } from "react"
+import React, { Fragment, useCallback, useEffect, useReducer, useState } from "react"
import {
ActivityIndicator,
Alert,
@@ -133,56 +134,49 @@ const MyProfilePayment: React.FC<{ me: MyProfilePayment_me$data; relay: RelayPag
const creditCards = extractNodes(me.creditCards)
return (
-
- }
- data={creditCards}
- keyExtractor={(item) => item.internalID}
- contentContainerStyle={{ paddingTop: creditCards.length === 0 ? 10 : 20 }}
- renderItem={({ item }) => (
-
-
- {deletingIDs[item.internalID] ? (
-
- ) : (
- onRemove(item.internalID)}
- hitSlop={{ top: 10, left: 20, right: 20, bottom: 10 }}
- >
-
- Remove
-
-
- )}
-
- )}
- onEndReached={onLoadMore}
- ItemSeparatorComponent={() => }
- ListFooterComponent={
-
-
- }
- />
-
+ }
+ data={creditCards}
+ keyExtractor={(item) => item.internalID}
+ contentContainerStyle={{ paddingTop: creditCards.length === 0 ? 10 : 20 }}
+ renderItem={({ item }) => (
+
+
+ {deletingIDs[item.internalID] ? (
+
+ ) : (
+ onRemove(item.internalID)}
+ hitSlop={{ top: 10, left: 20, right: 20, bottom: 10 }}
+ >
+
+ Remove
+
+
+ )}
+
+ )}
+ onEndReached={onLoadMore}
+ ItemSeparatorComponent={() => }
+ ListFooterComponent={
+
+
+ }
+ />
)
}
export const MyProfilePaymentPlaceholder: React.FC<{}> = () => (
-
-
- {times(2).map((index: number) => (
-
-
-
- ))}
-
-
+
+ {times(2).map((index: number) => (
+
+
+
+ ))}
+
)
const MyProfilePaymentContainer = createPaginationContainer(
@@ -228,22 +222,31 @@ const MyProfilePaymentContainer = createPaginationContainer(
)
export const MyProfilePaymentQueryRenderer: React.FC<{}> = ({}) => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
+
return (
-
- environment={getRelayEnvironment()}
- query={graphql`
- query MyProfilePaymentQuery($count: Int!) {
- me {
- ...MyProfilePayment_me @arguments(count: $count)
+
+
+ environment={getRelayEnvironment()}
+ query={graphql`
+ query MyProfilePaymentQuery($count: Int!) {
+ me {
+ ...MyProfilePayment_me @arguments(count: $count)
+ }
}
- }
- `}
- render={renderWithPlaceholder({
- Container: MyProfilePaymentContainer,
- renderPlaceholder: () => ,
- })}
- variables={{ count: NUM_CARDS_TO_FETCH }}
- cacheConfig={{ force: true }}
- />
+ `}
+ render={renderWithPlaceholder({
+ Container: MyProfilePaymentContainer,
+ renderPlaceholder: () => ,
+ })}
+ variables={{ count: NUM_CARDS_TO_FETCH }}
+ cacheConfig={{ force: true }}
+ />
+
)
}
diff --git a/src/app/Scenes/MyProfile/MyProfilePaymentNewCreditCard.tsx b/src/app/Scenes/MyProfile/MyProfilePaymentNewCreditCard.tsx
index 342cf35e4fa..f6b5953d0e5 100644
--- a/src/app/Scenes/MyProfile/MyProfilePaymentNewCreditCard.tsx
+++ b/src/app/Scenes/MyProfile/MyProfilePaymentNewCreditCard.tsx
@@ -1,15 +1,19 @@
-import { Input, Spacer } from "@artsy/palette-mobile"
+import { Flex, Input, Spacer, Text, Touchable } from "@artsy/palette-mobile"
+import { useNavigation } from "@react-navigation/native"
import { useStripe } from "@stripe/stripe-react-native"
import { CreateCardTokenParams } from "@stripe/stripe-react-native/lib/typescript/src/types/Token"
import { MyProfilePaymentNewCreditCardSaveCardMutation } from "__generated__/MyProfilePaymentNewCreditCardSaveCardMutation.graphql"
import { CountrySelect } from "app/Components/CountrySelect"
import { CreditCardField } from "app/Components/CreditCardField/CreditCardField"
+import { PageWithSimpleHeader } from "app/Components/PageWithSimpleHeader"
import { Select } from "app/Components/Select/SelectV2"
import { Stack } from "app/Components/Stack"
-import { MyAccountFieldEditScreen } from "app/Scenes/MyAccount/Components/MyAccountFieldEditScreen"
+import { goBack } from "app/system/navigation/navigate"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { Action, Computed, action, computed, useLocalStore } from "easy-peasy"
-import React, { useRef } from "react"
+import React, { Fragment, useEffect, useRef } from "react"
+import { Alert, ScrollView } from "react-native"
import { commitMutation, graphql } from "react-relay"
import { __triggerRefresh } from "./MyProfilePayment"
@@ -64,6 +68,7 @@ interface Store {
export const MyProfilePaymentNewCreditCard: React.FC<{}> = ({}) => {
const { createToken } = useStripe()
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
const [state, actions] = useLocalStore(() => ({
fields: {
@@ -91,7 +96,30 @@ export const MyProfilePaymentNewCreditCard: React.FC<{}> = ({}) => {
const stateRef = useRef(null)
const countryRef = useRef>(null)
- const screenRef = useRef(null)
+ const navigation = useNavigation()
+
+ const scrollViewRef = useRef(null)
+
+ useEffect(() => {
+ const isValid = state.allPresent
+
+ navigation.setOptions({
+ headerRight: () => {
+ return (
+ {
+ handleSave()
+ }}
+ disabled={!isValid}
+ >
+
+ Save
+
+
+ )
+ },
+ })
+ }, [navigation, state.allPresent])
const buildTokenParams = (): CreateCardTokenParams => {
return {
@@ -108,124 +136,131 @@ export const MyProfilePaymentNewCreditCard: React.FC<{}> = ({}) => {
}
}
- return (
- {
- try {
- const tokenBody = buildTokenParams()
- const stripeResult = await createToken(tokenBody)
- const tokenId = stripeResult.token?.id
+ const handleSave = async () => {
+ try {
+ const tokenBody = buildTokenParams()
+ const stripeResult = await createToken(tokenBody)
+ const tokenId = stripeResult.token?.id
- if (!stripeResult || stripeResult.error || !tokenId) {
- throw new Error(
- `Unexpected stripe card tokenization result ${JSON.stringify(stripeResult.error)}`
- )
- }
- const gravityResult = await saveCreditCard(tokenId)
- if (gravityResult.createCreditCard?.creditCardOrError?.creditCard) {
- await __triggerRefresh?.()
- } else {
- // TODO: we can probably present these errors to the user?
- throw new Error(
- `Error trying to save card ${JSON.stringify(
- gravityResult.createCreditCard?.creditCardOrError?.mutationError
- )}`
- )
- }
- dismiss()
- } catch (e) {
- console.error(e)
- alert(
- "Something went wrong while attempting to save your credit card. Please try again or contact us."
- )
- }
- }}
- >
-
- <>
- {
- actions.fields.creditCard.setValue({
- valid: cardDetails.complete,
- params: {
- expMonth: cardDetails.expiryMonth,
- expYear: cardDetails.expiryYear,
- last4: cardDetails.last4,
- },
- })
- }}
- />
- >
+ if (!stripeResult || stripeResult.error || !tokenId) {
+ throw new Error(
+ `Unexpected stripe card tokenization result ${JSON.stringify(stripeResult.error)}`
+ )
+ }
+ const gravityResult = await saveCreditCard(tokenId)
+ if (gravityResult.createCreditCard?.creditCardOrError?.creditCard) {
+ await __triggerRefresh?.()
+ } else {
+ // TODO: we can probably present these errors to the user?
+ throw new Error(
+ `Error trying to save card ${JSON.stringify(
+ gravityResult.createCreditCard?.creditCardOrError?.mutationError
+ )}`
+ )
+ }
+ goBack()
+ } catch (e) {
+ console.error(e)
+ Alert.alert(
+ "Something went wrong while attempting to save your credit card. Please try again or contact us."
+ )
+ }
+ }
+
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
+
+ return (
+
+
+
+
+ <>
+ {
+ actions.fields.creditCard.setValue({
+ valid: cardDetails.complete,
+ params: {
+ expMonth: cardDetails.expiryMonth,
+ expYear: cardDetails.expiryYear,
+ last4: cardDetails.last4,
+ },
+ })
+ }}
+ />
+ >
- addressLine1Ref.current?.focus()}
- />
- addressLine2Ref.current?.focus()}
- />
- cityRef.current?.focus()}
- />
- postalCodeRef.current?.focus()}
- />
- stateRef.current?.focus()}
- />
+ addressLine1Ref.current?.focus()}
+ />
+ addressLine2Ref.current?.focus()}
+ />
+ cityRef.current?.focus()}
+ />
+ postalCodeRef.current?.focus()}
+ />
+ stateRef.current?.focus()}
+ />
- {
- stateRef.current?.blur()
- screenRef.current?.scrollToEnd()
- setTimeout(() => {
- countryRef.current?.open()
- }, 100)
- }}
- returnKeyType="next"
- />
+ {
+ stateRef.current?.blur()
+ scrollViewRef.current?.scrollToEnd()
+ setTimeout(() => {
+ countryRef.current?.open()
+ }, 100)
+ }}
+ returnKeyType="next"
+ />
-
+
-
-
-
+
+
+
+
+
)
}
diff --git a/src/app/Scenes/MyProfile/MyProfilePushNotifications.tsx b/src/app/Scenes/MyProfile/MyProfilePushNotifications.tsx
index df77fcc4cb8..159439d2ee3 100644
--- a/src/app/Scenes/MyProfile/MyProfilePushNotifications.tsx
+++ b/src/app/Scenes/MyProfile/MyProfilePushNotifications.tsx
@@ -1,4 +1,4 @@
-import { Flex, Box, Text, Separator, Join, Button } from "@artsy/palette-mobile"
+import { Box, Button, Flex, Join, Separator, Text } from "@artsy/palette-mobile"
import { MyProfilePushNotificationsQuery } from "__generated__/MyProfilePushNotificationsQuery.graphql"
import { MyProfilePushNotifications_me$data } from "__generated__/MyProfilePushNotifications_me.graphql"
import { PageWithSimpleHeader } from "app/Components/PageWithSimpleHeader"
@@ -14,16 +14,8 @@ import { renderWithPlaceholder } from "app/utils/renderWithPlaceholder"
import { requestSystemPermissions } from "app/utils/requestPushNotificationsPermission"
import useAppState from "app/utils/useAppState"
import { debounce } from "lodash"
-import React, { useCallback, useEffect, useState } from "react"
-import {
- ActivityIndicator,
- Alert,
- Linking,
- Platform,
- RefreshControl,
- ScrollView,
- View,
-} from "react-native"
+import React, { Fragment, useCallback, useEffect, useState } from "react"
+import { Alert, Linking, Platform, RefreshControl, ScrollView, View } from "react-native"
import { createRefetchContainer, graphql, QueryRenderer, RelayRefetchProp } from "react-relay"
const INSTRUCTIONS = Platform.select({
@@ -302,21 +294,12 @@ export const MyProfilePushNotifications: React.FC<{
// TODO: the below logic may be broken on Android 13 with runtime push permissions
return (
- : null}
- >
- }
- >
- {notificationAuthorizationStatus === PushAuthorizationStatus.Denied && (
-
- )}
- {notificationAuthorizationStatus === PushAuthorizationStatus.NotDetermined &&
- Platform.OS === "ios" && }
- {renderContent()}
-
-
+ }>
+ {notificationAuthorizationStatus === PushAuthorizationStatus.Denied && }
+ {notificationAuthorizationStatus === PushAuthorizationStatus.NotDetermined &&
+ Platform.OS === "ios" && }
+ {renderContent()}
+
)
}
@@ -349,23 +332,32 @@ const MyProfilePushNotificationsContainer = createRefetchContainer(
)
export const MyProfilePushNotificationsQueryRenderer: React.FC<{}> = ({}) => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
+
return (
-
- environment={getRelayEnvironment()}
- query={graphql`
- query MyProfilePushNotificationsQuery {
- me {
- ...MyProfilePushNotifications_me
+
+
+ environment={getRelayEnvironment()}
+ query={graphql`
+ query MyProfilePushNotificationsQuery {
+ me {
+ ...MyProfilePushNotifications_me
+ }
}
- }
- `}
- render={renderWithPlaceholder({
- Container: MyProfilePushNotificationsContainer,
- renderPlaceholder: () => (
-
- ),
- })}
- variables={{}}
- />
+ `}
+ render={renderWithPlaceholder({
+ Container: MyProfilePushNotificationsContainer,
+ renderPlaceholder: () => (
+
+ ),
+ })}
+ variables={{}}
+ />
+
)
}
diff --git a/src/app/Scenes/MyProfile/MyProfileSettings.tests.tsx b/src/app/Scenes/MyProfile/MyProfileSettings.tests.tsx
index e1fd7903019..c3409e04604 100644
--- a/src/app/Scenes/MyProfile/MyProfileSettings.tests.tsx
+++ b/src/app/Scenes/MyProfile/MyProfileSettings.tests.tsx
@@ -1,10 +1,17 @@
import { screen } from "@testing-library/react-native"
+import { __globalStoreTestUtils__ } from "app/store/GlobalStore"
import { renderWithWrappers } from "app/utils/tests/renderWithWrappers"
import { MyProfileSettings } from "./MyProfileSettings"
jest.mock("./LoggedInUserInfo")
describe(MyProfileSettings, () => {
+ beforeEach(() => {
+ __globalStoreTestUtils__?.injectFeatureFlags({
+ AREnableNewNavigation: true,
+ })
+ })
+
it("renders Edit Profile", () => {
renderWithWrappers()
expect(screen.getByText("Edit Profile")).toBeOnTheScreen()
diff --git a/src/app/Scenes/MyProfile/MyProfileSettings.tsx b/src/app/Scenes/MyProfile/MyProfileSettings.tsx
index a9a3e07a2ac..36d4ae4d5b2 100644
--- a/src/app/Scenes/MyProfile/MyProfileSettings.tsx
+++ b/src/app/Scenes/MyProfile/MyProfileSettings.tsx
@@ -1,41 +1,36 @@
import { ActionType, ContextModule, OwnerType } from "@artsy/cohesion"
-import { Button, Flex, Separator, Spacer, Text, useColor } from "@artsy/palette-mobile"
-import { FancyModalHeader } from "app/Components/FancyModal/FancyModalHeader"
+import { Button, Flex, Separator, Spacer, Text, useColor, useSpace } from "@artsy/palette-mobile"
import { MenuItem } from "app/Components/MenuItem"
+import { PageWithSimpleHeader } from "app/Components/PageWithSimpleHeader"
import { presentEmailComposer } from "app/NativeModules/presentEmailComposer"
import { GlobalStore } from "app/store/GlobalStore"
import { navigate } from "app/system/navigation/navigate"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
+import { Fragment } from "react"
import { Alert, ScrollView } from "react-native"
import { useTracking } from "react-tracking"
-interface MyProfileSettingsProps {
- onSuccess?: () => void
-}
-
-export const MyProfileSettings: React.FC = ({ onSuccess }) => {
+export const MyProfileSettings: React.FC = () => {
const color = useColor()
+ const space = useSpace()
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
const tracking = useTracking()
const separatorColor = color("black5")
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
return (
- <>
- Account
-
-
+
+
Settings
-
- >
+
)
}
diff --git a/src/app/Scenes/Onboarding/Auth2/scenes/LoginOTPStep.tsx b/src/app/Scenes/Onboarding/Auth2/scenes/LoginOTPStep.tsx
index 4c9d0fc6093..6fdaa62881a 100644
--- a/src/app/Scenes/Onboarding/Auth2/scenes/LoginOTPStep.tsx
+++ b/src/app/Scenes/Onboarding/Auth2/scenes/LoginOTPStep.tsx
@@ -108,6 +108,7 @@ export const LoginOTPStep: React.FC = () => {
handleChange("otp")(text)
}}
onBlur={() => validateForm()}
+ onSubmitEditing={handleSubmit}
/>
diff --git a/src/app/Scenes/OrderHistory/OrderDetails/Components/OrderDetails.tsx b/src/app/Scenes/OrderHistory/OrderDetails/Components/OrderDetails.tsx
index 1cf3e5d7753..803d7687d2e 100644
--- a/src/app/Scenes/OrderHistory/OrderDetails/Components/OrderDetails.tsx
+++ b/src/app/Scenes/OrderHistory/OrderDetails/Components/OrderDetails.tsx
@@ -5,9 +5,11 @@ import { PageWithSimpleHeader } from "app/Components/PageWithSimpleHeader"
import { PendingOfferSection } from "app/Scenes/OrderHistory/OrderDetails/Components/PendingOfferSection"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import { extractNodes } from "app/utils/extractNodes"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { PlaceholderBox, PlaceholderText } from "app/utils/placeholders"
import { renderWithPlaceholder } from "app/utils/renderWithPlaceholder"
import { compact } from "lodash"
+import { Fragment } from "react"
import { SectionList } from "react-native"
import { createFragmentContainer, graphql, QueryRenderer } from "react-relay"
import { ArtworkInfoSectionFragmentContainer } from "./ArtworkInfoSection"
@@ -100,100 +102,96 @@ const OrderDetails: React.FC = ({ order }) => {
])
return (
-
- item.key + index.toString()}
- renderItem={({ item }) => {
- return (
-
- {item}
-
- )
- }}
- stickySectionHeadersEnabled={false}
- renderSectionHeader={({ section: { title, data } }) =>
- title && data ? (
-
-
- {title}
-
-
- ) : null
- }
- SectionSeparatorComponent={(data) => (
-
- )}
- />
-
+ item.key + index.toString()}
+ renderItem={({ item }) => {
+ return (
+
+ {item}
+
+ )
+ }}
+ stickySectionHeadersEnabled={false}
+ renderSectionHeader={({ section: { title, data } }) =>
+ title && data ? (
+
+
+ {title}
+
+
+ ) : null
+ }
+ SectionSeparatorComponent={(data) => (
+
+ )}
+ />
)
}
export const OrderDetailsPlaceholder: React.FC<{}> = () => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
-
+
+
+
+
)
export const OrderDetailsContainer = createFragmentContainer(OrderDetails, {
@@ -243,22 +241,31 @@ export const OrderDetailsContainer = createFragmentContainer(OrderDetails, {
})
export const OrderDetailsQueryRender: React.FC<{ orderID: string }> = ({ orderID: orderID }) => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
+
return (
-
- environment={getRelayEnvironment()}
- query={graphql`
- query OrderDetailsQuery($orderID: ID!) {
- order: commerceOrder(id: $orderID) @optionalField {
- ...OrderDetails_order
+
+
+ environment={getRelayEnvironment()}
+ query={graphql`
+ query OrderDetailsQuery($orderID: ID!) {
+ order: commerceOrder(id: $orderID) @optionalField {
+ ...OrderDetails_order
+ }
}
- }
- `}
- render={renderWithPlaceholder({
- Container: OrderDetailsContainer,
- renderPlaceholder: () => ,
- })}
- variables={{ orderID }}
- cacheConfig={{ force: true }}
- />
+ `}
+ render={renderWithPlaceholder({
+ Container: OrderDetailsContainer,
+ renderPlaceholder: () => ,
+ })}
+ variables={{ orderID }}
+ cacheConfig={{ force: true }}
+ />
+
)
}
diff --git a/src/app/Scenes/OrderHistory/OrderDetails/OrderDetails.tests.tsx b/src/app/Scenes/OrderHistory/OrderDetails/OrderDetails.tests.tsx
index 34bfc326caf..0a2413b3761 100644
--- a/src/app/Scenes/OrderHistory/OrderDetails/OrderDetails.tests.tsx
+++ b/src/app/Scenes/OrderHistory/OrderDetails/OrderDetails.tests.tsx
@@ -1,11 +1,9 @@
+import { screen } from "@testing-library/react-native"
import { OrderDetailsTestsQuery } from "__generated__/OrderDetailsTestsQuery.graphql"
-import { extractText } from "app/utils/tests/extractText"
import { renderWithWrappersLEGACY } from "app/utils/tests/renderWithWrappers"
-import { resolveMostRecentRelayOperation } from "app/utils/tests/resolveMostRecentRelayOperation"
+import { setupTestWrapper } from "app/utils/tests/setupTestWrapper"
import { SectionList } from "react-native"
-import { graphql, QueryRenderer } from "react-relay"
-import { act } from "react-test-renderer"
-import { createMockEnvironment, MockPayloadGenerator } from "relay-test-utils"
+import { graphql } from "react-relay"
import { ArtworkInfoSectionFragmentContainer } from "./Components/ArtworkInfoSection"
import {
OrderDetailsContainer,
@@ -27,109 +25,90 @@ const order = {
}
describe(OrderDetailsQueryRender, () => {
- let mockEnvironment: ReturnType
- beforeEach(() => (mockEnvironment = createMockEnvironment()))
-
- const TestRenderer = () => (
-
- environment={mockEnvironment}
- query={graphql`
- query OrderDetailsTestsQuery @relay_test_operation {
- commerceOrder(id: "order-id") {
- ...OrderDetails_order
- }
+ const { renderWithRelay } = setupTestWrapper({
+ Component: ({ order }) => {
+ if (!order) {
+ return null
+ }
+ return
+ },
+ query: graphql`
+ query OrderDetailsTestsQuery @relay_test_operation {
+ order: commerceOrder(id: "order-id") {
+ ...OrderDetails_order
}
- `}
- variables={{}}
- render={({ props }) => {
- if (props?.commerceOrder) {
- return
- }
- }}
- />
- )
-
- const getWrapper = (mockResolvers = {}) => {
- const tree = renderWithWrappersLEGACY()
- act(() => {
- mockEnvironment.mock.resolveMostRecentOperation((operation) =>
- MockPayloadGenerator.generate(operation, mockResolvers)
- )
- })
- return tree
- }
+ }
+ `,
+ })
it("renders without throwing an error", () => {
- const tree = renderWithWrappersLEGACY().root
- resolveMostRecentRelayOperation(mockEnvironment, { CommerceOrder: () => order })
- expect(tree.findByType(SectionList)).toBeTruthy()
- expect(tree.findByType(OrderDetailsHeaderFragmentContainer)).toBeTruthy()
- expect(tree.findByType(ArtworkInfoSectionFragmentContainer)).toBeTruthy()
- expect(tree.findByType(SummarySectionFragmentContainer)).toBeTruthy()
- expect(tree.findByType(PaymentMethodSummaryItemFragmentContainer)).toBeTruthy()
- expect(tree.findByType(TrackOrderSectionFragmentContainer)).toBeTruthy()
- expect(tree.findByType(ShipsToSectionFragmentContainer)).toBeTruthy()
- expect(tree.findByType(SoldBySectionFragmentContainer)).toBeTruthy()
- expect(tree.findAllByType(WirePaymentSectionFragmentContainer)).toHaveLength(0)
+ renderWithRelay({ CommerceOrder: () => order })
+
+ expect(screen.UNSAFE_getAllByType(SectionList)).toBeTruthy()
+ expect(screen.UNSAFE_getByType(OrderDetailsHeaderFragmentContainer)).toBeTruthy()
+ expect(screen.UNSAFE_getByType(ArtworkInfoSectionFragmentContainer)).toBeTruthy()
+ expect(screen.UNSAFE_getByType(SummarySectionFragmentContainer)).toBeTruthy()
+ expect(screen.UNSAFE_getByType(PaymentMethodSummaryItemFragmentContainer)).toBeTruthy()
+ expect(screen.UNSAFE_getByType(TrackOrderSectionFragmentContainer)).toBeTruthy()
+ expect(screen.UNSAFE_getByType(ShipsToSectionFragmentContainer)).toBeTruthy()
+ expect(screen.UNSAFE_getByType(SoldBySectionFragmentContainer)).toBeTruthy()
+ expect(() => screen.UNSAFE_getAllByType(WirePaymentSectionFragmentContainer)).toThrow()
})
it("not render ShipsToSection when CommercePickup", () => {
- const tree = renderWithWrappersLEGACY().root
order.requestedFulfillment.__typename = "CommercePickup"
- resolveMostRecentRelayOperation(mockEnvironment, { CommerceOrder: () => order })
- const sections: SectionListItem[] = tree.findByType(SectionList).props.sections
+
+ renderWithRelay({ CommerceOrder: () => order })
+
+ const sections: SectionListItem[] = screen.UNSAFE_getByType(SectionList).props.sections
expect(sections.filter(({ key }) => key === "ShipTo_Section")).toHaveLength(0)
})
it("not render TrackOrderSection when CommercePickup", () => {
- const tree = renderWithWrappersLEGACY().root
order.requestedFulfillment.__typename = "CommercePickup"
- resolveMostRecentRelayOperation(mockEnvironment, { CommerceOrder: () => order })
- const sections: SectionListItem[] = tree.findByType(SectionList).props.sections
+
+ renderWithRelay({ CommerceOrder: () => order })
+
+ const sections: SectionListItem[] = screen.UNSAFE_getByType(SectionList).props.sections
expect(sections.filter(({ key }) => key === "TrackOrder_Section")).toHaveLength(0)
})
it("not render SoldBySection when partnerName null", () => {
- const tree = renderWithWrappersLEGACY().root
- resolveMostRecentRelayOperation(mockEnvironment, {
+ renderWithRelay({
CommerceOrder: () => ({
...order,
lineItems: { edges: [{ node: { artwork: { partner: null } } }] },
}),
})
- const sections: SectionListItem[] = tree.findByType(SectionList).props.sections
+
+ const sections: SectionListItem[] = screen.UNSAFE_getByType(SectionList).props.sections
expect(sections.filter(({ key }) => key === "Sold_By")).toHaveLength(0)
})
it("renders without throwing an error", () => {
- getWrapper()
+ renderWithRelay({ CommerceOrder: () => order })
})
it("renders props for OrderDetails if feature flag is on", () => {
- const tree = getWrapper({
+ renderWithRelay({
CommerceOrder: () => ({
internalID: "222",
requestedFulfillment: { __typename: "CommerceShip", name: "my name" },
}),
})
- expect(extractText(tree.root)).toContain("my name")
- })
-
- it("doesn't render MyCollections app if feature flag is not on", () => {
- const tree = getWrapper()
- expect(extractText(tree.root)).not.toContain("my name")
+ expect(screen.getByText(/my name/)).toBeTruthy()
})
it("Loads OrderHistoryQueryRender with OrderDetailsPlaceholder", () => {
const tree = renderWithWrappersLEGACY(
).root
+
expect(tree.findAllByType(OrderDetailsPlaceholder)).toHaveLength(1)
})
it("renders WirePaymentSection when payment method is wire transfer and order in processing approval state", () => {
- const tree = renderWithWrappersLEGACY().root
- resolveMostRecentRelayOperation(mockEnvironment, {
+ renderWithRelay({
CommerceOrder: () => ({
...order,
code: "111111111",
@@ -138,6 +117,6 @@ describe(OrderDetailsQueryRender, () => {
}),
})
- expect(tree.findByType(WirePaymentSectionFragmentContainer)).toBeTruthy()
+ expect(screen.UNSAFE_getByType(WirePaymentSectionFragmentContainer)).toBeTruthy()
})
})
diff --git a/src/app/Scenes/OrderHistory/OrderHistory.tsx b/src/app/Scenes/OrderHistory/OrderHistory.tsx
index 989314ba64a..09c34f9d4b8 100644
--- a/src/app/Scenes/OrderHistory/OrderHistory.tsx
+++ b/src/app/Scenes/OrderHistory/OrderHistory.tsx
@@ -4,10 +4,11 @@ import { OrderHistory_me$data } from "__generated__/OrderHistory_me.graphql"
import { PageWithSimpleHeader } from "app/Components/PageWithSimpleHeader"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import { extractNodes } from "app/utils/extractNodes"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { PlaceholderBox, PlaceholderButton, PlaceholderText } from "app/utils/placeholders"
import { renderWithPlaceholder } from "app/utils/renderWithPlaceholder"
import { times } from "lodash"
-import React, { useCallback, useState } from "react"
+import React, { Fragment, useCallback, useState } from "react"
import { FlatList, RefreshControl } from "react-native"
import { createPaginationContainer, graphql, QueryRenderer, RelayPaginationProp } from "react-relay"
import { OrderHistoryRowContainer } from "./OrderHistoryRow"
@@ -18,7 +19,7 @@ export const OrderHistory: React.FC<{ me: OrderHistory_me$data; relay: RelayPagi
relay,
me,
}) => {
- const { color } = useTheme()
+ const { color, space } = useTheme()
const [isRefreshing, setIsRefreshing] = useState(false)
const [isLoadingMore, setIsLoadingMore] = useState(false)
@@ -41,74 +42,64 @@ export const OrderHistory: React.FC<{ me: OrderHistory_me$data; relay: RelayPagi
const orders = extractNodes(me.orders)
return (
-
- }
- data={orders}
- keyExtractor={(order) => order.code}
- contentContainerStyle={{ flexGrow: 1, paddingTop: orders.length === 0 ? 10 : 20 }}
- renderItem={({ item }) => (
-
-
-
- )}
- ListEmptyComponent={
-
-
- No orders
-
-
- }
- onEndReachedThreshold={0.25}
- onEndReached={onLoadMore}
- ItemSeparatorComponent={() => (
-
-
-
- )}
- />
-
+ }
+ data={orders}
+ keyExtractor={(order) => order.code}
+ contentContainerStyle={{ flexGrow: 1, paddingTop: space(2) }}
+ renderItem={({ item }) => (
+
+
+
+ )}
+ ListEmptyComponent={
+
+
+ No orders
+
+
+ }
+ onEndReachedThreshold={0.25}
+ onEndReached={onLoadMore}
+ ItemSeparatorComponent={() => (
+
+
+
+ )}
+ />
)
}
export const OrderHistoryPlaceholder: React.FC<{}> = () => (
-
-
- {times(2).map((index: number) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {times(2).map((index: number) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- ))}
-
-
+
+
+
+
+
+
+ ))}
+
)
export const OrderHistoryContainer = createPaginationContainer(
@@ -162,23 +153,33 @@ export const OrderHistoryContainer = createPaginationContainer(
)
export const OrderHistoryQueryRender: React.FC<{}> = ({}) => {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
+ const Wrapper = enableNewNavigation
+ ? Fragment
+ : ({ children }: { children: React.ReactNode }) => (
+ {children}
+ )
+
return (
-
- environment={getRelayEnvironment()}
- query={graphql`
- query OrderHistoryQuery($count: Int!) {
- me @optionalField {
- name
- ...OrderHistory_me @arguments(count: $count)
+
+
+ environment={getRelayEnvironment()}
+ query={graphql`
+ query OrderHistoryQuery($count: Int!) {
+ me @optionalField {
+ name
+ ...OrderHistory_me @arguments(count: $count)
+ }
}
- }
- `}
- render={renderWithPlaceholder({
- Container: OrderHistoryContainer,
- renderPlaceholder: () => ,
- })}
- variables={{ count: NUM_ORDERS_TO_FETCH }}
- cacheConfig={{ force: true }}
- />
+ `}
+ render={renderWithPlaceholder({
+ Container: OrderHistoryContainer,
+ renderPlaceholder: () => ,
+ })}
+ variables={{ count: NUM_ORDERS_TO_FETCH }}
+ cacheConfig={{ force: true }}
+ />
+
)
}
diff --git a/src/app/Scenes/Partner/Components/PartnerShowRailItem.tsx b/src/app/Scenes/Partner/Components/PartnerShowRailItem.tsx
index edd436e5c37..dca85d2c204 100644
--- a/src/app/Scenes/Partner/Components/PartnerShowRailItem.tsx
+++ b/src/app/Scenes/Partner/Components/PartnerShowRailItem.tsx
@@ -1,11 +1,10 @@
-import { Spacer, Flex, Text, useScreenDimensions, Image } from "@artsy/palette-mobile"
+import { Flex, Image, Spacer, Text, Touchable, useScreenDimensions } from "@artsy/palette-mobile"
import { PartnerShowRailItem_show$data } from "__generated__/PartnerShowRailItem_show.graphql"
import { exhibitionDates } from "app/Scenes/Map/exhibitionPeriodParser"
import { navigate } from "app/system/navigation/navigate"
import { Schema } from "app/utils/track"
import { first } from "lodash"
import React from "react"
-import { TouchableWithoutFeedback } from "react-native"
import { createFragmentContainer, graphql } from "react-relay"
import { useTracking } from "react-tracking"
@@ -29,7 +28,7 @@ export const PartnerShowRailItem: React.FC = (props) => {
const sectionWidth = windowWidth - 100
return (
-
+
{!!imageURL && (
@@ -44,7 +43,7 @@ export const PartnerShowRailItem: React.FC = (props) => {
)}
-
+
)
}
diff --git a/src/app/Scenes/Partner/Components/PartnerShows.tsx b/src/app/Scenes/Partner/Components/PartnerShows.tsx
index 990c0fd2866..b162c89639e 100644
--- a/src/app/Scenes/Partner/Components/PartnerShows.tsx
+++ b/src/app/Scenes/Partner/Components/PartnerShows.tsx
@@ -1,4 +1,4 @@
-import { Spacer, Flex, Box, Text, useSpace, Tabs } from "@artsy/palette-mobile"
+import { Box, Flex, Spacer, Tabs, Text, Touchable, useSpace } from "@artsy/palette-mobile"
import { themeGet } from "@styled-system/theme-get"
import { PartnerShows_partner$data } from "__generated__/PartnerShows_partner.graphql"
import { TabEmptyState } from "app/Components/TabEmptyState"
@@ -6,7 +6,7 @@ import { TabEmptyState } from "app/Components/TabEmptyState"
import { navigate } from "app/system/navigation/navigate"
import { extractNodes } from "app/utils/extractNodes"
import { useState } from "react"
-import { ActivityIndicator, ImageBackground, TouchableWithoutFeedback } from "react-native"
+import { ActivityIndicator, ImageBackground } from "react-native"
import { createPaginationContainer, graphql, RelayPaginationProp } from "react-relay"
import styled from "styled-components/native"
import { PartnerShowsRailContainer as PartnerShowsRail } from "./PartnerShowsRail"
@@ -35,7 +35,7 @@ const ShowGridItem: React.FC = (props) => {
const styles = itemIndex % 2 === 0 ? { paddingRight: space(1) } : { paddingLeft: space(1) }
return (
-
+
{showImageURL ? (
@@ -48,7 +48,7 @@ const ShowGridItem: React.FC = (props) => {
{show.exhibitionPeriod}
-
+
)
diff --git a/src/app/Scenes/Partner/Components/PartnerShowsRail.tsx b/src/app/Scenes/Partner/Components/PartnerShowsRail.tsx
index c1a28ea1a56..6f629fcc624 100644
--- a/src/app/Scenes/Partner/Components/PartnerShowsRail.tsx
+++ b/src/app/Scenes/Partner/Components/PartnerShowsRail.tsx
@@ -1,4 +1,4 @@
-import { Spacer, Text } from "@artsy/palette-mobile"
+import { Flex, Text } from "@artsy/palette-mobile"
import { PartnerShowsRail_partner$data } from "__generated__/PartnerShowsRail_partner.graphql"
import { extractNodes } from "app/utils/extractNodes"
import { isCloseToEdge } from "app/utils/isCloseToEdge"
@@ -32,25 +32,24 @@ const PartnerShowsRail: React.FC<{
})
}
+ if (!currentAndUpcomingShows?.length) {
+ return null
+ }
+
return (
- <>
- {!!currentAndUpcomingShows && !!currentAndUpcomingShows.length && (
- <>
- Current and upcoming shows
- item.id}
- renderItem={({ item }) => {
- return
- }}
- />
-
- >
- )}
- >
+
+ Current and upcoming shows
+ item.id}
+ renderItem={({ item }) => {
+ return
+ }}
+ />
+
)
}
diff --git a/src/app/Scenes/PrivacyRequest/PrivacyRequest.tsx b/src/app/Scenes/PrivacyRequest/PrivacyRequest.tsx
index 80837d8d581..0b52732af05 100644
--- a/src/app/Scenes/PrivacyRequest/PrivacyRequest.tsx
+++ b/src/app/Scenes/PrivacyRequest/PrivacyRequest.tsx
@@ -1,5 +1,4 @@
-import { Spacer, Box, Text, LinkText, Join, Button } from "@artsy/palette-mobile"
-import { PageWithSimpleHeader } from "app/Components/PageWithSimpleHeader"
+import { Box, Button, Join, LinkText, Spacer, Text } from "@artsy/palette-mobile"
import { presentEmailComposer } from "app/NativeModules/presentEmailComposer"
import { navigate } from "app/system/navigation/navigate"
import React from "react"
@@ -7,42 +6,40 @@ import { View } from "react-native"
export const PrivacyRequest: React.FC = () => {
return (
-
-
-
-
- }>
-
- Please see Artsy’s{" "}
- navigate("/privacy")}>Privacy Policy for more
- information about the information we collect, how we use it, and why we use it.
-
-
- To submit a personal data request tap the button below or email{" "}
- presentEmailComposer("privacy@artsy.net", "Personal Data Request")}
- >
- privacy@artsy.net.
- {" "}
-
-
-
-
-
-
+ privacy@artsy.net.
+ {" "}
+
+
+
+
+
)
}
diff --git a/src/app/Scenes/Sale/Sale.tests.tsx b/src/app/Scenes/Sale/Sale.tests.tsx
index 3bc38010eaf..959a54e0e77 100644
--- a/src/app/Scenes/Sale/Sale.tests.tsx
+++ b/src/app/Scenes/Sale/Sale.tests.tsx
@@ -1,6 +1,6 @@
import { waitFor } from "@testing-library/react-native"
import { CascadingEndTimesBanner } from "app/Scenes/Artwork/Components/CascadingEndTimesBanner"
-import { navigate, popParentViewController } from "app/system/navigation/navigate"
+import { navigate } from "app/system/navigation/navigate"
import { renderWithWrappersLEGACY } from "app/utils/tests/renderWithWrappers"
import { DateTime } from "luxon"
import { Suspense } from "react"
@@ -9,7 +9,6 @@ import { RegisterToBidButtonContainer } from "./Components/RegisterToBidButton"
import { SaleQueryRenderer } from "./Sale"
jest.mock("app/system/navigation/navigate", () => ({
- popParentViewController: jest.fn(),
navigate: jest.fn(),
}))
@@ -52,9 +51,11 @@ describe("Sale", () => {
expect(navigate).toHaveBeenCalledTimes(0)
await waitFor(() => expect(navigate).toHaveBeenCalledTimes(1))
await waitFor(() =>
- expect(navigate).toHaveBeenCalledWith("https://live-staging.artsy.net/live-sale-slug")
+ expect(navigate).toHaveBeenCalledWith("https://live-staging.artsy.net/live-sale-slug", {
+ replaceActiveModal: true,
+ replaceActiveScreen: true,
+ })
)
- await waitFor(() => expect(popParentViewController).toHaveBeenCalledTimes(1))
})
it("switches to live auction view when sale goes live with no endAt", async () => {
@@ -79,9 +80,11 @@ describe("Sale", () => {
expect(navigate).toHaveBeenCalledTimes(0)
await waitFor(() => expect(navigate).toHaveBeenCalledTimes(1))
await waitFor(() =>
- expect(navigate).toHaveBeenCalledWith("https://live-staging.artsy.net/live-sale-slug")
+ expect(navigate).toHaveBeenCalledWith("https://live-staging.artsy.net/live-sale-slug", {
+ replaceActiveModal: true,
+ replaceActiveScreen: true,
+ })
)
- await waitFor(() => expect(popParentViewController).toHaveBeenCalledTimes(1))
})
it("doesn't switch to live auction view when sale is closed", async () => {
@@ -104,7 +107,6 @@ describe("Sale", () => {
await waitFor(() => {
expect(navigate).toHaveBeenCalledTimes(0)
- expect(popParentViewController).toHaveBeenCalledTimes(0)
})
})
diff --git a/src/app/Scenes/Sale/Sale.tsx b/src/app/Scenes/Sale/Sale.tsx
index e3376fa964a..7aa79f41dfd 100644
--- a/src/app/Scenes/Sale/Sale.tsx
+++ b/src/app/Scenes/Sale/Sale.tsx
@@ -17,7 +17,7 @@ import { LoadFailureView } from "app/Components/LoadFailureView"
import Spinner from "app/Components/Spinner"
import { CascadingEndTimesBanner } from "app/Scenes/Artwork/Components/CascadingEndTimesBanner"
import { unsafe__getEnvironment } from "app/store/GlobalStore"
-import { navigate, popParentViewController } from "app/system/navigation/navigate"
+import { navigate } from "app/system/navigation/navigate"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import { AboveTheFoldQueryRenderer } from "app/utils/AboveTheFoldQueryRenderer"
import { AuctionWebsocketContextProvider } from "app/utils/Websockets/auctions/AuctionSocketContext"
@@ -137,8 +137,10 @@ export const Sale: React.FC = ({ sale, me, below, relay }) => {
const switchToLive = () => {
const liveBaseURL = unsafe__getEnvironment().predictionURL
const liveAuctionURL = `${liveBaseURL}/${sale.slug}`
- navigate(liveAuctionURL)
- setTimeout(popParentViewController, 500)
+ navigate(liveAuctionURL, {
+ replaceActiveScreen: true,
+ replaceActiveModal: true,
+ })
}
const viewConfigRef = useRef({ viewAreaCoveragePercentThreshold: 30 })
diff --git a/src/app/Scenes/SaleInfo/SaleInfo.tsx b/src/app/Scenes/SaleInfo/SaleInfo.tsx
index 01a7188277d..b135e6d885a 100644
--- a/src/app/Scenes/SaleInfo/SaleInfo.tsx
+++ b/src/app/Scenes/SaleInfo/SaleInfo.tsx
@@ -1,5 +1,5 @@
import { ContextModule, OwnerType } from "@artsy/cohesion"
-import { Flex, Text, Separator, Join } from "@artsy/palette-mobile"
+import { Flex, Text, Separator, Join, useSpace } from "@artsy/palette-mobile"
import { SaleInfoQueryRendererQuery } from "__generated__/SaleInfoQueryRendererQuery.graphql"
import { SaleInfo_me$data } from "__generated__/SaleInfo_me.graphql"
import { SaleInfo_sale$data } from "__generated__/SaleInfo_sale.graphql"
@@ -62,6 +62,8 @@ const AuctionIsLive = () => (
const markdownRules = defaultRules({ useNewTextStyles: true })
export const SaleInfo: React.FC = ({ sale, me }) => {
+ const space = useSpace()
+
const panResponder = useRef(null)
useEffect(() => {
panResponder.current = PanResponder.create({
@@ -92,10 +94,10 @@ export const SaleInfo: React.FC = ({ sale, me }) => {
return (
-
+
}>
{/* About Auction */}
-
+
About this auction
{sale.name}
@@ -200,8 +202,10 @@ const BuyersPremium: React.FC<{ sale: SaleInfo_sale$data }> = (props) => {
const SaleInfoPlaceholder = () => (
}>
-
- About this auction
+
+
+ About this auction
+
diff --git a/src/app/Scenes/SavedSearchAlert/SavedSearchAlertForm.tests.tsx b/src/app/Scenes/SavedSearchAlert/SavedSearchAlertForm.tests.tsx
index e3692a2898f..34eea26a748 100644
--- a/src/app/Scenes/SavedSearchAlert/SavedSearchAlertForm.tests.tsx
+++ b/src/app/Scenes/SavedSearchAlert/SavedSearchAlertForm.tests.tsx
@@ -271,13 +271,7 @@ describe("SavedSearchAlertForm", () => {
})
it("calls delete mutation when the delete alert button is pressed", async () => {
- const onDeletePressMock = jest.fn()
- renderWithWrappers(
-
- )
+ renderWithWrappers()
fireEvent.press(screen.getByTestId("delete-alert-button"))
fireEvent.press(screen.getByTestId("dialog-primary-action-button"))
@@ -285,12 +279,6 @@ describe("SavedSearchAlertForm", () => {
expect(mockEnvironment.mock.getMostRecentOperation().request.node.operation.name).toBe(
"deleteSavedSearchAlertMutation"
)
-
- await waitFor(() => {
- resolveMostRecentRelayOperation(mockEnvironment)
- })
-
- expect(onDeletePressMock).toHaveBeenCalled()
})
})
})
diff --git a/src/app/Scenes/SavedSearchAlert/SavedSearchAlertForm.tsx b/src/app/Scenes/SavedSearchAlert/SavedSearchAlertForm.tsx
index acefca5ae91..98af23efd0d 100644
--- a/src/app/Scenes/SavedSearchAlert/SavedSearchAlertForm.tsx
+++ b/src/app/Scenes/SavedSearchAlert/SavedSearchAlertForm.tsx
@@ -9,7 +9,7 @@ import {
import { updateMyUserProfile } from "app/Scenes/MyAccount/updateMyUserProfile"
import { getAlertByCriteria } from "app/Scenes/SavedSearchAlert/queries/getAlertByCriteria"
import { GlobalStore } from "app/store/GlobalStore"
-import { goBack, navigate } from "app/system/navigation/navigate"
+import { goBack, navigate, popToRoot } from "app/system/navigation/navigate"
import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { refreshSavedAlerts } from "app/utils/refreshHelpers"
import { FormikProvider, useFormik } from "formik"
@@ -56,7 +56,6 @@ export const SavedSearchAlertForm: React.FC = (props)
userAllowsEmails,
contentContainerStyle,
onComplete,
- onDeleteComplete,
...other
} = props
const enableAlertsFiltersSizeFiltering = useFeatureFlag("AREnableAlertsFiltersSizeFiltering")
@@ -276,7 +275,7 @@ export const SavedSearchAlertForm: React.FC = (props)
try {
await deleteSavedSearchMutation(savedSearchAlertId)
tracking.trackEvent(tracks.deletedSavedSearch(savedSearchAlertId))
- onDeleteComplete?.()
+ popToRoot()
} catch (error) {
console.error(error)
}
diff --git a/src/app/Scenes/Search/Search.tests.tsx b/src/app/Scenes/Search/Search.tests.tsx
index 9066bd5afcf..2aa1b3c387c 100644
--- a/src/app/Scenes/Search/Search.tests.tsx
+++ b/src/app/Scenes/Search/Search.tests.tsx
@@ -11,7 +11,7 @@ jest.mock("lodash/throttle", () => (fn: any) => {
describe("Search", () => {
const { renderWithRelay } = setupTestWrapper({
- Component: () => ,
+ Component: () => ,
})
it("should render a text input with placeholder and no pills", async () => {
diff --git a/src/app/Scenes/Search/Search.tsx b/src/app/Scenes/Search/Search.tsx
index ec4e114c54e..7cfc51b49a5 100644
--- a/src/app/Scenes/Search/Search.tsx
+++ b/src/app/Scenes/Search/Search.tsx
@@ -1,6 +1,7 @@
import { ActionType, ContextModule, OwnerType } from "@artsy/cohesion"
-import { Spacer, Flex, Box } from "@artsy/palette-mobile"
+import { Spacer, Flex, Box, Screen } from "@artsy/palette-mobile"
import { useNavigation } from "@react-navigation/native"
+import { StackScreenProps } from "@react-navigation/stack"
import { SearchQuery, SearchQuery$variables } from "__generated__/SearchQuery.graphql"
import { SearchInput } from "app/Components/SearchInput"
import { SearchPills } from "app/Scenes/Search/SearchPills"
@@ -186,10 +187,14 @@ export const SearchScreenQuery = graphql`
}
`
-export const SearchScreen: React.FC = () => (
- }>
-
-
+type SearchScreenProps = StackScreenProps
+
+export const SearchScreen: React.FC = () => (
+
+ }>
+
+
+
)
const Scrollable = styled(ScrollView).attrs(() => ({
diff --git a/src/app/Scenes/SellWithArtsy/ConsignmentInquiry/ConsignmentInquiryForm.tsx b/src/app/Scenes/SellWithArtsy/ConsignmentInquiry/ConsignmentInquiryForm.tsx
index 280979856a8..f9976f4f38b 100644
--- a/src/app/Scenes/SellWithArtsy/ConsignmentInquiry/ConsignmentInquiryForm.tsx
+++ b/src/app/Scenes/SellWithArtsy/ConsignmentInquiry/ConsignmentInquiryForm.tsx
@@ -2,7 +2,6 @@ import { Box, Button, Input, LinkText, Spacer, Text } from "@artsy/palette-mobil
import { useNavigation } from "@react-navigation/native"
import { PhoneInput } from "app/Components/Input/PhoneInput"
import { navigate } from "app/system/navigation/navigate"
-import { useScreenDimensions } from "app/utils/hooks"
import { useFormikContext } from "formik"
import { useEffect, useRef } from "react"
import { Platform, ScrollView } from "react-native"
@@ -13,7 +12,6 @@ export const ConsignmentInquiryForm: React.FC<{
canPopScreen: boolean
recipientName?: string
}> = ({ confirmLeaveEdit, canPopScreen, recipientName }) => {
- const { safeAreaInsets } = useScreenDimensions()
const { values, handleChange, errors, handleSubmit, isValid, dirty, validateField } =
useFormikContext()
@@ -78,7 +76,7 @@ export const ConsignmentInquiryForm: React.FC<{
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
>
-
+
{!!recipientName ? `Contact ${recipientName}` : "Contact a specialist"}
diff --git a/src/app/Scenes/SellWithArtsy/ConsignmentInquiry/ConsignmentInquiryScreen.tsx b/src/app/Scenes/SellWithArtsy/ConsignmentInquiry/ConsignmentInquiryScreen.tsx
index 66c11e98c0b..e960ad8808f 100644
--- a/src/app/Scenes/SellWithArtsy/ConsignmentInquiry/ConsignmentInquiryScreen.tsx
+++ b/src/app/Scenes/SellWithArtsy/ConsignmentInquiry/ConsignmentInquiryScreen.tsx
@@ -3,14 +3,13 @@ import { SentConsignmentInquiry } from "@artsy/cohesion/dist/Schema/Events/Consi
import { ConsignmentInquiryScreenMutation } from "__generated__/ConsignmentInquiryScreenMutation.graphql"
import { AbandonFlowModal } from "app/Components/AbandonFlowModal"
import { FancyModal } from "app/Components/FancyModal/FancyModal"
-import { FancyModalHeader } from "app/Components/FancyModal/FancyModalHeader"
import { useToast } from "app/Components/Toast/toastHook"
import { goBack } from "app/system/navigation/navigate"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import { ArtsyKeyboardAvoidingView } from "app/utils/ArtsyKeyboardAvoidingView"
import { FormikProvider, useFormik } from "formik"
import { useState } from "react"
-import { Environment, graphql, commitMutation } from "react-relay"
+import { commitMutation, Environment, graphql } from "react-relay"
import { useTracking } from "react-tracking"
import * as Yup from "yup"
import { ConsignmentInquiryConfirmation } from "./ConsignmentInquiryConfirmation"
@@ -135,8 +134,6 @@ export const ConsignmentInquiryScreen: React.FC = ({
<>
-
-
setShowAbandonModal(v)}
canPopScreen={canPopScreen}
diff --git a/src/app/Scenes/SellWithArtsy/SellWithArtsyHome.tsx b/src/app/Scenes/SellWithArtsy/SellWithArtsyHome.tsx
index 8a454d62f81..d3fb4fa2103 100644
--- a/src/app/Scenes/SellWithArtsy/SellWithArtsyHome.tsx
+++ b/src/app/Scenes/SellWithArtsy/SellWithArtsyHome.tsx
@@ -14,8 +14,9 @@ import { useBottomTabsScrollToTop } from "app/utils/bottomTabsHelper"
import { RefreshEvents, SELL_SCREEN_REFRESH_KEY } from "app/utils/refreshHelpers"
import { useSwitchStatusBarStyle } from "app/utils/useStatusBarStyle"
import { compact } from "lodash"
-import { Suspense, useEffect, useReducer } from "react"
+import { RefObject, Suspense, useEffect, useReducer } from "react"
import { StatusBarStyle } from "react-native"
+import { FlatList } from "react-native-gesture-handler"
import { graphql, useLazyLoadQuery } from "react-relay"
import { useTracking } from "react-tracking"
import { Footer } from "./Components/Footer"
@@ -140,7 +141,9 @@ export const SellWithArtsyHome: React.FC = () => {
renderItem={({ item }) => item.content}
ItemSeparatorComponent={() => }
showsVerticalScrollIndicator={false}
- innerRef={scrollViewRef}
+ windowSize={3}
+ initialNumToRender={__TEST__ ? 21 : 5}
+ innerRef={scrollViewRef as RefObject}
/>
diff --git a/src/app/Scenes/SellWithArtsy/SubmitArtwork/UploadPhotos/utils/index.tsx b/src/app/Scenes/SellWithArtsy/SubmitArtwork/UploadPhotos/utils/index.tsx
index 4bec8e0087f..b644c3335c4 100644
--- a/src/app/Scenes/SellWithArtsy/SubmitArtwork/UploadPhotos/utils/index.tsx
+++ b/src/app/Scenes/SellWithArtsy/SubmitArtwork/UploadPhotos/utils/index.tsx
@@ -1,3 +1,4 @@
+import { StackScreenProps } from "@react-navigation/stack"
import { BottomTabType } from "app/Scenes/BottomTabs/BottomTabType"
import { SellWithArtsyHomeQueryRenderer } from "app/Scenes/SellWithArtsy/SellWithArtsyHome"
import { GlobalStore } from "app/store/GlobalStore"
@@ -9,7 +10,9 @@ export interface SellTabProps {
overwriteHardwareBackButtonPath?: BottomTabType
}
-export const SellWithArtsy: React.FC = () => {
+type SellWithArtsyProps = StackScreenProps
+
+export const SellWithArtsy: React.FC = () => {
const sellTabProps = GlobalStore.useAppState((state) => {
return state.bottomTabs.sessionState.tabProps.sell ?? {}
}) as SellTabProps
diff --git a/src/app/Scenes/Show/Show.tsx b/src/app/Scenes/Show/Show.tsx
index 7af84dfbcf9..f3c47e9d69f 100644
--- a/src/app/Scenes/Show/Show.tsx
+++ b/src/app/Scenes/Show/Show.tsx
@@ -1,18 +1,16 @@
-import { Spacer, Flex, Box, Separator } from "@artsy/palette-mobile"
+import { Box, Flex, Separator, Spacer } from "@artsy/palette-mobile"
import { ShowQuery } from "__generated__/ShowQuery.graphql"
import { Show_show$data } from "__generated__/Show_show.graphql"
import { ArtworkFiltersStoreProvider } from "app/Components/ArtworkFilter/ArtworkFilterStore"
import { PlaceholderGrid } from "app/Components/ArtworkGrids/GenericGrid"
import { HeaderArtworksFilterWithTotalArtworks as HeaderArtworksFilter } from "app/Components/HeaderArtworksFilter/HeaderArtworksFilterWithTotalArtworks"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
-import { useScreenDimensions } from "app/utils/hooks"
import { PlaceholderBox, PlaceholderText } from "app/utils/placeholders"
import { renderWithPlaceholder } from "app/utils/renderWithPlaceholder"
import { ProvideScreenTracking, Schema } from "app/utils/track"
import { times } from "lodash"
import React, { useRef, useState } from "react"
import { Animated } from "react-native"
-import { useSafeAreaInsets } from "react-native-safe-area-context"
import { createFragmentContainer, graphql, QueryRenderer } from "react-relay"
import { ShowArtworksWithNavigation as ShowArtworks } from "./Components/ShowArtworks"
import { ShowArtworksEmptyStateFragmentContainer } from "./Components/ShowArtworksEmptyState"
@@ -48,7 +46,7 @@ export const Show: React.FC = ({ show }) => {
const artworkProps = { show, visible, toggleFilterArtworksModal }
const sections: Section[] = [
- { key: "header", element: },
+ { key: "header", element: },
...(Boolean(show.images?.length)
? [{ key: "install-shots", element: }]
@@ -109,11 +107,9 @@ export const Show: React.FC = ({ show }) => {
keyExtractor={({ key }) => key}
stickyHeaderIndices={[sections.findIndex((section) => section.key === "filter") + 1]}
viewabilityConfig={viewConfigRef.current}
- ListHeaderComponent={}
ListFooterComponent={}
ItemSeparatorComponent={() => }
contentContainerStyle={{
- paddingTop: useScreenDimensions().safeAreaInsets.top,
paddingBottom: 40,
}}
renderItem={({ item: { element } }) => element}
@@ -178,9 +174,8 @@ export const ShowQueryRenderer: React.FC = ({ showID })
}
export const ShowPlaceholder: React.FC = () => {
- const saInsets = useSafeAreaInsets()
return (
-
+
{/* Title */}
diff --git a/src/app/routes.ts b/src/app/routes.ts
index e53d1e9239b..a6b8891e21a 100644
--- a/src/app/routes.ts
+++ b/src/app/routes.ts
@@ -349,14 +349,12 @@ export function getDomainMap(): Record {
// Webview routes
addWebViewRoute("/auction-faq", {
alwaysPresentModally: true,
- safeAreaEdges: ["bottom"],
}),
addWebViewRoute("/buy-now-feature-faq"),
addWebViewRoute("/buyer-guarantee"),
addWebViewRoute("/categories"),
addWebViewRoute("/conditions-of-sale", {
alwaysPresentModally: true,
- safeAreaEdges: ["bottom"],
}),
addWebViewRoute("/identity-verification-faq"),
addWebViewRoute("/meet-the-specialists"),
@@ -364,16 +362,13 @@ export function getDomainMap(): Record {
mimicBrowserBackButton: true,
useRightCloseButton: true,
alwaysPresentModally: true,
- safeAreaEdges: ["bottom"],
}),
addWebViewRoute("/price-database"),
addWebViewRoute("/privacy", {
alwaysPresentModally: true,
- safeAreaEdges: ["bottom"],
}),
addWebViewRoute("/terms", {
alwaysPresentModally: true,
- safeAreaEdges: ["bottom"],
}),
addWebViewRoute("/unsubscribe"),
diff --git a/src/app/store/GlobalStore.tsx b/src/app/store/GlobalStore.tsx
index 8aa7541781a..3623df31508 100644
--- a/src/app/store/GlobalStore.tsx
+++ b/src/app/store/GlobalStore.tsx
@@ -250,3 +250,7 @@ export function unsafe__getEnvironment() {
} = globalStoreInstance().getState().devicePrefs
return { ...strings, stripePublishableKey, env, userIsDev: value }
}
+
+export function unsafe_getDevPrefs() {
+ return globalStoreInstance().getState().devicePrefs
+}
diff --git a/src/app/store/__tests__/AuthModel.tests.ts b/src/app/store/__tests__/AuthModel.tests.ts
index 2b8008b8da4..0468103db77 100644
--- a/src/app/store/__tests__/AuthModel.tests.ts
+++ b/src/app/store/__tests__/AuthModel.tests.ts
@@ -15,8 +15,6 @@ import {
} from "react-native-fbsdk-next"
import Keychain from "react-native-keychain"
-jest.unmock("app/NativeModules/LegacyNativeModules")
-
const mockFetch = jest.fn()
;(global as any).fetch = mockFetch
diff --git a/src/app/store/__tests__/migration.tests.ts b/src/app/store/__tests__/migration.tests.ts
index 75fb51fc5e2..2396f500878 100644
--- a/src/app/store/__tests__/migration.tests.ts
+++ b/src/app/store/__tests__/migration.tests.ts
@@ -7,25 +7,6 @@ import { sanitize } from "app/store/persistence"
import { max, min, range } from "lodash"
import { Platform } from "react-native"
-jest.mock("app/NativeModules/LegacyNativeModules", () => ({
- LegacyNativeModules: {
- ...jest.requireActual("app/NativeModules/LegacyNativeModules").LegacyNativeModules,
- ARNotificationsManager: {
- ...jest.requireActual("app/NativeModules/LegacyNativeModules").LegacyNativeModules
- .ARNotificationsManager,
- nativeState: {
- userAgent: "Jest Unit Tests",
- authenticationToken: null,
- onboardingState: "none",
- launchCount: 1,
- deviceId: "testDevice",
- userID: null,
- userEmail: null,
- },
- },
- },
-}))
-
describe(migrate, () => {
it("leaves an object untouched if there are no migrations pending", () => {
const result = migrate({
diff --git a/src/app/store/config/features.ts b/src/app/store/config/features.ts
index 870dd99f235..909ad39d521 100644
--- a/src/app/store/config/features.ts
+++ b/src/app/store/config/features.ts
@@ -293,6 +293,11 @@ export const features = {
readyForRelease: false,
showInDevMenu: true,
},
+ AREnableNewNavigation: {
+ description: "Enable new navigation infra (Requires App Restart!)",
+ readyForRelease: false,
+ showInDevMenu: true,
+ },
AREnablePaymentFailureBanner: {
description: "Enable payment failure banner",
readyForRelease: true,
diff --git a/src/app/system/devTools/DevMenu/Components/NavButtons.tsx b/src/app/system/devTools/DevMenu/Components/NavButtons.tsx
index db82dbfc302..1039ab5f3fb 100644
--- a/src/app/system/devTools/DevMenu/Components/NavButtons.tsx
+++ b/src/app/system/devTools/DevMenu/Components/NavButtons.tsx
@@ -7,7 +7,7 @@ export const NavButtons: React.FC<{ onClose(): void }> = ({ onClose }) => {
const isLoggedIn = !!GlobalStore.useAppState((state) => !!state.auth.userID)
return (
-
+
}>
{!!isLoggedIn && (
goBack() }: { onClose(): void }) => {
const userEmail = GlobalStore.useAppState((s) => s.auth.userEmail)
- const space = useSpace()
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+ const navigation = useNavigation>()
const handleBackButton = () => {
onClose()
@@ -26,45 +32,54 @@ export const DevMenu = ({ onClose = () => goBack() }: { onClose(): void }) => {
useBackHandler(handleBackButton)
- return (
-
-
-
- Dev Settings
-
-
-
-
-
- Build:{" "}
-
- v{DeviceInfo.getVersion()}, build {DeviceInfo.getBuildNumber()} (
- {ArtsyNativeModule.gitCommitShortHash})
-
-
-
- Email: {userEmail}
-
+ useEffect(() => {
+ if (enableNewNavigation) {
+ navigation?.setOptions({
+ headerRight: () => (
+
+
+
+ ),
+ })
+ }
+ }, [navigation])
- NativeModules?.DevMenu?.show()}
- />
+ return (
+
+ {!!enableNewNavigation && }
-
+ {!enableNewNavigation && (
+
+
+ Dev Settings
+
+
+
+ )}
- }>
-
-
-
-
-
-
-
-
-
+
+ Build:{" "}
+
+ v{DeviceInfo.getVersion()}, build {DeviceInfo.getBuildNumber()} (
+ {ArtsyNativeModule.gitCommitShortHash})
+
+
+
+ Email: {userEmail}
+
+ NativeModules?.DevMenu?.show()} />
+
+ }>
+
+
+
+
+
+
+
+
)
}
diff --git a/src/app/system/navigation/navigate.tests.tsx b/src/app/system/navigation/navigate.tests.tsx
index e05bdc594d4..aeed5d90f1a 100644
--- a/src/app/system/navigation/navigate.tests.tsx
+++ b/src/app/system/navigation/navigate.tests.tsx
@@ -237,6 +237,7 @@ describe(navigate, () => {
[
"inbox",
{
+ "hidesBackButton": true,
"moduleName": "Conversation",
"onlyShowInTabName": "inbox",
"props": {
diff --git a/src/app/system/navigation/navigate.ts b/src/app/system/navigation/navigate.ts
index 32d576b0f45..e14c099bb58 100644
--- a/src/app/system/navigation/navigate.ts
+++ b/src/app/system/navigation/navigate.ts
@@ -1,16 +1,24 @@
import { EventEmitter } from "events"
import { ActionType, OwnerType, Screen } from "@artsy/cohesion"
+import {
+ CommonActions,
+ NavigationContainerRef,
+ StackActions,
+ TabActions,
+} from "@react-navigation/native"
import { addBreadcrumb, captureMessage } from "@sentry/react-native"
-import { AppModule, ViewOptions, modules } from "app/AppRegistry"
+import { AppModule, modules, ViewOptions } from "app/AppRegistry"
import { LegacyNativeModules } from "app/NativeModules/LegacyNativeModules"
import { BottomTabType } from "app/Scenes/BottomTabs/BottomTabType"
import { matchRoute } from "app/routes"
-import { GlobalStore, unsafe__getSelectedTab } from "app/store/GlobalStore"
+import { GlobalStore, unsafe__getSelectedTab, unsafe_getFeatureFlag } from "app/store/GlobalStore"
import { propsStore } from "app/store/PropsStore"
import { postEventToProviders } from "app/utils/track/providers"
import { visualize } from "app/utils/visualizer"
import { InteractionManager, Linking, Platform } from "react-native"
+export const internal_navigationRef = { current: null as NavigationContainerRef | null }
+
export interface ViewDescriptor extends ViewOptions {
type: "react" | "native"
moduleName: AppModule
@@ -116,6 +124,34 @@ export async function navigate(url: string, options: NavigateOptions = {}) {
...module.options,
}
+ const enableNewNavigation = unsafe_getFeatureFlag("AREnableNewNavigation")
+
+ if (enableNewNavigation) {
+ if (internal_navigationRef.current?.isReady()) {
+ if (replaceActiveModal || replaceActiveScreen) {
+ internal_navigationRef.current.dispatch(
+ StackActions.replace(result.module, { ...result.params, ...options.passProps })
+ )
+ } else {
+ if (module.options.onlyShowInTabName) {
+ switchTab(module.options.onlyShowInTabName)
+ // We wait for a frame to allow the tab to be switched before we navigate
+ // This allows us to also override the back button behavior in the tab
+ requestAnimationFrame(() => {
+ internal_navigationRef.current?.dispatch(
+ CommonActions.navigate(result.module, { ...result.params, ...options.passProps })
+ )
+ })
+ } else {
+ internal_navigationRef.current?.dispatch(
+ CommonActions.navigate(result.module, { ...result.params, ...options.passProps })
+ )
+ }
+ }
+ }
+ return
+ }
+
// Set props which we will reinject later. See HACKS.md
propsStore.setPendingProps(screenDescriptor.moduleName, screenDescriptor.props)
@@ -165,6 +201,8 @@ export async function navigate(url: string, options: NavigateOptions = {}) {
export const navigationEvents = new EventEmitter()
export function switchTab(tab: BottomTabType, props?: object) {
+ const enableNewNavigation = unsafe_getFeatureFlag("AREnableNewNavigation")
+
// root tabs are only mounted once so cannot be tracked
// like other screens manually track screen views here
// home handles this on its own since it is default tab
@@ -175,8 +213,15 @@ export function switchTab(tab: BottomTabType, props?: object) {
if (props) {
GlobalStore.actions.bottomTabs.setTabProps({ tab, props })
}
+
GlobalStore.actions.bottomTabs.setSelectedTab(tab)
- LegacyNativeModules.ARScreenPresenterModule.switchTab(tab)
+
+ if (enableNewNavigation) {
+ internal_navigationRef?.current?.dispatch(TabActions.jumpTo(tab, props))
+ return
+ } else {
+ LegacyNativeModules.ARScreenPresenterModule.switchTab(tab)
+ }
}
const tracks = {
@@ -208,10 +253,16 @@ const tracks = {
}
export function dismissModal(after?: () => void) {
+ const enableNewNavigation = unsafe_getFeatureFlag("AREnableNewNavigation")
+
// We wait for interaction to finish before dismissing the modal, otherwise,
// we might get a race condition that causes the UI to freeze
InteractionManager.runAfterInteractions(() => {
- LegacyNativeModules.ARScreenPresenterModule.dismissModal()
+ if (enableNewNavigation) {
+ internal_navigationRef?.current?.dispatch(StackActions.pop())
+ } else {
+ LegacyNativeModules.ARScreenPresenterModule.dismissModal()
+ }
if (Platform.OS === "android") {
navigationEvents.emit("modalDismissed")
}
@@ -221,16 +272,27 @@ export function dismissModal(after?: () => void) {
}
export function goBack(backProps?: GoBackProps) {
- LegacyNativeModules.ARScreenPresenterModule.goBack(unsafe__getSelectedTab())
+ const enableNewNavigation = unsafe_getFeatureFlag("AREnableNewNavigation")
+
navigationEvents.emit("goBack", backProps)
-}
-export function popParentViewController() {
- LegacyNativeModules.ARScreenPresenterModule.popStack(unsafe__getSelectedTab())
+ if (enableNewNavigation) {
+ if (internal_navigationRef.current?.isReady()) {
+ internal_navigationRef.current.dispatch(StackActions.pop())
+ }
+ return
+ }
+
+ LegacyNativeModules.ARScreenPresenterModule.goBack(unsafe__getSelectedTab())
}
export function popToRoot() {
- LegacyNativeModules.ARScreenPresenterModule.popToRootAndScrollToTop(unsafe__getSelectedTab())
+ const enableNewNavigation = unsafe_getFeatureFlag("AREnableNewNavigation")
+ if (enableNewNavigation) {
+ internal_navigationRef?.current?.dispatch(StackActions.popToTop())
+ } else {
+ LegacyNativeModules.ARScreenPresenterModule.popToRootAndScrollToTop(unsafe__getSelectedTab())
+ }
}
export enum EntityType {
diff --git a/src/app/system/navigation/routes.tests.ts b/src/app/system/navigation/routes.tests.ts
index cb2eb43f06e..3e5a33220db 100644
--- a/src/app/system/navigation/routes.tests.ts
+++ b/src/app/system/navigation/routes.tests.ts
@@ -667,33 +667,27 @@ describe("artsy.net routes", () => {
it("routes to Terms and Conditions", () => {
expect(matchRoute("/terms")).toMatchInlineSnapshot(`
- {
- "module": "ModalWebView",
- "params": {
- "alwaysPresentModally": true,
- "safeAreaEdges": [
- "bottom",
- ],
- "url": "/terms",
- },
- "type": "match",
- }
+ {
+ "module": "ModalWebView",
+ "params": {
+ "alwaysPresentModally": true,
+ "url": "/terms",
+ },
+ "type": "match",
+ }
`)
})
it("routes to Privacy Policy", () => {
expect(matchRoute("/privacy")).toMatchInlineSnapshot(`
- {
- "module": "ModalWebView",
- "params": {
- "alwaysPresentModally": true,
- "safeAreaEdges": [
- "bottom",
- ],
- "url": "/privacy",
- },
- "type": "match",
- }
+ {
+ "module": "ModalWebView",
+ "params": {
+ "alwaysPresentModally": true,
+ "url": "/privacy",
+ },
+ "type": "match",
+ }
`)
})
@@ -876,9 +870,6 @@ describe("artsy.net routes", () => {
"module": "ModalWebView",
"params": {
"alwaysPresentModally": true,
- "safeAreaEdges": [
- "bottom",
- ],
"url": "/conditions-of-sale",
},
"type": "match",
@@ -1151,9 +1142,6 @@ describe("artsy.net routes", () => {
"module": "ModalWebView",
"params": {
"alwaysPresentModally": true,
- "safeAreaEdges": [
- "bottom",
- ],
"url": "/privacy",
},
"type": "match",
diff --git a/src/app/system/relay/defaultEnvironment.ts b/src/app/system/relay/defaultEnvironment.ts
index 2fab3724380..c961a1a49bd 100644
--- a/src/app/system/relay/defaultEnvironment.ts
+++ b/src/app/system/relay/defaultEnvironment.ts
@@ -1,4 +1,5 @@
import { cacheHeaderMiddleware } from "app/system/relay/middlewares/cacheHeaderMiddleware"
+import { logRelay } from "app/utils/loggers"
import { Environment as IEnvironment } from "react-relay"
import {
cacheMiddleware,
@@ -42,8 +43,8 @@ const network = new RelayNetworkLayer(
metaphysicsExtensionsLoggerMiddleware(),
cacheHeaderMiddleware(),
simpleLoggerMiddleware(),
- __DEV__ ? relayErrorMiddleware() : null,
- __DEV__ ? perfMiddleware() : null,
+ __DEV__ && logRelay ? relayErrorMiddleware() : null,
+ __DEV__ && logRelay ? perfMiddleware() : null,
timingMiddleware(),
checkAuthenticationMiddleware(), // KEEP AS CLOSE TO THE BOTTOM OF THIS ARRAY AS POSSIBLE. It needs to run as early as possible in the middlewares.
],
diff --git a/src/app/utils/bottomTabsHelper.tsx b/src/app/utils/bottomTabsHelper.tsx
index 0c673a33ad5..37c59856293 100644
--- a/src/app/utils/bottomTabsHelper.tsx
+++ b/src/app/utils/bottomTabsHelper.tsx
@@ -1,6 +1,8 @@
import EventEmitter from "events"
+import { useScrollToTop } from "@react-navigation/native"
import { BottomTabType } from "app/Scenes/BottomTabs/BottomTabType"
-import { useEffect, useRef } from "react"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
+import React, { useEffect, useRef } from "react"
import { FlatList, ScrollView } from "react-native"
export const BottomTabsEvents = new EventEmitter()
@@ -13,11 +15,21 @@ export const scrollTabToTop = (tab: BottomTabType) => {
}
export const useBottomTabsScrollToTop = (tab: BottomTabType, onScrollToTop?: () => void) => {
- const ref = useRef(null)
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
+ const ref = useRef(null)
+
+ useScrollToTop(
+ React.useRef({
+ scrollToTop: () => {
+ handleScrollToTopEvent()
+ },
+ })
+ )
const handleScrollToTopEvent = () => {
- const flatListRef = ref as React.RefObject | null
- const scrollViewRef = ref as React.RefObject | null
+ const flatListRef = ref as unknown as React.RefObject | null
+ const scrollViewRef = ref as unknown as React.RefObject | null
flatListRef?.current?.scrollToOffset?.({ offset: 0 })
scrollViewRef?.current?.scrollTo?.({})
@@ -26,6 +38,10 @@ export const useBottomTabsScrollToTop = (tab: BottomTabType, onScrollToTop?: ()
}
useEffect(() => {
+ if (enableNewNavigation) {
+ return
+ }
+
BottomTabsEvents.addListener(`${SCROLL_TO_TOP_EVENT}-${tab}`, handleScrollToTopEvent)
return () => {
diff --git a/src/app/utils/hooks/useBackHandler.tests.ts b/src/app/utils/hooks/useBackHandler.tests.ts
index 69924344f14..88753a39c8a 100644
--- a/src/app/utils/hooks/useBackHandler.tests.ts
+++ b/src/app/utils/hooks/useBackHandler.tests.ts
@@ -1,12 +1,20 @@
import { renderHook } from "@testing-library/react-hooks"
import { BackHandler } from "react-native"
-import { useBackHandler, useAndroidGoBack } from "./useBackHandler"
+import { useBackHandler } from "./useBackHandler"
jest.mock("react-native", () => ({
BackHandler: {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
},
+ Platform: {
+ OS: "ios",
+ },
+ NativeModules: {
+ ArtsyNativeModule: {
+ gitCommitShortHash: "1234567",
+ },
+ },
}))
describe("useBackHandler Hooks", () => {
@@ -60,25 +68,4 @@ describe("useBackHandler Hooks", () => {
expect(removeEventListenerMock).toBeCalledWith("hardwareBackPress", handler)
})
})
-
- describe("useAndroidGoBack", () => {
- it("should add back press listener on screen mount", () => {
- renderHook(() => useAndroidGoBack())
-
- expect(addEventListenerMock).toHaveBeenCalledTimes(1)
- expect(removeEventListenerMock).toHaveBeenCalledTimes(0)
- })
-
- it("should remove back press listener on screen unmount", () => {
- const { unmount } = renderHook(() => useAndroidGoBack())
-
- expect(addEventListenerMock).toHaveBeenCalledTimes(1)
- expect(removeEventListenerMock).toHaveBeenCalledTimes(0)
-
- unmount()
-
- expect(addEventListenerMock).toHaveBeenCalledTimes(1)
- expect(removeEventListenerMock).toHaveBeenCalledTimes(1)
- })
- })
})
diff --git a/src/app/utils/hooks/useBackHandler.ts b/src/app/utils/hooks/useBackHandler.ts
index 274d9a7ab52..b998bfe930b 100644
--- a/src/app/utils/hooks/useBackHandler.ts
+++ b/src/app/utils/hooks/useBackHandler.ts
@@ -1,5 +1,6 @@
import { useFocusEffect } from "@react-navigation/native"
import { goBack } from "app/system/navigation/navigate"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { useCallback, useEffect } from "react"
import { BackHandler, InteractionManager } from "react-native"
@@ -23,8 +24,14 @@ export function useBackHandler(handler: () => boolean) {
*
*/
export function useAndroidGoBack() {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
useFocusEffect(
useCallback(() => {
+ if (enableNewNavigation) {
+ return
+ }
+
const onBackPress = () => {
// this is needed in order to wait for the animation to finish
// before moving to the previous screen for better performance
diff --git a/src/app/utils/hooks/useSelectedTab.ts b/src/app/utils/hooks/useSelectedTab.ts
index 523f8981066..f3d07b5610c 100644
--- a/src/app/utils/hooks/useSelectedTab.ts
+++ b/src/app/utils/hooks/useSelectedTab.ts
@@ -1,12 +1,20 @@
import { useNavigationState } from "@react-navigation/native"
import { __unsafe_mainModalStackRef } from "app/NativeModules/ARScreenPresenterModule"
import { BottomTabType } from "app/Scenes/BottomTabs/BottomTabType"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
export function useSelectedTab(): BottomTabType {
+ const enableNewNavigation = useFeatureFlag("AREnableNewNavigation")
+
+ const routeName = useNavigationState((state) => state.routes[state.index].name)
const tabState = useNavigationState(
(state) => state.routes.find((r) => r.state?.type === "tab")?.state
)
+ if (enableNewNavigation) {
+ return routeName as BottomTabType
+ }
+
const _unsafe_tabState = __unsafe_mainModalStackRef?.current
?.getState()
?.routes.find((r) => r.state?.type === "tab")?.state
@@ -18,6 +26,7 @@ export function useSelectedTab(): BottomTabType {
if (index === undefined) {
return "home"
}
+
return routes[index].name as BottomTabType
}
}
diff --git a/src/app/utils/useDeepLinks.ts b/src/app/utils/useDeepLinks.ts
index 2f9427e589f..ef0fc7af7b1 100644
--- a/src/app/utils/useDeepLinks.ts
+++ b/src/app/utils/useDeepLinks.ts
@@ -8,6 +8,8 @@ import { useTracking } from "react-tracking"
export function useDeepLinks() {
const isLoggedIn = GlobalStore.useAppState((state) => !!state.auth.userAccessToken)
const isHydrated = GlobalStore.useAppState((state) => state.sessionState.isHydrated)
+ const isNavigationReady = GlobalStore.useAppState((state) => state.sessionState.isNavigationReady)
+
const launchURL = useRef(null)
const { trackEvent } = useTracking()
@@ -18,7 +20,7 @@ export function useDeepLinks() {
handleDeepLink(url)
}
})
- }, [])
+ }, [isNavigationReady])
useEffect(() => {
const subscription = Linking.addListener("url", ({ url }) => {
@@ -28,7 +30,7 @@ export function useDeepLinks() {
return () => {
subscription.remove()
}
- }, [isHydrated, isLoggedIn])
+ }, [isHydrated, isLoggedIn, isNavigationReady])
const handleDeepLink = async (url: string) => {
let targetURL
@@ -58,7 +60,7 @@ export function useDeepLinks() {
// If the state is hydrated and the user is logged in
// We navigate them to the the deep link
- if (isHydrated && isLoggedIn) {
+ if (isHydrated && isLoggedIn && isNavigationReady) {
// and we track the deep link
navigate(deepLinkUrl)
return
@@ -70,13 +72,13 @@ export function useDeepLinks() {
}
useEffect(() => {
- if (isLoggedIn && launchURL.current) {
+ if (isLoggedIn && launchURL.current && isNavigationReady) {
// Navigate to the saved launch url
navigate(launchURL.current)
// Reset the launchURL
launchURL.current = null
}
- }, [isLoggedIn, isHydrated, launchURL.current])
+ }, [isLoggedIn, isHydrated, launchURL.current, isNavigationReady])
}
const tracks = {
diff --git a/src/app/utils/useTabBarBadge.ts b/src/app/utils/useTabBarBadge.ts
new file mode 100644
index 00000000000..ad517942272
--- /dev/null
+++ b/src/app/utils/useTabBarBadge.ts
@@ -0,0 +1,27 @@
+import { FETCH_NOTIFICATIONS_INFO_INTERVAL } from "app/Scenes/BottomTabs/BottomTabs"
+import { GlobalStore } from "app/store/GlobalStore"
+import { useEffect } from "react"
+import { useInterval } from "react-use"
+
+export const useTabBarBadge = () => {
+ const unreadConversationsCount = GlobalStore.useAppState(
+ (state) => state.bottomTabs.sessionState.unreadCounts.conversations
+ )
+ const hasUnseenNotifications = GlobalStore.useAppState(
+ (state) => state.bottomTabs.hasUnseenNotifications
+ )
+
+ useEffect(() => {
+ GlobalStore.actions.bottomTabs.fetchNotificationsInfo()
+ }, [])
+
+ useInterval(() => {
+ GlobalStore.actions.bottomTabs.fetchNotificationsInfo()
+ // run this every 60 seconds
+ }, FETCH_NOTIFICATIONS_INFO_INTERVAL)
+
+ return {
+ unreadConversationsCount: unreadConversationsCount || undefined,
+ hasUnseenNotifications,
+ }
+}
diff --git a/src/setupJest.tsx b/src/setupJest.tsx
index b0155eb343b..92cb5730342 100644
--- a/src/setupJest.tsx
+++ b/src/setupJest.tsx
@@ -126,7 +126,9 @@ jest.mock("@react-navigation/native", () => {
navigate: mockNavigate,
dispatch: jest.fn(),
addListener: jest.fn(),
+ setOptions: jest.fn(),
}),
+ useScrollToTop: jest.fn(),
}
})
diff --git a/yarn.lock b/yarn.lock
index 569530093a1..452d8066868 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12928,10 +12928,10 @@ react-native-safe-area-context@3.4.0:
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-3.4.0.tgz#b751f492a7f7470bccb09f1a11ccc276a3c5fe98"
integrity sha512-kmzSK8L9LX+1rF6+qPBZR0kjGn5rE0IHNHL4px/lNwyxA+0siekTkCG+BlzbBy4V3yKeLzQ+UxgT9mEtDHs/Tg==
-react-native-screens@3.34.0:
- version "3.34.0"
- resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.34.0.tgz#1291a460c5bc59e2ba581b42d40fa9a58d3b1197"
- integrity sha512-8ri3Pd9QcpfXnVckOe/Lnto+BXmSPHV/Q0RB0XW0gDKsCv5wi5k7ez7g1SzgiYHl29MSdiqgjH30zUyOOowOaw==
+react-native-screens@3.35.0:
+ version "3.35.0"
+ resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.35.0.tgz#4acf4c7d331d47d33d0214d415779540b7664e0e"
+ integrity sha512-rmkqb/M/SQIrXwygk6pXcOhgHltYAhidf1WceO7ujAxkr6XtwmgFyd1HIztsrJa568GrAuwPdQ11I7TpVk+XsA==
dependencies:
react-freeze "^1.0.0"
warn-once "^0.1.0"
@@ -14259,16 +14259,7 @@ string-natural-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
-"string-width-cjs@npm:string-width@^4.2.0":
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -14382,7 +14373,7 @@ stringify-entities@^3.1.0:
character-entities-legacy "^1.0.0"
xtend "^4.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -14396,13 +14387,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0:
dependencies:
ansi-regex "^4.1.0"
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
strip-ansi@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
@@ -15818,7 +15802,7 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f"
integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -15836,15 +15820,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"