diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6e87a00 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/App.js b/App.js index 52f5fa9..aa0aeef 100644 --- a/App.js +++ b/App.js @@ -1,4 +1,3 @@ -import { StatusBar } from 'expo-status-bar'; import React, { useEffect, useState } from 'react'; import { ActivityIndicator, FlatList, Text, View, StyleSheet } from 'react-native'; @@ -10,14 +9,14 @@ export default function App() { const [data, setData] = useState([]); - function fromHex(hex,str){ - try{ - str = decodeURIComponent(hex.replace(/(..)/g,'%$1')) + function fromHex(hex, str) { + try { + str = decodeURIComponent(hex.replace(/(..)/g, '%$1')) str = JSON.parse(str) setData(str) setLoading(false) } - catch(e){ + catch (e) { return //str = hex //console.log('invalid hex input: ' + hex) @@ -34,12 +33,12 @@ export default function App() { body: JSON.stringify({ jsonrpc: '2.0', method: 'f_transaction_json', - params: {hash: transactions[tx].hash} + params: { hash: transactions[tx].hash } }) }) - .then((response) => response.json()) - .then((json) => fromHex(json.result.tx.extra.substring(66))) - .catch((error) => console.error(error)) + .then((response) => response.json()) + .then((json) => fromHex(json.result.tx.extra.substring(66))) + .catch((error) => console.error(error)) } } @@ -49,7 +48,7 @@ export default function App() { body: JSON.stringify({ jsonrpc: '2.0', method: 'f_block_json', - params: {hash: blockHash} + params: { hash: blockHash } }) }) .then((response) => response.json()) @@ -59,7 +58,7 @@ export default function App() { const getBlockHashes = (blockCount) => { let i; - for (i = 0; i < 100; i++) { + for (i = 0; i < 100; i++) { fetch(nodeURL, { method: 'POST', body: JSON.stringify({ @@ -86,12 +85,12 @@ export default function App() { .then((response) => response.json()) .then((json) => getBlockHashes(json.result.count)) .catch((error) => console.error(error)) - //.finally(() => setLoading(false)); + //.finally(() => setLoading(false)); }, []); return ( - {isLoading ? : ( + {isLoading ? : ( {JSON.stringify(data)} )} diff --git a/android/app/build.gradle b/android/app/build.gradle index 169c73b..ffdcd4b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -123,7 +123,7 @@ def enableHermes = project.ext.react.get("enableHermes", false); android { compileSdkVersion rootProject.ext.compileSdkVersion - compileOptions { + compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } diff --git a/android/build.gradle b/android/build.gradle index 3338dcb..0b3a74a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -4,7 +4,7 @@ def REACT_NATIVE_VERSION = new File(['node', '--print',"JSON.parse(require('fs') buildscript { ext { buildToolsVersion = "28.0.3" - minSdkVersion = 20 + minSdkVersion = 21 compileSdkVersion = 31 targetSdkVersion = 33 supportLibVersion = "28.0.0" diff --git a/package.json b/package.json index 9ae2277..7c2783a 100644 --- a/package.json +++ b/package.json @@ -145,4 +145,4 @@ "vm": "vm-browserify", "tls": false } -} +} \ No newline at end of file diff --git a/src/App.js b/src/App.js index 0935a0b..810549e 100644 --- a/src/App.js +++ b/src/App.js @@ -2,480 +2,471 @@ // // Please see the included LICENSE file for more information. -import Ionicons from 'react-native-vector-icons/Ionicons'; -import Entypo from 'react-native-vector-icons/Entypo'; import CustomIcon from './CustomIcon.js' -import SimpleLineIcons from 'react-native-vector-icons/SimpleLineIcons'; import React from 'react'; import { View, StatusBar } from 'react-native'; import { - createStackNavigator, createAppContainer, createBottomTabNavigator, - createSwitchNavigator + createStackNavigator, createAppContainer, createBottomTabNavigator, + createSwitchNavigator } from 'react-navigation'; - -import Config from './Config'; - import { Themes } from './Themes'; import { Globals } from './Globals'; -import { MainScreen } from './MainScreen'; import { SplashScreen } from './SplashScreen'; import { DisclaimerScreen } from './DisclaimerScreen'; import { loadPreferencesFromDatabase, openDB } from './Database'; import { ChatScreen, ModifyPayeeScreen, RecipientsScreen, CallScreen } from './Recipients'; import { GroupChatScreen, ModifyGroupScreen, GroupsScreen, NewGroupScreen } from './Groups'; import { WalletOptionScreen, CreateWalletScreen } from './CreateScreen'; -import { TransactionsScreen, TransactionDetailsScreen } from './TransactionsScreen'; + +import { MainScreen } from './MainScreen' +import { TransactionsScreen, TransactionDetailsScreen } from './TransactionsScreen' +import { SendTransactionScreen, QrScannerScreen, TransferScreen, ChoosePayeeScreen, NewPayeeScreen, ConfirmScreen } from './TransferScreen.js' import { - SetPinScreen, RequestPinScreen, ForgotPinScreen, RequestHardwareAuthScreen, - ChooseAuthMethodScreen, + SetPinScreen, RequestPinScreen, ForgotPinScreen, RequestHardwareAuthScreen, + ChooseAuthMethodScreen, } from './Authenticate'; import { - SettingsScreen, SwapCurrencyScreen, ExportKeysScreen, LoggingScreen, FaqScreen, - DisableDozeScreen, SwapNodeScreen, OptimizeScreen, SwapLanguageScreen, SwapAPIScreen + SettingsScreen, SwapCurrencyScreen, ExportKeysScreen, LoggingScreen, FaqScreen, + DisableDozeScreen, SwapNodeScreen, OptimizeScreen, SwapLanguageScreen, SwapAPIScreen } from './SettingsScreen'; import { - PickMonthScreen, PickBlockHeightScreen, PickExactBlockHeightScreen, + PickMonthScreen, PickBlockHeightScreen, PickExactBlockHeightScreen, } from './ScanHeightScreen'; -import { - TransferScreen, ChoosePayeeScreen, NewPayeeScreen, ConfirmScreen, - QrScannerScreen, SendTransactionScreen, -} from './TransferScreen'; + import { - ImportWalletScreen, ImportKeysOrSeedScreen, ImportSeedScreen, - ImportKeysScreen, + ImportWalletScreen, ImportKeysOrSeedScreen, ImportSeedScreen, + ImportKeysScreen, } from './ImportScreen'; /* Transactions screen and more info on transactions */ const TransactionNavigator = createStackNavigator( - { - Transactions: TransactionsScreen, - TransactionDetails: TransactionDetailsScreen, + { + Transactions: TransactionsScreen, + TransactionDetails: TransactionDetailsScreen, + }, + { + initialRouteName: 'Transactions', + headerLayoutPreset: 'center', + transitionConfig: () => ({ + transitionSpec: { + duration: 0, + }, + }), + defaultNavigationOptions: { + headerTitleStyle: { + fontWeight: 'bold', + color: 'Themes.darkMode.primaryColour', + }, + headerTransparent: true, + headerTintColor: Themes.darkMode.primaryColour, }, - { - initialRouteName: 'Transactions', - headerLayoutPreset: 'center', - transitionConfig: () => ({ - transitionSpec: { - duration: 0, - }, - }), - defaultNavigationOptions: { - headerTitleStyle: { - fontWeight: 'bold', - color: 'Themes.darkMode.primaryColour', - }, - headerTransparent: true, - headerTintColor: Themes.darkMode.primaryColour, - }, - } + } ); TransactionNavigator.navigationOptions = ({ navigation, screenProps }) => ({ - tabBarOptions: { - activeBackgroundColor: screenProps.theme.backgroundColour, - inactiveBackgroundColor: screenProps.theme.backgroundColour, - activeTintColor: screenProps.theme.primaryColour, - inactiveTintColor: screenProps.theme.slightlyMoreVisibleColour, - showLabel: false, - style: { - borderTopWidth: 0, - height: 46, - textAlignVertical: "bottom", - backgroundColor: "#FF00FF", - marginBottom: 5 - } + tabBarOptions: { + activeBackgroundColor: screenProps.theme.backgroundColour, + inactiveBackgroundColor: screenProps.theme.backgroundColour, + activeTintColor: screenProps.theme.primaryColour, + inactiveTintColor: screenProps.theme.slightlyMoreVisibleColour, + showLabel: false, + style: { + borderTopWidth: 0, + height: 46, + textAlignVertical: "bottom", + marginBottom: 5 } + } }); const TransferNavigator = createStackNavigator( - { - Transfer: TransferScreen, - ChoosePayee: ChoosePayeeScreen, - NewPayee: NewPayeeScreen, - Confirm: ConfirmScreen, - QrScanner: QrScannerScreen, - SendTransaction: SendTransactionScreen, - RequestPin: RequestPinScreen, - RequestHardwareAuth: RequestHardwareAuthScreen, + { + Transfer: TransferScreen, + ChoosePayee: ChoosePayeeScreen, + NewPayee: NewPayeeScreen, + Confirm: ConfirmScreen, + QrScanner: QrScannerScreen, + SendTransaction: SendTransactionScreen, + RequestPin: RequestPinScreen, + RequestHardwareAuth: RequestHardwareAuthScreen, + }, + { + initialRouteName: 'ChoosePayee', + headerLayoutPreset: 'center', + transitionConfig: () => ({ + transitionSpec: { + duration: 0, + }, + }), + defaultNavigationOptions: { + headerTitleStyle: { + fontWeight: 'bold', + color: Themes.darkMode.primaryColour, + }, + headerTransparent: true, + headerTintColor: Themes.darkMode.primaryColour, }, - { - initialRouteName: 'ChoosePayee', - headerLayoutPreset: 'center', - transitionConfig: () => ({ - transitionSpec: { - duration: 0, - }, - }), - defaultNavigationOptions: { - headerTitleStyle: { - fontWeight: 'bold', - color: Themes.darkMode.primaryColour, - }, - headerTransparent: true, - headerTintColor: Themes.darkMode.primaryColour, - }, - } + } ); TransferNavigator.navigationOptions = ({ navigation, screenProps }) => { - return { - tabBarVisible: navigation.state.index === 0, /* Only show tab bar on ChoosePayee */ - tabBarOptions: { - activeBackgroundColor: screenProps.theme.backgroundColour, - inactiveBackgroundColor: screenProps.theme.backgroundColour, - activeTintColor: screenProps.theme.primaryColour, - inactiveTintColor: screenProps.theme.slightlyMoreVisibleColour, - showLabel: false, - style: { - borderTopWidth: 0, - height: 46, - textAlignVertical: "bottom", - backgroundColor: "#FF00FF", - marginBottom: 5 - } - } - }; + return { + tabBarVisible: navigation.state.index === 0, /* Only show tab bar on ChoosePayee */ + tabBarOptions: { + activeBackgroundColor: screenProps.theme.backgroundColour, + inactiveBackgroundColor: screenProps.theme.backgroundColour, + activeTintColor: screenProps.theme.primaryColour, + inactiveTintColor: screenProps.theme.slightlyMoreVisibleColour, + showLabel: false, + style: { + borderTopWidth: 0, + height: 46, + textAlignVertical: "bottom", + marginBottom: 5 + } + } + }; }; const SettingsNavigator = createStackNavigator( - { - Settings: SettingsScreen, - SwapCurrency: SwapCurrencyScreen, - SwapNode: SwapNodeScreen, - SwapLanguage: SwapLanguageScreen, - ExportKeys: ExportKeysScreen, - Logging: LoggingScreen, - Faq: FaqScreen, - DisableDoze: DisableDozeScreen, - RequestPin: RequestPinScreen, - ForgotPin: ForgotPinScreen, - SetPin: SetPinScreen, - ChooseAuthMethod: ChooseAuthMethodScreen, - RequestHardwareAuth: RequestHardwareAuthScreen, - Optimize: OptimizeScreen, - SwapAPI: SwapAPIScreen + { + Settings: SettingsScreen, + SwapCurrency: SwapCurrencyScreen, + SwapNode: SwapNodeScreen, + SwapLanguage: SwapLanguageScreen, + ExportKeys: ExportKeysScreen, + Logging: LoggingScreen, + Faq: FaqScreen, + DisableDoze: DisableDozeScreen, + RequestPin: RequestPinScreen, + ForgotPin: ForgotPinScreen, + SetPin: SetPinScreen, + ChooseAuthMethod: ChooseAuthMethodScreen, + RequestHardwareAuth: RequestHardwareAuthScreen, + Optimize: OptimizeScreen, + SwapAPI: SwapAPIScreen + }, + { + initialRouteName: 'Settings', + headerLayoutPreset: 'center', + transitionConfig: () => ({ + transitionSpec: { + duration: 0, + }, + }), + defaultNavigationOptions: { + headerTitleStyle: { + fontWeight: 'bold', + color: Themes.darkMode.primaryColour, + }, + headerTransparent: true, + headerTintColor: Themes.darkMode.primaryColour, }, - { - initialRouteName: 'Settings', - headerLayoutPreset: 'center', - transitionConfig: () => ({ - transitionSpec: { - duration: 0, - }, - }), - defaultNavigationOptions: { - headerTitleStyle: { - fontWeight: 'bold', - color: Themes.darkMode.primaryColour, - }, - headerTransparent: true, - headerTintColor: Themes.darkMode.primaryColour, - }, - } + } ); SettingsNavigator.navigationOptions = ({ navigation, screenProps }) => ({ - tabBarOptions: { - activeBackgroundColor: screenProps.theme.backgroundColour, - inactiveBackgroundColor: screenProps.theme.backgroundColour, - activeTintColor: screenProps.theme.primaryColour, - inactiveTintColor: screenProps.theme.slightlyMoreVisibleColour, - showLabel: false, - style: { - borderTopWidth: 0, - height: 46, - textAlignVertical: "bottom", - backgroundColor: "#FF00FF", - marginBottom: 5 - } + tabBarOptions: { + activeBackgroundColor: screenProps.theme.backgroundColour, + inactiveBackgroundColor: screenProps.theme.backgroundColour, + activeTintColor: screenProps.theme.primaryColour, + inactiveTintColor: screenProps.theme.slightlyMoreVisibleColour, + showLabel: false, + style: { + borderTopWidth: 0, + height: 46, + textAlignVertical: "bottom", + marginBottom: 5 } + } }); const RecipientNavigator = createStackNavigator( - { - Recipients: RecipientsScreen, - ModifyPayee: ModifyPayeeScreen, - ChatScreen: ChatScreen, - NewPayee: NewPayeeScreen, - CallScreen: CallScreen + { + Recipients: RecipientsScreen, + ModifyPayee: ModifyPayeeScreen, + ChatScreen: ChatScreen, + NewPayee: NewPayeeScreen, + CallScreen: CallScreen + }, + { + initialRouteName: '', + headerLayoutPreset: 'center', + transitionConfig: () => ({ + transitionSpec: { + duration: 0, + }, + }), + defaultNavigationOptions: { + headerTitleStyle: { + fontWeight: 'bold', + color: Themes.darkMode.primaryColour, + }, + headerTransparent: true, + headerTintColor: Themes.darkMode.primaryColour, }, - { - initialRouteName: '', - headerLayoutPreset: 'center', - transitionConfig: () => ({ - transitionSpec: { - duration: 0, - }, - }), - defaultNavigationOptions: { - headerTitleStyle: { - fontWeight: 'bold', - color: Themes.darkMode.primaryColour, - }, - headerTransparent: true, - headerTintColor: Themes.darkMode.primaryColour, - }, - } + } ); RecipientNavigator.navigationOptions = ({ navigation, screenProps }) => ({ - tabBarOptions: { - activeBackgroundColor: screenProps.theme.backgroundColour, - inactiveBackgroundColor: screenProps.theme.backgroundColour, - activeTintColor: screenProps.theme.primaryColour, - inactiveTintColor: screenProps.theme.slightlyMoreVisibleColour, - showLabel: false, - style: { - borderTopWidth: 0, - height: 46, - textAlignVertical: "bottom", - backgroundColor: "#FF00FF", - marginBottom: 5 - } + tabBarOptions: { + activeBackgroundColor: screenProps.theme.backgroundColour, + inactiveBackgroundColor: screenProps.theme.backgroundColour, + activeTintColor: screenProps.theme.primaryColour, + inactiveTintColor: screenProps.theme.slightlyMoreVisibleColour, + showLabel: false, + style: { + borderTopWidth: 0, + height: 46, + textAlignVertical: "bottom", + marginBottom: 5 } + } }); const GroupsNavigator = createStackNavigator( - { - Groups: GroupsScreen, - ModifyGroup: ModifyGroupScreen, - GroupChatScreen: GroupChatScreen, - NewGroup: NewGroupScreen, + { + Groups: GroupsScreen, + ModifyGroup: ModifyGroupScreen, + GroupChatScreen: GroupChatScreen, + NewGroup: NewGroupScreen, + }, + { + initialRouteName: '', + headerLayoutPreset: 'center', + transitionConfig: () => ({ + transitionSpec: { + duration: 0, + }, + }), + defaultNavigationOptions: { + headerTitleStyle: { + fontWeight: 'bold', + color: Themes.darkMode.primaryColour, + }, + headerTransparent: true, + headerTintColor: Themes.darkMode.primaryColour, }, - { - initialRouteName: '', - headerLayoutPreset: 'center', - transitionConfig: () => ({ - transitionSpec: { - duration: 0, - }, - }), - defaultNavigationOptions: { - headerTitleStyle: { - fontWeight: 'bold', - color: Themes.darkMode.primaryColour, - }, - headerTransparent: true, - headerTintColor: Themes.darkMode.primaryColour, - }, - } + } ); GroupsNavigator.navigationOptions = ({ navigation, screenProps }) => ({ - tabBarOptions: { - activeBackgroundColor: screenProps.theme.backgroundColour, - inactiveBackgroundColor: screenProps.theme.backgroundColour, - activeTintColor: screenProps.theme.primaryColour, - inactiveTintColor: screenProps.theme.slightlyMoreVisibleColour, - showLabel: false, - style: { - borderTopWidth: 0, - height: 46, - textAlignVertical: "bottom", - backgroundColor: "#FF00FF", - marginBottom: 5 - } + tabBarOptions: { + activeBackgroundColor: screenProps.theme.backgroundColour, + inactiveBackgroundColor: screenProps.theme.backgroundColour, + activeTintColor: screenProps.theme.primaryColour, + inactiveTintColor: screenProps.theme.slightlyMoreVisibleColour, + showLabel: false, + style: { + borderTopWidth: 0, + height: 46, + textAlignVertical: "bottom", + marginBottom: 5 } + } }); /* Main screen for a logged in wallet */ const HomeNavigator = createBottomTabNavigator( - { - Main: MainScreen, - Transactions: TransactionNavigator, - Transfer: TransferNavigator, - Recipients: RecipientNavigator, - Groups: GroupsNavigator, - Settings: SettingsNavigator, + { + Main: MainScreen, + Transactions: TransactionNavigator, + Transfer: TransferNavigator, + Recipients: RecipientNavigator, + Groups: GroupsNavigator, + Settings: SettingsNavigator, + }, + { + initialRouteName: 'Main', + tabBarOptions: { + activeBackgroundColor: Themes.darkMode.backgroundColour, + inactiveBackgroundColor: Themes.darkMode.backgroundColour, + activeTintColor: Themes.darkMode.primaryColour, + showLabel: false, + style: { + borderTopWidth: 0, + height: 46, + textAlignVertical: "bottom", + marginBottom: 5 + } + }, - { - initialRouteName: 'Main', - tabBarOptions: { - activeTintColor: "Themes.darkMode.primaryColour", - showLabel: false, - style: { - borderTopWidth: 0, - height: 46, - textAlignVertical: "bottom", - backgroundColor: "#FF00FF", - marginBottom: 5 - } - - }, - defaultNavigationOptions: ({ navigation }) => ({ - tabBarIcon: ({focused, horizontal, tintColor}) => { - const { routeName } = navigation.state; - - let iconName; - let IconComponent; - - if (routeName === 'Main') { - IconComponent = CustomIcon; - iconName = 'profile'; - } else if (routeName === 'Transactions') { - IconComponent = CustomIcon; - iconName = 'wallet'; - } else if (routeName === 'Transfer') { - IconComponent = CustomIcon; - iconName = 'money-send'; - } else if (routeName === 'Recipients') { - IconComponent = CustomIcon; - iconName = 'message'; - } else if (routeName === 'Settings') { - IconComponent = CustomIcon; - iconName = 'setting-2'; - } else if (routeName === 'Groups') { - IconComponent = CustomIcon; - iconName = 'messages'; - } - - return ; - }, - }), - } + defaultNavigationOptions: ({ navigation }) => ({ + tabBarIcon: ({ focused, horizontal, tintColor }) => { + const { routeName } = navigation.state; + + let iconName; + let IconComponent; + + if (routeName === 'Main') { + IconComponent = CustomIcon; + iconName = 'profile'; + } else if (routeName === 'Transactions') { + IconComponent = CustomIcon; + iconName = 'wallet'; + } else if (routeName === 'Transfer') { + IconComponent = CustomIcon; + iconName = 'money-send'; + } else if (routeName === 'Recipients') { + IconComponent = CustomIcon; + iconName = 'message'; + } else if (routeName === 'Settings') { + IconComponent = CustomIcon; + iconName = 'setting-2'; + } else if (routeName === 'Groups') { + IconComponent = CustomIcon; + iconName = 'messages'; + } + + return ; + }, + }), + } ); + + /* Login or create/import a wallet */ const LoginNavigator = createStackNavigator( - { - /* Create a wallet */ - CreateWallet: CreateWalletScreen, + { + /* Create a wallet */ + CreateWallet: CreateWalletScreen, - /* Set a pin for the created wallet */ - SetPin: SetPinScreen, + /* Set a pin for the created wallet */ + SetPin: SetPinScreen, - /* Request the pin for an existing wallet */ - RequestPin: RequestPinScreen, + /* Request the pin for an existing wallet */ + RequestPin: RequestPinScreen, - /* Allow deleting the wallet if pin forgotten */ - ForgotPin: ForgotPinScreen, + /* Allow deleting the wallet if pin forgotten */ + ForgotPin: ForgotPinScreen, - /* Launcing screen */ - Splash: SplashScreen, + /* Launcing screen */ + Splash: SplashScreen, - /* Create a wallet, import a wallet */ - WalletOption: WalletOptionScreen, + /* Create a wallet, import a wallet */ + WalletOption: WalletOptionScreen, - /* Import a wallet */ - ImportWallet: ImportWalletScreen, + /* Import a wallet */ + ImportWallet: ImportWalletScreen, - /* Pick between seed or keys */ - ImportKeysOrSeed: ImportKeysOrSeedScreen, + /* Pick between seed or keys */ + ImportKeysOrSeed: ImportKeysOrSeedScreen, - /* Import with a mnemonic seed */ - ImportSeed: ImportSeedScreen, + /* Import with a mnemonic seed */ + ImportSeed: ImportSeedScreen, - /* Import with a set of keys */ - ImportKeys: ImportKeysScreen, + /* Import with a set of keys */ + ImportKeys: ImportKeysScreen, - /* Pick a month to start the wallet scanning from */ - PickMonth: PickMonthScreen, + /* Pick a month to start the wallet scanning from */ + PickMonth: PickMonthScreen, - /* Pick a block range to start the wallet scanning from */ - PickBlockHeight: PickBlockHeightScreen, + /* Pick a block range to start the wallet scanning from */ + PickBlockHeight: PickBlockHeightScreen, - /* Pick a specific height to start the wallet scanning from */ - PickExactBlockHeight: PickExactBlockHeightScreen, + /* Pick a specific height to start the wallet scanning from */ + PickExactBlockHeight: PickExactBlockHeightScreen, - /* Explain fee, I'm not responsible for anything, etc */ - Disclaimer: DisclaimerScreen, + /* Explain fee, I'm not responsible for anything, etc */ + Disclaimer: DisclaimerScreen, - /* Request authentication via fingerprint, touchid, etc */ - RequestHardwareAuth: RequestHardwareAuthScreen, + /* Request authentication via fingerprint, touchid, etc */ + RequestHardwareAuth: RequestHardwareAuthScreen, - /* Whether we should use pin, fingerprint, or no auth */ - ChooseAuthMethod: ChooseAuthMethodScreen, + /* Whether we should use pin, fingerprint, or no auth */ + ChooseAuthMethod: ChooseAuthMethodScreen, + }, + { + initialRouteName: 'Splash', + headerLayoutPreset: 'center', + defaultNavigationOptions: { + headerTitleStyle: { + fontWeight: 'bold', + color: Themes.darkMode.primaryColour, + }, + headerTransparent: true, + headerTintColor: Themes.darkMode.primaryColour, }, - { - initialRouteName: 'Splash', - headerLayoutPreset: 'center', - defaultNavigationOptions: { - headerTitleStyle: { - fontWeight: 'bold', - color: Themes.darkMode.primaryColour, - }, - headerTransparent: true, - headerTintColor: Themes.darkMode.primaryColour, - }, - } + } ); const AppContainer = createAppContainer(createSwitchNavigator( - { - Login: { - screen: LoginNavigator, - }, - Home: { - screen: HomeNavigator, - }, + { + Login: { + screen: LoginNavigator, }, - { - initialRouteName: 'Login', - } + Home: { + screen: HomeNavigator, + }, + }, + { + initialRouteName: 'Login', + } )); /* TODO: Need to load preferences to set theme */ export default class App extends React.Component { - constructor(props) { - super(props); - - this.state = { - loaded: false, - screenProps: { - theme: Themes[Globals.preferences.theme], - }, - } + constructor(props) { + super(props); + + this.state = { + loaded: false, + screenProps: { + theme: Themes[Globals.preferences.theme], + }, + } + + this.init(); + } - this.init(); + async init() { + await openDB(); + + const prefs = await loadPreferencesFromDatabase(); + + if (prefs !== undefined) { + Globals.preferences = prefs; } - async init() { - await openDB(); + console.log(Globals.preferences); - const prefs = await loadPreferencesFromDatabase(); + this.setState({ + screenProps: { + theme: Themes[Globals.preferences.theme], + }, + loaded: true, + }); - if (prefs !== undefined) { - Globals.preferences = prefs; + Globals.updateTheme = () => { + this.setState({ + screenProps: { + theme: Themes[Globals.preferences.theme], } - - console.log(Globals.preferences); - - this.setState({ - screenProps: { - theme: Themes[Globals.preferences.theme], - }, - loaded: true, - }); - - Globals.updateTheme = () => { - this.setState({ - screenProps: { - theme: Themes[Globals.preferences.theme], - } - }); - }; - } + }); + }; + } - render() { - const loadedComponent = ; - const notLoadedComponent = ; + render() { + const loadedComponent = ; + const notLoadedComponent = ; - return( - - - {this.state.loaded ? loadedComponent : notLoadedComponent} - - ); - } + return ( + + + {this.state.loaded ? loadedComponent : notLoadedComponent} + + ); + } } diff --git a/src/Authenticate.js b/src/Authenticate.js index a8778d6..efc68d7 100644 --- a/src/Authenticate.js +++ b/src/Authenticate.js @@ -30,329 +30,333 @@ import { withTranslation } from 'react-i18next'; /* Dummy component that redirects to pin auth or hardware auth as appropriate */ export async function Authenticate(navigation, subtitle, finishFunction, disableBack = false) { - /* No auth, just go straight to the finish function */ - if (Globals.preferences.authenticationMethod === 'none') { - finishFunction(navigation); - return; - } - - let route = 'RequestPin'; + /* No auth, just go straight to the finish function */ + if (Globals.preferences.authenticationMethod === 'none') { + finishFunction(navigation); + return; + } - try { - const sensorType = await FingerprintScanner.isSensorAvailable(); + let route = 'RequestPin'; - /* User wants to use hardware authentication, and we have it available */ - if (Globals.preferences.authenticationMethod === 'hardware-auth') { - route = 'RequestHardwareAuth'; - } - } catch (err) { - // No fingerprint sensor - } + try { + const sensorType = await FingerprintScanner.isSensorAvailable(); - if (disableBack) { - navigation.dispatch( - navigateWithDisabledBack(route, { - finishFunction, - subtitle, - }), - ); - } else { - navigation.navigate(route, { - finishFunction, - subtitle, - }); + /* User wants to use hardware authentication, and we have it available */ + if (Globals.preferences.authenticationMethod === 'hardware-auth') { + route = 'RequestHardwareAuth'; } + } catch (err) { + // No fingerprint sensor + } + + if (disableBack) { + navigation.dispatch( + navigateWithDisabledBack(route, { + finishFunction, + subtitle, + }), + ); + } else { + navigation.navigate(route, { + finishFunction, + subtitle, + }); + } } const authErrorToHumanError = new Map([ - ['AuthenticationNotMatch', 'Fingerprint does not match stored fingerprint.'], - ['AuthenticationFailed', 'Fingerprint does not match stored fingerprint.'], - ['UserCancel', 'Authentication was cancelled.'], - ['UserFallback', 'Authentication was cancelled.'], - ['SystemCancel', 'Authentication was cancelled by the system.'], - ['PasscodeNotSet', 'No fingerprints have been registered.'], - ['FingerprintScannerNotAvailable', 'This device does not support fingerprint scanning.'], - ['FingerprintScannerNotEnrolled', 'No fingerprints have been registered.'], - ['FingerprintScannerUnknownError', 'Failed to authenticate for an unknown reason.'], - ['FingerprintScannerNotSupported', 'This device does not support fingerprint scanning.'], - ['DeviceLocked', 'Authentication failed too many times.'], + ['AuthenticationNotMatch', 'Fingerprint does not match stored fingerprint.'], + ['AuthenticationFailed', 'Fingerprint does not match stored fingerprint.'], + ['UserCancel', 'Authentication was cancelled.'], + ['UserFallback', 'Authentication was cancelled.'], + ['SystemCancel', 'Authentication was cancelled by the system.'], + ['PasscodeNotSet', 'No fingerprints have been registered.'], + ['FingerprintScannerNotAvailable', 'This device does not support fingerprint scanning.'], + ['FingerprintScannerNotEnrolled', 'No fingerprints have been registered.'], + ['FingerprintScannerUnknownError', 'Failed to authenticate for an unknown reason.'], + ['FingerprintScannerNotSupported', 'This device does not support fingerprint scanning.'], + ['DeviceLocked', 'Authentication failed too many times.'], ]); export class RequestHardwareAuthScreen extends React.Component { - constructor(props) { - super(props); - - this.onAuthAttempt = this.onAuthAttempt.bind(this); - } - - componentDidMount() { - this.auth(); + constructor(props) { + super(props); + + this.onAuthAttempt = this.onAuthAttempt.bind(this); + } + + componentDidMount() { + this.auth(); + } + + componentWillUnmount() { + FingerprintScanner.release(); + } + + onAuthAttempt(error) { + const detailedError = authErrorToHumanError.get(error.name) || error.message; + + const usePinInsteadErrors = [ + 'UserCancel', + 'UserFallback', + 'SystemCancel', + 'PasscodeNotSet', + 'FingerprintScannerNotAvailable', + 'FingerprintScannerNotEnrolled', + 'FingerprintScannerUnknownError', + 'FingerprintScannerNotSupported', + 'DeviceLocked', + ]; + + /* Use pin instead of fingerprint scanner if a specific + type of error is thrown */ + if (usePinInsteadErrors.includes(error.name)) { + Alert.alert( + 'Failed ' + this.props.navigation.state.params.subtitle, + `${detailedError} Please use PIN Auth instead.`, + [ + { + text: 'OK', onPress: () => { + this.props.navigation.navigate('RequestPin', { + subtitle: this.props.navigation.state.params.subtitle, + finishFunction: this.props.navigation.state.params.finishFunction + }) + } + }, + ] + ); + } else { + Alert.alert( + 'Failed ' + this.props.navigation.state.params.subtitle, + `Please try again (Error: ${detailedError})`, + [ + { + text: 'OK', onPress: () => { + this.auth(); + } + }, + ] + ); } + } - componentWillUnmount() { - FingerprintScanner.release(); - } + async auth() { + try { + await FingerprintScanner.authenticate({ + onAttempt: this.onAuthAttempt, + }); + + this.props.navigation.state.params.finishFunction(this.props.navigation); + } catch (error) { + this.onAuthAttempt(error); + }; + } + + render() { + return ( + + {Platform.OS === 'android' && + + + + + + Touch the fingerprint sensor {this.props.navigation.state.params.subtitle} + + + + - onAuthAttempt(error) { - const detailedError = authErrorToHumanError.get(error.name) || error.message; - - const usePinInsteadErrors = [ - 'UserCancel', - 'UserFallback', - 'SystemCancel', - 'PasscodeNotSet', - 'FingerprintScannerNotAvailable', - 'FingerprintScannerNotEnrolled', - 'FingerprintScannerUnknownError', - 'FingerprintScannerNotSupported', - 'DeviceLocked', - ]; - - /* Use pin instead of fingerprint scanner if a specific - type of error is thrown */ - if (usePinInsteadErrors.includes(error.name)) { - Alert.alert( - 'Failed ' + this.props.navigation.state.params.subtitle, - `${detailedError} Please use PIN Auth instead.`, - [ - {text: 'OK', onPress: () => { - this.props.navigation.navigate('RequestPin', { - subtitle: this.props.navigation.state.params.subtitle, - finishFunction: this.props.navigation.state.params.finishFunction - }) - }}, - ] - ); - } else { - Alert.alert( - 'Failed ' + this.props.navigation.state.params.subtitle, - `Please try again (Error: ${detailedError})`, - [ - {text: 'OK', onPress: () => { - this.auth(); - }}, - ] - ); + + + + + + } - - - - - - ); - } + + + + + + ); + } } +export const MainScreen = withTranslation()(MainScreenNoTranslation); + /* Display address, and QR code */ class AddressComponent extends React.PureComponent { - constructor(props) { - super(props); - - this.state = { - address: Globals.wallet.getPrimaryAddress(), - }; - } + constructor(props) { + super(props); + this.state = { + address: Globals.wallet.getPrimaryAddress(), + }; + } - render() { - const { t } = this.props; - - return( - - - {t('paymentAddress')} - - { - Clipboard.setString(this.state.address); - toastPopUp(this.state.address + t('copied')); - } - } - > - {this.state.address} - - - - {t('messageKey')} - - - { - Clipboard.setString(Buffer.from(getKeyPair().publicKey).toString('hex')); - toastPopUp(Buffer.from(getKeyPair().publicKey).toString('hex') + t('copied')); - } - } numberOfLines={2} style={[Styles.centeredText, { - color: this.props.screenProps.theme.primaryColour, - width: 215, - fontSize: 15, - marginTop: 0, - marginBottom: 5, - marginRight: 20, - marginLeft: 20, - fontFamily: 'Montserrat-Regular' - }]}> - {Buffer.from(getKeyPair().publicKey).toString('hex')} - - - - - ); - } + render() { + const { t } = this.props; + + return ( + + + {t('paymentAddress')} + + { + Clipboard.setString(this.state.address); + toastPopUp(this.state.address + t('copied')); + } + } + > + {this.state.address} + + + + {t('messageKey')} + + + { + Clipboard.setString(Buffer.from(getKeyPair().publicKey).toString('hex')); + toastPopUp(Buffer.from(getKeyPair().publicKey).toString('hex') + t('copied')); + } + } numberOfLines={2} style={[Styles.centeredText, { + color: this.props.screenProps.theme.primaryColour, + width: 215, + fontSize: 15, + marginTop: 0, + marginBottom: 5, + marginRight: 20, + marginLeft: 20, + fontFamily: 'Montserrat-Regular' + }]}> + {Buffer.from(getKeyPair().publicKey).toString('hex')} + + + ); + } } const AddressComponentWithTranslation = withTranslation()(AddressComponent) @@ -768,122 +656,107 @@ const AddressComponentWithTranslation = withTranslation()(AddressComponent) * Balance component at top of screen */ class BalanceComponentNoTranslation extends React.Component { - constructor(props) { - super(props); + constructor(props) { + super(props); - this.state = { - expandedBalance: false, - }; + this.state = { + expandedBalance: false, + }; - this.balanceRef = (ref) => this.balance = ref; - this.valueRef = (ref) => this.value = ref; - } + this.balanceRef = (ref) => this.balance = ref; + this.valueRef = (ref) => this.value = ref; + } - componentWillMount() { - } + componentWillMount() { + } - componentDidMount() { + componentDidMount() { - let flipFlop = false; + let flipFlop = false; - } + } - componentWillReceiveProps(nextProps) { - if (nextProps.unlockedBalance !== this.props.unlockedBalance || - nextProps.lockedBalance !== this.props.lockedBalance) { - } + componentWillReceiveProps(nextProps) { + if (nextProps.unlockedBalance !== this.props.unlockedBalance || + nextProps.lockedBalance !== this.props.lockedBalance) { } + } - render() { - const {t} = this.props; - const hasBalance = (this.props.unlockedBalance + this.props.lockedBalance > 0) ? true : false; - const compactBalance = - -  - - {prettyPrintAmountMainScreen(this.props.unlockedBalance)} - - ; - - const lockedBalance = - - this.setState({ - expandedBalance: !this.state.expandedBalance - })}> - {prettyPrintAmount(this.props.lockedBalance, Config).slice(0,-4)} - - ; - - const unlockedBalance = - - this.setState({ - expandedBalance: !this.props.expandedBalance - })}> - {prettyPrintAmount(this.props.unlockedBalance, Config).slice(0,-4)} - - ; - - const expandedBalance = - {unlockedBalance} - {lockedBalance} - ; - - - const OpenURLButton = () => { - const handlePress = useCallback(async () => { - - // Opening the link with some app, if the URL scheme is "http" the web link should be opened - // by some browser in the mobile - await Linking.openURL('https://kryptokrona.org/en/faucet?address=' + this.props.address); - - }); - if (false) { - return + + + + + + {t('shouldArriveIn')} {getArrivalTime([t('minute'), t('second')])} + + + ); + } } export const TransferScreen = withTranslation()(TransferScreenNoTranslation) class AddressBook extends React.Component { - constructor(props) { - super(props); - - this.state = { - payees: Globals.payees, - index: 0, - }; - - Globals.updatePayeeFunctions.push(() => { - this.setState(prevState => ({ - payees: Globals.payees, - index: prevState.index + 1, - })) - }); - } - - render() { - const payees = this.state.payees; - function get_avatar(hash) { - // Displays a fixed identicon until user adds new contact address in the input field - if (hash.length < 15) { - hash = 'SEKReYanL2qEQF2HA8tu9wTpKBqoCA8TNb2mNRL5ZDyeFpxsoGNgBto3s3KJtt5PPrRH36tF7DBEJdjUn5v8eaESN2T5DPgRLVY'; - } - // Get custom color scheme based on address - let rgb = intToRGB(hashCode(hash)); - - // Options for avatar - var options = { - foreground: [rgb.red, rgb.green, rgb.blue, 255], // rgba black - background: [parseInt(rgb.red/10), parseInt(rgb.green/10), parseInt(rgb.blue/10), 0], // rgba white - margin: 0.2, // 20% margin - size: 50, // 420px square - format: 'png' // use SVG instead of PNG - }; - - // create a base64 encoded SVG - return 'data:image/png;base64,' + new Identicon(hash, options).toString(); - } + constructor(props) { + super(props); + this.state = { + payees: Globals.payees, + index: 0, + }; - return( + Globals.updatePayeeFunctions.push(() => { + this.setState(prevState => ({ + payees: Globals.payees, + index: prevState.index + 1, + })) + }); + } - - item.nickname} - renderItem={({item}) => ( - - } - titleStyle={{ - color: this.props.screenProps.theme.primaryColour, - fontFamily: 'Montserrat-SemiBold' - }} - subtitleStyle={{ - color: this.props.screenProps.theme.slightlyMoreVisibleColour, - fontFamily: 'Montserrat-Regular' - }} - onLongPress={() => { - Alert.alert( - i18next.t('deleteContactWarning'), - `${item.nickname} (${item.address})`, - [ - { - text: i18next.t('delete'), onPress: () => { - (async () => { - /* Disabling saving */ - - Globals.removePayee(item.nickname, true); - this.setState({ - payees: Globals.payees - }); - - })(); - } - }, - { text: i18next.t('cancel'), style: 'cancel' }, - ], - ); - }} - delayLongPress={1500} - onPress={() => { - this.props.navigation.navigate( - 'Transfer', { - payee: item, - } - ); - }} - /> - )} - /> - - ); + render() { + const payees = this.state.payees; + function get_avatar(hash) { + // Displays a fixed identicon until user adds new contact address in the input field + if (hash.length < 15) { + hash = 'SEKReYanL2qEQF2HA8tu9wTpKBqoCA8TNb2mNRL5ZDyeFpxsoGNgBto3s3KJtt5PPrRH36tF7DBEJdjUn5v8eaESN2T5DPgRLVY'; + } + // Get custom color scheme based on address + let rgb = intToRGB(hashCode(hash)); + + // Options for avatar + var options = { + foreground: [rgb.red, rgb.green, rgb.blue, 255], // rgba black + background: [parseInt(rgb.red / 10), parseInt(rgb.green / 10), parseInt(rgb.blue / 10), 0], // rgba white + margin: 0.2, // 20% margin + size: 50, // 420px square + format: 'png' // use SVG instead of PNG + }; + + // create a base64 encoded SVG + return 'data:image/png;base64,' + new Identicon(hash, options).toString(); } -} -class ExistingPayeesNoTranslation extends React.Component { - constructor(props) { - super(props); - } - render() { - const { t } = this.props; - const noPayeesComponent = - - - {t('emptyAddressBook')} - - + return ( + + + item.nickname} + renderItem={({ item }) => ( + + } + titleStyle={{ + color: this.props.screenProps.theme.primaryColour, + fontFamily: 'Montserrat-SemiBold' + }} + subtitleStyle={{ + color: this.props.screenProps.theme.slightlyMoreVisibleColour, + fontFamily: 'Montserrat-Regular' + }} + onLongPress={() => { + Alert.alert( + i18next.t('deleteContactWarning'), + `${item.nickname} (${item.address})`, + [ + { + text: i18next.t('delete'), onPress: () => { + (async () => { + /* Disabling saving */ + + Globals.removePayee(item.nickname, true); + this.setState({ + payees: Globals.payees + }); + + })(); + } + }, + { text: i18next.t('cancel'), style: 'cancel' }, + ], + ); + }} + delayLongPress={1500} + onPress={() => { + this.props.navigation.navigate( + 'Transfer', { + payee: item, + } + ); + }} + /> + )} + /> + + ); + } +} - return( - - +class ExistingPayeesNoTranslation extends React.Component { + constructor(props) { + super(props); + } - {Globals.payees.length > 0 ? : noPayeesComponent} - - - ); - } + render() { + const { t } = this.props; + const noPayeesComponent = + + + {t('emptyAddressBook')} + + + + return ( + + {Globals.payees.length > 0 ? : noPayeesComponent} + + ); + } } const ExistingPayees = withTranslation()(ExistingPayeesNoTranslation) export class NewPayeeScreenNoTranslation extends React.Component { - static navigationOptions = ({ navigation }) => { - return { - headerRight: ( - - ), - } + static navigationOptions = ({ navigation }) => { + return { + headerRight: ( + + ), + } + }; + + constructor(props) { + super(props); + + const address = this.props.navigation.getParam('address', ''); + const paymentID = this.props.navigation.getParam('paymentID', ''); + + this.state = { + nickname: '', + address, + paymentID, + paymentIDEnabled: address.length !== Config.integratedAddressLength, + addressError: '', + paymentIDError: '', + nicknameError: '', }; - constructor(props) { - super(props); - - const address = this.props.navigation.getParam('address', ''); - const paymentID = this.props.navigation.getParam('paymentID', ''); - - this.state = { - nickname: '', - address, - paymentID, - paymentIDEnabled: address.length !== Config.integratedAddressLength, - addressError: '', - paymentIDError: '', - nicknameError: '', - }; - } + this.onContinuePress = this.onContinuePress.bind(this); - setAddressFromQrCode(address) { - this.setState({ - address, - }, () => this.checkErrors()); - } + } - async validAddress(address) { - let errorMessage = ''; + setAddressFromQrCode(address) { + this.setState({ + address, + }, () => this.checkErrors()); + } - if (Globals.payees.some((payee) => payee.address === address)) { - errorMessage = `A payee with the address ${address} already exists.`; - return [false, errorMessage]; - } + async validAddress(address) { + let errorMessage = ''; - if (address === '' || address === undefined || address === null) { - return [false, errorMessage]; - } - /* Disable payment ID and wipe input if integrated address */ - if (address.length === Config.integratedAddressLength) { - await this.setState({ - paymentID: '', - paymentIDEnabled: false, - }); - } else { - this.setState({ - paymentIDEnabled: true, - }); - } + if (Globals.payees.some((payee) => payee.address === address)) { + errorMessage = `A payee with the address ${address} already exists.`; + return [false, errorMessage]; + } + + if (address === '' || address === undefined || address === null) { + return [false, errorMessage]; + } - if (address.length === 163) { - // Hugin address + /* Disable payment ID and wipe input if integrated address */ + if (address.length === Config.integratedAddressLength) { + await this.setState({ + paymentID: '', + paymentIDEnabled: false, + }); + } else { + this.setState({ + paymentIDEnabled: true, + }); + } - await this.setState({ - address: address.substring(0,99), - paymentID: address.substring(99), - paymentIDEnabled: true - }); - address = address.substring(0,99); - } - const addressError = await validateAddresses([address], true, Config); + if (address.length === 163) { + // Hugin address - if (addressError.errorCode !== WalletErrorCode.SUCCESS) { - errorMessage = addressError.toString(); + await this.setState({ + address: address.substring(0, 99), + paymentID: address.substring(99), + paymentIDEnabled: true + }); + address = address.substring(0, 99); + } + const addressError = await validateAddresses([address], true, Config); - return [false, errorMessage]; - } + if (addressError.errorCode !== WalletErrorCode.SUCCESS) { + errorMessage = addressError.toString(); - return [true, errorMessage]; + return [false, errorMessage]; } - validPaymentID(paymentID) { - let errorMessage = ''; + return [true, errorMessage]; + } - if (paymentID === '') { - return [true, errorMessage]; - } + validPaymentID(paymentID) { + let errorMessage = ''; - if (paymentID === undefined || paymentID === null) { - return [false, errorMessage]; - } + if (paymentID === '') { + return [true, errorMessage]; + } - const paymentIDError = validatePaymentID(paymentID); + if (paymentID === undefined || paymentID === null) { + return [false, errorMessage]; + } - if (paymentIDError.errorCode !== WalletErrorCode.SUCCESS) { - errorMessage = paymentIDError.toString(); + const paymentIDError = validatePaymentID(paymentID); - return [false, errorMessage]; - } + if (paymentIDError.errorCode !== WalletErrorCode.SUCCESS) { + errorMessage = paymentIDError.toString(); - return [true, errorMessage]; + return [false, errorMessage]; } - validNickname(nickname) { - let errorMessage = ''; + return [true, errorMessage]; + } - if (nickname === '' || nickname === undefined || nickname === null) { - return [false, errorMessage]; - } + validNickname(nickname) { + let errorMessage = ''; - if (Globals.payees.some((payee) => payee.nickname === nickname)) { - errorMessage = `A payee with the name ${nickname} already exists.`; - return [false, errorMessage]; - } + if (nickname === '' || nickname === undefined || nickname === null) { + return [false, errorMessage]; + } - return [true, errorMessage]; + if (Globals.payees.some((payee) => payee.nickname === nickname)) { + errorMessage = `A payee with the name ${nickname} already exists.`; + return [false, errorMessage]; } - checkErrors() { - (async() => { + return [true, errorMessage]; + } + + checkErrors() { + (async () => { - const [addressValid, addressError] = await this.validAddress(this.state.address); - const [paymentIDValid, paymentIDError] = this.validPaymentID(this.state.paymentID); - const [nicknameValid, nicknameError] = this.validNickname(this.state.nickname); + const [addressValid, addressError] = await this.validAddress(this.state.address); + const [paymentIDValid, paymentIDError] = this.validPaymentID(this.state.paymentID); + const [nicknameValid, nicknameError] = this.validNickname(this.state.nickname); - this.setState({ - continueEnabled: addressValid && paymentIDValid && nicknameValid, - addressError, - paymentIDError, - nicknameError, - }); + this.setState({ + continueEnabled: addressValid && paymentIDValid && nicknameValid, + addressError, + paymentIDError, + nicknameError, + }); - })(); - } + })(); + } - render() { - const { t } = this.props; - return( - - - - {t('newContact')} - - - - - - { - this.setState({ - nickname: text, - }, () => this.checkErrors()); - }} - errorMessage={this.state.nicknameError} - /> - - { - this.setState({ - address: text, - }, () => this.checkErrors()); - }} - errorMessage={this.state.addressError} - /> - {this.state.address != '' && - { - this.setState({ - paymentID: text - }, () => this.checkErrors()); - }} - editable={this.state.paymentIDEnabled} - errorMessage={this.state.paymentIDError} - /> - } - - - { - const func = (data) => { - if (data.startsWith(Config.uriPrefix)) { - handleURI(data, this.props.navigation); - } else { - this.setState({ - address: data, - // paymentID: data.substring(99), - }, () => this.checkErrors()); - } - }; - - this.props.navigation.navigate('QrScanner', { - setAddress: func - }); - }} - > - - - {t('scanQR')} - - - - { - const payee = { - nickname: this.state.nickname, - address: this.state.address, - paymentID: this.state.paymentID, - }; - - /* Add payee to global payee store */ - Globals.addPayee(payee); - - const finishFunction = Globals.fromChat; // = this.props.navigation.getParam('finishFunction', undefined); - - if (finishFunction) { - Globals.fromChat = false; - this.props.navigation.dispatch(StackActions.popToTop()); - this.props.navigation.navigate( - 'ChatScreen', { - payee: payee, - }); - - return; - - } else { - const amount = this.props.navigation.getParam('amount', undefined); - - /* Already have an amount, don't need to go to transfer screen */ - if (amount) { - this.props.navigation.navigate( - 'Confirm', { - payee, - amount, - } - ); - - } else { - this.props.navigation.navigate( - 'Transfer', { - payee, - } - ); - } - } - }} - disabled={!this.state.continueEnabled} - {...this.props} - /> - - - ); + onContinuePress = () => { + const payee = { + nickname: this.state.nickname, + address: this.state.address, + paymentID: this.state.paymentID, + }; + + Globals.addPayee(payee); + + const finishFunction = Globals.fromChat; + + if (finishFunction) { + Globals.fromChat = false; + this.props.navigation.dispatch(StackActions.popToTop()); + this.props.navigation.navigate('ChatScreen', { payee }); + } else { + const amount = this.props.navigation.getParam('amount', undefined); + if (amount) { + this.props.navigation.navigate('Confirm', { payee, amount }); + } else { + this.props.navigation.navigate('Transfer', { payee }); + } } + } + + render() { + const { t } = this.props; + + return ( + + + {t('newContact')} + + + + { + this.setState({ + nickname: text, + }, () => this.checkErrors()); + }} + errorMessage={this.state.nicknameError} + /> + + { + this.setState({ + address: text, + }, () => this.checkErrors()); + }} + errorMessage={this.state.addressError} + /> + {/* {this.state.address != '' && + { + this.setState({ + paymentID: text + }, () => this.checkErrors()); + }} + editable={this.state.paymentIDEnabled} + errorMessage={this.state.paymentIDError} + /> + } */} + + + + + + + + + ); + } } export const NewPayeeScreen = withTranslation()(NewPayeeScreenNoTranslation) class ModifyMemo extends React.Component { - constructor(props) { - super(props); - } + constructor(props) { + super(props); + } - render() { - return( - { - if (this.props.onChange) { - this.props.onChange(text); - } - }} - /> - ); - } + render() { + return ( + { + if (this.props.onChange) { + this.props.onChange(text); + } + }} + /> + ); + } } export class ConfirmScreenNoTranslation extends React.Component { - static navigationOptions = ({ navigation }) => { - return { - headerRight: ( - - ), - } + static navigationOptions = ({ navigation }) => { + return { + headerRight: ( + + ), + } + }; + + constructor(props) { + super(props); + + this.state = { }; + } - constructor(props) { - super(props); + async componentDidMount() { + const [unlockedBalance, lockedBalance] = await Globals.wallet.getBalance(); - this.state = { - }; - } + const { payee, amount, sendAll } = this.props.navigation.state.params; - async componentDidMount() { - const [unlockedBalance, lockedBalance] = await Globals.wallet.getBalance(); + const fullAmount = sendAll ? unlockedBalance : amount; - const { payee, amount, sendAll } = this.props.navigation.state.params; + const devFee = Math.floor((fullAmount * Config.devFeePercentage) / 100); - const fullAmount = sendAll ? unlockedBalance : amount; + const [feeAddress, nodeFee] = Globals.wallet.getNodeFee(); - const devFee = Math.floor((fullAmount * Config.devFeePercentage) / 100); + this.setState({ + memo: '', + modifyMemo: false, + preparedTransaction: false, + haveError: false, + payee, + amount, + sendAll, + unlockedBalance, + devFee, + nodeFee, + }); - const [feeAddress, nodeFee] = Globals.wallet.getNodeFee(); + this.prepareTransaction(); + } - this.setState({ - memo: '', - modifyMemo: false, - preparedTransaction: false, - haveError: false, - payee, - amount, - sendAll, - unlockedBalance, - devFee, - nodeFee, - }); + async prepareTransaction() { + const payments = []; + + /* User payment */ + if (this.state.sendAll) { + payments.push([ + this.state.payee.address, + 1, /* Amount does not matter for sendAll destination */ + ]); + } else { + payments.push([ + this.state.payee.address, + this.state.amount, + ]); + } - this.prepareTransaction(); + if (this.state.devFee > 0) { + /* Dev payment */ + payments.push([ + Config.devFeeAddress, + this.state.devFee, + ]); } - async prepareTransaction() { - const payments = []; - - /* User payment */ - if (this.state.sendAll) { - payments.push([ - this.state.payee.address, - 1, /* Amount does not matter for sendAll destination */ - ]); - } else { - payments.push([ - this.state.payee.address, - this.state.amount, - ]); + // let [new_address, error] = await Globals.wallet.addSubWallet(); + + const result = await Globals.wallet.sendTransactionAdvanced( + payments, // destinations, + 3, // mixin + { fixedFee: 1000, isFixedFee: true }, // fee + this.state.payee.paymentID, + undefined, // subWalletsToTakeFrom + undefined, // changeAddress + false, // relayToNetwork + this.state.sendAll, + ); + + if (result.success) { + let actualAmount = this.state.amount; + for (const input of result.preparedTransaction.inputs) { + } + if (this.state.sendAll) { + let transactionSum = 0; + + /* We could just get the sum by calling getBalance.. but it's + * possibly just changed. Safest to iterate over prepared + * transaction and calculate it. */ + for (const input of result.preparedTransaction.inputs) { + transactionSum += input.input.amount; } - if (this.state.devFee > 0) { - /* Dev payment */ - payments.push([ - Config.devFeeAddress, - this.state.devFee, - ]); - } + actualAmount = transactionSum + - result.fee + - this.state.devFee + - this.state.nodeFee; + } + + this.setState({ + preparedTransaction: true, + haveError: false, + fee: result.fee, + hash: result.transactionHash, + recipientAmount: actualAmount, + feeTotal: result.fee + this.state.devFee + this.state.nodeFee, + }); + } else { + this.setState({ + preparedTransaction: true, + haveError: true, + error: result.error, + }); + } + } - // let [new_address, error] = await Globals.wallet.addSubWallet(); - - const result = await Globals.wallet.sendTransactionAdvanced( - payments, // destinations, - 3, // mixin - {fixedFee: 1000, isFixedFee: true}, // fee - this.state.payee.paymentID, - undefined, // subWalletsToTakeFrom - undefined, // changeAddress - false, // relayToNetwork - this.state.sendAll, - ); - - if (result.success) { - let actualAmount = this.state.amount; - for (const input of result.preparedTransaction.inputs) { - } - if (this.state.sendAll) { - let transactionSum = 0; - - /* We could just get the sum by calling getBalance.. but it's - * possibly just changed. Safest to iterate over prepared - * transaction and calculate it. */ - for (const input of result.preparedTransaction.inputs) { - transactionSum += input.input.amount; - } + preparingScreen(t) { + return ( + + + {t('estimating')} + + + ); + } - actualAmount = transactionSum - - result.fee - - this.state.devFee - - this.state.nodeFee; - } + errorScreen() { + let errorMessage = this.state.error.toString(); - this.setState({ - preparedTransaction: true, - haveError: false, - fee: result.fee, - hash: result.transactionHash, - recipientAmount: actualAmount, - feeTotal: result.fee + this.state.devFee + this.state.nodeFee, - }); - } else { - this.setState({ - preparedTransaction: true, - haveError: true, - error: result.error, - }); - } + if (this.state.error.errorCode === WalletErrorCode.NOT_ENOUGH_BALANCE) { + if (this.state.sendAll) { + errorMessage = 'Unfortunately, your balance is too low to cover the network fee required to send your funds.'; + } else { + errorMessage = 'Not enough balance to cover amount including fees!\n' + + 'Either reduce the amount you are sending, or use the "send all" option to send the max possible.\n\n'; + } } - preparingScreen(t) { - return( + return ( + + + Estimating fee and preparing transaction failed! + + + + {errorMessage} + + + ); + } + + confirmScreen(t) { + return ( + + + + {t('reviewTitle')} + + + + + + + + {prettyPrintAmount(this.state.recipientAmount, Config)}{' '} + + will reach{' '} + + {this.state.payee.nickname}'s{' '} + + account, in {getArrivalTime([t('minute'), t('second')])} + + - + + {t('notes')} + + + + - ); - } - errorScreen() { - let errorMessage = this.state.error.toString(); - - if (this.state.error.errorCode === WalletErrorCode.NOT_ENOUGH_BALANCE) { - if (this.state.sendAll) { - errorMessage = 'Unfortunately, your balance is too low to cover the network fee required to send your funds.'; - } else { - errorMessage = 'Not enough balance to cover amount including fees!\n' + - 'Either reduce the amount you are sending, or use the "send all" option to send the max possible.\n\n'; + + + + + {this.state.modifyMemo ? + { + this.setState({ + memo: text, + }) + }} + {...this.props} + /> + : + + {this.state.memo === '' ? t('none') : this.state.memo} + } - } + - return( + - - Estimating fee and preparing transaction failed! - - - - {errorMessage} - + + {this.state.payee.nickname}{t('details')} + + + - ); - } - - confirmScreen(t) { - return( - - - - {t('reviewTitle')} - - - - - - - {prettyPrintAmount(this.state.recipientAmount, Config)}{' '} - - will reach{' '} - - {this.state.payee.nickname}'s{' '} - - account, in {getArrivalTime([t('minute'), t('second')])} - - - - - - {t('notes')} - - - + + + + ); + } +} + +export const ChoosePayeeScreen = withTranslation()(ChoosePayeeScreenNoTranslation) + +export class SendTransactionScreenNoTranslation extends React.Component { + static navigationOptions = { + header: null, + } + + constructor(props) { + super(props); + + this.state = { + txInfo: 'Sending transaction, please wait...', + errMsg: '', + hash: this.props.navigation.state.params.hash, + amount: this.props.navigation.state.params.amount, + address: this.props.navigation.state.params.address, + nickname: this.props.navigation.state.params.nickname, + memo: this.props.navigation.state.params.memo, + homeEnabled: false, + sent: false, + } + + /* Send the tx in the background (it's async) */ + this.sendTransaction(); + } + + async sendTransaction() { + /* Wait for UI to load before blocking thread */ + await delay(500); + + const result = await Globals.wallet.sendPreparedTransaction( + this.state.hash, + ); + + if (!result.success) { + /* TODO: Optionally allow retries in case of network error? */ + Globals.wallet.deletePreparedTransaction(this.state.hash); + + this.setState({ + errMsg: result.error.toString(), + homeEnabled: true, + }); + } else { + this.setState({ + homeEnabled: true, + sent: true, + }); + + Globals.addTransactionDetails({ + hash: this.state.hash, + memo: this.state.memo, + address: this.state.address, + payee: this.state.nickname, + }); + } + } + + render() { + const { t } = this.props; + const sending = + + + {t('sendingMsg')} + + ; + + const fail = + + + {t('failedMsg')} + + + + {this.state.errMsg} + + ; + + const success = + + + {t('completeMsg')} + + + + + {prettyPrintAmount(this.state.amount, Config)}{' '} + + was sent to{' '} + + {this.state.nickname}'s{' '} + + account. + + + + {t('transactionHash')} + + + + {this.state.hash} + + + + ; + + return ( + + + {this.state.sent ? success : this.state.errMsg === '' ? sending : fail} + + + { + this.props.navigation.dispatch(navigateWithDisabledBack('ChoosePayee')); + this.props.navigation.navigate('Main'); + }} + disabled={!this.state.homeEnabled} + {...this.props} + /> + + ); + } } export const SendTransactionScreen = withTranslation()(SendTransactionScreenNoTranslation) diff --git a/src/Utilities.js b/src/Utilities.js index 952f51f..727d3d2 100644 --- a/src/Utilities.js +++ b/src/Utilities.js @@ -19,7 +19,7 @@ import { Text, Platform, ToastAndroid, Alert } from 'react-native'; import { StackActions, NavigationActions } from 'react-navigation'; import { - validateAddresses, WalletErrorCode, validatePaymentID, prettyPrintAmount, + validateAddresses, WalletErrorCode, validatePaymentID, } from 'kryptokrona-wallet-backend-js'; import * as Qs from 'query-string'; @@ -28,43 +28,43 @@ import Config from './Config'; import { Globals } from './Globals'; -import { addFee, toAtomic } from './Fee'; +import { toAtomic } from './Fee'; -export function delay(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); +export function delay(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); } export function toastPopUp(message, short = true) { - /* IOS doesn't have toast support */ - /* TODO */ - if (Platform.OS === 'ios') { - return; - } + /* IOS doesn't have toast support */ + /* TODO */ + if (Platform.OS === 'ios') { + return; + } - ToastAndroid.show(message, short ? ToastAndroid.SHORT : ToastAndroid.LONG); + ToastAndroid.show(message, short ? ToastAndroid.SHORT : ToastAndroid.LONG); } /* Navigate to a route, resetting the stack, so the user cannot go back. We want to do this so when we go from the splash screen to the menu screen, the user can't return, and get stuck there. */ export function navigateWithDisabledBack(route, routeParams) { - return StackActions.reset({ - index: 0, - actions: [ - NavigationActions.navigate({ - routeName: route, - params: routeParams, - }), - ] - }); + return StackActions.reset({ + index: 0, + actions: [ + NavigationActions.navigate({ + routeName: route, + params: routeParams, + }), + ] + }); } export function prettyPrintUnixTimestamp(timestamp) { - return {timestamp} + return {timestamp} } export function prettyPrintDate2(timestamp) { - let date = new Date(timestamp*1000); + let date = new Date(timestamp * 1000); return date.toLocaleString(Globals.language); @@ -72,321 +72,321 @@ export function prettyPrintDate2(timestamp) { } export function prettyPrintDate(date) { - if (date === undefined) { - date = moment(); - } + if (date === undefined) { + date = moment(); + } - if (moment().year() === date.year()) { - return date.format('D MMM, HH:mm'); - } + if (moment().year() === date.year()) { + return date.format('D MMM, HH:mm'); + } - return date.format('D MMM, YYYY HH:mm'); + return date.format('D MMM, YYYY HH:mm'); } /** * Gets the approximate height of the blockchain, based on the launch timestamp */ export function getApproximateBlockHeight(date) { - const difference = (date - Config.chainLaunchTimestamp) / 1000; + const difference = (date - Config.chainLaunchTimestamp) / 1000; - let blockHeight = Math.floor(difference / 90); + let blockHeight = Math.floor(difference / 90); - if (blockHeight < 0) { - blockHeight = 0; - } + if (blockHeight < 0) { + blockHeight = 0; + } - return blockHeight; + return blockHeight; } /** * Converts a date to a scan height. Note, takes a moment date. */ export function dateToScanHeight(date) { - let jsDate = date.toDate(); - const now = new Date(); + let jsDate = date.toDate(); + const now = new Date(); - if (jsDate > now) { - jsDate = now; - } + if (jsDate > now) { + jsDate = now; + } - return getApproximateBlockHeight(jsDate); + return getApproximateBlockHeight(jsDate); } export function getArrivalTime(timeUnitTranslation) { - const minutes = Config.blockTargetTime >= 60; + const minutes = Config.blockTargetTime >= 60; - if (minutes) { - return Math.ceil(Config.blockTargetTime / 60) + ' ' + timeUnitTranslation[0]; - } else { - return Config.blockTargetTime + ' ' + timeUnitTranslation[1]; - } + if (minutes) { + return Math.ceil(Config.blockTargetTime / 60) + ' ' + timeUnitTranslation[0]; + } else { + return Config.blockTargetTime + ' ' + timeUnitTranslation[1]; + } } export async function handleURI(data, navigation) { console.log(data); - if (data.url ) { - - const params = Qs.parse(data.url.replace('xkr://', '')); + if (data.url) { - if(params.board != undefined) { + const params = Qs.parse(data.url.replace('xkr://', '')); - console.log(params.board); + if (params.board != undefined) { - navigation.navigate( - 'BoardsHome', { - board: params.board - }); + console.log(params.board); + navigation.navigate( + 'BoardsHome', { + board: params.board + }); - return; - } else if (params.group != undefined) { - // To do: Separate between a new group and a notification call + return; + } else if (params.group != undefined) { - const group_object = Globals.groups.filter(group => { - return group.key == params.group; - }) + // To do: Separate between a new group and a notification call - console.log(group_object[0]); + const group_object = Globals.groups.filter(group => { + return group.key == params.group; + }) - navigation.navigate( - 'GroupChatScreen', { - group: {group: group_object[0].group, key: group_object[0].key} - }); + console.log(group_object[0]); - return; + navigation.navigate( + 'GroupChatScreen', { + group: { group: group_object[0].group, key: group_object[0].key } + }); - } + return; - if(params.istip != undefined) { + } - const newPayee = { - nickname: params.name, - address: params.address, - paymentID: params.paymentid - } + if (params.istip != undefined) { - console.log(newPayee); - const result = { - payee: newPayee, - suggestedAction: 'Transfer', - valid: true, - }; - - console.log(params); - console.log(result); - if (!result.valid) { - Alert.alert( - 'Cannot send transaction', - result.error, - [ - {text: 'OK'}, - ] - ); - } else { - - /* Hop into the transfer stack */ - navigation.navigate('ChoosePayee'); - // navigation.navigate('Recipients'); - /* Then navigate to the nested route, if needed */ - navigation.navigate('Transfer', {...result}); - return; - } + const newPayee = { + nickname: params.name, + address: params.address, + paymentID: params.paymentid } - const address = params.address; - const name = params.name; + console.log(newPayee); + const result = { + payee: newPayee, + suggestedAction: 'Transfer', + valid: true, + }; + + console.log(params); + console.log(result); + if (!result.valid) { + Alert.alert( + 'Cannot send transaction', + result.error, + [ + { text: 'OK' }, + ] + ); + } else { - if (name == undefined) { - handleURI(data.url); + /* Hop into the transfer stack */ + navigation.navigate('ChoosePayee'); + // navigation.navigate('Recipients'); + /* Then navigate to the nested route, if needed */ + navigation.navigate('Transfer', { ...result }); return; } + } - const paymentID = params.paymentID; - - navigation.navigate( - 'ChatScreen', { - payee: {address: address, nickname: name, paymentID: paymentID}, - }); + const address = params.address; + const name = params.name; - } else if (!data.startsWith('xkr://SEKR')) { + if (name == undefined) { + handleURI(data.url); return; } - const result = await parseURI(data); + const paymentID = params.paymentID; - if (!result.valid) { - Alert.alert( - 'Cannot send transaction', - result.error, - [ - {text: 'OK'}, - ] - ); - } else { + navigation.navigate( + 'ChatScreen', { + payee: { address: address, nickname: name, paymentID: paymentID }, + }); - /* Hop into the transfer stack */ - navigation.navigate('ChoosePayee'); - // navigation.navigate('Recipients'); - /* Then navigate to the nested route, if needed */ - navigation.navigate(result.suggestedAction, {...result}); - } + } else if (!data.startsWith('xkr://SEKR')) { + return; + } + + const result = await parseURI(data); + + if (!result.valid) { + Alert.alert( + 'Cannot send transaction', + result.error, + [ + { text: 'OK' }, + ] + ); + } else { + + /* Hop into the transfer stack */ + navigation.navigate('ChoosePayee'); + // navigation.navigate('Recipients'); + /* Then navigate to the nested route, if needed */ + navigation.navigate(result.suggestedAction, { ...result }); + } } export async function parseURI(qrData) { - /* It's a URI, try and get the data from it */ - if (qrData.startsWith(Config.uriPrefix)) { - /* Remove the turtlecoin:// prefix */ - let data = qrData.replace(Config.uriPrefix, ''); - let index = data.indexOf('?'); - - /* Doesn't have any params */ - if (index === -1) { - index = data.length; - } - - const address = data.substr(0, index); - const params = Qs.parse(data.substr(index)); - - console.log(params); - console.log(address); - const amount = params.amount; - const name = params.name; - let paymentID = params.paymentid; - - if (paymentID) { - const pidError = validatePaymentID(paymentID); - - /* Payment ID isn't valid. */ - if (pidError.errorCode !== WalletErrorCode.SUCCESS) { - return { - valid: false, - error: 'QR Code is invalid', - }; - } - - /* Both integrated address and payment ID given */ - if (address.length === Config.integratedAddressLength && paymentID.length !== 0) { - return { - valid: false, - error: 'QR Code is invalid', - }; - } - } - - const addressError = await validateAddresses([address], true, Config); - - - /* Address isn't valid */ - if (addressError.errorCode !== WalletErrorCode.SUCCESS) { - return { - valid: false, - error: 'QR Code is invalid', - }; - } - - const amountAtomic = Number(amount); - - /* No name, need to pick one.. */ - if (!name) { - return { - paymentID: paymentID || '', - address, - // amount: !isNaN(amountAtomic) ? amountAtomic : undefined, - suggestedAction: 'NewPayee', - valid: true, - } - } - - const newPayee = { - nickname: name, - address: address, - paymentID: paymentID || '', - } - - const existingPayee = Globals.payees.find((p) => p.nickname === name); - - /* Payee exists already */ - if (existingPayee) { - /* New payee doesn't match existing payee, get them to enter a new name */ - if (existingPayee.address !== newPayee.address || - existingPayee.paymentID !== newPayee.paymentID) { - return { - paymentID: paymentID || '', - address, - amount: amountAtomic, - suggestedAction: 'NewPayee', - valid: true, - }; - } - /* Save payee to database for later use */ - } else { - Globals.addPayee(newPayee); - } - - if (!amount) { - return { - payee: newPayee, - suggestedAction: 'Transfer', - valid: true, - }; - } else { - return { - payee: newPayee, - amount: amountAtomic, - suggestedAction: 'Confirm', - valid: true, - }; - } - /* It's a standard address, try and parse it (or something else) */ - } else { - const addressError = validateAddresses([qrData], true, Config); + /* It's a URI, try and get the data from it */ + if (qrData.startsWith(Config.uriPrefix)) { + /* Remove the turtlecoin:// prefix */ + let data = qrData.replace(Config.uriPrefix, ''); + let index = data.indexOf('?'); + + /* Doesn't have any params */ + if (index === -1) { + index = data.length; + } + + const address = data.substr(0, index); + const params = Qs.parse(data.substr(index)); + + console.log(params); + console.log(address); + const amount = params.amount; + const name = params.name; + let paymentID = params.paymentid; - if (addressError.errorCode !== WalletErrorCode.SUCCESS) { - return { - valid: false, - error: 'QR code is invalid', - }; - } + if (paymentID) { + const pidError = validatePaymentID(paymentID); + /* Payment ID isn't valid. */ + if (pidError.errorCode !== WalletErrorCode.SUCCESS) { return { - valid: true, - address: qrData, - suggestedAction: 'NewPayee', - } + valid: false, + error: 'QR Code is invalid', + }; + } + + /* Both integrated address and payment ID given */ + if (address.length === Config.integratedAddressLength && paymentID.length !== 0) { + return { + valid: false, + error: 'QR Code is invalid', + }; + } } -} -export function validAmount(amount, unlockedBalance) { - if (amount === '' || amount === undefined || amount === null) { - return [false, '']; + const addressError = await validateAddresses([address], true, Config); + + + /* Address isn't valid */ + if (addressError.errorCode !== WalletErrorCode.SUCCESS) { + return { + valid: false, + error: 'QR Code is invalid', + }; + } + + const amountAtomic = Number(amount); + + /* No name, need to pick one.. */ + if (!name) { + return { + paymentID: paymentID || '', + address, + // amount: !isNaN(amountAtomic) ? amountAtomic : undefined, + suggestedAction: 'NewPayee', + valid: true, + } } - /* Remove commas in input */ - amount = amount.replace(/,/g, ''); + const newPayee = { + nickname: name, + address: address, + paymentID: paymentID || '', + } - let numAmount = Number(amount); + const existingPayee = Globals.payees.find((p) => p.nickname === name); - if (isNaN(numAmount)) { - return [false, 'Amount is not a number!']; + /* Payee exists already */ + if (existingPayee) { + /* New payee doesn't match existing payee, get them to enter a new name */ + if (existingPayee.address !== newPayee.address || + existingPayee.paymentID !== newPayee.paymentID) { + return { + paymentID: paymentID || '', + address, + amount: amountAtomic, + suggestedAction: 'NewPayee', + valid: true, + }; + } + /* Save payee to database for later use */ + } else { + Globals.addPayee(newPayee); } - /* Remove fractional component and convert to atomic */ - numAmount = Math.floor(toAtomic(numAmount)); + if (!amount) { + return { + payee: newPayee, + suggestedAction: 'Transfer', + valid: true, + }; + } else { + return { + payee: newPayee, + amount: amountAtomic, + suggestedAction: 'Confirm', + valid: true, + }; + } + /* It's a standard address, try and parse it (or something else) */ + } else { + const addressError = validateAddresses([qrData], true, Config); - /* Must be above min send */ - if (numAmount < 1) { - return [false, 'Amount is below minimum send!']; + if (addressError.errorCode !== WalletErrorCode.SUCCESS) { + return { + valid: false, + error: 'QR code is invalid', + }; } - if (numAmount > unlockedBalance) { - return [false, 'Not enough funds available!']; + return { + valid: true, + address: qrData, + suggestedAction: 'NewPayee', } + } +} + +export function validAmount(amount, unlockedBalance) { + if (amount === '' || amount === undefined || amount === null) { + return [false, '']; + } + + /* Remove commas in input */ + amount = amount.replace(/,/g, ''); + + let numAmount = Number(amount); + + if (isNaN(numAmount)) { + return [false, 'Amount is not a number!']; + } + + /* Remove fractional component and convert to atomic */ + numAmount = Math.floor(toAtomic(numAmount)); + + /* Must be above min send */ + if (numAmount < 1) { + return [false, 'Amount is below minimum send!']; + } + + if (numAmount > unlockedBalance) { + return [false, 'Not enough funds available!']; + } - return [true, '']; + return [true, '']; } export function prettyPrintAmountMainScreen(amount) { @@ -402,7 +402,7 @@ export function prettyPrintAmountMainScreen(amount) { amount = amount; } else { // Between 1K & 100K XKR - amount = amount.toString().split('.')[0] = amount.toString().slice(0,-3); + amount = amount.toString().split('.')[0] = amount.toString().slice(0, -3); } return amount; } diff --git a/src/components/button.js b/src/components/button.js new file mode 100644 index 0000000..9c3379a --- /dev/null +++ b/src/components/button.js @@ -0,0 +1,43 @@ +import React from "react"; +import { StyleSheet, TouchableOpacity } from "react-native"; + +import { Themes } from "../Themes"; + +export class Button extends React.Component { + + constructor(props) { + super(props); + } + + render() { + const { children, onPress, backgroundColor: mBackgroundColor, borderColor: mBorderColor, color: mColor, style, icon, disabled, primary } = this.props; + const backgroundColor = mBackgroundColor ?? Themes.darkMode.backgroundEmphasis; + const borderColor = mBorderColor ?? primary ? Themes.darkMode.primaryColour : Themes.darkMode.borderColour; + const color = mColor ?? Themes.darkMode.primaryColour; + + if (!onPress || !children) { + return null; + } + + return ( + + {icon} + {children} + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + borderRadius: 15, + borderWidth: 1, + padding: 12, + marginVertical: 4, + // overflow: 'hidden', + alignItems: 'center', + flex: 1, + minHeight: 50 + } +}); diff --git a/src/components/card.js b/src/components/card.js new file mode 100644 index 0000000..b79e1ec --- /dev/null +++ b/src/components/card.js @@ -0,0 +1,39 @@ + +import React from 'react'; + +import { StyleSheet, View } from 'react-native'; +import { Themes } from '../Themes'; + +export class Card extends React.Component { + constructor(props) { + super(props); + } + + render() { + const { children, centered, flexDirection: mFlexDirection, backgroundColor: mBackgroundColor, borderColor: mBorderColor } = this.props; + const flexDirection = mFlexDirection ?? 'column'; + const backgroundColor = mBackgroundColor ?? Themes.darkMode.backgroundEmphasis; + const borderColor = mBorderColor ?? Themes.darkMode.borderColour; + const alignItems = centered && 'center'; + + if (!children) { + return null; + } + + return ( + + {children} + + ); + } +} + +const styles = StyleSheet.create({ + container: { + width: '100%', + borderRadius: 15, + borderWidth: 1, + padding: 12, + margin: 4, + } +}); diff --git a/src/components/index.js b/src/components/index.js new file mode 100644 index 0000000..1c63c27 --- /dev/null +++ b/src/components/index.js @@ -0,0 +1,7 @@ + +export * from './button' +export * from './card' +export * from './input' +export * from './screen-header' +export * from './screen-layout' +export * from './text-field' diff --git a/src/components/input.js b/src/components/input.js new file mode 100644 index 0000000..5815bb1 --- /dev/null +++ b/src/components/input.js @@ -0,0 +1,75 @@ + +import React from 'react'; +import { Themes } from '../Themes'; +import { StyleSheet } from 'react-native'; + +import { Input } from 'react-native-elements'; + +export class InputField extends React.Component { + constructor(props) { + super(props); + this.state = { + value: props.value, + }; + } + + render() { + return ( + + ); + } +} + +const styles = StyleSheet.create({ + container: { + width: '100%', + marginBottom: 15, + marginHorizontal: 0, + fontFamily: 'Montserrat-Regular', + }, + input: { + borderRadius: 15, + fontSize: 14, + marginLeft: 5, + fontFamily: 'Montserrat-SemiBold', + }, + label: { + fontFamily: 'Montserrat-Regular', + fontSize: 12, + marginBottom: 5, + marginRight: 2, + }, + inputContainer: { + width: '100%', + borderWidth: 0, + borderRadius: 15, + backgroundColor: "rgba(0,0,0,0.2)", + borderColor: 'transparent' + } +}); diff --git a/src/components/screen-header.js b/src/components/screen-header.js new file mode 100644 index 0000000..1ad7b13 --- /dev/null +++ b/src/components/screen-header.js @@ -0,0 +1,37 @@ +import React from "react"; + +import { StyleSheet, View, Text } from "react-native"; + +import { Themes } from "../Themes"; + +export class ScreenHeader extends React.Component { + + constructor(props) { + super(props); + } + + render() { + const { children, style } = this.props; + + const color = Themes.darkMode.primaryColour; + + return children && ( + + {children} + + ); + } +} + +const styles = StyleSheet.create({ + container: { + padding: 12, + fontFamily: "Montserrat-SemiBold", + marginVertical: 16 + }, + text: { + fontSize: 24, + fontFamily: "Montserrat-SemiBold", + flexShrink: 1 + } +}); diff --git a/src/components/screen-layout.js b/src/components/screen-layout.js new file mode 100644 index 0000000..a6c20ea --- /dev/null +++ b/src/components/screen-layout.js @@ -0,0 +1,29 @@ +import React from "react"; + +import { StyleSheet, View } from "react-native"; +import { Themes } from "../Themes"; + +export class ScreenLayout extends React.Component { + + constructor(props) { + super(props); + } + + render() { + const { children, style } = this.props; + return ( + + {children} + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: Themes.darkMode.backgroundColour, + padding: 12, + alignItems: 'stretch' + } +}); diff --git a/src/components/text-field.js b/src/components/text-field.js new file mode 100644 index 0000000..ce11348 --- /dev/null +++ b/src/components/text-field.js @@ -0,0 +1,29 @@ +import React from "react"; +import { StyleSheet, Text } from "react-native"; +import { Themes } from "../Themes"; + +export class TextField extends React.Component { + + constructor(props) { + super(props); + } + render() { + const { children, style, color: mColor, centered } = this.props; + const color = mColor ?? Themes.darkMode.primaryColour; + + return {children}; + + } +} + +const styles = StyleSheet.create({ + text: { + fontSize: 14, + fontFamily: 'Montserrat-Bold', + flexShrink: 1 + }, + centered: { + textAlign: 'center', + width: '100%' + } +}); diff --git a/src/i18n/en.json b/src/i18n/en.json index 3a3ea39..e40aeb4 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -3,8 +3,8 @@ messageKey: "Message key", copy: "Copy", copied: " copied", - noTxMessage: "You don\'t have any transactions yet 😥", - notSyncedMessage: "\n\n Your wallet is still syncing, please wait..", + noTxMessage: "You don't have any transactions yet 😥", + notSyncedMessage: "Your wallet is still syncing, please wait..", emptyAddressBook: "Your address book is empty! Add a new recipient above to populate it.", messagesTitle: "Private Messages", sendToWho: "Send transaction", @@ -199,4 +199,4 @@ clearKnownMessagesDescr: "This will resync all messages from the past 24h", alreadyOptimizing: "Already optimizing. Please wait until the ongoing optimization completes.", deleteContactWarning: "Are you sure you want to delete this contact?" -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ace2592..af1a000 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5045,10 +5045,10 @@ kryptokrona-utils@^1.3.8: optionalDependencies: "@ledgerhq/hw-transport-node-hid" "^5.22.0" -kryptokrona-wallet-backend-js@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/kryptokrona-wallet-backend-js/-/kryptokrona-wallet-backend-js-2.5.1.tgz#c361121e0c8a05250ba7bb8481a6f300b13a1526" - integrity sha512-bjOShLXCLKljrCGtvo+Og0hVHd9J3sUwunHK7D509DNVTb/QtvTnX/sspxOGZ9oZ8uetDvylEC1nloUYdJqlnA== +kryptokrona-wallet-backend-js@^2.5.5: + version "2.5.5" + resolved "https://registry.yarnpkg.com/kryptokrona-wallet-backend-js/-/kryptokrona-wallet-backend-js-2.5.5.tgz#7a804b11b521383d3cd1e2aeda4a38abd5cff075" + integrity sha512-QQNZ4/R7ieuNt0XTOXYS59QbQiFq0uYdcCRSZkBgqJ+Of0sC3EKLNH9moJPZgAvjrnuipCvymA4uvga7JbqiIw== dependencies: kryptokrona-utils "^1.3.8" lodash "^4.17.15" @@ -6976,6 +6976,11 @@ react-native-hyperlink@^0.0.19: linkify-it "^2.2.0" mdurl "^1.0.0" +react-native-incall-manager@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/react-native-incall-manager/-/react-native-incall-manager-4.1.0.tgz#38601a2796932c0d815654f4c838722a2207bf56" + integrity sha512-v1c+XOGu5VudY5//E3i5xiaRA9v6RvevMzZ4RumLqI+hte+4XslB2z6HSek2FF0EmAnY1rCn4ckiwgkTI1Tmtw== + react-native-invertible-scroll-view@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/react-native-invertible-scroll-view/-/react-native-invertible-scroll-view-2.0.0.tgz#799ee99eeac17600e35418dce288ba8b99ab8cd7"