Unverified Commit 91410fe6 authored by Hanwen Cheng's avatar Hanwen Cheng Committed by GitHub
Browse files

refactor: Imporve navigation (#562)

* fix: upgrade react navigation v5

* update project settings in ios

* improve navigation by upgrading to react navigation v5

* make compiler happy

* unlink react-native-secure-storage

* make detox and react-navigation happy again

* make e2e test happy

* use safe area context app wide

* delete stray comment

* fix screen heading styles

* fix pin backup navigation

* revert change to rust target

* fix ui overlap on android

* remove bounce in ios scroll view

* lint fix
parent ea3e4f4e
Pipeline #83701 failed with stages
in 3 minutes and 48 seconds
......@@ -53,6 +53,7 @@ module.exports = {
"@typescript-eslint/no-use-before-define": ["error", { "variables": false }], // enable defining variables after react component;
"@typescript-eslint/no-non-null-assertion": 0,
'@typescript-eslint/camelcase': 0,
'@typescript-eslint/ban-ts-ignore': 0,
"no-void": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
......
......@@ -202,7 +202,6 @@ android {
}
dependencies {
implementation project(':react-native-secure-storage')
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.facebook.react:react-native:+"
......
rootProject.name = 'Parity Signer'
include ':react-native-secure-storage'
project(':react-native-secure-storage').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-secure-storage/android')
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'
This diff is collapsed.
......@@ -233,7 +233,7 @@ PODS:
- ReactCommon/jscallinvoker (= 0.61.5)
- RNCMaskedView (0.1.6):
- React
- RNGestureHandler (1.4.1):
- RNGestureHandler (1.6.0):
- React
- RNScreens (2.0.0-alpha.32):
- React
......@@ -392,7 +392,7 @@ SPEC CHECKSUMS:
React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad
ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
RNCMaskedView: a88953beefbd347a29072d9eba90e42945fe291e
RNGestureHandler: 4cb47a93019c1a201df2644413a0a1569a51c8aa
RNGestureHandler: dde546180bf24af0b5f737c8ad04b6f3fa51609a
RNScreens: a55364dc1833101f836cee70975ce1fea615a12f
RNSecureStorage: af0099b5b9f3c41692848b7778fe98b4a6b0358d
RNSVG: 9f7446f2e8fb1a5dc66a4aff58c33d367291d34c
......
......@@ -16,259 +16,99 @@
import '../shim';
import 'utils/iconLoader';
import * as React from 'react';
import { StatusBar, View, YellowBox } from 'react-native';
import {
createAppContainer,
createSwitchNavigator,
NavigationInjectedProps,
NavigationScreenProp,
withNavigation
} from 'react-navigation';
import {
CardStyleInterpolators,
createStackNavigator,
HeaderBackButton
} from 'react-navigation-stack';
import { StackNavigationOptions } from 'react-navigation-stack/lib/typescript/src/vendor/types';
import { StatusBar, StyleSheet, View, YellowBox } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { Provider as UnstatedProvider } from 'unstated';
import { MenuProvider } from 'react-native-popup-menu';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import {
AppNavigator,
TocAndPrivacyPolicyNavigator,
ScreenStack
} from './screens';
import Background from 'components/Background';
import colors from 'styles/colors';
import HeaderLeftHome from 'components/HeaderLeftHome';
import SecurityHeader from 'components/SecurityHeader';
import '../ReactotronConfig';
import About from 'screens/About';
import LegacyAccountBackup from 'screens/LegacyAccountBackup';
import AccountDetails from 'screens/AccountDetails';
import AccountEdit from 'screens/AccountEdit';
import AccountNetworkChooser from 'screens/AccountNetworkChooser';
import AccountNew from 'screens/AccountNew';
import AccountPin from 'screens/AccountPin';
import { AccountUnlock, AccountUnlockAndSign } from 'screens/AccountUnlock';
import LegacyAccountList from 'screens/LegacyAccountList';
import Loading from 'screens/Loading';
import IdentityBackup from 'screens/IdentityBackup';
import IdentityManagement from 'screens/IdentityManagement';
import IdentityNew from 'screens/IdentityNew';
import IdentityPin from 'screens/IdentityPin';
import MessageDetails from 'screens/MessageDetails';
import PathDerivation from 'screens/PathDerivation';
import PathDetails from 'screens/PathDetails';
import PathsList from 'screens/PathsList';
import PathManagement from 'screens/PathManagement';
import PrivacyPolicy from 'screens/PrivacyPolicy';
import QrScanner from 'screens/QrScanner';
import Security from 'screens/Security';
import SignedMessage from 'screens/SignedMessage';
import SignedTx from 'screens/SignedTx';
import TermsAndConditions from 'screens/TermsAndConditions';
import TxDetails from 'screens/TxDetails';
import LegacyNetworkChooser from 'screens/LegacyNetworkChooser';
import testIDs from 'e2e/testIDs';
import { AppProps, getLaunchArgs } from 'e2e/injections';
import { GlobalState, GlobalStateContext } from 'stores/globalStateContext';
import { loadToCAndPPConfirmation } from 'utils/db';
import { migrateAccounts, migrateIdentity } from 'utils/migrationUtils';
export default class App extends React.Component<AppProps> {
constructor(props: AppProps) {
super(props);
getLaunchArgs(props);
if (__DEV__) {
YellowBox.ignoreWarnings([
'Warning: componentWillReceiveProps',
'Warning: componentWillMount',
'Warning: componentWillUpdate',
'Warning: Sending `onAnimatedValueUpdate`'
]);
}
export default function App(props: AppProps): React.ReactElement {
getLaunchArgs(props);
if (__DEV__) {
YellowBox.ignoreWarnings([
'Warning: componentWillReceiveProps',
'Warning: componentWillMount',
'Warning: componentWillUpdate',
'Sending `onAnimatedValueUpdate`',
'Non-serializable values were found in the navigation state' // https://reactnavigation.org/docs/troubleshooting/#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state
]);
}
render(): React.ReactNode {
return (
const [policyConfirmed, setPolicyConfirmed] = React.useState<boolean>(false);
const [dataLoaded, setDataLoaded] = React.useState<boolean>(false);
React.useEffect(() => {
const loadPolicyConfirmationAndMigrateData = async (): Promise<void> => {
const tocPP = await loadToCAndPPConfirmation();
setPolicyConfirmed(tocPP);
if (!tocPP) {
await migrateAccounts();
await migrateIdentity();
}
};
setDataLoaded(true);
loadPolicyConfirmationAndMigrateData();
}, []);
const globalContext: GlobalState = {
dataLoaded,
policyConfirmed,
setDataLoaded,
setPolicyConfirmed
};
const renderStacks = (): React.ReactElement => {
if (dataLoaded) {
return policyConfirmed ? (
<AppNavigator />
) : (
<TocAndPrivacyPolicyNavigator />
);
} else {
return (
<ScreenStack.Navigator>
<ScreenStack.Screen name="Empty">
{(navigationProps: any): React.ReactElement => (
<View style={emptyScreenStyles} {...navigationProps} />
)}
</ScreenStack.Screen>
</ScreenStack.Navigator>
);
}
};
return (
<SafeAreaProvider>
<UnstatedProvider>
<MenuProvider backHandler={true}>
<StatusBar barStyle="light-content" backgroundColor={colors.bg} />
<Background />
<ScreensContainer />
<GlobalStateContext.Provider value={globalContext}>
<NavigationContainer>{renderStacks()}</NavigationContainer>
</GlobalStateContext.Provider>
</MenuProvider>
</UnstatedProvider>
);
}
</SafeAreaProvider>
);
}
const globalStackNavigationOptions = ({
navigation
}: {
navigation: NavigationScreenProp<{ index: number }, {}>;
}): StackNavigationOptions => ({
//more transition animations refer to: https://reactnavigation.org/docs/en/stack-navigator.html#animations
cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
headerBackTitleStyle: {
color: colors.bg_text_sec
},
headerBackTitleVisible: false,
headerLeft: (): React.ReactElement =>
navigation.isFirstRouteInParent() ? (
<HeaderLeftHome />
) : (
<HeaderLeftWithBack />
),
headerRight: (): React.ReactElement => <SecurityHeader />,
headerStyle: {
const emptyScreenStyles = StyleSheet.create({
body: {
backgroundColor: colors.bg,
borderBottomColor: colors.bg,
borderBottomWidth: 0,
elevation: 0,
height: 60,
shadowColor: 'transparent'
},
headerTintColor: colors.bg_text_sec,
headerTitle: (): React.ReactNode => null
});
const HeaderLeftWithBack = withNavigation(
class HeaderBackButtonComponent extends React.PureComponent<
NavigationInjectedProps
> {
render(): React.ReactNode {
const { navigation } = this.props;
return (
<View
style={{ flexDirection: 'row' }}
testID={testIDs.Header.headerBackButton}
>
<HeaderBackButton
{...this.props}
labelStyle={
globalStackNavigationOptions({ navigation }).headerBackTitleStyle!
}
labelVisible={false}
tintColor={colors.bg_text}
onPress={(): boolean => navigation.goBack()}
/>
</View>
);
}
flex: 1,
flexDirection: 'column',
padding: 20
}
);
/* eslint-disable sort-keys */
const tocAndPrivacyPolicyScreens = {
TermsAndConditions: {
navigationOptions: {
headerRight: (): React.ReactNode => null
},
screen: TermsAndConditions
},
PrivacyPolicy: {
navigationOptions: {
headerRight: (): React.ReactNode => null
},
screen: PrivacyPolicy
}
};
const Screens = createSwitchNavigator(
{
Loading: {
screen: Loading
},
TocAndPrivacyPolicy: createStackNavigator(tocAndPrivacyPolicyScreens, {
defaultNavigationOptions: globalStackNavigationOptions
}),
Welcome: {
screen: createStackNavigator(
{
AccountNetworkChooser: {
screen: AccountNetworkChooser
},
AccountPin: {
screen: AccountPin
},
AccountUnlock: {
screen: AccountUnlock
},
About: {
screen: About
},
AccountDetails: {
screen: AccountDetails
},
AccountEdit: {
screen: AccountEdit
},
AccountNew: {
screen: AccountNew
},
AccountUnlockAndSign: {
screen: AccountUnlockAndSign
},
LegacyAccountBackup: {
screen: LegacyAccountBackup
},
LegacyAccountList: {
screen: LegacyAccountList
},
LegacyNetworkChooser: {
screen: LegacyNetworkChooser
},
IdentityBackup: {
screen: IdentityBackup
},
IdentityManagement: {
screen: IdentityManagement
},
IdentityNew: {
screen: IdentityNew
},
IdentityPin: {
screen: IdentityPin
},
MessageDetails: {
screen: MessageDetails
},
PathDerivation: {
screen: PathDerivation
},
PathDetails: {
screen: PathDetails
},
PathsList: {
screen: PathsList
},
PathManagement: {
screen: PathManagement
},
QrScanner: {
screen: QrScanner
},
SignedMessage: {
screen: SignedMessage
},
SignedTx: {
screen: SignedTx
},
TxDetails: {
screen: TxDetails
},
Security: {
navigationOptions: {
headerRight: (): React.ReactNode => null
},
screen: Security
},
...tocAndPrivacyPolicyScreens
},
{
defaultNavigationOptions: globalStackNavigationOptions
}
)
}
},
{
defaultNavigationOptions: globalStackNavigationOptions
}
);
const ScreensContainer = createAppContainer(Screens);
});
......@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//Deprecated Component
import React from 'react';
import { StyleSheet, View } from 'react-native';
......
......@@ -20,15 +20,14 @@ import {
LayoutChangeEvent,
ScrollView,
StyleSheet,
View,
ViewStyle
} from 'react-native';
import { SafeAreaViewContainer } from 'components/SafeAreaContainer';
import colors from 'styles/colors';
export default class CustomScrollview extends React.PureComponent<
export default class CustomScrollView extends React.PureComponent<
{
containerStyle?: ViewStyle;
contentContainerStyle: ViewStyle;
},
{
......@@ -58,8 +57,9 @@ export default class CustomScrollview extends React.PureComponent<
: 1;
return (
<View style={this.props.containerStyle}>
<SafeAreaViewContainer>
<ScrollView
bounces={false}
showsVerticalScrollIndicator={false}
onContentSizeChange={(width: number, height: number): void => {
this.setState({ wholeHeight: height });
......@@ -99,7 +99,7 @@ export default class CustomScrollview extends React.PureComponent<
}
]}
/>
</View>
</SafeAreaViewContainer>
);
}
}
......
......@@ -27,15 +27,12 @@ export default class HeaderLeftHome extends React.PureComponent<{
render(): React.ReactElement {
return (
<View
style={[
{
alignItems: 'center',
flexDirection: 'row',
marginTop: -10,
paddingLeft: 12
},
this.props.style
]}
style={{
alignItems: 'center',
flexDirection: 'row',
height: 48,
paddingLeft: 12
}}
>
<Image source={iconLogo} style={styles.logo} />
<Text style={[styles.headerTextLeft, styles.t_bold]}>parity</Text>
......
......@@ -14,14 +14,17 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { StackNavigationProp } from '@react-navigation/stack';
import React, { useState } from 'react';
import { FlatList, StyleSheet, View } from 'react-native';
import { withNavigation, ScrollView, NavigationParams } from 'react-navigation';
import { ScrollView, StyleSheet, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import ButtonIcon from './ButtonIcon';
import Separator from './Separator';
import TransparentBackground from './TransparentBackground';
import { RootStackParamList } from 'types/routes';
import AccountsStore from 'stores/AccountsStore';
import testIDs from 'e2e/testIDs';
import colors from 'styles/colors';
import fontStyles from 'styles/fontStyles';
......@@ -30,36 +33,52 @@ import { getIdentityName } from 'utils/identitiesUtils';
import {
navigateToLegacyAccountList,
resetNavigationTo,
resetNavigationWithNetworkChooser
resetNavigationWithNetworkChooser,
unlockSeedPhrase
} from 'utils/navigationHelpers';
import { NavigationAccountProps } from 'types/props';
import { Identity } from 'types/identityTypes';
function IdentitiesSwitch({
navigation,
accounts
}: NavigationAccountProps<{ isSwitchOpen?: boolean }>): React.ReactElement {
const defaultVisible = navigation.getParam('isSwitchOpen', false);
const [visible, setVisible] = useState(defaultVisible);
}: {
accounts: AccountsStore;
}): React.ReactElement {
const navigation: StackNavigationProp<RootStackParamList> = useNavigation();
const [visible, setVisible] = useState(false);
const { currentIdentity, identities } = accounts.state;
// useEffect(() => {
// const firstLogin: boolean = identities.length === 0;
// if (currentIdentity === null && !firstLogin) {
// setVisible(true);
// }
// }, [currentIdentity, identities]);
const closeModalAndNavigate = (
screenName: string,
params?: NavigationParams
const closeModalAndNavigate = <RouteName extends keyof RootStackParamList>(
screenName: RouteName,
params?: RootStackParamList[RouteName]
): void => {
setVisible(false);
// @ts-ignore
navigation.navigate(screenName, params);
};
const onIdentitySelectedAndNavigate = async (
const onIdentitySelectedAndNavigate = async <
RouteName extends keyof RootStackParamList
>(
identity: Identity,
screenName: string,
params?: NavigationParams
screenName: RouteName,
params?: RootStackParamList[RouteName]
): Promise<void> => {
await accounts.selectIdentity(identity);
setVisible(false);
if (screenName === 'AccountNetworkChooser') {
resetNavigationTo(navigation, screenName, params);
} else if (screenName === 'IdentityBackup') {
const seedPhrase = await unlockSeedPhrase(navigation);
resetNavigationWithNetworkChooser(navigation, screenName, {
isNew: false,
seedPhrase
});
} else {
resetNavigationWithNetworkChooser(navigation, screenName, params);
}
......@@ -90,9 +109,7 @@ function IdentitiesSwitch({
<ButtonIcon
title="Show Recovery Phrase"
onPress={(): Promise<void> =>
onIdentitySelectedAndNavigate(identity, 'IdentityBackup', {
isNew: false
})
onIdentitySelectedAndNavigate(identity, 'IdentityBackup')
}
iconBgStyle={styles.i_arrowBg}
iconType="antdesign"
......@@ -146,11 +163,7 @@ function IdentitiesSwitch({
/>
<ButtonIcon
title="Terms and Conditions"
onPress={(): void =>
closeModalAndNavigate('TermsAndConditions', {
disableButtons: true
})
}
onPress={(): void => closeModalAndNavigate('TermsAndConditions')}
iconBgStyle={styles.i_arrowBg}
iconType="antdesign"
iconName="arrowright"
......@@ -172,12 +185,9 @@ function IdentitiesSwitch({
);
};
const renderNonSelectedIdentity = ({
item
}: {
item: Identity;
}): React.ReactElement => {
const identity = item;
const renderNonSelectedIdentity = (
identity: Identity
): React.ReactElement => {