diff --git a/src/mobile/android/app/build.gradle b/src/mobile/android/app/build.gradle index f3f3fd20ab..191912644f 100644 --- a/src/mobile/android/app/build.gradle +++ b/src/mobile/android/app/build.gradle @@ -92,7 +92,7 @@ android { buildToolsVersion '27.0.3' defaultConfig { applicationId "com.iota.trinity" - versionCode 41 + versionCode 42 versionName "0.6.1" minSdkVersion 21 targetSdkVersion 26 diff --git a/src/mobile/ios/iotaWallet-tvOS/Info.plist b/src/mobile/ios/iotaWallet-tvOS/Info.plist index 6ba8f8b118..f343b1a4da 100644 --- a/src/mobile/ios/iotaWallet-tvOS/Info.plist +++ b/src/mobile/ios/iotaWallet-tvOS/Info.plist @@ -19,7 +19,7 @@ CFBundleSignature ???? CFBundleVersion - 41 + 42 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/src/mobile/ios/iotaWallet-tvOSTests/Info.plist b/src/mobile/ios/iotaWallet-tvOSTests/Info.plist index c7363ad129..cf3b3e3d16 100644 --- a/src/mobile/ios/iotaWallet-tvOSTests/Info.plist +++ b/src/mobile/ios/iotaWallet-tvOSTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 41 + 42 diff --git a/src/mobile/ios/iotaWallet.xcodeproj/project.pbxproj b/src/mobile/ios/iotaWallet.xcodeproj/project.pbxproj index 6d1edbcbef..c099f2e455 100644 --- a/src/mobile/ios/iotaWallet.xcodeproj/project.pbxproj +++ b/src/mobile/ios/iotaWallet.xcodeproj/project.pbxproj @@ -2910,7 +2910,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 41; + CURRENT_PROJECT_VERSION = 42; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = YES; @@ -3305,7 +3305,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 41; + CURRENT_PROJECT_VERSION = 42; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = YES; FRAMEWORK_SEARCH_PATHS = ( diff --git a/src/mobile/ios/iotaWallet/Info.plist b/src/mobile/ios/iotaWallet/Info.plist index 15b67428e4..9ba45d3e1f 100644 --- a/src/mobile/ios/iotaWallet/Info.plist +++ b/src/mobile/ios/iotaWallet/Info.plist @@ -25,7 +25,7 @@ CFBundleSignature ???? CFBundleVersion - 41 + 42 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/src/mobile/ios/iotaWalletTests/Info.plist b/src/mobile/ios/iotaWalletTests/Info.plist index 2c142cde1a..7b7776f67e 100644 --- a/src/mobile/ios/iotaWalletTests/Info.plist +++ b/src/mobile/ios/iotaWalletTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 41 + 42 diff --git a/src/mobile/ios/iotaWalletUITests/Info.plist b/src/mobile/ios/iotaWalletUITests/Info.plist index 19665b427c..c3a73854c0 100644 --- a/src/mobile/ios/iotaWalletUITests/Info.plist +++ b/src/mobile/ios/iotaWalletUITests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 0.6.1 CFBundleVersion - 41 + 42 diff --git a/src/mobile/src/ui/components/Migration.js b/src/mobile/src/ui/components/Migration.js index 633bca690e..7dd657ddb4 100644 --- a/src/mobile/src/ui/components/Migration.js +++ b/src/mobile/src/ui/components/Migration.js @@ -1,4 +1,5 @@ import size from 'lodash/size'; +import sample from 'lodash/sampleSize'; import React, { Component } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import PropTypes from 'prop-types'; @@ -8,13 +9,16 @@ import { startTrackingProgress } from 'shared-modules/actions/progress'; import { connect } from 'react-redux'; import { Styling } from 'ui/theme/general'; import { migrate } from 'shared-modules/actions/migrations'; +import { setFullNode } from 'shared-modules/actions/settings'; import { reduxPersistStorageAdapter } from 'libs/store'; import ProgressSteps from 'libs/progressSteps'; import { getThemeFromState } from 'shared-modules/selectors/global'; import Header from 'ui/components/Header'; import InfoBox from 'ui/components/InfoBox'; +import NotificationButtonComponent from 'ui/components/NotificationButton'; import ProgressBar from 'ui/components/OldProgressBar'; import { height } from 'libs/dimensions'; +import DualFooterButtons from './DualFooterButtons'; const styles = StyleSheet.create({ container: { @@ -29,15 +33,25 @@ const styles = StyleSheet.create({ paddingTop: height / 16, }, midContainer: { - flex: 2.6, + flex: 1.9, alignItems: 'center', }, + bottomContainer: { + flex: 0.7, + alignItems: 'center', + justifyContent: 'flex-end', + }, infoText: { fontFamily: 'SourceSansPro-Light', fontSize: Styling.fontSize3, textAlign: 'left', backgroundColor: 'transparent', }, + notificationButton: { + position: 'absolute', + top: height / 30 + Styling.statusBarHeight, + left: height / 30, + }, }); /** @@ -59,11 +73,27 @@ class Migration extends Component { startTrackingProgress: PropTypes.func.isRequired, /** @ignore */ completedMigration: PropTypes.bool.isRequired, + /** @ignore */ + notificationLog: PropTypes.array.isRequired, + /** @ignore */ + nodes: PropTypes.array.isRequired, + /** @ignore */ + setFullNode: PropTypes.func.isRequired, + /** @ignore */ + isChangingNode: PropTypes.bool.isRequired, }; + constructor() { + super(); + this.state = { + hasFailedMigration: false, + }; + this.changeNode = this.changeNode.bind(this); + this.retryMigration = this.retryMigration.bind(this); + } + componentDidMount() { this.props.startTrackingProgress(ProgressSteps.migration); - this.props.migrate(reduxPersistStorageAdapter); } @@ -71,6 +101,9 @@ class Migration extends Component { if (!this.props.completedMigration && newProps.completedMigration) { this.navigateToLoadingScreen(); } + if (size(this.props.notificationLog) !== size(newProps.notificationLog)) { + this.setState({ hasFailedMigration: true }); + } } /** @@ -107,6 +140,25 @@ class Migration extends Component { }); } + /** + * Changes to a random node + * + * @method changeNode + */ + changeNode() { + this.props.setFullNode(...sample(this.props.nodes)); + } + + /** + * Retries migration in case of failure + * + * @method retryMigration + */ + retryMigration() { + this.setState({ hasFailedMigration: false }); + this.props.migrate(reduxPersistStorageAdapter); + } + /** * Renders progress bar textual information * @@ -119,7 +171,7 @@ class Migration extends Component { } render() { - const { t, theme: { body, primary }, activeSteps, activeStepIndex } = this.props; + const { t, theme: { body, primary }, activeSteps, activeStepIndex, isChangingNode } = this.props; const textColor = { color: body.color }; const sizeOfActiveSteps = size(activeSteps) - 1; @@ -135,18 +187,36 @@ class Migration extends Component { - - {t(this.renderProgressBarChildren())} - + {activeStepIndex > -1 && ( + + {t(this.renderProgressBarChildren())} + + )} + + + {this.state.hasFailedMigration && ( + + )} + {this.state.hasFailedMigration && ( + + + + )} ); } @@ -157,11 +227,15 @@ const mapStateToProps = (state) => ({ activeStepIndex: state.progress.activeStepIndex, activeSteps: state.progress.activeSteps, completedMigration: state.settings.completedMigration, + notificationLog: state.alerts.notificationLog, + nodes: state.settings.nodes, + isChangingNode: state.ui.isChangingNode, }); const mapDispatchToProps = { migrate, startTrackingProgress, + setFullNode, }; export default withNamespaces(['migration'])(connect(mapStateToProps, mapDispatchToProps)(Migration)); diff --git a/src/mobile/src/ui/components/NotificationButton.js b/src/mobile/src/ui/components/NotificationButton.js new file mode 100644 index 0000000000..e9b1381698 --- /dev/null +++ b/src/mobile/src/ui/components/NotificationButton.js @@ -0,0 +1,104 @@ +import size from 'lodash/size'; +import React, { Component } from 'react'; +import { StyleSheet, View, TouchableOpacity } from 'react-native'; +import { getThemeFromState } from 'shared-modules/selectors/global'; +import { clearLog } from 'shared-modules/actions/alerts'; +import { toggleModalActivity } from 'shared-modules/actions/ui'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { withNamespaces } from 'react-i18next'; +import { width } from 'libs/dimensions'; +import { Icon } from 'ui/theme/icons'; + +const styles = StyleSheet.create({ + container: { + width: width / 9, + height: width / 9, + }, + innerContainer: { + justifyContent: 'center', + alignItems: 'center', + width: width / 9, + flex: 1, + }, + disabled: { + color: '#a9a9a9', + }, +}); + +export class NotificationButton extends Component { + static propTypes = { + /** @ignore */ + theme: PropTypes.object.isRequired, + /** @ignore */ + isModalActive: PropTypes.bool.isRequired, + /** @ignore */ + clearLog: PropTypes.func.isRequired, + /** @ignore */ + toggleModalActivity: PropTypes.func.isRequired, + /** @ignore */ + isTransitioning: PropTypes.bool.isRequired, + /** @ignore */ + notificationLog: PropTypes.array.isRequired, + /** Determines whether to display topBar when modal is open */ + displayTopBar: PropTypes.bool, + }; + + static defaultProps = { + displayTopBar: true, + }; + + /** + * Displays error log + * + * @method showModal + */ + showModal() { + const { isTransitioning, theme, notificationLog, displayTopBar } = this.props; + if (!isTransitioning) { + this.props.toggleModalActivity('notificationLog', { + hideModal: this.props.toggleModalActivity, + theme, + notificationLog, + clearLog: this.props.clearLog, + displayTopBar, + }); + } + } + + render() { + const { theme: { bar }, isModalActive, notificationLog } = this.props; + const hasNotifications = size(notificationLog) && notificationLog.length > 0; + + return ( + + {(hasNotifications && ( + this.showModal()} style={styles.innerContainer}> + + + )) || } + + ); + } +} + +const mapStateToProps = (state) => ({ + theme: getThemeFromState(state), + isModalActive: state.ui.isModalActive, + isTransitioning: state.ui.isTransitioning, + notificationLog: state.alerts.notificationLog, +}); + +const mapDispatchToProps = { + clearLog, + toggleModalActivity, +}; + +export default withNamespaces(['enterSeed', 'global'])( + connect(mapStateToProps, mapDispatchToProps)(NotificationButton), +); diff --git a/src/mobile/src/ui/components/NotificationLogModal.js b/src/mobile/src/ui/components/NotificationLogModal.js index 5abcd5acf4..72fee5754a 100644 --- a/src/mobile/src/ui/components/NotificationLogModal.js +++ b/src/mobile/src/ui/components/NotificationLogModal.js @@ -52,6 +52,12 @@ export class NotificationLogModal extends PureComponent { clearLog: PropTypes.func.isRequired, /** @ignore */ t: PropTypes.func.isRequired, + /** Deteremines whether to display the topbar */ + displayTopBar: PropTypes.bool, + }; + + static defaultProps = { + displayTopBar: true, }; constructor() { @@ -84,7 +90,7 @@ export class NotificationLogModal extends PureComponent { return ( this.clearNotificationLog()} onRightButtonPress={() => this.props.hideModal()} diff --git a/src/mobile/src/ui/components/TopBar.js b/src/mobile/src/ui/components/TopBar.js index 18250088ec..7c6f32b49b 100644 --- a/src/mobile/src/ui/components/TopBar.js +++ b/src/mobile/src/ui/components/TopBar.js @@ -8,7 +8,6 @@ import { connect } from 'react-redux'; import { withNamespaces } from 'react-i18next'; import { toggleTopBarDisplay } from 'shared-modules/actions/home'; import { setSeedIndex } from 'shared-modules/actions/wallet'; -import { clearLog } from 'shared-modules/actions/alerts'; import { toggleModalActivity } from 'shared-modules/actions/ui'; import { getBalanceForSelectedAccount, @@ -34,6 +33,7 @@ import { accumulateBalance } from 'shared-modules/libs/iota/addresses'; import { Icon } from 'ui/theme/icons'; import { isIPhoneX } from 'libs/device'; import { Styling } from 'ui/theme/general'; +import NotificationButtonComponent from 'ui/components/NotificationButton'; const { height, width } = Dimensions.get('window'); @@ -130,10 +130,6 @@ class TopBar extends Component { theme: PropTypes.object.isRequired, /** @ignore */ setPollFor: PropTypes.func.isRequired, - /** @ignore */ - notificationLog: PropTypes.array.isRequired, - /** @ignore */ - clearLog: PropTypes.func.isRequired, /** Top bar height */ topBarHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), /** @ignore */ @@ -277,18 +273,6 @@ class TopBar extends Component { ); } - showModal() { - const { isTransitioning, theme, notificationLog } = this.props; - if (!isTransitioning) { - this.props.toggleModalActivity('notificationLog', { - hideModal: () => this.hideModal(), - theme, - notificationLog, - clearLog: this.props.clearLog, - }); - } - } - hideModal() { this.props.toggleModalActivity(); } @@ -302,7 +286,6 @@ class TopBar extends Component { seedIndex, theme: { bar, primary }, isKeyboardActive, - notificationLog, mode, minimised, currentRoute, @@ -327,7 +310,6 @@ class TopBar extends Component { const withSubtitles = (title, index) => ({ title, subtitle: getBalance(index), index }); const titles = map(accountNames, withSubtitles); const hasMultipleSeeds = size(TopBar.filterSeedTitles(accountNames, seedIndex)); - const hasNotifications = size(notificationLog) && notificationLog.length > 0; const shouldDisable = this.shouldDisable(); const baseContent = ( @@ -344,22 +326,10 @@ class TopBar extends Component { {(!isKeyboardActive && !minimised && ( - - {(hasNotifications && - !isKeyboardActive && - mode === 'Advanced' && ( - this.showModal()} - style={styles.iconWrapper} - > - - - )) || } + + {(!isKeyboardActive && mode === 'Advanced' && ) || ( + + )} - + {(hasMultipleSeeds && ( ({ isTopBarActive: state.home.isTopBarActive, selectedAccount: selectAccountInfo(state), theme: getThemeFromState(state), - notificationLog: state.alerts.notificationLog, isFetchingLatestAccountInfo: state.ui.isFetchingAccountInfo, currentRoute: state.home.childRoute, isKeyboardActive: state.ui.isKeyboardActive, @@ -557,7 +526,6 @@ const mapDispatchToProps = { toggleModalActivity, setSeedIndex, setPollFor, - clearLog, }; export default withNamespaces('global')(connect(mapStateToProps, mapDispatchToProps)(TopBar)); diff --git a/src/mobile/src/ui/routes/entry.js b/src/mobile/src/ui/routes/entry.js index 797ecd1529..d5aaca435f 100644 --- a/src/mobile/src/ui/routes/entry.js +++ b/src/mobile/src/ui/routes/entry.js @@ -95,6 +95,7 @@ const renderInitialScreen = (initialScreen, state) => { drawBehind: true, backgroundColor: theme.body.bg, }, + popGesture: false, }; Navigation.setDefaultOptions(options); @@ -203,7 +204,7 @@ onAppStart() const { settings: { versions, completedMigration } } = reduxState; if ( - versions.buildNumber < 42 && + versions.buildNumber < 43 && completedMigration === false && // Also check if there is persisted data in AsyncStorage that needs to be migrated // If this check is omitted, the condition will be satisfied on a fresh install. diff --git a/src/mobile/src/ui/views/wallet/History.js b/src/mobile/src/ui/views/wallet/History.js index f732fc208d..15d9652afe 100644 --- a/src/mobile/src/ui/views/wallet/History.js +++ b/src/mobile/src/ui/views/wallet/History.js @@ -146,6 +146,7 @@ class History extends Component { newProps.isPromotingTransaction || newProps.isRetryingFailedTransaction, isRetryingFailedTransaction: newProps.isRetryingFailedTransaction, + isFailedTransaction: !newBundleProps.broadcasted, bundleIsBeingPromoted: newProps.currentlyPromotingBundleHash === modalProps.bundle && !newBundleProps.persistence, }); diff --git a/src/shared/actions/migrations.js b/src/shared/actions/migrations.js index aa740136c3..4ba6103355 100644 --- a/src/shared/actions/migrations.js +++ b/src/shared/actions/migrations.js @@ -95,6 +95,7 @@ export const migrate = (oldStorageAdapter) => (dispatch) => { // If there is not data to migrate, just mark migration as complete onComplete(); } else { + dispatch(resetProgress()); dispatch( generateAlert( 'error', diff --git a/src/shared/locales/en/translation.json b/src/shared/locales/en/translation.json index 2cfbba9f60..9dc667a6fc 100644 --- a/src/shared/locales/en/translation.json +++ b/src/shared/locales/en/translation.json @@ -781,7 +781,7 @@ }, "migration": { "dataMigration": "Data migration", - "dataMigrationExplanation": "Due to performance and storage limitations, Trinity is using a new database for storing settings and account data. Please wait while Trinity automatically migrates your existing data", + "dataMigrationExplanation": "Due to performance and storage limitations, Trinity is using a new database for storing settings and account data. Please wait while Trinity automatically migrates your existing data.", "problemMigratingYourData": "There was a problem migrating your data. Please try again" }, "ledger": { @@ -816,4 +816,4 @@ "applicationNotInitialised": "Warning unconfirmed", "applicationNotInitialisedExplanation": "Reopen the IOTA app on your Ledger device, accept the informational warning and try again." } -} \ No newline at end of file +} diff --git a/src/shared/storage/index.js b/src/shared/storage/index.js index 66cb548480..4f1a1b395d 100644 --- a/src/shared/storage/index.js +++ b/src/shared/storage/index.js @@ -170,7 +170,7 @@ class Account { realm.write(() => { // Create account with new name. - realm.create('Account', assign({}, accountData, { accountName: to })); + realm.create('Account', assign({}, accountData, { name: to })); // Delete account with old name. realm.delete(accountData); });