@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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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>
</>
);
}