@react-navigation/native#useFocusEffect TypeScript Examples

The following examples show how to use @react-navigation/native#useFocusEffect. 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: DataUpload.tsx    From ito-app with GNU General Public License v3.0 6 votes vote down vote up
DataUpload: React.FC<{
  navigation: DataUploadScreenNavigationProp;
}> = ({navigation}) => {
  const {t} = useTranslation();

  useFocusEffect(
    useCallback(() => {
      const onBackPress = (): boolean => {
        navigation.navigate('Home');
        return true;
      };
      BackHandler.addEventListener('hardwareBackPress', onBackPress);
      return (): void =>
        BackHandler.removeEventListener('hardwareBackPress', onBackPress);
    }, [navigation]),
  );

  return (
    <View style={global.container}>
      <Header
        navigationButton={{
          title: 'home',
          fn: (): void => navigation.navigate('Home'),
        }}
        showHelp={true}
      />
      <Text style={styles.thanks}>{t('dataUpload.thanks')}</Text>
    </View>
  );
}
Example #2
Source File: index.tsx    From NextLevelWeek with MIT License 5 votes vote down vote up
OrphanagesMap: React.FC = () => {
    const navigation = useNavigation();

    const [orphanages, setOrphanages] = useState<IOrphanage[]>([]);

    // Sempre que o usuário sair e voltar para a tela o useFocusEffect é disparado
    useFocusEffect(() => {
        api.get('/orphanages').then(res => {
            setOrphanages(res.data);
        });
    });

    function handleNavigateToOrphanage(id: number) {
        navigation.navigate('Orphanage', { id });
    }

    function handleNavigateToCreateOrphanage() {
        navigation.navigate('SelectMapPosition');
    }

    return (
        <MapContainer>
            <Map
                provider={PROVIDER_GOOGLE}
                initialRegion={{
                    latitude: -5.8026889,
                    longitude: -35.2224104,
                    latitudeDelta: 0.060,
                    longitudeDelta: 0.060,
                }}
            >
                {orphanages.map(orphanage => {
                    return (
                        <Marker
                            key={orphanage.id}
                            icon={mapMarker}
                            calloutAnchor={{
                                x: 2.7,
                                y: 0.8,
                            }}
                            coordinate={{
                                latitude: orphanage.latitude,
                                longitude: orphanage.longitude,
                            }}
                        >
                            <Callout tooltip onPress={() => handleNavigateToOrphanage(orphanage.id)}>
                                <CalloutContainer>
                                    <CalloutText>{orphanage.name}</CalloutText>
                                </CalloutContainer>
                            </Callout>
                        </Marker>
                    );
                })}
            </Map>

            <Footer>
                <FooterText>{orphanages.length} orfanatos encontrados</FooterText>

                <CreateOtphanageButton onPress={handleNavigateToCreateOrphanage}>
                    <Feather name="plus-circle" size={20} color="#FFF" />
                </CreateOtphanageButton>
            </Footer>
        </MapContainer>
    );
}
Example #3
Source File: Login.tsx    From kratos-selfservice-ui-react-native with Apache License 2.0 5 votes vote down vote up
Login = ({ navigation, route }: Props) => {
  const { project } = useContext(ProjectContext)
  const { setSession, session, sessionToken } = useContext(AuthContext)
  const [flow, setFlow] = useState<SelfServiceLoginFlow | undefined>(undefined)

  const initializeFlow = () =>
    newKratosSdk(project)
      .initializeSelfServiceLoginFlowWithoutBrowser(
        route.params.refresh,
        route.params.aal,
        sessionToken
      )
      .then((response) => {
        const { data: flow } = response
        // The flow was initialized successfully, let's set the form data:
        setFlow(flow)
      })
      .catch(console.error)

  // When the component is mounted, we initialize a new use login flow:
  useFocusEffect(
    React.useCallback(() => {
      initializeFlow()

      return () => {
        setFlow(undefined)
      }
    }, [project])
  )

  // This will update the login flow with the user provided input:
  const onSubmit = (payload: SubmitSelfServiceLoginFlowBody) =>
    flow
      ? newKratosSdk(project)
          .submitSelfServiceLoginFlow(flow.id, sessionToken, payload)
          .then(({ data }) => Promise.resolve(data as SessionContext))
          // Looks like everything worked and we have a session!
          .then((session) => {
            setSession(session)
            setTimeout(() => {
              navigation.navigate('Home')
            }, 100)
          })
          .catch(handleFormSubmitError(setFlow, initializeFlow))
      : Promise.resolve()

  return (
    <AuthLayout>
      <StyledCard>
        <AuthSubTitle>Sign in to your account</AuthSubTitle>
        <SelfServiceFlow flow={flow} onSubmit={onSubmit} />
      </StyledCard>

      <NavigationCard
        testID="nav-signup"
        description="Need an account?"
        cta="Sign up!"
        onPress={() => navigation.navigate('Registration')}
      />

      <ProjectPicker />
    </AuthLayout>
  )
}
Example #4
Source File: useBackgroundOverlay.tsx    From react-native-template with MIT License 5 votes vote down vote up
function useBackgroundOverlay(visible: boolean, onTouchStart: () => void) {
  const isFirstRender = useRef(true)
  const opacity = useMemo(
    () =>
      isFirstRender.current
        ? 0
        : timing({
            from: visible ? 0 : 0.2,
            to: visible ? 0.2 : 0,
          }),
    [visible]
  )

  useEffect(() => {
    isFirstRender.current = false
  }, [])

  useFocusEffect(
    React.useCallback(() => {
      const onBackPress = () => {
        if (visible) {
          onTouchStart()
          return true
        } else {
          return false
        }
      }
      BackHandler.addEventListener("hardwareBackPress", onBackPress)

      return () =>
        BackHandler.removeEventListener("hardwareBackPress", onBackPress)
    }, [onTouchStart, visible])
  )

  return (
    <>
      {visible && <StatusBar backgroundColor="grey" animated />}
      <Animated.View
        pointerEvents={visible ? "auto" : "none"}
        onTouchStart={() => {
          Keyboard.dismiss()
          onTouchStart()
        }}
        style={[
          styles.overlay,
          {
            opacity,
          },
        ]}
      />
    </>
  )
}
Example #5
Source File: OrphanagesMap.tsx    From happy with MIT License 5 votes vote down vote up
export default function OrphanagesMap() {
  const [orphanages, setOrphanages] = useState<Orphanage[]>([]);
  const navigation = useNavigation();

  useFocusEffect(
    useCallback(() => {
      api.get('orphanages').then(response => {
        setOrphanages(response.data);
      });
    }, [])
  );

  function handleNavigateToOphanageDetails(id: number) {
    navigation.navigate('OrphanageDetails', { id })
  }

  function handleNavigateToOrphanage() {
    navigation.navigate('SelectMapPosition')
  }

  return (
    <View style={styles.container}>
      <MapView
        provider={PROVIDER_GOOGLE}
        style={styles.map}
        initialRegion={{
          latitude: -27.2092052,
          longitude: -49.6401092,
          latitudeDelta: 0.008,
          longitudeDelta: 0.008,
        }}
      >
        {orphanages.map(orphanage => (
          <Marker
            key={orphanage.id}
            icon={mapMarker}
            calloutAnchor={{
              x: 2.7,
              y: 0.8,
            }}
            coordinate={{
              latitude: orphanage.latitude,
              longitude: orphanage.longitude,
            }}
          >
            <Callout tooltip onPress={() => handleNavigateToOphanageDetails(orphanage.id)}>
              <View style={styles.calloutContainer}>
                <Text style={styles.calloutText}>{orphanage.name}</Text>
              </View>
            </Callout>
          </Marker>
        ))}
      </MapView>

      <View style={styles.footer}>
        <Text style={styles.footerText}>{orphanages.length} orfanatos encontrados</Text>

        <RectButton style={styles.createOrphanageButton} onPress={handleNavigateToOrphanage}>
          <Feather name="plus" size={20} color="#fff" />
        </RectButton>
      </View>
    </View>
  );
}
Example #6
Source File: index.tsx    From nlw-02-omnistack with MIT License 5 votes vote down vote up
function Favorites() {
  const [favorites, setFavorites] = useState([]);

  function loadFavorites() {
    AsyncStorage.getItem('favorites').then(response => {
      if (response) {
        const favoritedTeachers = JSON.parse(response);

        setFavorites(favoritedTeachers);
      }
    });
  }

  useFocusEffect(() => {
    loadFavorites();
  });

  return (
    <View style={styles.container}>
      <PageHeader title="Meus proffys favoritos" />

      <ScrollView
        style={styles.teacherList}
        contentContainerStyle={{
          paddingHorizontal: 16,
          paddingBottom: 16,
        }}
      >
        {favorites.map((teacher: Teacher) => {
          return (
            <TeacherItem
              key={teacher.id}
              teacher={teacher}
              favorited
            />
          )
        })}
      </ScrollView>
    </View>
  )
}
Example #7
Source File: useAccessibilityAutoFocus.ts    From mobile with Apache License 2.0 5 votes vote down vote up
useAccessibilityAutoFocus = (isActive = true) => {
  const [autoFocusRef, setAutoFocusRef] = useState<any>();

  const {isScreenReaderEnabled} = useAccessibilityService();
  const [isFocus, setIsFocus] = useState(false);
  const [isLayoutUpdated, setIsLayoutUpdated] = useState(false);

  useLayoutEffect(() => {
    setIsLayoutUpdated(true);
    return () => {
      setIsLayoutUpdated(false);
    };
  }, []);

  useFocusEffect(
    useCallback(() => {
      setIsFocus(true);
      return () => {
        setIsFocus(false);
      };
    }, []),
  );

  useLayoutEffect(() => {
    if (!isScreenReaderEnabled || !isActive || !isFocus || !isLayoutUpdated || !autoFocusRef) {
      return;
    }

    // Call focus as soon as all considition is met
    focusOnElement(autoFocusRef);

    // Attempt to call it again just in case AccessibilityInfo.setAccessibilityFocus is delayed
    const timeoutId = setTimeout(() => {
      focusOnElement(autoFocusRef);
    }, AUTO_FOCUS_DELAY);
    return () => {
      clearTimeout(timeoutId);
    };
  }, [autoFocusRef, isActive, isFocus, isLayoutUpdated, isScreenReaderEnabled]);

  return setAutoFocusRef;
}
Example #8
Source File: BatteryPermission.tsx    From hamagen-react-native with MIT License 5 votes vote down vote up
BatteryPermission: FunctionComponent<Props> = ({ onEnd }) => {
  // const [userPressed,setUserPressed] = useState(false)
  const dispatch = useDispatch();
  const { strings: {
    general: { additionalInfo },
    battery: { title, description, approveButton, notApproveButton }
  }
  } = useSelector<Store, LocaleReducer>(state => state.locale);

  const { params } = useRoute();
  const [intervalDelay, setIntervalDelay] = useState<number | null>(null);

  useInterval(async () => {
    const isEnabled = await RNDisableBatteryOptimizationsAndroid.isBatteryOptimizationEnabled();
    if (!isEnabled) {
      dispatch({ type: USER_DISABLED_BATTERY, payload: true });
      await AsyncStorage.setItem(USER_AGREED_TO_BATTERY, 'true');
      onEnd();
    }
  }, intervalDelay);

  // stop interval if user moved on
  useFocusEffect(React.useCallback(() => () => setIntervalDelay(null), []));

  return (
    <>
      <View style={[{ alignItems: 'center', paddingHorizontal: IS_SMALL_SCREEN ? 20 : 40 }, IS_SMALL_SCREEN && { paddingTop: 5 }]}>
        {!IS_SMALL_SCREEN && (
          <Icon
            width={80}
            customStyles={{ marginBottom: 20 }}
            source={require('../../assets/onboarding/batteryBig.png')}
          />
        )}
        <Text style={styles.title} bold>{title}</Text>
        <Text style={styles.description}>{description}</Text>
      </View>
      <View style={{ alignItems: 'center' }}>
        <ActionButton
          text={approveButton}
          onPress={() => {
            setIntervalDelay(200);
            RNDisableBatteryOptimizationsAndroid.openBatteryModal();
          }}
          containerStyle={{ marginBottom: 20 }}
        />
        {params?.showSkip && (
        <TouchableOpacity onPress={async () => {
          onEnd();
          dispatch({ type: USER_DISABLED_BATTERY, payload: false });
          AsyncStorage.setItem(USER_AGREED_TO_BATTERY, 'false');
        }}
        >
          <Text style={{ color: MAIN_COLOR }} bold>{notApproveButton}</Text>
        </TouchableOpacity>
)}
      </View>
    </>
  );
}
Example #9
Source File: AllSet.tsx    From hamagen-react-native with MIT License 5 votes vote down vote up
AllSet = ({ navigation, strings: { allSet: { allGood } }, locale, notificationData, setOnboardingRoutes }: Props) => {
  useEffect(() => {
    setTimeout(() => {
      onboardingDoneActions();
    }, 3000);
  }, []);

  useFocusEffect(
    React.useCallback(() => {
      const onBackPress = () => {
        return true;
      };

      BackHandler.addEventListener('hardwareBackPress', onBackPress);

      return () => {
        BackHandler.removeEventListener('hardwareBackPress', onBackPress);
      };
    }, [])
  );

  const onboardingDoneActions = async () => {
    try {
      await AsyncStorage.multiSet([
        [IS_FIRST_TIME, 'true'],
        [DID_CLUSTER_LOCATIONS, 'true'],
        [SICK_DB_UPDATED, 'true'],
        [MENU_DOT_LAST_SEEN, VERSION_BUILD]
      ]);
      // TODO: figure out why replace crash android on first upload
      setOnboardingRoutes(false);

      await startForegroundTimer();
      await startSampling(locale, notificationData);
      await scheduleTask();
    } catch (error) {
      onError({ error });
    }
  };

  return (
    <View style={styles.container}>
      <LottieView
        style={styles.loader}
        source={require('../../assets/lottie/loader no mask.json')}
        resizeMode="cover"
        autoPlay
        loop
      />
      <Text style={styles.text} black>{allGood}</Text>
    </View>
  );
}
Example #10
Source File: App.tsx    From react-native-gallery-toolkit with MIT License 5 votes vote down vote up
export function Home() {
  useFocusEffect(() => {
    StatusBar.setHidden(false);
  });

  return <RoutesList routes={routes} />;
}
Example #11
Source File: index.tsx    From nlw-02-omnistack with MIT License 4 votes vote down vote up
function TeacherList() {
  const [teachers, setTeachers] = useState([]);
  const [favorites, setFavorites] = useState<number[]>([]);
  const [isFiltersVisible, setIsFiltersVisible] = useState(false);

  const [subject, setSubject] = useState('');
  const [week_day, setWeekDay] = useState('');
  const [time, setTime] = useState('');

  function loadFavorites() {
    AsyncStorage.getItem('favorites').then(response => {
      if (response) {
        const favoritedTeachers = JSON.parse(response);
        const favoritedTeachersIds = favoritedTeachers.map((teacher: Teacher) => {
          return teacher.id;
        })

        setFavorites(favoritedTeachersIds);
      }
    });
  }

  useFocusEffect(() => {
    loadFavorites();
  });

  function handleToggleFiltersVisible() {
    setIsFiltersVisible(!isFiltersVisible);
  }

  async function handleFiltersSubmit() {
    loadFavorites();

    const response = await api.get('classes', {
      params: {
        subject,
        week_day,
        time,
      }
    });

    setIsFiltersVisible(false);
    setTeachers(response.data);
  }

  return (
    <View style={styles.container}>
      <PageHeader 
        title="Proffys disponíveis" 
        headerRight={(
          <BorderlessButton onPress={handleToggleFiltersVisible}>
            <Feather name="filter" size={20} color="#FFF" />
          </BorderlessButton>
        )}
      >
        { isFiltersVisible && (
          <View style={styles.searchForm}>
            <Text style={styles.label}>Matéria</Text>
            <TextInput
              style={styles.input}
              value={subject}
              onChangeText={text => setSubject(text)}
              placeholder="Qual a matéria?"
              placeholderTextColor="#c1bccc"
            />

            <View style={styles.inputGroup}>
              <View style={styles.inputBlock}>
                <Text style={styles.label}>Dia da semana</Text>
                <TextInput
                  style={styles.input}
                  value={week_day}
                  onChangeText={text => setWeekDay(text)}
                  placeholder="Qual o dia?"
                  placeholderTextColor="#c1bccc"
                />
              </View>

              <View style={styles.inputBlock}>
                <Text style={styles.label}>Horário</Text>
                <TextInput
                  style={styles.input}
                  value={time}
                  onChangeText={text => setTime(text)}
                  placeholder="Qual horário?"
                  placeholderTextColor="#c1bccc"
                />
              </View>
            </View>

            <RectButton onPress={handleFiltersSubmit} style={styles.submitButton}>
              <Text style={styles.submitButtonText}>Filtrar</Text>
            </RectButton>
          </View>
        )}          
      </PageHeader>

      <ScrollView
        style={styles.teacherList}
        contentContainerStyle={{
          paddingHorizontal: 16,
          paddingBottom: 16,
        }}
      >
        {teachers.map((teacher: Teacher) => {
          return (
            <TeacherItem 
              key={teacher.id} 
              teacher={teacher}
              favorited={favorites.includes(teacher.id)}
            />
          )
        })}
      </ScrollView>
    </View>
  );
}
Example #12
Source File: index.tsx    From krmanga with MIT License 4 votes vote down vote up
function Download({
                      dispatch, navigation, isEdit, isLogin, ids, downloadList,
                      refreshing, hasMore, loading
                  }: IProps) {

    const translateX: Animated.Value = useRef(new Animated.Value(0)).current;
    const [endReached, setEndReached] = useState<boolean>(false);

    useFocusEffect(
        React.useCallback(() => {
            loadData(true);
            return () => {
                dispatch({
                    type: "downloadManage/setState",
                    payload: {
                        isEdit: false,
                        ids: []
                    }
                });
            };
        }, [isLogin])
    );

    const loadData = (refreshing: boolean, callback?: () => void) => {
        if (isLogin) {
            dispatch({
                type: "downloadManage/fetchDownloadList",
                payload: {
                    refreshing: refreshing
                },
                callback
            });
        }
    };

    const onRefresh = () => {
        dispatch({
            type: "downloadManage/setScreenReload"
        });
        loadData(true);
    };

    const onEndReached = () => {
        if (!hasMore || loading) {
            return;
        }
        setEndReached(true);

        loadData(false, () => {
            setEndReached(false);
        });
    };

    const getBeforeX = () => {
        Animated.timing(translateX,
            {
                useNativeDriver: false,
                toValue: wp(5),
                duration: 150
            }
        ).start();
    };

    const getAfterX = () => {
        Animated.timing(translateX,
            {
                useNativeDriver: false,
                toValue: 0,
                duration: 150
            }
        ).start();
    };

    const onClickItem = useCallback((item: IDownList) => {
        if (isEdit) {
            let i = ids.indexOf(item.book_id);
            if (i > -1) {
                ids.splice(i, 1);
                dispatch({
                    type: "downloadManage/setState",
                    payload: {
                        ids: [...ids]
                    }
                });
            } else {
                dispatch({
                    type: "downloadManage/setState",
                    payload: {
                        ids: [...ids, item.book_id]
                    }
                });
            }
        } else {
            navigation.navigate("ChapterManage", {
                book_id: item.book_id,
                book_image: item.image,
                headerTitle: item.title
            });
        }
    }, [isEdit, ids]);

    const goMangaView = useCallback((item: IDownList) => {
        navigation.navigate("Brief", {
            id: item.book_id
        });
        navigation.navigate("MangaView", {
            chapter_num: item.chapter_num ? item.chapter_num : 1,
            markRoast: item.roast,
            book_id: item.book_id
        });
    }, []);

    const renderFooter = () => {
        if (endReached) {
            return <More />;
        }
        if (!hasMore) {
            return <End />;
        }

        return null;
    };

    const renderItem = ({ item }: ListRenderItemInfo<IDownList>) => {
        const selected = ids.indexOf(item.book_id) > -1;
        return (
            <Touchable
                key={item.book_id}
                onPress={() => onClickItem(item)}
            >
                <Animated.View
                    style={{ transform: [{ translateX: translateX }] }}
                >
                    <Item
                        data={item}
                        isEdit={isEdit}
                        selected={selected}
                        goMangaView={goMangaView}
                    />
                </Animated.View>
            </Touchable>
        );
    };

    const cancel = () => {
        const newData = downloadList.map(item => item.book_id);
        if (downloadList.length === ids.length) {
            dispatch({
                type: "downloadManage/setState",
                payload: {
                    ids: []
                }
            });
        } else {
            dispatch({
                type: "downloadManage/setState",
                payload: {
                    ids: newData
                }
            });
        }
    };

    const destroy = () => {
        dispatch({
            type: "downloadManage/delBookCache",
            payload: {
                ids
            }
        });
    };

    if (isEdit) {
        getBeforeX();
    } else {
        getAfterX();
    }

    return (
        !isLogin ? null :
            (loading && refreshing) ? <ListPlaceholder /> :
                <View style={styles.container}>
                    <FlatList
                        keyExtractor={(item, key) => `item-${item.book_id}-key-${key}`}
                        numColumns={1}
                        onRefresh={onRefresh}
                        refreshing={refreshing}
                        data={downloadList}
                        renderItem={renderItem}
                        extraData={endReached}
                        ListFooterComponent={renderFooter}
                        onEndReached={onEndReached}
                        onEndReachedThreshold={0.1}
                    />
                    <EditView
                        data_length={downloadList.length}
                        isEdit={isEdit}
                        ids={ids}
                        cancel={cancel}
                        destroy={destroy}
                    />
                </View>
    );
}
Example #13
Source File: AddressManagementScene.tsx    From sellflow with MIT License 4 votes vote down vote up
export default function AddressManagementScene() {
  let { navigate } = useNavigation<StackNavProp<'AddressManagement'>>();
  let {
    params: { customerAccessToken },
  } = useRoute<StackRouteProp<'AddressManagement'>>();
  let [addressId, setAddressId] = useState('');
  let [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
  let { screenSize } = useDimensions();
  let first = screenSize === ScreenSize.Medium ? 10 : 5;

  let {
    addresses,
    error,
    loading: loadingAddresses,
    hasMore,
    isFetchingMore,
    refetch: refetchAddresses,
  } = useGetCustomerAddresses(first, customerAccessToken);

  let {
    customerAddressDelete,
    loading: loadingDeleteAddress,
  } = useCustomerAddressDelete({
    onCompleted: () => {
      refetchAddresses('update', { first, customerAccessToken, after: null });
    },
  });

  let {
    setDefaultAddress,
    loading: loadingSetDefaultAddress,
  } = useCustomerSetDefaultAddress({
    onCompleted: () => {
      refetchAddresses('update', { first, customerAccessToken, after: null });
    },
  });

  useFocusEffect(
    useCallback(() => {
      refetchAddresses('update', { first, customerAccessToken, after: null });

      return undefined;
    }, []), // eslint-disable-line react-hooks/exhaustive-deps
  );

  let toggleDeleteModal = () => {
    setIsDeleteModalVisible(!isDeleteModalVisible);
  };

  let addNewAddress = () => {
    navigate('AddEditAddress', { rootScene: 'AddressManagement' });
  };

  let onPressEdit = (address: AddressItem) => {
    navigate('AddEditAddress', { address, rootScene: 'AddressManagement' });
  };

  let onPressCancel = () => {
    toggleDeleteModal();
  };

  let onPressDelete = async (id: string) => {
    toggleDeleteModal();
    await customerAddressDelete({
      variables: {
        id,
        customerAccessToken,
      },
    });
  };

  let onPressSetPrimary = async (addressId: string) => {
    await setDefaultAddress({
      variables: { customerAccessToken, addressId },
    });
  };

  let onEndReached = () => {
    if (!isFetchingMore && hasMore) {
      refetchAddresses('scroll', {
        first,
        customerAccessToken,
        after: addresses[addresses.length - 1].cursor || null,
      });
    }
  };

  if (error) {
    return (
      <ErrorPage
        onRetry={() => {
          refetchAddresses('update', {
            first,
            customerAccessToken,
            after: null,
          });
        }}
      />
    );
  }

  let loading =
    loadingAddresses || loadingDeleteAddress || loadingSetDefaultAddress;
  if (loading && !isFetchingMore) {
    return <ActivityIndicator style={styles.centered} />;
  }

  return (
    <SafeAreaView style={styles.flex}>
      <DeleteAddressModal
        deleteVisible={isDeleteModalVisible}
        toggleModal={toggleDeleteModal}
        onPressCancel={onPressCancel}
        onPressDelete={() => onPressDelete(addressId)}
      />
      {addresses.length > 0 ? (
        <FlatList
          data={addresses}
          renderItem={({ item }) => (
            <ManageAddress
              data={item}
              style={styles.item}
              onPressSetPrimary={onPressSetPrimary}
              onPressEdit={() => onPressEdit(item)}
              onPressDelete={() => {
                setAddressId(item.id);
                toggleDeleteModal();
              }}
            />
          )}
          keyExtractor={(item) => item.id}
          contentContainerStyle={styles.contentContainer}
          onEndReached={onEndReached}
          onEndReachedThreshold={0.25}
          ListFooterComponent={() => {
            return hasMore ? <ActivityIndicator /> : null;
          }}
        />
      ) : (
        <View style={[styles.centered, styles.imageContainer]}>
          <Image
            source={emptyAddressImage}
            style={styles.image}
            resizeMode="contain"
          />
          <Text style={styles.message}>
            {t('Address is Empty. Please add new address')}
          </Text>
        </View>
      )}
      <Button
        onPress={addNewAddress}
        style={[defaultButton, styles.bottomButton]}
        labelStyle={defaultButtonLabel}
      >
        {t('Add New Address')}
      </Button>
    </SafeAreaView>
  );
}
Example #14
Source File: CheckoutScene.tsx    From sellflow with MIT License 4 votes vote down vote up
export default function CheckoutScene() {
  let { navigate } = useNavigation<StackNavProp<'Checkout'>>();
  let {
    params: {
      cartData: { id: cartId, subtotalPrice: cartSubtotalPrice, totalPrice },
    },
  } = useRoute<StackRouteProp<'Checkout'>>();

  let [paymentInfo, setPaymentInfo] = useState<PaymentInfo>({
    subtotalPrice: cartSubtotalPrice,
    totalPrice: totalPrice,
  });
  let { subtotalPrice } = paymentInfo;
  let { authToken } = useAuth();
  let [address, setAddress] = useState<AddressItem>(emptyAddress);
  let [selectedAddress, setSelectedAddress] = useState<AddressItem>(
    emptyAddress,
  );
  let [isModalVisible, setIsModalVisible] = useState(false);
  let { screenSize } = useDimensions();
  const first = 5;
  let formatCurrency = useCurrencyFormatter();

  let toggleModalVisible = () => setIsModalVisible(!isModalVisible);

  let {
    updateCartAddress,
    data: updateAddressData,
    loading: updateAddressLoading,
  } = useCheckoutUpdateAddress({
    onCompleted: ({ checkoutShippingAddressUpdateV2 }) => {
      if (
        checkoutShippingAddressUpdateV2 &&
        checkoutShippingAddressUpdateV2.checkout
      ) {
        let { subtotalPriceV2 } = checkoutShippingAddressUpdateV2.checkout;

        setPaymentInfo({
          ...paymentInfo,
          subtotalPrice: Number(subtotalPriceV2.amount),
        });
      }
    },
  });

  let {
    addresses,
    error,
    refetch: refetchAddresses,
    hasMore,
    isFetchingMore,
    loading,
  } = useGetCustomerAddresses(first, authToken);

  useFocusEffect(
    useCallback(() => {
      let firstIn = async () => {
        await updateAddress(selectedAddress);
      };
      firstIn();
      refetchAddresses('update', { first, customerAccessToken: authToken });
      return undefined;
    }, []), // eslint-disable-line react-hooks/exhaustive-deps
  );

  useEffect(() => {
    let defaultAddress = addresses.find((item) => item.default === true);

    if (defaultAddress) {
      setSelectedAddress(defaultAddress);
      updateAddress(defaultAddress);
    }
    if (address !== emptyAddress) {
      updateAddress(address);
    }
  }, [addresses, address]); // eslint-disable-line react-hooks/exhaustive-deps

  let updateAddress = async (address: AddressItem) => {
    let { id, name, default: defaultStatus, cursor, ...usedAddress } = address;

    await updateCartAddress({
      variables: {
        checkoutId: cartId,
        shippingAddress: {
          ...usedAddress,
        },
      },
    });
  };

  let navigateToPayment = (webUrl: string) => {
    navigate('WebView', { webUrl, type: 'payment' });
  };

  let containerStyle = () => {
    if (screenSize === ScreenSize.Small) {
      return styles.normal;
    } else {
      return styles.tab;
    }
  };
  let addNewAddress = () => {
    navigate('AddEditAddress', { rootScene: 'Checkout' });
  };

  let onPressEdit = (address: AddressItem) => {
    navigate('AddEditAddress', { address, rootScene: 'Checkout' });
  };

  let onSelect = async (item: AddressItem) => {
    setSelectedAddress(item);
    await updateAddress(item);
  };

  let onProceedPressed = async () => {
    if (!updateAddressLoading) {
      if (
        updateAddressData &&
        updateAddressData.checkoutShippingAddressUpdateV2?.checkoutUserErrors
          .length === 0
      ) {
        navigateToPayment(
          updateAddressData.checkoutShippingAddressUpdateV2.checkout?.webUrl,
        );
      } else {
        toggleModalVisible();
      }
    }
  };

  let onEndReached = () => {
    if (!isFetchingMore && hasMore) {
      refetchAddresses('scroll', {
        first,
        customerAccessToken: authToken,
        after: addresses[addresses.length - 1].cursor || null,
      });
    }
  };

  let isDisabled = authToken
    ? selectedAddress === emptyAddress || false
    : !address.address1 ||
      !address.city ||
      !address.country ||
      !address.firstName ||
      !address.lastName ||
      !address.phone ||
      !address.province ||
      !address.zip;

  if (error) {
    return (
      <ErrorPage
        onRetry={() => {
          refetchAddresses('update', { first, customerAccessToken: authToken });
        }}
      />
    );
  }

  let renderShippingAddress = () => {
    if (authToken) {
      return (
        <View style={styles.flex}>
          <Text style={styles.opacity}>{t('Shipping Address')}</Text>
          {loading ? (
            <ActivityIndicator />
          ) : (
            <AddressList
              addresses={addresses}
              selectedAddress={selectedAddress}
              onEditAddress={onPressEdit}
              onSelectAddress={onSelect}
              onEndReached={onEndReached}
              hasMore={hasMore}
            />
          )}
          <Button
            preset="secondary"
            style={[defaultButton, styles.newAddressButton]}
            icon="plus"
            labelStyle={defaultButtonLabel}
            onPress={addNewAddress}
          >
            {t('Add New Address')}
          </Button>
        </View>
      );
    } else {
      return (
        <ShippingAddressForm address={address} onChangeAddress={setAddress} />
      );
    }
  };

  let paymentData: Array<PaymentDetailsProps> = [
    {
      name: t('Subtotal'),
      value: formatCurrency(subtotalPrice),
    },
    {
      name: t('Shipping'),
      value: t('Calculated at next step'),
    },
    {
      name: t('Total'),
      value: formatCurrency(subtotalPrice),
    },
  ];

  let renderPaymentView = () => (
    <View
      style={
        screenSize === ScreenSize.Large && [
          styles.flex,
          styles.priceViewLandscape,
        ]
      }
    >
      <PaymentDetails
        data={paymentData}
        containerStyle={styles.surfacePaymentDetails}
      />
      <Button
        style={[defaultButton, styles.proceedButtonStyle]}
        labelStyle={defaultButtonLabel}
        onPress={onProceedPressed}
        disabled={isDisabled || updateAddressLoading}
      >
        {t('Proceed to payment')}
      </Button>
    </View>
  );
  let renderBottomModal = () => (
    <ModalBottomSheet
      title={t('An Error Occured!')}
      isModalVisible={isModalVisible}
      toggleModal={toggleModalVisible}
    >
      <ModalBottomSheetMessage
        isError={true}
        message={t('Please insert a valid address')}
        onPressModalButton={toggleModalVisible}
        buttonText={t('Close')}
      />
    </ModalBottomSheet>
  );

  if (authToken) {
    return (
      <SafeAreaView
        style={[
          styles.container,
          containerStyle(),
          screenSize === ScreenSize.Large && styles.landscape,
        ]}
      >
        {renderBottomModal()}
        {renderShippingAddress()}
        {renderPaymentView()}
      </SafeAreaView>
    );
  } else if (screenSize === ScreenSize.Large) {
    return (
      <KeyboardAvoidingView>
        <SafeAreaView style={[styles.flex, styles.landscape, containerStyle()]}>
          {renderBottomModal()}
          <ScrollView style={styles.flex}>{renderShippingAddress()}</ScrollView>
          {renderPaymentView()}
        </SafeAreaView>
      </KeyboardAvoidingView>
    );
  } else {
    return (
      <KeyboardAvoidingView>
        <SafeAreaView style={[styles.flex, containerStyle()]}>
          {renderBottomModal()}
          <ScrollView style={styles.flex}>
            {renderShippingAddress()}
            {renderPaymentView()}
          </ScrollView>
        </SafeAreaView>
      </KeyboardAvoidingView>
    );
  }
}
Example #15
Source File: PartyScreen.tsx    From lets-fork-native with MIT License 4 votes vote down vote up
PartyScreen = React.memo((props: Props) => {
  const {
    navigation, party, route, setParty, ws,
  } = props

  const [snapIndex, setSnapIndex] = React.useState(2)
  const [finished, setFinished] = React.useState<boolean>(false)
  const [restaurants, setRestaurants] = React.useState<Restaurant[]>()
  const headerHeight = useHeaderHeight()
  const viewHeight = env.ADS ? height - headerHeight - 50 : height - headerHeight

  if (party?.error) {
    Alert.alert(
      'Yike! Something went wrong',
      party.error,
      [
        {
          text: 'OK',
          onPress: (): void => {
            navigation.navigate('Home')
            setParty({} as Party)
          },
        },
      ],
      { cancelable: false },
    )
  }

  // Request more cards with 3 remaining to prevent
  // having to show loader
  React.useEffect(() => {
    if (restaurants && restaurants?.length === 3) {
      ws.send(JSON.stringify({ type: 'request-more' }))
    }
  }, [restaurants, restaurants?.length, ws])

  // Deep linking will open the app to the party screen
  // but the party still needs to be joined
  React.useEffect(() => {
    if (route?.params?.id) {
      ws.send(JSON.stringify({ type: 'join', payload: { party_id: route?.params?.id } }))
    }
  }, [route?.params?.id, ws])

  // When anyone requests more cards, they are set in current
  // and this useEffect loads the new cards into the restaurants array
  const prevState = usePrevious(party || {} as Party)
  React.useEffect(() => {
    if (JSON.stringify(prevState.current) !== JSON.stringify(party?.current)) {
      if (party?.current?.length && restaurants) {
        const res = [...restaurants, ...party?.current]
        setRestaurants(res)
      }
    }
  }, [party, prevState, restaurants])

  // Custom android back button
  useFocusEffect( // eslint-disable-line
    React.useCallback(() => {
      const onBackPress = (): boolean => {
        Alert.alert(
          'Are you sure you want to exit?',
          'Exiting will make you lose all data in this party',
          [
            { text: 'Cancel' },
            {
              text: 'OK',
              onPress: (): void => {
                ws.send(JSON.stringify({ type: 'quit' }))
                navigation.navigate('Home')
                setParty({} as Party)
              },
            },
          ],
          { cancelable: true },
        )
        return true
      }

      BackHandler.addEventListener('hardwareBackPress', onBackPress)

      return (): void => BackHandler.removeEventListener('hardwareBackPress', onBackPress)
    }, [navigation, setParty, ws]),
  )

  const handleSwipeRight = (id: string): void => {
    ws.send(JSON.stringify({ type: 'swipe-right', payload: { restaurant_id: id } }))
  }

  if (party?.status === 'waiting') {
    return <Share party={party} ws={ws} />
  }

  if (finished || party?.total === 0) {
    return (
      <View
        style={{
          ...styles.waiting,
          height: viewHeight,
        }}
      >
        <Text style={styles.text}>
          No more restaurants.
          Go through the list again or try expanding your search range.
        </Text>
        <Button
          size="sm"
          color="purple"
          onPress={(): void => {
            setFinished(false)
            setRestaurants(party?.restaurants)
          }}
        >
          START OVER
        </Button>
      </View>
    )
  }

  if (!party || !party.restaurants) {
    return (
      <View
        style={{
          ...styles.waiting,
          height: viewHeight,
        }}
      >
        <ActivityIndicator size="large" />
      </View>
    )
  }

  const current = restaurants?.length
    ? restaurants[0] : party.restaurants[0]

  return (
    <SafeAreaView style={styles.container}>
      <View
        // disable swiping while BottomSheet is open
        pointerEvents={snapIndex !== 2 ? 'none' : 'auto'}
        style={{ height: viewHeight, zIndex: 0 }}
      >
        <SwipeWindow
          handleSwipeRight={handleSwipeRight}
          restaurants={restaurants || party.restaurants}
          setFinished={setFinished}
          setRestaurants={setRestaurants}
        />
      </View>
      <ScrollBottomSheet
        componentType="ScrollView"
        contentContainerStyle={styles.scrollBottomSheet}
        snapPoints={[100, 100, viewHeight - BOTTOM_BAR_HEIGHT]}
        initialSnapIndex={2}
        onSettle={setSnapIndex}
        renderHandle={(): React.ReactElement => <Handle />}
        animationConfig={{
          duration: 100,
        }}
      >
        <Details restaurant={current} />
      </ScrollBottomSheet>
    </SafeAreaView>
  )
})
Example #16
Source File: ProductDetailsScene.tsx    From sellflow with MIT License 4 votes vote down vote up
export default function ProductDetailsScene() {
  let {
    params: { productHandle },
  } = useRoute<StackRouteProp<'ProductDetails'>>();

  let [isToastVisible, setIsToastVisible] = useState(false);
  let [isWishlistActive, setWishlistActive] = useState(false);
  let [quantity, setQuantity] = useState(1);
  let [selectedOptions, setSelectedOptions] = useState<OptionsData>({});
  let [isImageModalVisible, setIsImageModalVisible] = useState(false);
  let [activeIndex, setActiveIndex] = useState(0);
  let [bottomButtonHeight, setBottomButtonHeight] = useState(0);

  let { authToken } = useAuth();
  let { setShoppingCartID } = useSetShoppingCartID();
  let { shoppingCartCustomerAssociate } = useCheckoutCustomerAssociate();
  let {
    data: { countryCode },
  } = useDefaultCountry();

  let onPressImage = (index: number) => {
    setIsImageModalVisible(!isImageModalVisible);
    setActiveIndex(index);
  };

  let { createCheckout } = useCheckoutCreate({
    onCompleted: async ({ checkoutCreate }) => {
      if (checkoutCreate && checkoutCreate.checkout) {
        await setShoppingCartID({
          variables: { id: checkoutCreate.checkout.id },
        });
        if (authToken) {
          await shoppingCartCustomerAssociate({
            variables: {
              checkoutId: checkoutCreate.checkout.id,
              customerAccessToken: authToken,
            },
          });
        }
      }
    },
  });

  let { getCustomer } = useGetCustomerData({
    onCompleted: async ({ customer }) => {
      if (customer && customer.lastIncompleteCheckout == null) {
        await createCheckout({
          variables: {
            checkoutCreateInput: {
              lineItems: [],
            },
            country: countryCode,
          },
        });
      }
    },
  });

  useFocusEffect(
    useCallback(() => {
      if (authToken) {
        getCustomer({ variables: { accessToken: authToken } });
      }
      return undefined;
    }, []), // eslint-disable-line react-hooks/exhaustive-deps
  );
  useGetCart({
    fetchPolicy: 'cache-only',
    notifyOnNetworkStatusChange: true,
    onCompleted: async ({ shoppingCart }) => {
      if (shoppingCart.id === '') {
        createCheckout({
          variables: {
            checkoutCreateInput: {
              lineItems: [],
            },
            country: countryCode,
          },
        });
      }
    },
  });
  let showToast = (duration: number) => {
    setIsToastVisible(true);
    setTimeout(() => {
      setIsToastVisible(false);
    }, duration);
  };

  let hideToast = () => {
    setIsToastVisible(false);
  };

  let extractOptionsData = (
    optionsData: OptionsData,
  ): Array<VariantQueryData> => {
    let result: Array<VariantQueryData> = [];

    for (let option in optionsData) {
      if (option) {
        let processedForm: VariantQueryData = {
          name: option,
          value: optionsData[option],
        };
        result.push(processedForm);
      }
    }
    return result;
  };

  let changeSelectedOptions = (key: string, value: string) => {
    setSelectedOptions({ ...selectedOptions, [key]: value });
  };

  let {
    shoppingCartReplaceItems,
    loading: shoppingCartLoading,
  } = useCheckoutReplaceItem();

  let { addToCart, loading: addToCartLoading } = useAddToCart({
    onCompleted: async ({ addToShoppingCart }) => {
      let shoppingCartItems = addToShoppingCart.items.map(
        ({ variantId, quantity }) => {
          return { variantId, quantity };
        },
      );
      await shoppingCartReplaceItems({
        variables: {
          checkoutID: addToShoppingCart.id,
          lineItems: shoppingCartItems,
          country: countryCode,
        },
      });
      showToast(11000);
    },
  });

  let {
    getVariant,
    data: productDetails,
    loading: getProductDetailsLoading,
    error: getProductDetailsError,
    refetch: getProductDetailsRefetch,
  } = useGetProductDetails({
    variables: {
      productHandle,
      country: countryCode,
    },
    fetchPolicy: 'network-only',

    onCompleted(value) {
      let defaultOptions: OptionsData = {};
      value.productByHandle?.options.map(({ name, values }) => {
        return (defaultOptions[name] = values[0]);
      });
      setSelectedOptions(defaultOptions);
    },
  });

  useEffect(() => {
    let queryVariantID = extractOptionsData(selectedOptions);
    getVariant({
      variables: {
        selectedOptions: queryVariantID,
        handle: productHandle,
        country: countryCode,
      },
    });
  }, [selectedOptions, getVariant]); // eslint-disable-line react-hooks/exhaustive-deps

  let isLoading =
    getProductDetailsLoading || addToCartLoading || shoppingCartLoading;

  let { data: wishlistData } = useGetWishlistData({
    onCompleted: ({ wishlist }) => {
      if (wishlist.find((item) => item.handle === productHandle)) {
        setWishlistActive(true);
      }
    },
  });
  let onAddToCart = async () => {
    addToCart({ variables: { variantId: productDetails.id, quantity } });
  };

  let isFirstLoading = !wishlistData || !productDetails.id;

  let { screenSize } = useDimensions();
  let isLandscape = screenSize === ScreenSize.Large;

  if (getProductDetailsError) {
    return <ErrorPage onRetry={getProductDetailsRefetch} />;
  }

  return isFirstLoading ? (
    <ActivityIndicator style={styles.centered} />
  ) : (
    <KeyboardAvoidingView keyboardVerticalOffset={bottomButtonHeight}>
      <View style={[styles.flex, isLandscape && styles.flexRow]}>
        {isLandscape && (
          <ImageList product={productDetails} onImagePress={onPressImage} />
        )}
        <View style={styles.flex}>
          <ScrollView style={styles.flex}>
            {!isLandscape && (
              <ImageList product={productDetails} onImagePress={onPressImage} />
            )}
            <ProductInfo
              selectedOptions={selectedOptions}
              onSelectionOptionChange={changeSelectedOptions}
              quantity={quantity}
              onChangeQuantity={setQuantity}
              product={productDetails}
              options={productDetails.options ? productDetails.options : []}
            />
          </ScrollView>
          <View
            style={[
              styles.bottomContainer,
              isLandscape && styles.bottomLandscapeContainer,
            ]}
            onLayout={({ nativeEvent }) =>
              setBottomButtonHeight(nativeEvent.layout.height)
            }
          >
            <BottomActionBar
              isButtonDisabled={!productDetails.availableForSale}
              onAddToCartPress={onAddToCart}
              product={productDetails}
              isLoading={isLoading}
              isWishlistActive={isWishlistActive}
              onWishlistPress={(isActive) => {
                setWishlistActive(isActive);
              }}
            />
          </View>
        </View>
      </View>
      <Toast
        data={{
          message: t('Item successfully added'),
          isVisible: isToastVisible,
          hideToast,
        }}
      />
      <ImageModal
        activeIndex={activeIndex}
        images={productDetails.images}
        isVisible={isImageModalVisible}
        setVisible={setIsImageModalVisible}
      />
    </KeyboardAvoidingView>
  );
}
Example #17
Source File: Registration.tsx    From kratos-selfservice-ui-react-native with Apache License 2.0 4 votes vote down vote up
Registration = ({ navigation }: Props) => {
  const [flow, setConfig] = useState<SelfServiceRegistrationFlow | undefined>(
    undefined
  )
  const { project } = useContext(ProjectContext)
  const { setSession, isAuthenticated } = useContext(AuthContext)

  const initializeFlow = () =>
    newKratosSdk(project)
      .initializeSelfServiceRegistrationFlowWithoutBrowser()
      // The flow was initialized successfully, let's set the form data:
      .then(({ data: flow }) => {
        setConfig(flow)
      })
      .catch(console.error)

  // When the component is mounted, we initialize a new use login flow:
  useFocusEffect(
    React.useCallback(() => {
      initializeFlow()

      return () => {
        setConfig(undefined)
      }
    }, [project])
  )

  useEffect(() => {
    if (isAuthenticated) {
      navigation.navigate('Home')
    }
  }, [isAuthenticated])

  if (isAuthenticated) {
    return null
  }

  // This will update the registration flow with the user provided input:
  const onSubmit = (
    payload: SubmitSelfServiceRegistrationFlowBody
  ): Promise<void> =>
    flow
      ? newKratosSdk(project)
          .submitSelfServiceRegistrationFlow(flow.id, payload)
          .then(({ data }) => {
            // ORY Kratos can be configured in such a way that it requires a login after
            // registration. You could handle that case by navigating to the Login screen
            // but for simplicity we'll just print an error here:
            if (!data.session_token || !data.session) {
              const err = new Error(
                'It looks like you configured ORY Kratos to not issue a session automatically after registration. This edge-case is currently not supported in this example app. You can find more information on enabling this feature here: https://www.ory.sh/kratos/docs/next/self-service/flows/user-registration#successful-registration'
              )
              return Promise.reject(err)
            }

            // Looks like we got a session!
            return Promise.resolve({
              session: data.session,
              session_token: data.session_token
            })
          })
          // Let's log the user in!
          .then(setSession)
          .catch(
            handleFormSubmitError<SelfServiceRegistrationFlow | undefined>(
              setConfig,
              initializeFlow
            )
          )
      : Promise.resolve()

  return (
    <AuthLayout>
      <StyledCard>
        <AuthSubTitle>Create an account</AuthSubTitle>
        <SelfServiceFlow
          textInputOverride={(field, props) => {
            switch (getNodeId(field)) {
              case 'traits.email':
                return {
                  autoCapitalize: 'none',
                  autoCompleteType: 'email',
                  textContentType: 'username',
                  autoCorrect: false
                }
              case 'password':
                const iOS12Plus =
                  Platform.OS === 'ios' &&
                  parseInt(String(Platform.Version), 10) >= 12
                return {
                  textContentType: iOS12Plus ? 'newPassword' : 'password',
                  secureTextEntry: true
                }
            }
            return props
          }}
          flow={flow}
          onSubmit={onSubmit}
        />
      </StyledCard>

      <NavigationCard
        description="Already have an account?"
        cta="Sign in!"
        onPress={() => navigation.navigate({ key: 'Login' })}
      />

      <ProjectPicker />
    </AuthLayout>
  )
}
Example #18
Source File: index.tsx    From krmanga with MIT License 4 votes vote down vote up
function Shelf({
                   navigation, dispatch, isLogin, loading,
                   refreshing, hasMore, collectionList, ids, isEdit
               }: IProps) {

    const scrollY: Animated.Value = useRef(new Animated.Value(0)).current;
    const [endReached, setEndReached] = useState<boolean>(false);

    useFocusEffect(
        React.useCallback(() => {
            loadData(true);
            return () => {
                dispatch({
                    type: "collection/setState",
                    payload: {
                        isEdit: false,
                        ids: []
                    }
                });
            };
        }, [isLogin])
    );

    const loadData = (refreshing: boolean, callback?: () => void) => {
        if (isLogin) {
            dispatch({
                type: "collection/fetchCollectionList",
                payload: {
                    refreshing: refreshing
                },
                callback
            });
        }
    };

    const onEndReached = () => {
        if (!hasMore || loading) {
            return;
        }
        setEndReached(true);
        loadData(false, () => {
            setEndReached(false);
        });
    };

    const onClickItem = useCallback((item: ICollection) => {
        if (isEdit) {
            const i = ids.indexOf(item.id);
            if (i > -1) {
                ids.splice(i, 1);
                dispatch({
                    type: "collection/setState",
                    payload: {
                        ids: [...ids]
                    }
                });
            } else {
                dispatch({
                    type: "collection/setState",
                    payload: {
                        ids: [...ids, item.id]
                    }
                });
            }
        } else {
            navigation.navigate("Brief", {
                id: item.book_id
            });
        }
    }, [isEdit, ids]);

    const cancel = () => {
        const newData = collectionList.map(item => item.id);
        if (collectionList.length === ids.length) {
            dispatch({
                type: "collection/setState",
                payload: {
                    ids: []
                }
            });
        } else {
            dispatch({
                type: "collection/setState",
                payload: {
                    ids: newData
                }
            });
        }
    };

    const destroy = () => {
        dispatch({
            type: "collection/delUserCollection",
            payload: {
                ids
            }
        });
    };

    const renderItem = ({ item }: ListRenderItemInfo<ICollection>) => {
        const selected = ids.indexOf(item.id) > -1;
        return (
            <Item
                key={item.id}
                data={item}
                isEdit={isEdit}
                selected={selected}
                onClickItem={onClickItem}
            />
        );
    };

    const renderFooter = () => {
        if (endReached) {
            return <More />;
        }
        if (!hasMore) {
            return <End />;
        }

        return null;
    };

    const getHeaderOpacity = () => {
        return scrollY.interpolate({
            inputRange: [-50, 0],
            outputRange: [1, 0],
            extrapolate: "clamp"
        });
    };

    return (
        !isLogin ? null :
            refreshing ? <BookPlaceholder /> :
                <View style={styles.container}>
                        <Animated.Text style={[styles.total,{
                            opacity: getHeaderOpacity()
                        }]}>总收藏{collectionList.length}本</Animated.Text>
                    <FlatList
                        keyExtractor={(item, key) => `item-${item.id}-key-${key}`}
                        scrollEventThrottle={1}
                        data={collectionList}
                        style={styles.container}
                        numColumns={3}
                        onScroll={Animated.event(
                            [{
                                nativeEvent: { contentOffset: { y: scrollY } }
                            }],
                            {
                                useNativeDriver: false
                            }
                        )}
                        renderItem={renderItem}
                        extraData={endReached}
                        ListFooterComponent={renderFooter}
                        onEndReached={onEndReached}
                        onEndReachedThreshold={0.1}
                    />
                    <EditView
                        data_length={collectionList.length}
                        ids={ids}
                        isEdit={isEdit}
                        cancel={cancel}
                        destroy={destroy}
                    />
                </View>
    );
}
Example #19
Source File: index.tsx    From krmanga with MIT License 4 votes vote down vote up
function History({ dispatch, navigation, isLogin, isEdit, historyList, ids, loading, refreshing, hasMore, pages }: IProps) {

    const translateX: Animated.Value = useRef(new Animated.Value(0)).current;
    const [endReached, setEndReached] = useState<boolean>(false);

    useFocusEffect(
        React.useCallback(() => {
            loadData(true);
            return () => {
                dispatch({
                    type: "history/setState",
                    payload: {
                        isEdit: false,
                        ids: []
                    }
                });
            };
        }, [isLogin])
    );

    const loadData = (refreshing: boolean, callback?: () => void) => {
        if (isLogin) {
            dispatch({
                type: "history/fetchHistoryList",
                payload: {
                    refreshing: refreshing
                },
                callback
            });
        }
    };

    const renderSectionHeader = ({ section: { title } }: any) => {
        return (
            <View style={styles.headerView}>
                <Text style={styles.headerTitle}>{title}</Text>
            </View>
        );
    };

    const getBeforeX = () => {
        Animated.timing(translateX,
            {
                useNativeDriver: false,
                toValue: wp(5),
                duration: 150
            }
        ).start();
    };

    const getAfterX = () => {
        Animated.timing(translateX,
            {
                useNativeDriver: false,
                toValue: 0,
                duration: 150
            }
        ).start();
    };

    const onClickItem = useCallback((item: IHistory[]) => {
        if (isEdit) {
            let i = ids.indexOf(item["book_id"]);
            if (i > -1) {
                ids.splice(i, 1);
                dispatch({
                    type: "history/setState",
                    payload: {
                        ids: [...ids]
                    }
                });
            } else {
                dispatch({
                    type: "history/setState",
                    payload: {
                        ids: [...ids, item["book_id"]]
                    }
                });
            }
        } else {
            navigation.navigate("Brief", {
                id: item["book_id"]
            });
        }
    }, [isEdit, ids]);

    const goMangaView = useCallback((item: IHistory[]) => {
        navigation.navigate("Brief", {
            id: item["book_id"]
        });
        navigation.navigate("MangaView", {
            chapter_num: item["chapter_num"],
            markRoast: item["roast"],
            book_id: item["book_id"]
        });
    }, []);

    const renderItem = ({ item }: SectionListRenderItemInfo<IHistory[]>) => {
        const selected = ids.indexOf(item["book_id"]) > -1;
        return (
            <Touchable
                key={item["id"]}
                onPress={() => onClickItem(item)}
            >
                <Animated.View
                    style={{ transform: [{ translateX: translateX }] }}
                >
                    <Item
                        data={item}
                        isEdit={isEdit}
                        selected={selected}
                        goMangaView={goMangaView}
                    />
                </Animated.View>
            </Touchable>
        );
    };

    const onRefresh = () => {
        dispatch({
            type: "history/setScreenReload"
        });
        loadData(true);
    };

    const onEndReached = () => {
        if (!hasMore || loading) {
            return;
        }
        setEndReached(true);
        loadData(false, () => {
            setEndReached(false);
        });
    };

    const renderFooter = () => {
        if (endReached) {
            return <More />;
        }
        if (!hasMore) {
            return <End />;
        }

        return null;
    };

    const cancel = () => {
        let newData: string[] = [];
        historyList.forEach(items => {
                items.data.forEach(item => {
                    newData = newData.concat(item["book_id"]);
                });
            }
        );
        if (newData.length === ids.length) {
            dispatch({
                type: "history/setState",
                payload: {
                    ids: []
                }
            });
        } else {
            dispatch({
                type: "history/setState",
                payload: {
                    ids: newData
                }
            });
        }
    };

    const destroy = () => {
        dispatch({
            type: "history/delUserHistory",
            payload: {
                ids
            }
        });
    };

    if (isEdit) {
        getBeforeX();
    } else {
        getAfterX();
    }

    return (
        !isLogin ? null :
            (loading && refreshing) ? <ListPlaceholder /> :
                <View style={styles.container}>
                    <SectionList
                        keyExtractor={(item, index) => `section-item-${index}`}
                        renderSectionHeader={renderSectionHeader}
                        onRefresh={onRefresh}
                        refreshing={refreshing}
                        sections={historyList}
                        style={styles.container}
                        stickySectionHeadersEnabled={true}
                        onEndReached={onEndReached}
                        onEndReachedThreshold={0.1}
                        renderItem={renderItem}
                        extraData={endReached}
                        ListFooterComponent={renderFooter}
                    />
                    <EditView
                        data_length={pages.current_page * pages.page_size < pages.total ?
                            pages.current_page * pages.page_size : pages.total}
                        isEdit={isEdit}
                        ids={ids}
                        cancel={cancel}
                        destroy={destroy}
                    />
                </View>
    );
}
Example #20
Source File: index.tsx    From krmanga with MIT License 4 votes vote down vote up
function Category({ dispatch, navigation, category_id, activeStatus, activeModel, bookList, loading, hasMore, refreshing }: IProps) {

    let scrollViewStartOffsetY: number = 0;
    const [endReached, setEndReached] = useState<boolean>(false);

    useFocusEffect(
        React.useCallback(() => {
            dispatch({
                type: "category/setState",
                payload: {
                    activeCategory: category_id
                }
            });
            loadData(true);
        }, [activeStatus])
    );

    const loadData = (refreshing: boolean, callback?: () => void) => {
        dispatch({
            type: `${activeModel}/fetchBookList`,
            payload: {
                refreshing,
                category_id
            },
            callback
        });
    };

    const goBrief = useCallback((data: IBook) => {
        navigation.navigate("Brief", {
            id: data.id
        });
    }, []);

    const renderItem = ({ item }: ListRenderItemInfo<IBook>) => {
        return (
            <BookCover
                key={item.id}
                data={item}
                goBrief={goBrief}
            />
        );
    };

    const onRefresh = () => {
        dispatch({
            type: `${activeModel}/fetchBookList`,
            payload: {
                refreshing: true,
                onRefresh: true,
                category_id
            }
        });
    };

    const renderFooter = () => {
        if (endReached) {
            return <More />;
        }
        if (!hasMore) {
            return <End />;
        }

        return null;
    };

    const onScrollBeginDrag = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
        scrollViewStartOffsetY = nativeEvent.contentOffset.y;
    };

    const onScrollEndDrag = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
        if (scrollViewStartOffsetY > nativeEvent.contentOffset.y) {
            dispatch({
                type: "category/setState",
                payload: {
                    hideHeader: false
                }
            });
        } else {
            dispatch({
                type: "category/setState",
                payload: {
                    hideHeader: true
                }
            });
        }
    };

    const onEndReached = () => {
        if (!hasMore || loading) {
            return;
        }
        setEndReached(true);

        loadData(false, () => {
            setEndReached(false);
        });
    };

    return (
        (loading && refreshing) ? <BookPlaceholder /> :
            <FlatList
                keyExtractor={(item, key) => `item-${item.id}-key-${key}`}
                data={bookList}
                extraData={endReached}
                renderItem={renderItem}
                refreshing={refreshing}
                style={styles.container}
                onRefresh={onRefresh}
                ListFooterComponent={renderFooter}
                scrollEventThrottle={1}
                onScrollBeginDrag={onScrollBeginDrag}
                onScrollEndDrag={onScrollEndDrag}
                numColumns={3}
                onEndReached={onEndReached}
                onEndReachedThreshold={0.1}
            />
    );
}
Example #21
Source File: home.tsx    From bext with MIT License 4 votes vote down vote up
HomeScreen: FC = () => {
  const navigation = useNavigation();
  const [modalVisible, setModalVisible] = useState(false);
  useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <Icon name="add" onPress={() => setModalVisible(true)} />
      ),
    });
  }, [navigation]);
  const db = useDb();
  const { loading, data, run } = useRequest(getDrafts, {
    ready: db.ready,
  });
  const [name, setName] = useState('');
  useFocusEffect(useMemoizedFn(run));

  const onDelete = useMemoizedFn(async (id: number) => {
    await deleteDraft(id);
    run();
  });

  const onPress = useMemoizedFn((id: number) => {
    navigation.navigate(
      'dev' as never,
      {
        id,
      } as never,
    );
  });

  const createDraft = useMemoizedFn(async (empty: boolean) => {
    const id = await addDraft(name);
    setName('');
    run();
    setModalVisible(false);
    if (id) {
      navigation.navigate(
        'dev' as never,
        {
          id,
          modify: empty ? undefined : true,
        } as never,
      );
    }
  });

  return (
    <View style={{ flex: 1 }}>
      <Overlay
        transparent
        isVisible={modalVisible}
        onBackdropPress={() => setModalVisible(false)}
        overlayStyle={styles.overlay}
      >
        <Input
          label="输入草稿名称(不是脚本名称)"
          value={name}
          onChangeText={setName}
        />
        <View style={styles.buttons}>
          <Button
            title="创建空白草稿"
            disabled={!name.length}
            onPress={() => createDraft(true)}
            containerStyle={styles.button}
          />
          <View style={styles.space} />
          <Button
            title="从现有脚本创建"
            disabled={!name.length}
            onPress={() => createDraft(false)}
            containerStyle={styles.button}
          />
        </View>
      </Overlay>
      <FlatList
        data={data || []}
        keyExtractor={(item) => String(item.id)}
        renderItem={({ item }) => (
          <DraftItem
            draft={item}
            onDelete={() => onDelete(item.id)}
            onPress={() => onPress(item.id)}
          />
        )}
        onRefresh={run}
        refreshing={loading}
      />
    </View>
  );
}
Example #22
Source File: MatchesScreen.tsx    From vsinder with Apache License 2.0 4 votes vote down vote up
MatchesScreen: React.FC<MatchesStackNav<"matchy">> = ({
  navigation,
}) => {
  const {
    buttonSecondaryHoverBackground,
    buttonSecondaryBackground,
  } = useTheme();
  const { data, isLoading } = useQuery<MatchesResponse>(
    "/matches/0",
    defaultQueryFn
  );

  const cache = useQueryCache();

  useOnWebSocket((e) => {
    if (e.type === "unmatch") {
      const d = cache.getQueryData<MatchesResponse>("/matches/0");
      if (d) {
        cache.setQueryData<MatchesResponse>("/matches/0", {
          matches: d.matches.filter((m) => m.userId !== e.userId),
        });
      }
    } else if (e.type === "new-match") {
      cache.invalidateQueries("/matches/0");
    } else if (e.type === "new-like") {
      cache.setQueryData<MeResponse>("/me", (u) => {
        return {
          user: {
            ...u!.user!,
            numLikes: u!.user!.numLikes + 1,
          },
        };
      });
    } else if (e.type === "new-message") {
      const state = navigation.dangerouslyGetState();
      const route: any = state.routes[state.index];
      const read = !!(route && route.params?.userId === e.message.senderId);
      const d = cache.getQueryData<MatchesResponse>("/matches/0");
      if (d) {
        cache.setQueryData<MatchesResponse>("/matches/0", {
          matches: d.matches.map((m) =>
            m.userId === e.message.senderId
              ? {
                  ...m,
                  read,
                  message: {
                    text: e.message.text,
                    createdAt: e.message.createdAt,
                  },
                }
              : m
          ),
        });
      }
    }
  });

  useEffect(() => {
    if (data?.matches) {
      const d0 = cache.getQueryData<MeResponse>("/me");
      if (!d0 || !d0.user) {
        return;
      }
      const newUnreadMatchUserIds: Array<{
        userId1: string;
        userId2: string;
      }> = [];
      data.matches.forEach((m) => {
        if (!m.read) {
          const [u1, u2] = [m.userId, d0.user!.id].sort();
          newUnreadMatchUserIds.push({ userId1: u1, userId2: u2 });
        }
      });
      cache.setQueryData<MeResponse>("/me", {
        user: {
          ...d0.user,
          unreadMatchUserIds: newUnreadMatchUserIds,
        },
      });
    }
  }, [data?.matches]);

  const isClearing = useRef(false);
  useFocusEffect(() => {
    getPresentedNotificationsAsync().then((x) => {
      if (x.length && !isClearing.current) {
        isClearing.current = true;
        dismissAllNotificationsAsync().finally(() => {
          isClearing.current = false;
        });
      }
    });
  });

  if (isLoading) {
    return <FullscreenLoading />;
  }

  if (!data?.matches.length) {
    return <FullscreenMessage message="No matches yet ?" />;
  }

  return (
    <ScreenWrapper noPadding>
      <FlatList
        showsVerticalScrollIndicator={false}
        style={{ paddingTop: 16 }}
        data={data.matches.sort(compareMatches)}
        keyExtractor={(x) => x.userId}
        renderItem={({ item }) => (
          <TouchableOpacity
            onPress={() =>
              navigation.navigate("messages", { ...item, id: item.userId })
            }
            style={{
              flexDirection: "row",
              alignItems: "center",
              backgroundColor: item.read
                ? undefined
                : buttonSecondaryHoverBackground,
              padding: 15,
              borderBottomColor: buttonSecondaryBackground,
              borderBottomWidth: 1,
            }}
          >
            <TouchableOpacity
              style={{ paddingRight: 12 }}
              onPress={() =>
                navigation.navigate(`viewCard`, { id: item.userId })
              }
            >
              <Avatar src={item.photoUrl} />
            </TouchableOpacity>
            <View style={{ flex: 1 }}>
              <View
                style={{
                  alignItems: "center",
                  flexDirection: "row",
                  width: "100%",
                }}
              >
                <MyText
                  style={{ fontSize: 20, marginRight: 4 }}
                  numberOfLines={1}
                >
                  {item.displayName}
                </MyText>
                <Flair name={item.flair as any} />
                <MyText style={{ marginLeft: "auto" }} numberOfLines={1}>
                  {dtToHumanStr(
                    new Date(
                      item.message ? item.message.createdAt : item.createdAt
                    )
                  )}
                </MyText>
              </View>
              <MyText style={{ marginTop: 4 }}>
                {item.message?.text || " "}
              </MyText>
            </View>
          </TouchableOpacity>
        )}
      />
    </ScreenWrapper>
  );
}
Example #23
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 #24
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 #25
Source File: ScanHome.tsx    From hamagen-react-native with MIT License 4 votes vote down vote up
ScanHome: FunctionComponent<ScanHomeProps> = (
  {
    navigation,
    route,
    isRTL,
    strings,
    locale,
    languages,
    externalUrls,
    exposures,
    pastExposures,
    firstPoint,
    enableBle,
    batteryDisabled,
    hideLocationHistory,
    checkForceUpdate,
    checkIfHideLocationHistory,
    checkIfBleEnabled,
    checkIfBatteryDisabled
  }
) => {
  const appStateStatus = useRef<AppStateStatus>('active');
  const [{ hasLocation, hasNetwork, hasGPS }, setIsConnected] = useState({ hasLocation: true, hasNetwork: true, hasGPS: true });

  useEffect(() => {
    init();
  }, []);

  const init = async () => {
    checkIfHideLocationHistory();
    checkIfBatteryDisabled();
    checkConnectionStatusOnLoad();
    checkIfBleEnabled();
    SplashScreen.hide();

    if (exposures.length > 0) {
      navigation.navigate('ExposureDetected');
    } else {
      checkForceUpdate();

      await goToFilterDrivingIfNeeded(navigation);

      const url = await Linking.getInitialURL();


      if (url) {
        return onOpenedFromDeepLink(url, navigation);
      }

      await syncLocationsDBOnLocationEvent();
    }
  };

  useEffect(() => {
    AppState.addEventListener('change', onAppStateChange);
    NetInfo.addEventListener((state: NetInfoState) => setIsConnected({ hasLocation, hasNetwork: state.isConnected, hasGPS }));
    DeviceEventEmitter.addListener(RNSettings.GPS_PROVIDER_EVENT, handleGPSProviderEvent);

    return () => {
      AppState.removeEventListener('change', onAppStateChange);
      DeviceEventEmitter.removeListener(RNSettings.GPS_PROVIDER_EVENT, handleGPSProviderEvent);
    };
  }, [hasLocation, hasNetwork, hasGPS]);

  useFocusEffect(
    React.useCallback(() => {
      const onBackPress = () => {
        BackHandler.exitApp();
        return true;
      };

      BackHandler.addEventListener('hardwareBackPress', onBackPress);

      return () => {
        BackHandler.removeEventListener('hardwareBackPress', onBackPress);
      };
    }, [])
  );

  const checkConnectionStatusOnLoad = async () => {
    const locationPermission = await checkLocationPermissions();
    const networkStatus = await NetInfo.fetch();
    const GPSStatus = await RNSettings.getSetting(RNSettings.LOCATION_SETTING);

    setIsConnected({ hasLocation: locationPermission === RESULTS.GRANTED, hasNetwork: networkStatus.isConnected, hasGPS: GPSStatus === RNSettings.ENABLED });
  };

  const onAppStateChange = async (state: AppStateStatus) => {
    if (state === 'active' && appStateStatus.current !== 'active') {
      checkIfHideLocationHistory();
      checkConnectionStatusOnLoad();
      checkIfBatteryDisabled();
    }

    appStateStatus.current = state;
  };

  const handleGPSProviderEvent = (e: any) => {
    setIsConnected({ hasLocation, hasNetwork, hasGPS: e[RNSettings.LOCATION_SETTING] === RNSettings.ENABLED });
  };

  const exposureState = () => {
    // user never got any exposure detected
    if (exposures.length + pastExposures.length === 0) {
      return 'pristine';
    }

    // check if user past exposures are relevant
    // ie: is less then 14 days old
    if (exposures.some(isAfter14Days) || pastExposures.some(isAfter14Days)) {
      return 'relevant';
    }

    return 'notRelevant';
  };


  const RelevantState = () => {
    if (!hasGPS || !hasLocation) return (<NoGPS {...strings.scanHome.noGPS} />);
    if (!hasNetwork) return (<NoNetwork {...strings.scanHome.noNetwork} />);
    return (
      <NoExposures
        isRTL={isRTL}
        strings={strings}
        firstPoint={firstPoint}
        exposureState={exposureState()}
        hideLocationHistory={hideLocationHistory}
        enableBle={enableBle}
        batteryDisabled={batteryDisabled}
        locale={locale}
        languages={languages}
        externalUrls={externalUrls}
        goToLocationHistory={() => navigation.navigate('LocationHistory')}
        goToBluetoothPermission={() => navigation.navigate('Bluetooth')}
        goToBatteryPermission={() => navigation.navigate('Battery')}
        showBleInfo={route.params?.showBleInfo}
      />
    );
  };


  return (
    <View style={styles.container}>
      <ScanHomeHeader
        enableBle={enableBle}
        languages={languages}
        isRTL={isRTL}
        locale={locale}
        externalUrls={externalUrls}
        strings={strings}
        openDrawer={navigation.openDrawer}
      />
      {RelevantState()}
    </View>
  );
}
Example #26
Source File: ExposuresDetected.tsx    From hamagen-react-native with MIT License 4 votes vote down vote up
ExposuresDetected = ({ navigation }: ExposuresDetectedProps) => {
  const dispatch = useDispatch();
  const { isRTL, strings: { scanHome: { inDate, fromHour, wereYouThere, wasNotMe, wasMe, doneBtn, suspectedExposure, events, possibleExposure, atPlace, showOnMap, betweenHours, possibleExposureBLE, locationCloseTag, deviceCloseTag, wasMeBle, wasMeOnly } } } = useSelector<Store, LocaleReducer>(state => state.locale);
  const { exposures } = useSelector<Store, ExposuresReducer>(state => state.exposures);

  const [anim] = useState(new Animated.Value(SCREEN_HEIGHT * 0.08));
  const isOneBle = useMemo(() => exposures.length === 1 && exposures[0].properties.BLETimestamp !== null, [exposures]);
  const flatListRef = useRef(null);

  useEffect(() => {
    if (exposures.length === 0) {
      navigation.navigate('ScanHome');
    } else {
      SplashScreen.hide();
      AsyncStorage.setItem(INIT_ROUTE_NAME, 'ExposureDetected');
      BackHandler.addEventListener('hardwareBackPress', () => true);

      return () => {
        BackHandler.removeEventListener('hardwareBackPress', () => true);
      };
    }
  }, []);


  const showButton = (duration: number = 300) => {
    Animated.timing(anim, {
      toValue: 0,
      duration,
      useNativeDriver: true,
      delay: 300
    }).start();
  };

  // show button when moving to another page
  //  use case for single exposure. the user moves on click but if he returns for edit
  useFocusEffect(
    useCallback(() => {
      if (!isOneBle
        && exposures.every(exposure => exposure.properties.wasThere !== null)) {
        showButton(0);
      }
    }, [])
  );

  const setSelected = (index: number, wasThere: boolean) => {
    dispatch(setExposureSelected({ index, wasThere }));
    if (exposures.length === 1) {
      editDone();
    } else {
      // find index of first card user didn't checked(was or not) and go to there˝
      const emptyIndex = exposures.findIndex(exposure => exposure.properties.wasThere === null || exposure.properties.wasThere === undefined);

      if (emptyIndex === -1) {
        showButton();
      } else if (index + 1 < exposures.length) {
        setTimeout(() => {
          if (flatListRef?.current) {
            flatListRef?.current?.scrollToIndex({
              index: index + 1,
              viewOffset: 10
            });
          }
        }, 300);
      } else {
        // all selected show finish button and findIndex get me last index
        if (emptyIndex === -1 || exposures.length - 1 === emptyIndex) {
          showButton();
        } else {
          flatListRef?.current?.scrollToIndex({
            index: emptyIndex,
            viewOffset: 10
          });
        }
      }
    }
  };

  const editDone = () => {
    dispatch(dismissExposures());
    // check if at least one exposure was checked a been there
    const isExposed = exposures.some((exposure: Exposure) => exposure.properties.wasThere);

    if (isExposed) {
      // move to ExposureInstructions
      const showEdit = exposures.some((exposure: Exposure) => !exposure.properties.BLETimestamp);
      navigation.navigate('ExposureInstructions', { showEdit });
    } else {
      // move to ExposureRelief
      navigation.navigate('ExposureRelief');
      AsyncStorage.removeItem(INIT_ROUTE_NAME);
    }
  };

  const RenderBleExposure = ({ index, exposure: { properties: { BLETimestamp, OBJECTID, Place } } }) => {
    const [exposureDate, exposureStartHour, exposureEndHour] = useMemo(() => {
      const time = moment(BLETimestamp).startOf('hour');

      return [
        time.format('DD.MM.YY'),
        time.format('HH:mm'),
        time.add(1, 'hour').format('HH:mm')
      ];
    }, [BLETimestamp]);


    let LocationText = null;

    if (OBJECTID) {
      LocationText = (
        <>
          <Text style={styles.exposureCardPlace} bold>
            {`${atPlace}${Place}`}
          </Text>
          <View style={styles.exposureCardMapContainer}>
            <Text style={styles.exposureCardMapText} onPress={() => dispatch(showMapModal(exposures[index]))}>{showOnMap}</Text>
          </View>
        </>
      );
    }

    return (
      <Animated.View style={[styles.detailsContainer]}>
        <View style={{ flex: 1, alignItems: 'center' }}>
          <Text style={styles.exposureLength}>{`${index + 1}/${exposures.length}`}</Text>
          <Text style={styles.exposureCardTitle}>{possibleExposureBLE}</Text>
          <Text style={{ fontSize: 17 }} bold>{`${inDate} ${exposureDate}${OBJECTID ? ' ' : '\n'}${betweenHours} ${exposureStartHour}-${exposureEndHour}`}</Text>
          {LocationText}
        </View>
        <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>

          <TouchableOpacity
            style={[styles.bleActionBtn]}
            onPress={() => setSelected(index, true)}
          >
            <Text style={[styles.actionBtnText, styles.actionBtnSelectedText]} bold>{exposures.length === 1 ? wasMeOnly : wasMeBle}</Text>
          </TouchableOpacity>
        </View>
        <CardIdentifyTag isRTL={isRTL} text={deviceCloseTag} color="rgba(44,191,220,0.5)" />
      </Animated.View>
    );
  };


  const RenderGeoExposure = ({ index, exposure: { properties: { Place, fromTime, OBJECTID, wasThere } } }: RenderExposureProps) => {
    const [wasThereSelected, wasNotThereSelected] = useMemo(() => {
      if (wasThere === null) { return [false, false]; }
      return [wasThere, !wasThere];
    }, [wasThere]);

    const [exposureDate, exposureHour] = useMemo(() => {
      const time = moment(fromTime);
      return [time.format('DD.MM.YY'), time.format('HH:mm')];
    }, [fromTime]);

    return (
      <Animated.View style={[styles.detailsContainer]}>
        <View style={{ alignItems: 'center' }}>
          <Text style={styles.exposureLength}>{`${index + 1}/${exposures.length}`}</Text>
          <Text style={styles.exposureCardTitle}>{possibleExposure}</Text>
          <Text style={styles.exposureCardPlace} bold>
            {`${atPlace}${Place} ${inDate} ${exposureDate} ${fromHour} ${exposureHour}`}
          </Text>
          <View style={styles.exposureCardMapContainer}>
            <Text style={styles.exposureCardMapText} onPress={() => dispatch(showMapModal(exposures[index]))}>{showOnMap}</Text>
          </View>
        </View>
        <View>
          <Text style={styles.actionBtnTitle}>{wereYouThere}</Text>
          <View style={styles.actionBtnContainer}>
            <TouchableOpacity
              style={[styles.actionBtnTouch, wasThereSelected && styles.actionBtnSelected]}
              onPress={() => setSelected(index, true)}
            >
              <Text style={[styles.actionBtnText, wasThereSelected && styles.actionBtnSelectedText]} bold={wasThereSelected}>{wasMe}</Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={[styles.actionBtnTouch, wasNotThereSelected && styles.actionBtnSelected]}
              onPress={() => setSelected(index, false)}
            >
              <Text style={[styles.actionBtnText, wasNotThereSelected && styles.actionBtnSelectedText]} bold={wasNotThereSelected}>{wasNotMe}</Text>
            </TouchableOpacity>
          </View>
        </View>
        <CardIdentifyTag isRTL={isRTL} text={locationCloseTag} color="rgba(217,228,140,0.6)" />
      </Animated.View>
    );
  };

  return (
    <>
      <ScrollView
        contentContainerStyle={styles.container}
        bounces={false}
        showsVerticalScrollIndicator={false}
      >
        <View style={{ alignItems: 'center', marginBottom: IS_SMALL_SCREEN ? 10 : 42 }}>
          <Icon source={require('../../assets/main/exposures.png')} width={IS_SMALL_SCREEN ? 66 : 99} height={IS_SMALL_SCREEN ? 40 : 59} customStyles={{ marginBottom: 33 }} />
          <Text style={styles.title} bold>{`${suspectedExposure} ${exposures.length} ${events}`}</Text>
        </View>

        <FlatList
          horizontal
          bounces={false}
          ref={flatListRef}
          data={exposures}
          nestedScrollEnabled
          keyExtractor={(item: Exposure) => {
            if (item?.properties?.BLETimestamp) { return item.properties.BLETimestamp.toString(); }
            return item.properties.OBJECTID.toString();
          }}
          renderItem={({ item, index }) => (item.properties?.BLETimestamp ? <RenderBleExposure exposure={item} index={index} /> : <RenderGeoExposure exposure={item} index={index} />)}
          showsHorizontalScrollIndicator={false}
          contentContainerStyle={{ paddingLeft: 14, paddingRight: 5 }}
        />
      </ScrollView>
      <Animated.View style={{ transform: [{ translateY: anim }] }}>
        <TouchableOpacity
          onPress={editDone}
          style={{
            width: SCREEN_WIDTH,
            height: SCREEN_HEIGHT * 0.08,
            backgroundColor: MAIN_COLOR,
            justifyContent: 'center',
            flexDirection: isRTL ? 'row-reverse' : 'row',
            alignItems: 'center',
            paddingBottom: PADDING_BOTTOM()
          }}
        >
          <Icon
            source={require('../../assets/main/imDoneUpdate.png')}
            width={IS_SMALL_SCREEN ? 12 : 14}
            height={IS_SMALL_SCREEN ? 10 : 12}
          />
          <Text style={{ fontSize: IS_SMALL_SCREEN ? 15 : 18, color: 'white', marginHorizontal: 6 }} bold>{doneBtn}</Text>
        </TouchableOpacity>
      </Animated.View>
    </>
  );
}