diff --git a/app/app.tsx b/app/app.tsx index fa0198a9dd..dccd4424d3 100644 --- a/app/app.tsx +++ b/app/app.tsx @@ -32,6 +32,7 @@ import { ErrorScreen } from "./screens/error-screen" import { PersistentStateProvider } from "./store/persistent-state" import { detectDefaultLocale } from "./utils/locale-detector" import "./utils/logs" +import { ActionModals, ActionsProvider } from "./components/actions" // FIXME should we only load the currently used local? // this would help to make the app load faster @@ -52,19 +53,22 @@ export const App = () => ( - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/app/components/actions/index.tsx b/app/components/actions/index.tsx new file mode 100644 index 0000000000..1baa94a705 --- /dev/null +++ b/app/components/actions/index.tsx @@ -0,0 +1,55 @@ +import React from "react" +import { SetLightningAddressModal } from "../set-lightning-address-modal" +import { SetDefaultAccountModal } from "../set-default-account-modal" +import { UpgradeAccountModal } from "../upgrade-account-modal" + +export const Action = { + SetLnAddress: "SetLnAddress", + SetDefaultAccount: "SetDefaultAccount", + UpgradeAccount: "UpgradeAccount", +} as const + +export type Action = (typeof Action)[keyof typeof Action] + +type ActionsContextType = { + setActiveAction: (action: Action | null) => void + activeAction: Action | null +} + +const ActionsContext = React.createContext({ + setActiveAction: () => {}, + activeAction: null, +}) + +export const ActionsProvider: React.FC = ({ children }) => { + const [activeAction, setActiveAction] = React.useState(null) + + return ( + + {children} + + ) +} + +export const ActionModals: React.FC = () => { + const { activeAction, setActiveAction } = useActionsContext() + const closeModal = () => setActiveAction(null) + return ( + <> + + + + + ) +} + +export const useActionsContext = () => React.useContext(ActionsContext) diff --git a/app/components/push-notification/push-notification.tsx b/app/components/push-notification/push-notification.tsx index 9ecbdf2efc..19a167e7d3 100644 --- a/app/components/push-notification/push-notification.tsx +++ b/app/components/push-notification/push-notification.tsx @@ -2,62 +2,26 @@ import React, { useEffect } from "react" import { useApolloClient } from "@apollo/client" import { useIsAuthed } from "@app/graphql/is-authed-context" -import { useAuthenticationContext } from "@app/navigation/navigation-container-wrapper" import { addDeviceToken, hasNotificationPermission } from "@app/utils/notifications" import messaging, { FirebaseMessagingTypes } from "@react-native-firebase/messaging" -import { useLinkTo } from "@react-navigation/native" - -import { useNotifications } from "../notifications" - -const circlesNotificationTypes = [ - "InnerCircleGrew", - "OuterCircleGrew", - "InnerCircleThisMonthThresholdReached", - "InnerCircleAllTimeThresholdReached", - "OuterCircleThisMonthThresholdReached", - "OuterCircleAllTimeThresholdReached", - "LeaderboardThisMonthThresholdReached", - "LeaderboardAllTimeThresholdReached", -] +import { Linking } from "react-native" export const PushNotificationComponent = (): JSX.Element => { const client = useApolloClient() const isAuthed = useIsAuthed() - const { notifyCard } = useNotifications() - - const linkTo = useLinkTo() - const isAppLocked = useAuthenticationContext().isAppLocked useEffect(() => { - if (isAppLocked) { - return - } - - const showNotification = (remoteMessage: FirebaseMessagingTypes.RemoteMessage) => { + const followNotificationLink = ( + remoteMessage: FirebaseMessagingTypes.RemoteMessage, + ) => { try { - if (remoteMessage.notification?.body) { - // TODO: add notifee library to show local notifications - console.log( - remoteMessage.notification.title || "", - remoteMessage.notification.body, - ) - } - - const notificationType = remoteMessage.data?.notificationType ?? "" - if ( - typeof notificationType === "string" && - circlesNotificationTypes.includes(notificationType) - ) { - linkTo("/people/circles") - } - const linkToScreen = remoteMessage.data?.linkTo ?? "" if ( typeof linkToScreen === "string" && linkToScreen && linkToScreen.startsWith("/") ) { - linkTo(linkToScreen) + Linking.openURL("blink:" + linkToScreen) } // linkTo throws an error if the link is invalid } catch (error) { @@ -68,19 +32,13 @@ export const PushNotificationComponent = (): JSX.Element => { // When the application is running, but in the background. const unsubscribeBackground = messaging().onNotificationOpenedApp( (remoteMessage: FirebaseMessagingTypes.RemoteMessage) => { - showNotification(remoteMessage) + followNotificationLink(remoteMessage) }, ) const unsubscribeInApp = messaging().onMessage(async (remoteMessage) => { - notifyCard({ - text: remoteMessage.notification?.body ?? "", - title: remoteMessage.notification?.title ?? "", - action: async () => { - showNotification(remoteMessage) - }, - icon: "bell", - }) + console.log("A new FCM message arrived!", remoteMessage) + // TODO: add notifee library to show local notifications }) // When the application is opened from a quit state. @@ -88,7 +46,7 @@ export const PushNotificationComponent = (): JSX.Element => { .getInitialNotification() .then((remoteMessage: FirebaseMessagingTypes.RemoteMessage | null) => { if (remoteMessage) { - showNotification(remoteMessage) + followNotificationLink(remoteMessage) } }) @@ -96,7 +54,7 @@ export const PushNotificationComponent = (): JSX.Element => { unsubscribeInApp() unsubscribeBackground() } - }, [linkTo, isAppLocked, notifyCard]) + }, []) useEffect(() => { ;(async () => { diff --git a/app/components/set-default-account-modal/set-default-account-modal.tsx b/app/components/set-default-account-modal/set-default-account-modal.tsx index e1bbb09ebf..6ad00fc259 100644 --- a/app/components/set-default-account-modal/set-default-account-modal.tsx +++ b/app/components/set-default-account-modal/set-default-account-modal.tsx @@ -12,10 +12,7 @@ import { } from "@app/graphql/generated" import { getBtcWallet, getUsdWallet } from "@app/graphql/wallets-utils" import { useI18nContext } from "@app/i18n/i18n-react" -import { RootStackParamList } from "@app/navigation/stack-param-lists" import crashlytics from "@react-native-firebase/crashlytics" -import { useNavigation } from "@react-navigation/native" -import { StackNavigationProp } from "@react-navigation/stack" import { makeStyles, Text, useTheme } from "@rneui/themed" import { GaloyCurrencyBubble } from "../atomic/galoy-currency-bubble" @@ -51,7 +48,6 @@ export const SetDefaultAccountModal = ({ const [usdLoading, setUsdLoading] = React.useState(false) const [accountUpdateDefaultWallet] = useAccountUpdateDefaultWalletIdMutation() - const navigation = useNavigation>() const { data } = useSetDefaultAccountModalQuery({ fetchPolicy: "cache-only", @@ -83,7 +79,6 @@ export const SetDefaultAccountModal = ({ setHasPromptedSetDefaultAccount(client) toggleModal() - navigation.navigate("receiveBitcoin") } const onPressBtcAccount = async () => { @@ -107,7 +102,6 @@ export const SetDefaultAccountModal = ({ setHasPromptedSetDefaultAccount(client) toggleModal() - navigation.navigate("receiveBitcoin") } return ( diff --git a/app/navigation/navigation-container-wrapper.tsx b/app/navigation/navigation-container-wrapper.tsx index 69fa1f1749..6ac81268f9 100644 --- a/app/navigation/navigation-container-wrapper.tsx +++ b/app/navigation/navigation-container-wrapper.tsx @@ -1,5 +1,5 @@ import * as React from "react" -import { useRef } from "react" +import { useEffect, useRef } from "react" import { Linking } from "react-native" import RNBootSplash from "react-native-bootsplash" @@ -16,6 +16,7 @@ import { useTheme } from "@rneui/themed" import { useIsAuthed } from "../graphql/is-authed-context" import { RootStackParamList } from "./stack-param-lists" +import { Action, useActionsContext } from "@app/components/actions" export type AuthenticationContextType = { isAppLocked: boolean @@ -32,31 +33,48 @@ export const AuthenticationContextProvider = AuthenticationContext.Provider export const useAuthenticationContext = () => React.useContext(AuthenticationContext) +const processLinkForAction = (url: string): Action | null => { + // grab action query param + const urlObj = new URL(url) + const action = urlObj.searchParams.get("action") + + switch ((action || "").toLocaleLowerCase()) { + case "set-ln-address": + return Action.SetLnAddress + case "set-default-account": + return Action.SetDefaultAccount + case "upgrade-account": + return Action.UpgradeAccount + } + return null +} + export const NavigationContainerWrapper: React.FC = ({ children, }) => { const isAuthed = useIsAuthed() + const [isAppLocked, setIsAppLocked] = React.useState(true) + const [urlAfterUnlockAndAuth, setUrlAfterUnlockAndAuth] = React.useState( + null, + ) + const { setActiveAction } = useActionsContext() - const processLink = useRef<((url: string) => void) | null>(() => { - return undefined - }) + useEffect(() => { + if (isAuthed && !isAppLocked && urlAfterUnlockAndAuth) { + Linking.openURL(urlAfterUnlockAndAuth) + setUrlAfterUnlockAndAuth(null) + } + }, [isAuthed, isAppLocked, urlAfterUnlockAndAuth]) const setAppUnlocked = React.useMemo( () => async () => { setIsAppLocked(false) - const url = await Linking.getInitialURL() - - if (url && isAuthed && processLink.current) { - return processLink.current(url) - } }, - [isAuthed], + [], ) const setAppLocked = React.useMemo(() => () => setIsAppLocked(true), []) - const [isAppLocked, setIsAppLocked] = React.useState(true) - const routeName = useRef("Initial") const { @@ -101,6 +119,18 @@ export const NavigationContainerWrapper: React.FC = ({ receiveBitcoin: "receive", conversionDetails: "convert", scanningQRCode: "scan-qr", + chatbot: "chat", + totpRegistrationInitiate: "settings/2fa", + currency: "settings/display-currency", + defaultWallet: "settings/default-account", + language: "settings/language", + theme: "settings/theme", + security: "settings/security", + accountScreen: "settings/account", + transactionLimitsScreen: "settings/tx-limits", + notificationSettingsScreen: "settings/notifications", + emailRegistrationInitiate: "settings/email", + settings: "settings", transactionDetail: { path: "transaction/:txid", }, @@ -109,17 +139,20 @@ export const NavigationContainerWrapper: React.FC = ({ }, getInitialURL: async () => { const url = await Linking.getInitialURL() - console.log("getInitialURL", url) - if (Boolean(url) && isAuthed && !isAppLocked) { - return url - } + setUrlAfterUnlockAndAuth(url) return null }, subscribe: (listener) => { - processLink.current = listener const onReceiveURL = ({ url }: { url: string }) => { - console.log("onReceiveURL", url) - listener(url) + if (!isAppLocked && isAuthed) { + const maybeAction = processLinkForAction(url) + if (maybeAction) { + setActiveAction(maybeAction) + } + listener(url) + } else { + setUrlAfterUnlockAndAuth(url) + } } // Listen to incoming links from deep linking const subscription = Linking.addEventListener("url", onReceiveURL) @@ -127,7 +160,6 @@ export const NavigationContainerWrapper: React.FC = ({ return () => { // Clean up the event listeners subscription.remove() - processLink.current = null } }, } diff --git a/app/screens/home-screen/home-screen.tsx b/app/screens/home-screen/home-screen.tsx index 884808894f..820f944c66 100644 --- a/app/screens/home-screen/home-screen.tsx +++ b/app/screens/home-screen/home-screen.tsx @@ -414,7 +414,10 @@ export const HomeScreen: React.FC = () => { { + toggleSetDefaultAccountModal() + navigation.navigate("receiveBitcoin") + }} />