@react-navigation/native#useIsFocused TypeScript Examples

The following examples show how to use @react-navigation/native#useIsFocused. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: a11y-element.tsx    From protect-scotland with Apache License 2.0 6 votes vote down vote up
export function useA11yElement() {
  const {
    accessibility: {screenReaderEnabled}
  } = useApplication();
  const focusRef = useRef(null);
  const isFocused = useIsFocused();

  const focusA11yElement = useCallback(
    (timeout = 250) => {
      if (screenReaderEnabled && focusRef.current && isFocused) {
        const tag = findNodeHandle(focusRef.current);
        if (tag) {
          const id = setTimeout(
            () => AccessibilityInfo.setAccessibilityFocus(tag),
            timeout
          );
          return () => clearTimeout(id);
        }
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [screenReaderEnabled, focusRef.current, isFocused]
  );

  const a11yProps = useMemo(
    () => ({
      ref: focusRef
    }),
    []
  );

  return {a11yProps, focusRef, focusA11yElement};
}
Example #2
Source File: common.ts    From iotc-cpm-sample with MIT License 6 votes vote down vote up
export function useHeaderTitle(title: string): void {
  const isFocused = useIsFocused();
  const navigation = useNavigation<NavigationProperty>();
  const counter = useRef(0);

  useEffect(() => {
    if (isFocused) {
      counter.current++;
      navigation.setParams({title});
    }
  }, [navigation, title, isFocused]);
}
Example #3
Source File: IterableInbox.tsx    From react-native-sdk with MIT License 4 votes vote down vote up
IterableInbox = ({
   returnToInboxTrigger = true,
   messageListItemLayout = () => { return null },
   customizations = {} as IterableInboxCustomizations,
   tabBarHeight = 80,
   tabBarPadding = 20,
   safeAreaMode = true
}: inboxProps) => {
   const defaultInboxTitle = "Inbox"
   const inboxDataModel = new IterableInboxDataModel()

   let { height, width, isPortrait } = useDeviceOrientation()
   const appState = useAppStateListener()
   const isFocused = useIsFocused()

   const [selectedRowViewModelIdx, setSelectedRowViewModelIdx] = useState<number>(0)
   const [rowViewModels, setRowViewModels] = useState<InboxRowViewModel[]>([])
   const [loading, setLoading] = useState<boolean>(true)
   const [animatedValue] = useState<any>(new Animated.Value(0))
   const [isMessageDisplay, setIsMessageDisplay] = useState<boolean>(false)
   
   const [visibleMessageImpressions, setVisibleMessageImpressions] = useState<InboxImpressionRowInfo[]>([])

   const styles = StyleSheet.create({
      loadingScreen: {
         height: '100%',
         backgroundColor: 'whitesmoke'
      },
   
      container: {
         flex: 1,
         flexDirection: 'row',
         alignItems: 'center',
         justifyContent: 'flex-start',
         height: '100%',
         width: 2 * width,
         paddingBottom: 0,
         paddingLeft: 0,
         paddingRight: 0
      },
   
      messageListContainer: {
         height: '100%',
         width: width,
         flexDirection: 'column',
         justifyContent: 'flex-start',
      },
   
      headline: {
         fontWeight: 'bold',
         fontSize: 40,
         width: '100%',
         height: 60,
         marginTop: 0,
         paddingTop: 10,
         paddingBottom: 10,
         paddingLeft: 30,
         backgroundColor: 'whitesmoke'
      }
   })

   let {
      loadingScreen,
      container,
      headline,
      messageListContainer
   } = styles

   const navTitleHeight = headline.height + headline.paddingTop + headline.paddingBottom
   headline = { ...headline, height: Platform.OS === "android" ? 70 : 60 }
   headline = (!isPortrait) ? { ...headline, paddingLeft: 70 } : headline

   //fetches inbox messages and adds listener for inbox changes on mount
   useEffect(() => {
      fetchInboxMessages()
      addInboxChangedListener()

      //removes listener for inbox changes on unmount and ends inbox session
      return () => {
         removeInboxChangedListener()
         inboxDataModel.endSession(visibleMessageImpressions)
      }
   }, [])

   //starts session when user is on inbox and app is active
   //ends session when app is in background or app is closed
   useEffect(() => {
      if(isFocused) {
         if(appState === 'active') {
            inboxDataModel.startSession(visibleMessageImpressions)
         } else if(appState === 'background' && Platform.OS === 'android' || appState === 'inactive') {
            inboxDataModel.endSession(visibleMessageImpressions)
         }
      }
   }, [appState])

   //starts session when user is on inbox
   //ends session when user navigates away from inbox
   useEffect(() => {
      if(appState === 'active') {
         if(isFocused) {
            inboxDataModel.startSession(visibleMessageImpressions)
         } else {
            inboxDataModel.endSession(visibleMessageImpressions)
         }
      }
   }, [isFocused])

   //updates the visible rows when visible messages changes
   useEffect(() => {
      inboxDataModel.updateVisibleRows(visibleMessageImpressions)
   }, [visibleMessageImpressions])

   //if return to inbox trigger is provided, runs the return to inbox animation whenever the trigger is toggled
   useEffect(() => {
      if(isMessageDisplay) {
         returnToInbox()
      }
   }, [returnToInboxTrigger])

   function addInboxChangedListener() {
      RNEventEmitter.addListener(
         "receivedIterableInboxChanged",
         () => {
            fetchInboxMessages()
         }
      )
   }

   function removeInboxChangedListener() {
      RNEventEmitter.removeAllListeners("receivedIterableInboxChanged")
   }

   async function fetchInboxMessages() {
      let newMessages = await inboxDataModel.refresh()

      newMessages = newMessages.map((message, index) => {
         return { ...message, last: index === newMessages.length - 1 }
      })

      setRowViewModels(newMessages)
      setLoading(false)
   }

   function getHtmlContentForRow(id: string) {
      return inboxDataModel.getHtmlContentForMessageId(id)
   }

   function handleMessageSelect(id: string, index: number, rowViewModels: InboxRowViewModel[]) {
      let newRowViewModels = rowViewModels.map((rowViewModel) => {
         return (rowViewModel.inAppMessage.messageId === id) ?
            { ...rowViewModel, read: true } : rowViewModel
      })
      setRowViewModels(newRowViewModels)
      inboxDataModel.setMessageAsRead(id)
      setSelectedRowViewModelIdx(index)

      Iterable.trackInAppOpen(rowViewModels[index].inAppMessage, IterableInAppLocation.inbox)

      slideLeft()
   }

   function deleteRow(messageId: string) {
      inboxDataModel.deleteItemById(messageId, IterableInAppDeleteSource.inboxSwipe)
      fetchInboxMessages()
   }

   function returnToInbox(callback?: Function) {
      Animated.timing(animatedValue, {
         toValue: 0,
         duration: 300,
         useNativeDriver: false
      }).start(() => typeof callback === 'function' && callback())
      setIsMessageDisplay(false)
   }

   function updateVisibleMessageImpressions(messageImpressions: InboxImpressionRowInfo[]) {
      setVisibleMessageImpressions(messageImpressions)
   }

   function showMessageDisplay(rowViewModelList: InboxRowViewModel[], index: number) {
      const selectedRowViewModel = rowViewModelList[index]

      return (
         selectedRowViewModel ?
            <IterableInboxMessageDisplay
               rowViewModel={selectedRowViewModel}
               inAppContentPromise={getHtmlContentForRow(selectedRowViewModel.inAppMessage.messageId)}
               returnToInbox={(callback: Function) => returnToInbox(callback)}
               deleteRow={(messageId: string) => deleteRow(messageId)}
               contentWidth={width}
               isPortrait={isPortrait}
            /> : null
      )
   }

   function showMessageList(loading: boolean) {
      return (
         <View style={messageListContainer}>
            <Text style={headline}>
               {customizations?.navTitle ? customizations?.navTitle : defaultInboxTitle}
            </Text>
            {rowViewModels.length ?
               <IterableInboxMessageList
                  dataModel={inboxDataModel}
                  rowViewModels={rowViewModels}
                  customizations={customizations}
                  messageListItemLayout={messageListItemLayout}
                  deleteRow={(messageId: string) => deleteRow(messageId)}
                  handleMessageSelect={(messageId: string, index: number) => handleMessageSelect(messageId, index, rowViewModels)}
                  updateVisibleMessageImpressions={(messageImpressions: InboxImpressionRowInfo[]) => updateVisibleMessageImpressions(messageImpressions)}
                  contentWidth={width}
                  isPortrait={isPortrait}
               /> :
               renderEmptyState()
            }
         </View>)
   }

   function renderEmptyState() {
      return loading ?
         <View style={loadingScreen} /> :
         <IterableInboxEmptyState
            customizations={customizations}
            tabBarHeight={tabBarHeight}
            tabBarPadding={tabBarPadding}
            navTitleHeight={navTitleHeight}
            contentWidth={width}
            height={height}
            isPortrait={isPortrait}
         />
   }

   function slideLeft() {
      Animated.timing(animatedValue, {
         toValue: 1,
         duration: 300,
         useNativeDriver: false
      }).start()
      setIsMessageDisplay(true)
   }

   const inboxAnimatedView =
      <Animated.View
         style={{
               transform: [
                  {
                     translateX: animatedValue.interpolate({
                        inputRange: [0, 1],
                        outputRange: [0, -width]
                     })
                  }
               ],
               height: '100%',
               flexDirection: 'row',
               width: 2 * width,
               justifyContent: 'flex-start'
            }}
         >
            {showMessageList(loading)}
            {showMessageDisplay(rowViewModels, selectedRowViewModelIdx)}
      </Animated.View>

   return(
      (safeAreaMode) ?
         <SafeAreaView style={container}>{inboxAnimatedView}</SafeAreaView> : 
         <View style={container}>{inboxAnimatedView}</View> 
   )
}
Example #4
Source File: close-contact.tsx    From protect-scotland with Apache License 2.0 4 votes vote down vote up
CloseContact: FC<CloseContactProps> = () => {
  const {t} = useTranslation();
  const {getTranslation} = useAgeGroupTranslation();
  const navigation = useNavigation();
  const {contacts, getCloseContacts} = useExposure();
  const isFocused = useIsFocused();
  const {
    isolationDuration,
    testingInstructions,
    helplineNumber,
    showCertUnderage
  } = useSettings();
  const [showSendNotice, setShowSendNotice] = useState(false);

  PushNotification.setApplicationIconBadgeNumber(0);

  const exposureDate = getExposureDate(contacts);

  const remainingDays = getSelfIsolationRemainingDays(
    isolationDuration,
    contacts
  );

  const {
    accessibility: {screenReaderEnabled},
    user: {ageGroup = UserAgeGroup.ageGroup1} = {}
  } = useApplication();

  useEffect(() => {
    getCloseContacts();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const steps = [
    {
      title: t('closeContact:nextSteps:step1:title'),
      text: getTranslation('closeContact:nextSteps:step1:text'),
      link: t('links:d'),
      linkText: getTranslation('closeContact:nextSteps:step1:link')
    },
    {
      title: t('closeContact:nextSteps:step2:title'),
      text: t('closeContact:nextSteps:step2:text'),
      link: t('links:s'),
      linkText: t('closeContact:nextSteps:step2:link')
    },
    {
      title: t('closeContact:nextSteps:step3:title'),
      text: t('closeContact:nextSteps:step3:text'),
      link: t('links:t'),
      linkText: t('closeContact:nextSteps:step3:link')
    },
    {
      title: t('closeContact:nextSteps:step4:title'),
      text: t('closeContact:nextSteps:step4:text')
    },
    {
      title: t('closeContact:nextSteps:step5:title'),
      text: (
        <Markdown
          markdownStyles={markdownStylesBlock}
          accessibleLink={`tel:${helplineNumber.split(' ').join('')}`}>
          {getTranslation('closeContact:nextSteps:step5:text', {
            helplineNumber,
            helplineNumberTrimmed: helplineNumber.split(' ').join('')
          })}
        </Markdown>
      )
    }
  ];

  useFocusEffect(
    useCallback(() => {
      const getCertificateKey = async () => {
        try {
          const selfIsolationEndDate = getIsolationEndDate(
            isolationDuration,
            contacts,
            'yyyy-MM-dd'
          );

          const data = await SecureStore.getItemAsync('createNoticeCertKey');

          if (data) {
            const {key, selfIsolationDate} = JSON.parse(data);

            if (key && selfIsolationDate) {
              if (selfIsolationDate !== selfIsolationEndDate?.formatted) {
                return setShowSendNotice(true);
              }

              const isValidKey = await validateNoticeKey(key);

              return setShowSendNotice(Boolean(isValidKey));
            }
          }

          setShowSendNotice(true);
        } catch (err) {
          console.log('Error retrieving createNoticeCertKey', err);

          setShowSendNotice(false);
        }
      };

      if (!isFocused) {
        return;
      }
      getCertificateKey();
    }, [isFocused, contacts, isolationDuration])
  );

  return (
    <ScrollView style={styles.container}>
      <View style={styles.content}>
        <ModalHeader type="inline" heading={t('closeContact:title')} />
        <Text variant="h1" color="errorRed">
          {getTranslation('closeContact:warning', {
            days: remainingDays
          })}
        </Text>
        <Spacing s={32} />
        <Markdown>
          {getTranslation('closeContact:body1', {
            days: remainingDays,
            date: exposureDate ? format(exposureDate, DATE_FORMAT) : undefined
          })}
        </Markdown>
      </View>
      {ageGroup !== UserAgeGroup.ageGroup1 && <Spacing s={24} />}

      <Divider color="white" />
      <View style={styles.content}>
        <Spacing s={24} />
        <ArrowLink
          externalLink={t('links:c')}
          accessibilityHint={t('closeContact:link1Hint')}>
          <Text variant="h4" color="primaryPurple">
            {t('closeContact:link1')}
          </Text>
        </ArrowLink>
        <Spacing s={24} />
      </View>
      <Divider color="white" />

      <View style={styles.content}>
        <Spacing s={50} />
        <Text variant="h3" color="errorRed">
          {t('closeContact:nextSteps:title')}
        </Text>
        <Spacing s={23} />
        {ageGroup !== UserAgeGroup.ageGroup1 && (
          <Text>{t('closeContact:nextSteps:intro')}</Text>
        )}
        <Spacing s={32} />

        {steps.map(({...step}, i) => (
          <Fragment key={i}>
            <CloseContactStep number={i + 1} {...step} />
            <Spacing s={i === steps.length - 1 ? 24 : 40} />
          </Fragment>
        ))}

        {showSendNotice &&
          (ageGroup === UserAgeGroup.ageGroup1 ? true : showCertUnderage) && (
            <>
              <Spacing s={20} />
              <RoundedBox style={styles.sendNoticeBox}>
                <Spacing s={10} />
                <Markdown markdownStyles={markdownStyles}>
                  {t('closeContact:sendNotice:text')}
                </Markdown>
                <Button
                  variant="dark"
                  onPress={() => navigation.navigate(ScreenNames.sendNotice)}
                  label={t('closeContact:sendNotice:button')}
                  hint={t('closeContact:sendNotice:button')}>
                  {t('closeContact:sendNotice:button')}
                </Button>
                <Spacing s={10} />
              </RoundedBox>
              <Spacing s={50} />
            </>
          )}

        {ageGroup === UserAgeGroup.ageGroup1 && (
          <>
            <ArrowLink externalLink={t('links:k')}>
              <Text variant="h4" color="primaryPurple">
                {t('closeContact:link3')}
              </Text>
            </ArrowLink>
            <Spacing s={40} />
          </>
        )}

        <Markdown>{testingInstructions}</Markdown>
      </View>

      <Spacing s={8} />
      <View style={styles.content}>
        <RoundedBox style={styles.notWellBox}>
          <Spacing s={10} />
          <Markdown markdownStyles={markdownStyles}>
            {t('closeContact:notWell')}
          </Markdown>
          <Spacing s={20} />
          <Button
            variant="dark"
            onPress={() => openBrowserAsync(t('links:e'))}
            label={t('closeContact:button1')}
            hint={t('closeContact:button1')}>
            {t('closeContact:button1')}
          </Button>
          <Spacing s={20} />
          <Markdown markdownStyles={markdownStyles}>
            {t('closeContact:notWell1', {
              phone: screenReaderEnabled
                ? t('closeContact:accessiblePhoneLink')
                : t('closeContact:phoneLink')
            })}
          </Markdown>
          <Spacing s={20} />
          <Button
            variant="dark"
            icon={TelIcon}
            onPress={() => Linking.openURL('tel:111')}
            label={t('closeContact:button2')}
            hint={t('closeContact:button2')}>
            {t('closeContact:button2')}
          </Button>
          <Spacing s={10} />
        </RoundedBox>
      </View>
    </ScrollView>
  );
}
Example #5
Source File: dashboard.tsx    From protect-scotland with Apache License 2.0 4 votes vote down vote up
Dashboard: FC = () => {
  const {t} = useTranslation();
  const {
    initialised,
    enabled,
    status,
    contacts,
    getCloseContacts,
    permissions,
    readPermissions
  } = useExposure();
  const [appState] = useAppState();
  const {checked, paused} = useReminder();
  const navigation = useNavigation();
  const {onboarded, setContext, loadAppData} = useApplication();
  const {
    isolationDuration,
    isolationCompleteDuration,
    latestVersion: appLatestVersion
  } = useSettings();
  const [refreshing, setRefreshing] = useState(false);
  const {
    focusRef: tourFocus,
    focusA11yElement: focusTourElem
  } = useA11yElement();
  const {
    focusRef: dashboardFocus,
    focusA11yElement: focusDashboardElem
  } = useA11yElement();
  const isFocused = useIsFocused();
  const messageOpacity = useRef(new Animated.Value(0)).current;
  const contentOpacity = useRef(new Animated.Value(0)).current;
  const gridOpacity = useRef(new Animated.Value(0)).current;
  const exposureEnabled = useRef(enabled);
  const bluetoothDisabled = useRef(
    status.state === 'disabled' && status.type?.includes(StatusType.bluetooth)
  );
  const pushNotificationsDisabled = useRef(
    permissions.notifications.status === 'not_allowed'
  );

  const [state, setState] = useState<{
    stage: number;
    exposurePrompt: boolean;
    bluetoothPrompt: boolean;
    pushNotificationsPrompt: boolean;
    disabled: boolean;
    current: string;
    isolationMessage: string | null;
    isolationComplete: boolean;
    default: string;
    messages: string[];
  }>({
    stage: onboarded ? -1 : 0,
    exposurePrompt: false,
    bluetoothPrompt: false,
    pushNotificationsPrompt: false,
    disabled: false,
    current: t(
      getMessage({
        onboarded,
        enabled,
        status,
        messages: t('dashboard:tour', {returnObjects: true}),
        stage: onboarded ? -1 : 0,
        paused
      })
    ),
    isolationMessage: null,
    isolationComplete: false,
    messages: t('dashboard:tour', {returnObjects: true}),
    default: t('dashboard:message:standard')
  });

  const version = useVersion();

  const resetToNormal = () =>
    setState((s) => ({
      ...s,
      isolationComplete: false,
      isolationMessage: null
    }));

  const setExposed = () =>
    setState((s) => ({
      ...s,
      isolationComplete: false,
      isolationMessage: t('dashboard:exposed')
    }));

  const setIsolationComplete = () =>
    setState((s) => ({
      ...s,
      isolationComplete: true,
      isolationMessage: t('dashboard:isolationComplete')
    }));

  const processContactsForMessaging = async () => {
    let currentStatus = null;
    try {
      currentStatus = await SecureStore.getItemAsync('niexposuredate');
    } catch (err) {
      await SecureStore.deleteItemAsync('niexposuredate');
      console.log('processContactsForMessaging', err);
    }

    if (currentStatus) {
      const daysDiff = differenceInCalendarDays(
        new Date(),
        new Date(Number(currentStatus))
      );

      const withIsolation = isolationDuration + isolationCompleteDuration;

      if (daysDiff >= withIsolation) {
        await SecureStore.deleteItemAsync('niexposuredate');
        return resetToNormal();
      }

      if (daysDiff >= isolationDuration && daysDiff < withIsolation) {
        return setIsolationComplete();
      }

      if (contacts && contacts.length > 0) {
        return setExposed();
      }
    }

    return resetToNormal();
  };

  const checkLatestExposure = async () => {
    const latestExposure = getExposureDate(contacts);

    if (latestExposure) {
      await SecureStore.setItemAsync(
        'niexposuredate',
        String(latestExposure.getTime())
      );
    }

    processContactsForMessaging();
  };

  const onRefresh = () => {
    setRefreshing(true);
    loadAppData().then(() => setRefreshing(false));
  };

  useEffect(() => {
    onRefresh();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    getCloseContacts();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [status]);

  useEffect(() => {
    checkLatestExposure();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [contacts, status]);

  useFocusEffect(
    React.useCallback(() => {
      if (!isFocused || appState !== 'active') {
        return;
      }

      readPermissions();
    }, [isFocused, appState, readPermissions])
  );

  useEffect(() => {
    setState((s) => ({
      ...s,
      current: t(
        getMessage({
          onboarded,
          enabled,
          status,
          messages: state.messages,
          stage: state.stage,
          paused
        })
      )
    }));

    exposureEnabled.current = enabled;
    bluetoothDisabled.current =
      status.state === 'disabled' &&
      status.type?.includes(StatusType.bluetooth);
    pushNotificationsDisabled.current =
      permissions.notifications.status === 'not_allowed';

    if (!exposureEnabled.current && onboarded) {
      setTimeout(() => {
        if (!exposureEnabled.current) {
          setState((s) => ({
            ...s,
            exposurePrompt: true
          }));
        }
      }, PROMPT_OFFSET);
    } else if (bluetoothDisabled.current && onboarded) {
      setTimeout(() => {
        if (bluetoothDisabled.current) {
          setState((s) => ({
            ...s,
            bluetoothPrompt: true
          }));
        }
      }, PROMPT_OFFSET);
    } else if (pushNotificationsDisabled.current && onboarded) {
      setTimeout(() => {
        if (
          pushNotificationsDisabled.current &&
          exposureEnabled.current &&
          !bluetoothDisabled.current
        ) {
          setState((s) => ({
            ...s,
            pushNotificationsPrompt: true
          }));
        }
      }, PROMPT_OFFSET);
    } else if (onboarded && exposureEnabled.current) {
      setState((s) => ({
        ...s,
        exposurePrompt: false
      }));
    }

    setTimeout(() => checkLatestExposure(), 100);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [enabled, onboarded, status, permissions]);

  const animateTourIn = () => {
    setState((s) => ({...s, disabled: true}));
    Animated.parallel([
      Animated.timing(messageOpacity, {
        toValue: 1,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      }),
      Animated.timing(gridOpacity, {
        toValue: 1,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      })
    ]).start(() => {
      setState((s) => ({...s, disabled: false}));
    });
  };

  const animateTourOut = () => {
    setState((s) => ({...s, disabled: true}));
    Animated.parallel([
      Animated.timing(messageOpacity, {
        toValue: 0,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      }),
      Animated.timing(gridOpacity, {
        toValue: 0,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      })
    ]).start(() => {
      if (state.stage < state.messages.length - 1) {
        setState((s) => ({
          ...s,
          stage: state.stage + 1,
          current: getMessage({
            onboarded,
            enabled,
            status,
            messages: state.messages,
            stage: state.stage + 1
          })
        }));
        animateTourIn();
      } else {
        setState((s) => ({
          ...s,
          stage: -1,
          current: s.default
        }));
        setContext({onboarded: true});
        animateDashboard();
      }
    });
  };

  const animateDashboard = () => {
    setState((s) => ({...s, disabled: true}));
    Animated.parallel([
      Animated.timing(messageOpacity, {
        toValue: 1,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      }),
      Animated.timing(contentOpacity, {
        toValue: 1,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      }),
      Animated.timing(gridOpacity, {
        toValue: 1,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      })
    ]).start(() => {
      AsyncStorage.setItem('scot.onboarded', 'true');
      setState((s) => ({...s, disabled: false}));
    });
  };

  useEffect(() => {
    if (onboarded) {
      setTimeout(() => animateDashboard(), 200);
    } else {
      setTimeout(() => animateTourIn(), 200);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    if (!state.disabled) {
      focusTourElem();
    }
  }, [focusTourElem, state.disabled]);

  useEffect(() => {
    if (onboarded && !state.disabled) {
      focusDashboardElem();
    }
  }, [focusDashboardElem, onboarded, state.disabled]);

  const handleTour = () => {
    animateTourOut();
  };

  if (!initialised || !checked) {
    return (
      <>
        <Container center="both">
          <ActivityIndicator color={colors.darkGrey} size="large" />
        </Container>
      </>
    );
  }

  return (
    <>
      <Header />
      <ScrollView
        refreshControl={
          <RefreshControl
            refreshing={onboarded && refreshing}
            onRefresh={onRefresh}
          />
        }>
        {onboarded &&
          appLatestVersion &&
          version &&
          appLatestVersion !== version?.display && (
            <View style={blockStyles.block}>
              <NewVersionCard />
              <Spacing s={16} />
            </View>
          )}
        <Spacing s={onboarded ? 15 : 65} />
        {!onboarded && state.stage > -1 && (
          <>
            <View style={styles.dots}>
              {[-1, 0, 1, 2].map((x) => (
                <Animated.View
                  key={`step-${x}`}
                  style={[
                    styles.dot,
                    {
                      backgroundColor:
                        state.stage > x
                          ? colors.primaryPurple
                          : colors.lighterPurple
                    }
                  ]}
                />
              ))}
            </View>
            <A11yView
              ref={tourFocus}
              accessible
              accessibilityHint={
                t('dashboard:tourA11y', {returnObjects: true})[state.stage]
              }
            />
            <Animated.View style={{opacity: messageOpacity}}>
              <TouchableWithoutFeedback onPress={() => handleTour()}>
                <Markdown markdownStyles={markDownStyles}>
                  {state.current}
                </Markdown>
              </TouchableWithoutFeedback>
              <Spacing s={30} />
              <ArrowLink
                onPress={() => {
                  if (!state.disabled) {
                    handleTour();
                  }
                }}
                accessibilityHint={t('dashboard:tourActionHint')}
                invert>
                <Text variant="h3" color="pink" style={styles.nextLink}>
                  {t('dashboard:tourAction')}
                </Text>
              </ArrowLink>
            </Animated.View>
          </>
        )}

        {onboarded && state.isolationMessage && (
          <>
            <Animated.View
              style={{
                opacity: messageOpacity
              }}>
              <View accessible ref={dashboardFocus}>
                <Markdown
                  markdownStyles={
                    state.isolationComplete
                      ? markDownStyles
                      : markDownStylesExposed
                  }>
                  {state.isolationMessage}
                </Markdown>
              </View>
              {!state.isolationComplete && (
                <>
                  <Spacing s={30} />
                  <ArrowLink
                    onPress={() =>
                      navigation.navigate(ScreenNames.closeContact)
                    }
                    accessibilityHint={t('dashboard:exposedAction')}
                    invert>
                    <Text variant="h3" color="pink" style={styles.nextLink}>
                      {t('dashboard:tourAction')}
                    </Text>
                  </ArrowLink>
                </>
              )}
              {state.isolationComplete && (
                <>
                  <Spacing s={20} />
                  <Text style={blockStyles.block} inline color="darkGrey">
                    {t('dashboard:isolationCompleteSupplemental')}
                  </Text>
                </>
              )}
            </Animated.View>
            <Spacing s={30} />
            <Animated.View
              style={[{opacity: contentOpacity}, blockStyles.block]}>
              <Message />
            </Animated.View>
          </>
        )}

        {onboarded && !state.isolationMessage && (
          <Animated.View style={{opacity: messageOpacity}}>
            <View accessible ref={dashboardFocus}>
              <Markdown markdownStyles={markDownStyles}>
                {state.current}
              </Markdown>
            </View>
            {state.stage === -1 && !paused && (
              <>
                <Spacing s={20} />
                <Text style={blockStyles.block} inline color="darkGrey">
                  {t(`dashboard:message:bluetooth:${Platform.OS}`)}
                </Text>
              </>
            )}
            {state.stage === -1 && paused && (
              <>
                <Spacing s={20} />
                <Text style={blockStyles.block} inline color="darkGrey">
                  {t('dashboard:message:pausedSupplemental')}
                </Text>
              </>
            )}
          </Animated.View>
        )}
        <Spacing s={30} />
        <Grid
          onboarded={onboarded}
          stage={state.stage}
          opacity={gridOpacity}
          onboardingCallback={() => handleTour()}
        />
        {state.isolationMessage && <Spacing s={34} />}
        {onboarded && !state.isolationMessage && (
          <>
            <Animated.View
              style={[{opacity: contentOpacity}, blockStyles.block]}>
              <Spacing s={30} />
              <Message />
              <Spacing s={16} />
              <Message
                image={RestrictionsImage}
                markdown={t('restrictions:message')}
                accessibilityLabel={t('restrictions:a11y:label')}
                accessibilityHint={t('restrictions:a11y:hint')}
                link={t('links:r')}
              />
              <Spacing s={45} />
            </Animated.View>
          </>
        )}
        {onboarded && (
          <Text variant="h4" color="primaryPurple" align="center">
            {t('dashboard:thanks')}
          </Text>
        )}
        <Spacing s={60} />
      </ScrollView>
      {checked && !paused && state.exposurePrompt && (
        <ExposureNotificationsModal
          isVisible={state.exposurePrompt}
          onBackdropPress={() =>
            setState((s) => ({...s, exposurePrompt: false}))
          }
          onClose={() => setState((s) => ({...s, exposurePrompt: false}))}
        />
      )}
      {checked && !paused && state.bluetoothPrompt && (
        <BluetoothNotificationsModal
          isVisible={state.bluetoothPrompt}
          onBackdropPress={() =>
            setState((s) => ({...s, bluetoothPrompt: false}))
          }
          onClose={() => setState((s) => ({...s, bluetoothPrompt: false}))}
        />
      )}
      {checked && !paused && state.pushNotificationsPrompt && (
        <PushNotificationsModal
          isVisible={state.pushNotificationsPrompt}
          onBackdropPress={() =>
            setState((s) => ({...s, pushNotificationsPrompt: false}))
          }
          onClose={() =>
            setState((s) => ({...s, pushNotificationsPrompt: false}))
          }
        />
      )}
    </>
  );
}
Example #6
Source File: about.tsx    From beancount-mobile with MIT License 4 votes vote down vote up
About = connect(
  (state: AppState) => ({
    authToken: state.base.authToken,
    locale: state.base.locale,
    currentTheme: state.base.currentTheme,
    userId: state.base.userId,
  }),
  (dispatch) => ({
    logout(authToken: string): void {
      dispatch(actionLogout(authToken));
    },
    updateReduxState(payload: { base: { locale: string } }): void {
      dispatch(actionUpdateReduxState(payload));
    },
  })
)(
  ({
    authToken,
    locale,
    logout,
    updateReduxState,
    currentTheme,
    userId,
    navigation,
  }: Props) => {
    const theme = useTheme().colorTheme;
    const { setLocale } = React.useContext(LocalizationContext);
    const pickerSource = [
      { value: ReportStatus.WEEKLY, label: i18n.t("weekly") },
      { value: ReportStatus.MONTHLY, label: i18n.t("monthly") },
      { value: ReportStatus.OFF, label: i18n.t("off") },
    ];

    useEffect(() => {
      async function init() {
        await registerForPushNotificationAsync();
      }
      init();
    }, []);

    const [reportAnimateCount, setReportAnimateCount] = useState(0);
    const [subscriptionFlash, setSubscriptionFlash] = useState(false);
    const isFocused = useIsFocused();

    React.useEffect(() => {
      async function init() {
        try {
          const value = await AsyncStorage.getItem("@SubscriptionFlash:key");
          if (value !== null) {
            setSubscriptionFlash(value === "true");
          } else {
            setSubscriptionFlash(false);
          }
          await AsyncStorage.setItem("@SubscriptionFlash:key", "false");
        } catch (error) {
          console.error(`failed to get subscription flash value: ${error}`);
        }
      }
      init();
    }, [isFocused]);

    useEffect(() => {
      if (subscriptionFlash) {
        const interval = setInterval(() => {
          if (reportAnimateCount < 5) {
            setReportAnimateCount(reportAnimateCount + 1);
          }
        }, 300);
        return () => clearInterval(interval);
      }
      setReportAnimateCount(0);
      return undefined;
    }, [subscriptionFlash, reportAnimateCount]);

    const { emailReportStatus } = useUserProfile(userId);
    const [reportStatus, setReportStatue] = useState<string>(
      emailReportStatus ? emailReportStatus.toString() : ""
    );

    useEffect(() => {
      setReportStatue(emailReportStatus ? emailReportStatus.toString() : "");
    }, [emailReportStatus]);

    const { error, mutate } = useUpdateReportSubscribeToRemote();

    const getReportStatusLabel = (status: string) => {
      switch (status) {
        case ReportStatus.OFF:
          return i18n.t("off");
        case ReportStatus.WEEKLY:
          return i18n.t("weekly");
        case ReportStatus.MONTHLY:
          return i18n.t("monthly");
        default:
          return i18n.t("off");
      }
    };

    const getReportStatusEnum = (status: string) => {
      switch (status) {
        case ReportStatus.OFF:
          return ReportStatus.OFF;
        case ReportStatus.WEEKLY:
          return ReportStatus.WEEKLY;
        case ReportStatus.MONTHLY:
          return ReportStatus.MONTHLY;
        default:
          return ReportStatus.OFF;
      }
    };

    const renderAppSection = () => {
      const backgroundColor = {
        backgroundColor: theme.white,
        color: theme.text01,
      };

      const { spendingReportSubscription } = useFeatureFlags(userId);

      return (
        // @ts-ignore
        <List
          style={backgroundColor}
          renderHeader={<ListHeader>{i18n.t("about")}</ListHeader>}
        >
          <Item
            disabled
            extra={Platform.OS === "ios" ? "Apple Store" : "Google Play"}
            arrow="horizontal"
            style={backgroundColor}
            onPress={async () => {
              const storeUrl =
                Platform.OS === "ios"
                  ? "https://apps.apple.com/us/app/id1527950512"
                  : "https://play.google.com/store/apps/details?id=io.beancount.android";
              if (storeUrl) {
                await WebBrowser.openBrowserAsync(storeUrl);
                await analytics.track("tap_review_app", { storeUrl });
              }
            }}
          >
            {i18n.t("reviewApp")}
          </Item>

          {spendingReportSubscription && (
            <Picker
              data={pickerSource}
              cols={1}
              extra={getReportStatusLabel(reportStatus)}
              onChange={async (value) => {
                const newValue = value ? String(value[0]) : "";
                if (newValue === reportStatus) {
                  return;
                }
                setReportStatue(newValue);
                const loadingKey = Toast.loading(i18n.t("updating"));
                await mutate({
                  variables: { userId, status: getReportStatusEnum(newValue) },
                });
                Portal.remove(loadingKey);
                if (!error) {
                  Toast.success(i18n.t("updateSuccess"));
                } else {
                  console.error("failed to update report status", error);
                  Toast.fail(i18n.t("updateFailed"));
                }
              }}
            >
              <Item
                style={[
                  backgroundColor,
                  {
                    backgroundColor:
                      reportAnimateCount % 2 === 1
                        ? theme.warning
                        : theme.white,
                  },
                ]}
                arrow="horizontal"
              >
                {i18n.t("subscribe")}
              </Item>
            </Picker>
          )}

          <Item
            disabled
            style={backgroundColor}
            extra={
              <Switch
                value={String(locale).startsWith("en")}
                onValueChange={async (value) => {
                  const changeTo = value ? "en" : "zh";
                  updateReduxState({
                    base: { locale: changeTo },
                  });
                  i18n.locale = changeTo;
                  setLocale(changeTo);
                  await analytics.track("tap_switch_language", { changeTo });
                }}
              />
            }
          >
            {i18n.t("currentLanguage")}
            <Brief>
              {String(locale).startsWith("en")
                ? i18n.t("english")
                : i18n.t("chinese")}
            </Brief>
          </Item>

          <Item
            style={backgroundColor}
            disabled
            extra={
              <Switch
                value={currentTheme === "dark"}
                onValueChange={async (value) => {
                  const mode = value ? "dark" : "light";
                  updateReduxState({
                    base: { currentTheme: mode },
                  });
                  await analytics.track("tap_switch_theme", { mode });
                }}
              />
            }
          >
            {i18n.t("theme")}
            <Brief>{currentTheme === "dark" ? "Dark" : "Light"}</Brief>
          </Item>
          <Item
            style={backgroundColor}
            disabled
            extra={Constants.nativeAppVersion}
          >
            {i18n.t("currentVersion")}
          </Item>
          {authToken ? (
            <Item
              style={backgroundColor}
              disabled
              onPress={() => {
                Alert.alert(
                  "",
                  i18n.t("logoutAlertMsg"),
                  [
                    { text: i18n.t("logoutAlertCancel"), style: "cancel" },
                    {
                      text: i18n.t("logoutAlertConfirm"),
                      onPress: () => {
                        logout(authToken);
                      },
                    },
                  ],
                  { cancelable: false }
                );
              }}
            >
              {i18n.t("logout")}
            </Item>
          ) : (
            <View />
          )}
        </List>
      );
    };

    return (
      <ScrollView style={{ backgroundColor: theme.white }}>
        <AccountHeader />
        <InviteSection navigation={navigation} />
        {renderAppSection()}
      </ScrollView>
    );
  }
)