diff --git a/native/account/registration/avatar-selection.react.js b/native/account/registration/avatar-selection.react.js
index 147081ded..f0bde1c3c 100644
--- a/native/account/registration/avatar-selection.react.js
+++ b/native/account/registration/avatar-selection.react.js
@@ -1,199 +1,202 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { Text, View } from 'react-native';
import RegistrationButtonContainer from './registration-button-container.react.js';
import RegistrationButton from './registration-button.react.js';
import RegistrationContainer from './registration-container.react.js';
import RegistrationContentContainer from './registration-content-container.react.js';
import { RegistrationContext } from './registration-context.js';
import type { RegistrationNavigationProp } from './registration-navigator.react.js';
import {
type CoolOrNerdMode,
type AccountSelection,
type AvatarData,
ensAvatarSelection,
} from './registration-types.js';
import {
EditUserAvatarContext,
type UserAvatarSelection,
} from '../../avatars/edit-user-avatar-provider.react.js';
import EditUserAvatar from '../../avatars/edit-user-avatar.react.js';
-import type { NavigationRoute } from '../../navigation/route-names.js';
+import { useCurrentLeafRouteName } from '../../navigation/nav-selectors.js';
+import {
+ type NavigationRoute,
+ RegistrationTermsRouteName,
+ AvatarSelectionRouteName,
+ EmojiAvatarSelectionRouteName,
+ RegistrationUserAvatarCameraModalRouteName,
+} from '../../navigation/route-names.js';
import { useStyles } from '../../themes/colors.js';
export type AvatarSelectionParams = {
+userSelections: {
+coolOrNerdMode: CoolOrNerdMode,
+keyserverUsername: string,
+accountSelection: AccountSelection,
},
};
type Props = {
+navigation: RegistrationNavigationProp<'AvatarSelection'>,
+route: NavigationRoute<'AvatarSelection'>,
};
function AvatarSelection(props: Props): React.Node {
const { userSelections } = props.route.params;
const { accountSelection } = userSelections;
const usernameOrETHAddress =
accountSelection.accountType === 'username'
? accountSelection.username
: accountSelection.address;
const registrationContext = React.useContext(RegistrationContext);
invariant(registrationContext, 'registrationContext should be set');
- const { cachedSelections, setCachedSelections, register } =
- registrationContext;
+ const { cachedSelections, setCachedSelections } = registrationContext;
const editUserAvatarContext = React.useContext(EditUserAvatarContext);
invariant(editUserAvatarContext, 'editUserAvatarContext should be set');
const { setRegistrationMode } = editUserAvatarContext;
const prefetchedAvatarURI =
accountSelection.accountType === 'ethereum'
? accountSelection.avatarURI
: undefined;
let initialAvatarData = cachedSelections.avatarData;
if (!initialAvatarData && prefetchedAvatarURI) {
initialAvatarData = ensAvatarSelection;
}
const [avatarData, setAvatarData] =
React.useState(initialAvatarData);
const setClientAvatarFromSelection = React.useCallback(
(selection: UserAvatarSelection) => {
if (selection.needsUpload) {
const newAvatarData = {
...selection,
clientAvatar: {
type: 'image',
uri: selection.mediaSelection.uri,
},
};
setAvatarData(newAvatarData);
setCachedSelections(oldUserSelections => ({
...oldUserSelections,
avatarData: newAvatarData,
}));
} else if (selection.updateUserAvatarRequest.type !== 'remove') {
const clientRequest = selection.updateUserAvatarRequest;
invariant(
clientRequest.type !== 'image',
'image avatars need to be uploaded',
);
const newAvatarData = {
...selection,
clientAvatar: clientRequest,
};
setAvatarData(newAvatarData);
setCachedSelections(oldUserSelections => ({
...oldUserSelections,
avatarData: newAvatarData,
}));
} else {
setAvatarData(undefined);
setCachedSelections(oldUserSelections => ({
...oldUserSelections,
avatarData: undefined,
}));
}
},
[setCachedSelections],
);
- const [registrationInProgress, setRegistrationInProgress] =
- React.useState(false);
-
+ const currentRouteName = useCurrentLeafRouteName();
+ const avatarSelectionHappening =
+ currentRouteName === AvatarSelectionRouteName ||
+ currentRouteName === EmojiAvatarSelectionRouteName ||
+ currentRouteName === RegistrationUserAvatarCameraModalRouteName;
React.useEffect(() => {
- if (registrationInProgress) {
+ if (!avatarSelectionHappening) {
return undefined;
}
setRegistrationMode({
registrationMode: 'on',
successCallback: setClientAvatarFromSelection,
});
return () => {
setRegistrationMode({ registrationMode: 'off' });
};
}, [
- registrationInProgress,
+ avatarSelectionHappening,
setRegistrationMode,
setClientAvatarFromSelection,
]);
+ const { navigate } = props.navigation;
const onProceed = React.useCallback(async () => {
- setRegistrationInProgress(true);
- try {
- await register({
- ...userSelections,
- avatarData,
- });
- } finally {
- setRegistrationInProgress(false);
- }
- }, [register, userSelections, avatarData]);
+ const newUserSelections = {
+ ...userSelections,
+ avatarData,
+ };
+ navigate<'RegistrationTerms'>({
+ name: RegistrationTermsRouteName,
+ params: { userSelections: newUserSelections },
+ });
+ }, [userSelections, avatarData, navigate]);
const clientAvatar = avatarData?.clientAvatar;
const userInfoOverride = React.useMemo(
() => ({
username: usernameOrETHAddress,
avatar: clientAvatar,
}),
[usernameOrETHAddress, clientAvatar],
);
const styles = useStyles(unboundStyles);
return (
Pick an avatar
-
+
);
}
const unboundStyles = {
scrollViewContentContainer: {
paddingHorizontal: 0,
},
header: {
fontSize: 24,
color: 'panelForegroundLabel',
paddingBottom: 16,
paddingHorizontal: 16,
},
stagedAvatarSection: {
marginTop: 16,
backgroundColor: 'panelForeground',
paddingVertical: 24,
alignItems: 'center',
},
editUserAvatar: {
alignItems: 'center',
justifyContent: 'center',
},
};
export default AvatarSelection;
diff --git a/native/account/registration/registration-terms.react.js b/native/account/registration/registration-terms.react.js
index 6889b9716..ed1dedb06 100644
--- a/native/account/registration/registration-terms.react.js
+++ b/native/account/registration/registration-terms.react.js
@@ -1,61 +1,77 @@
// @flow
+import invariant from 'invariant';
import * as React from 'react';
import { Text } from 'react-native';
import RegistrationButtonContainer from './registration-button-container.react.js';
import RegistrationButton from './registration-button.react.js';
import RegistrationContainer from './registration-container.react.js';
import RegistrationContentContainer from './registration-content-container.react.js';
+import { RegistrationContext } from './registration-context.js';
import type { RegistrationNavigationProp } from './registration-navigator.react.js';
import type {
CoolOrNerdMode,
AccountSelection,
AvatarData,
} from './registration-types.js';
import type { NavigationRoute } from '../../navigation/route-names.js';
import { useStyles } from '../../themes/colors.js';
export type RegistrationTermsParams = {
+userSelections: {
+coolOrNerdMode: CoolOrNerdMode,
+keyserverUsername: string,
+accountSelection: AccountSelection,
+avatarData: ?AvatarData,
},
};
type Props = {
+navigation: RegistrationNavigationProp<'RegistrationTerms'>,
+route: NavigationRoute<'RegistrationTerms'>,
};
-// eslint-disable-next-line no-unused-vars
function RegistrationTerms(props: Props): React.Node {
- const onProceed = React.useCallback(() => {}, []);
+ const registrationContext = React.useContext(RegistrationContext);
+ invariant(registrationContext, 'registrationContext should be set');
+ const { register } = registrationContext;
+
+ const [registrationInProgress, setRegistrationInProgress] =
+ React.useState(false);
+
+ const { userSelections } = props.route.params;
+ const onProceed = React.useCallback(async () => {
+ setRegistrationInProgress(true);
+ try {
+ await register(userSelections);
+ } finally {
+ setRegistrationInProgress(false);
+ }
+ }, [register, userSelections]);
const styles = useStyles(unboundStyles);
return (
Finish registration
);
}
const unboundStyles = {
header: {
fontSize: 24,
color: 'panelForegroundLabel',
paddingBottom: 16,
},
};
export default RegistrationTerms;
diff --git a/native/navigation/nav-selectors.js b/native/navigation/nav-selectors.js
index 530c7a2f7..271f57da6 100644
--- a/native/navigation/nav-selectors.js
+++ b/native/navigation/nav-selectors.js
@@ -1,333 +1,345 @@
// @flow
import type { PossiblyStaleNavigationState } from '@react-navigation/native';
import _memoize from 'lodash/memoize.js';
import * as React from 'react';
import { createSelector } from 'reselect';
import { nonThreadCalendarFiltersSelector } from 'lib/selectors/calendar-filter-selectors.js';
import { currentCalendarQuery } from 'lib/selectors/nav-selectors.js';
import type { CalendarQuery } from 'lib/types/entry-types.js';
import type { CalendarFilter } from 'lib/types/filter-types.js';
import type { NavContextType } from './navigation-context.js';
import { NavContext } from './navigation-context.js';
import {
getStateFromNavigatorRoute,
getThreadIDFromRoute,
+ currentLeafRoute,
} from './navigation-utils.js';
import {
AppRouteName,
TabNavigatorRouteName,
MessageListRouteName,
ChatRouteName,
CalendarRouteName,
ThreadPickerModalRouteName,
ActionResultModalRouteName,
accountModals,
scrollBlockingModals,
chatRootModals,
threadRoutes,
CommunityDrawerNavigatorRouteName,
} from './route-names.js';
import { useSelector } from '../redux/redux-utils.js';
import type { NavPlusRedux } from '../types/selector-types.js';
import type { GlobalTheme } from '../types/themes.js';
const baseCreateIsForegroundSelector = (routeName: string) =>
createSelector(
(context: ?NavContextType) => context && context.state,
(navigationState: ?PossiblyStaleNavigationState) => {
if (!navigationState) {
return false;
}
return navigationState.routes[navigationState.index].name === routeName;
},
);
const createIsForegroundSelector: (
routeName: string,
) => (context: ?NavContextType) => boolean = _memoize(
baseCreateIsForegroundSelector,
);
function useIsAppLoggedIn(): boolean {
const navContext = React.useContext(NavContext);
return React.useMemo(() => {
if (!navContext) {
return false;
}
const { state } = navContext;
return !accountModals.includes(state.routes[state.index].name);
}, [navContext]);
}
const baseCreateActiveTabSelector = (routeName: string) =>
createSelector(
(context: ?NavContextType) => context && context.state,
(navigationState: ?PossiblyStaleNavigationState) => {
if (!navigationState) {
return false;
}
const currentRootSubroute = navigationState.routes[navigationState.index];
if (currentRootSubroute.name !== AppRouteName) {
return false;
}
const appState = getStateFromNavigatorRoute(currentRootSubroute);
const [firstAppSubroute] = appState.routes;
if (firstAppSubroute.name !== CommunityDrawerNavigatorRouteName) {
return false;
}
const communityDrawerState = getStateFromNavigatorRoute(firstAppSubroute);
const [firstCommunityDrawerSubroute] = communityDrawerState.routes;
if (firstCommunityDrawerSubroute.name !== TabNavigatorRouteName) {
return false;
}
const tabState = getStateFromNavigatorRoute(firstCommunityDrawerSubroute);
return tabState.routes[tabState.index].name === routeName;
},
);
const createActiveTabSelector: (
routeName: string,
) => (context: ?NavContextType) => boolean = _memoize(
baseCreateActiveTabSelector,
);
const scrollBlockingModalsClosedSelector: (
context: ?NavContextType,
) => boolean = createSelector(
(context: ?NavContextType) => context && context.state,
(navigationState: ?PossiblyStaleNavigationState) => {
if (!navigationState) {
return false;
}
const currentRootSubroute = navigationState.routes[navigationState.index];
if (currentRootSubroute.name !== AppRouteName) {
return true;
}
const appState = getStateFromNavigatorRoute(currentRootSubroute);
for (let i = appState.index; i >= 0; i--) {
const route = appState.routes[i];
if (scrollBlockingModals.includes(route.name)) {
return false;
}
}
return true;
},
);
function selectBackgroundIsDark(
navigationState: ?PossiblyStaleNavigationState,
theme: ?GlobalTheme,
): boolean {
if (!navigationState) {
return false;
}
const currentRootSubroute = navigationState.routes[navigationState.index];
if (currentRootSubroute.name !== AppRouteName) {
// Very bright... we'll call it non-dark. Doesn't matter right now since
// we only use this selector for determining ActionResultModal appearance
return false;
}
const appState = getStateFromNavigatorRoute(currentRootSubroute);
let appIndex = appState.index;
let currentAppSubroute = appState.routes[appIndex];
while (currentAppSubroute.name === ActionResultModalRouteName) {
currentAppSubroute = appState.routes[--appIndex];
}
if (scrollBlockingModals.includes(currentAppSubroute.name)) {
// All the scroll-blocking chat modals have a dark background
return true;
}
return theme === 'dark';
}
function activeThread(
navigationState: ?PossiblyStaleNavigationState,
validRouteNames: $ReadOnlyArray,
): ?string {
if (!navigationState) {
return null;
}
let rootIndex = navigationState.index;
let currentRootSubroute = navigationState.routes[rootIndex];
while (currentRootSubroute.name !== AppRouteName) {
if (!chatRootModals.includes(currentRootSubroute.name)) {
return null;
}
if (rootIndex === 0) {
return null;
}
currentRootSubroute = navigationState.routes[--rootIndex];
}
const appState = getStateFromNavigatorRoute(currentRootSubroute);
const [firstAppSubroute] = appState.routes;
if (firstAppSubroute.name !== CommunityDrawerNavigatorRouteName) {
return null;
}
const communityDrawerState = getStateFromNavigatorRoute(firstAppSubroute);
const [firstCommunityDrawerSubroute] = communityDrawerState.routes;
if (firstCommunityDrawerSubroute.name !== TabNavigatorRouteName) {
return null;
}
const tabState = getStateFromNavigatorRoute(firstCommunityDrawerSubroute);
const currentTabSubroute = tabState.routes[tabState.index];
if (currentTabSubroute.name !== ChatRouteName) {
return null;
}
const chatState = getStateFromNavigatorRoute(currentTabSubroute);
const currentChatSubroute = chatState.routes[chatState.index];
return getThreadIDFromRoute(currentChatSubroute, validRouteNames);
}
const activeThreadSelector: (context: ?NavContextType) => ?string =
createSelector(
(context: ?NavContextType) => context && context.state,
(navigationState: ?PossiblyStaleNavigationState): ?string =>
activeThread(navigationState, threadRoutes),
);
const messageListRouteNames = [MessageListRouteName];
const activeMessageListSelector: (context: ?NavContextType) => ?string =
createSelector(
(context: ?NavContextType) => context && context.state,
(navigationState: ?PossiblyStaleNavigationState): ?string =>
activeThread(navigationState, messageListRouteNames),
);
function useActiveThread(): ?string {
const navContext = React.useContext(NavContext);
return React.useMemo(() => {
if (!navContext) {
return null;
}
const { state } = navContext;
return activeThread(state, threadRoutes);
}, [navContext]);
}
function useActiveMessageList(): ?string {
const navContext = React.useContext(NavContext);
return React.useMemo(() => {
if (!navContext) {
return null;
}
const { state } = navContext;
return activeThread(state, messageListRouteNames);
}, [navContext]);
}
const calendarTabActiveSelector = createActiveTabSelector(CalendarRouteName);
const threadPickerActiveSelector = createIsForegroundSelector(
ThreadPickerModalRouteName,
);
const calendarActiveSelector: (context: ?NavContextType) => boolean =
createSelector(
calendarTabActiveSelector,
threadPickerActiveSelector,
(calendarTabActive: boolean, threadPickerActive: boolean) =>
calendarTabActive || threadPickerActive,
);
const nativeCalendarQuery: (input: NavPlusRedux) => () => CalendarQuery =
createSelector(
(input: NavPlusRedux) => currentCalendarQuery(input.redux),
(input: NavPlusRedux) => calendarActiveSelector(input.navContext),
(
calendarQuery: (calendarActive: boolean) => CalendarQuery,
calendarActive: boolean,
) =>
() =>
calendarQuery(calendarActive),
);
const nonThreadCalendarQuery: (input: NavPlusRedux) => () => CalendarQuery =
createSelector(
nativeCalendarQuery,
(input: NavPlusRedux) => nonThreadCalendarFiltersSelector(input.redux),
(
calendarQuery: () => CalendarQuery,
filters: $ReadOnlyArray,
) => {
return (): CalendarQuery => {
const query = calendarQuery();
return {
startDate: query.startDate,
endDate: query.endDate,
filters,
};
};
},
);
function useCalendarQuery(): () => CalendarQuery {
const navContext = React.useContext(NavContext);
return useSelector(state =>
nonThreadCalendarQuery({
redux: state,
navContext,
}),
);
}
const drawerSwipeEnabledSelector: (context: ?NavContextType) => boolean =
createSelector(
(context: ?NavContextType) => context && context.state,
(navigationState: ?PossiblyStaleNavigationState) => {
if (!navigationState) {
return true;
}
// First, we recurse into the navigation state until we find the tab route
// The tab route should always be accessible by recursing through the
// first routes of each subsequent nested navigation state
const [firstRootSubroute] = navigationState.routes;
if (firstRootSubroute.name !== AppRouteName) {
return true;
}
const appState = getStateFromNavigatorRoute(firstRootSubroute);
const [firstAppSubroute] = appState.routes;
if (firstAppSubroute.name !== CommunityDrawerNavigatorRouteName) {
return true;
}
const communityDrawerState = getStateFromNavigatorRoute(firstAppSubroute);
const [firstCommunityDrawerSubroute] = communityDrawerState.routes;
if (firstCommunityDrawerSubroute.name !== TabNavigatorRouteName) {
return true;
}
const tabState = getStateFromNavigatorRoute(firstCommunityDrawerSubroute);
// Once we have the tab state, we want to figure out if we currently have
// an active StackNavigator
const currentTabSubroute = tabState.routes[tabState.index];
if (!currentTabSubroute.state) {
return true;
}
const currentTabSubrouteState =
getStateFromNavigatorRoute(currentTabSubroute);
if (currentTabSubrouteState.type !== 'stack') {
return true;
}
// Finally, we want to disable the swipe gesture if there is a stack with
// more than one subroute, since then the stack will have its own swipe
// gesture that will conflict with the drawer's
return currentTabSubrouteState.routes.length < 2;
},
);
+function useCurrentLeafRouteName(): ?string {
+ const navContext = React.useContext(NavContext);
+ return React.useMemo(() => {
+ if (!navContext) {
+ return undefined;
+ }
+ return currentLeafRoute(navContext.state).name;
+ }, [navContext]);
+}
+
export {
createIsForegroundSelector,
useIsAppLoggedIn,
createActiveTabSelector,
scrollBlockingModalsClosedSelector,
selectBackgroundIsDark,
activeThreadSelector,
activeMessageListSelector,
useActiveThread,
useActiveMessageList,
calendarActiveSelector,
nativeCalendarQuery,
nonThreadCalendarQuery,
useCalendarQuery,
drawerSwipeEnabledSelector,
+ useCurrentLeafRouteName,
};