mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2025-12-23 22:18:36 -05:00
283 lines
7.6 KiB
TypeScript
283 lines
7.6 KiB
TypeScript
import { useNetInfo } from "@react-native-community/netinfo";
|
|
import { useNavigation } from "@react-navigation/native";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
import {
|
|
signOut
|
|
} from "components/LoginSignUp/AuthenticationService";
|
|
import {
|
|
Body1,
|
|
INatIcon,
|
|
List2, TextInputSheet,
|
|
UserIcon,
|
|
WarningSheet
|
|
} from "components/SharedComponents";
|
|
import { Pressable, ScrollView, View } from "components/styledComponents";
|
|
import { RealmContext } from "providers/contexts";
|
|
import React, { useCallback, useState } from "react";
|
|
import { Alert } from "react-native";
|
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
import User from "realmModels/User";
|
|
import { log } from "sharedHelpers/logger";
|
|
import { useCurrentUser, useTranslation } from "sharedHooks";
|
|
import useStore, { zustandStorage } from "stores/useStore";
|
|
import colors from "styles/tailwindColors";
|
|
|
|
import MenuItem from "./MenuItem";
|
|
|
|
const { useRealm } = RealmContext;
|
|
|
|
function isDefaultMode( ) {
|
|
return useStore.getState( ).layout.isDefaultMode === true;
|
|
}
|
|
|
|
interface BaseMenuOption {
|
|
label: string;
|
|
icon: string;
|
|
color?: string;
|
|
testID?: string;
|
|
isLogout?: boolean;
|
|
}
|
|
|
|
interface MenuOptionWithNavigation extends BaseMenuOption {
|
|
navigation: string;
|
|
onPress?: never;
|
|
}
|
|
|
|
interface MenuOptionWithOnPress extends BaseMenuOption {
|
|
onPress: ( ) => void;
|
|
navigation?: never;
|
|
}
|
|
|
|
export type MenuOption = MenuOptionWithNavigation | MenuOptionWithOnPress;
|
|
|
|
export enum MenuModalState {
|
|
ConfirmLogout = "confirmLogout",
|
|
ProvideFeedback = "provideFeedback"
|
|
}
|
|
|
|
const feedbackLogger = log.extend( "feedback" );
|
|
|
|
function showOfflineAlert( t: ( _: string ) => string ) {
|
|
Alert.alert( t( "You-are-offline" ), t( "Please-try-again-when-you-are-online" ) );
|
|
}
|
|
|
|
const Menu = ( ) => {
|
|
const isDebug = zustandStorage.getItem( "debugMode" ) === "true";
|
|
const realm = useRealm( );
|
|
const navigation = useNavigation( );
|
|
const queryClient = useQueryClient( );
|
|
const currentUser = useCurrentUser( );
|
|
const { t } = useTranslation( );
|
|
const insets = useSafeAreaInsets();
|
|
|
|
const { isConnected } = useNetInfo( );
|
|
|
|
const [modalState, setModalState] = useState<MenuModalState | null>( null );
|
|
|
|
const menuItems: Record<string, MenuOption> = {
|
|
projects: {
|
|
label: t( "PROJECTS" ),
|
|
navigation: "Projects",
|
|
icon: "briefcase"
|
|
},
|
|
about: {
|
|
label: t( "ABOUT" ),
|
|
navigation: "About",
|
|
icon: "inaturalist"
|
|
},
|
|
donate: {
|
|
label: t( "DONATE" ),
|
|
navigation: "Donate",
|
|
icon: "heart",
|
|
color: colors.inatGreen
|
|
},
|
|
help: {
|
|
label: t( "HELP" ),
|
|
navigation: "Help",
|
|
icon: "help-circle"
|
|
},
|
|
settings: {
|
|
testID: "settings",
|
|
label: t( "SETTINGS" ),
|
|
navigation: "Settings",
|
|
icon: "gear"
|
|
},
|
|
|
|
feedback: {
|
|
label: t( "FEEDBACK" ),
|
|
icon: "feedback",
|
|
onPress: () => {
|
|
if ( isConnected ) {
|
|
setModalState( MenuModalState.ProvideFeedback );
|
|
} else {
|
|
showOfflineAlert( t );
|
|
}
|
|
}
|
|
},
|
|
|
|
...( currentUser
|
|
? {
|
|
logout: {
|
|
label: t( "LOG-OUT" ),
|
|
icon: "door-exit",
|
|
onPress: () => setModalState( MenuModalState.ConfirmLogout ),
|
|
isLogout: true
|
|
}
|
|
}
|
|
: {
|
|
login: {
|
|
label: t( "LOG-IN" ),
|
|
icon: "door-enter",
|
|
color: colors.inatGreen,
|
|
onPress: () => navigation.navigate( "LoginStackNavigator" )
|
|
}
|
|
} ),
|
|
|
|
...( isDebug
|
|
? {
|
|
debug: {
|
|
label: "DEBUG",
|
|
navigation: "Debug",
|
|
icon: "triangle-exclamation",
|
|
color: "deeppink"
|
|
}
|
|
}
|
|
: {} )
|
|
};
|
|
|
|
const onSignOut = async ( ) => {
|
|
await signOut( { realm, clearRealm: true, queryClient } );
|
|
setModalState( null );
|
|
|
|
// TODO might be necessary to restart the app at this point. We just
|
|
// deleted the realm file on disk, but the RealmProvider may still have a
|
|
// copy of realm in local state
|
|
navigation.goBack( );
|
|
};
|
|
|
|
const onSubmitFeedback = useCallback( ( text: string ) => {
|
|
if ( !isConnected ) {
|
|
showOfflineAlert( t );
|
|
return false;
|
|
}
|
|
const mode = isDefaultMode( )
|
|
? "DEFAULT:"
|
|
: "ADVANCED:";
|
|
feedbackLogger.info( mode, text );
|
|
Alert.alert( t( "Feedback-Submitted" ), t( "Thank-you-for-sharing-your-feedback" ) );
|
|
setModalState( null );
|
|
return true;
|
|
}, [isConnected, t] );
|
|
|
|
return (
|
|
<ScrollView
|
|
bounces={false}
|
|
className="bg-white h-full"
|
|
style={{ paddingTop: insets.top }}
|
|
>
|
|
<View>
|
|
{/* Header */}
|
|
<Pressable
|
|
testID="menu-header"
|
|
accessible
|
|
accessibilityRole="button"
|
|
accessibilityHint={
|
|
currentUser
|
|
? t( "Navigates-to-user-profile" )
|
|
: t( "Navigates-to-log-in-screen" )
|
|
}
|
|
className="px-[26px] pt-[68px] pb-[31px] border-b border-lightGray"
|
|
onPress={( ) => {
|
|
if ( !currentUser ) {
|
|
navigation.navigate( "LoginStackNavigator" );
|
|
} else {
|
|
navigation.navigate( "TabNavigator", {
|
|
screen: "ObservationsTab",
|
|
params: {
|
|
screen: "UserProfile",
|
|
params: { userId: currentUser.id }
|
|
}
|
|
} );
|
|
}
|
|
}}
|
|
>
|
|
<View className="flex-row">
|
|
{currentUser
|
|
? (
|
|
<UserIcon
|
|
uri={User.uri( currentUser )}
|
|
medium
|
|
/>
|
|
)
|
|
: (
|
|
<INatIcon
|
|
name="inaturalist"
|
|
color={colors.inatGreen}
|
|
size={62}
|
|
/>
|
|
) }
|
|
<View className="ml-5 justify-center">
|
|
<Body1>
|
|
{currentUser
|
|
? currentUser?.login
|
|
: t( "Log-in-to-iNaturalist" )}
|
|
</Body1>
|
|
{currentUser && (
|
|
<List2>
|
|
{t( "X-Observations", { count: currentUser.observations_count } )}
|
|
</List2>
|
|
)}
|
|
</View>
|
|
</View>
|
|
</Pressable>
|
|
|
|
{/* Menu Items */}
|
|
<View>
|
|
{Object.entries( menuItems ).map( ( [key, item] ) => (
|
|
<MenuItem
|
|
key={key}
|
|
item={item}
|
|
onPress={() => {
|
|
if ( item.navigation ) {
|
|
navigation.navigate( "TabNavigator", {
|
|
screen: "MenuTab",
|
|
params: {
|
|
screen: menuItems[key].navigation
|
|
}
|
|
} );
|
|
}
|
|
item.onPress?.();
|
|
}}
|
|
/>
|
|
) )}
|
|
</View>
|
|
</View>
|
|
|
|
{modalState === MenuModalState.ConfirmLogout && (
|
|
<WarningSheet
|
|
onPressClose={() => setModalState( null )}
|
|
headerText={t( "LOG-OUT--question" )}
|
|
text={t( "Are-you-sure-you-want-to-log-out" )}
|
|
handleSecondButtonPress={() => setModalState( null )}
|
|
secondButtonText={t( "CANCEL" )}
|
|
confirm={onSignOut}
|
|
buttonText={t( "LOG-OUT" )}
|
|
loading={false}
|
|
/>
|
|
)}
|
|
{modalState === MenuModalState.ProvideFeedback && (
|
|
<TextInputSheet
|
|
buttonText={t( "SUBMIT" )}
|
|
onPressClose={() => setModalState( null )}
|
|
headerText={t( "FEEDBACK" )}
|
|
confirm={onSubmitFeedback}
|
|
description={t( "Thanks-for-using-any-suggestions" )}
|
|
maxLength={1000}
|
|
/>
|
|
)}
|
|
</ScrollView>
|
|
);
|
|
};
|
|
|
|
export default Menu;
|