merged with master

pull/173/head
James Sangalli 7 years ago
commit cdeb7ebc43
  1. 16
      Trust.xcodeproj/project.pbxproj
  2. 55
      Trust/InCoordinator.swift
  3. 8
      Trust/Localization/en.lproj/Localizable.strings
  4. 2
      Trust/Tokens/ViewModels/TokensViewModel.swift
  5. 51
      Trust/Transactions/Coordinators/TicketsCoordinator.swift
  6. 4
      Trust/Transactions/Coordinators/TransactionCoordinator.swift
  7. 23
      Trust/Transactions/ViewControllers/TransactionsViewController.swift
  8. 13
      Trust/Transactions/Views/TransactionsFooterView.swift
  9. 9
      Trust/Transfer/Coordinators/PaymentCoordinator.swift
  10. 24
      Trust/Transfer/Coordinators/SendCoordinator.swift
  11. 47
      Trust/Transfer/ViewControllers/ChooseTicketTransferModeViewController.swift
  12. 638
      Trust/Transfer/ViewControllers/SendViewController.swift
  13. 89
      Trust/Transfer/ViewModels/SendHeaderViewViewModel.swift
  14. 135
      Trust/Transfer/ViewModels/SendViewModel.swift
  15. 132
      Trust/Transfer/Views/SendHeaderView.swift
  16. 2
      TrustTests/Coordinators/SendCoordinatorTests.swift

@ -49,7 +49,6 @@
2923D9B51FDA4E07000CF3F8 /* PasswordGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2923D9B41FDA4E07000CF3F8 /* PasswordGenerator.swift */; }; 2923D9B51FDA4E07000CF3F8 /* PasswordGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2923D9B41FDA4E07000CF3F8 /* PasswordGenerator.swift */; };
2923D9B71FDA5E51000CF3F8 /* PasswordGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2923D9B61FDA5E51000CF3F8 /* PasswordGeneratorTests.swift */; }; 2923D9B71FDA5E51000CF3F8 /* PasswordGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2923D9B61FDA5E51000CF3F8 /* PasswordGeneratorTests.swift */; };
29282B531F7630970067F88D /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29282B521F7630970067F88D /* Token.swift */; }; 29282B531F7630970067F88D /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29282B521F7630970067F88D /* Token.swift */; };
29285B421F6FB3E60044CF29 /* SendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29285B411F6FB3E60044CF29 /* SendViewController.swift */; };
293112101FC4ADCB00966EEA /* InCoordinatorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931120F1FC4ADCB00966EEA /* InCoordinatorViewModel.swift */; }; 293112101FC4ADCB00966EEA /* InCoordinatorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931120F1FC4ADCB00966EEA /* InCoordinatorViewModel.swift */; };
293112121FC4F48400966EEA /* ServiceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 293112111FC4F48400966EEA /* ServiceProvider.swift */; }; 293112121FC4F48400966EEA /* ServiceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 293112111FC4F48400966EEA /* ServiceProvider.swift */; };
2931122E1FC94E4200966EEA /* SettingsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931122D1FC94E4200966EEA /* SettingsError.swift */; }; 2931122E1FC94E4200966EEA /* SettingsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931122D1FC94E4200966EEA /* SettingsError.swift */; };
@ -287,6 +286,7 @@
5E7C70EEFB9D9745C6CF7578 /* ChooseTicketTransferModeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D177F5297D4A3DB3F33 /* ChooseTicketTransferModeViewController.swift */; }; 5E7C70EEFB9D9745C6CF7578 /* ChooseTicketTransferModeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D177F5297D4A3DB3F33 /* ChooseTicketTransferModeViewController.swift */; };
5E7C70FF17622C0FFD45A542 /* AlphaWalletSettingPushRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D2AAB777BF35B8B56BD /* AlphaWalletSettingPushRow.swift */; }; 5E7C70FF17622C0FFD45A542 /* AlphaWalletSettingPushRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D2AAB777BF35B8B56BD /* AlphaWalletSettingPushRow.swift */; };
5E7C710331196CD591B51785 /* LockCreatePasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C741196D9D9C9C3EE5E30 /* LockCreatePasscodeViewController.swift */; }; 5E7C710331196CD591B51785 /* LockCreatePasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C741196D9D9C9C3EE5E30 /* LockCreatePasscodeViewController.swift */; };
5E7C713ACE8C72642B1C9F93 /* SendHeaderViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B7A45EDFA8ED1E25863 /* SendHeaderViewViewModel.swift */; };
5E7C71A6B0BDF301747A49AE /* ScreenChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C77E1E6194F5A1DC8D645 /* ScreenChecker.swift */; }; 5E7C71A6B0BDF301747A49AE /* ScreenChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C77E1E6194F5A1DC8D645 /* ScreenChecker.swift */; };
5E7C71A7D2BD6FCE3980CC51 /* ImportWalletHelpBubbleViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7A16ABC8BD5D508AA641 /* ImportWalletHelpBubbleViewViewModel.swift */; }; 5E7C71A7D2BD6FCE3980CC51 /* ImportWalletHelpBubbleViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7A16ABC8BD5D508AA641 /* ImportWalletHelpBubbleViewViewModel.swift */; };
5E7C71B52A77008694BFA5D1 /* TokensDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74B9EB81C51E956566E7 /* TokensDataStore.swift */; }; 5E7C71B52A77008694BFA5D1 /* TokensDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74B9EB81C51E956566E7 /* TokensDataStore.swift */; };
@ -299,6 +299,7 @@
5E7C72AF95DCE8BC65490BCA /* StatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B82CC07F290B9CAA4E4 /* StatusViewController.swift */; }; 5E7C72AF95DCE8BC65490BCA /* StatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B82CC07F290B9CAA4E4 /* StatusViewController.swift */; };
5E7C72B0A10A92E591696E48 /* ContactUsBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AE6FAE0DF969B4F52E9 /* ContactUsBannerView.swift */; }; 5E7C72B0A10A92E591696E48 /* ContactUsBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AE6FAE0DF969B4F52E9 /* ContactUsBannerView.swift */; };
5E7C72C8A15397C5A40BFE76 /* WhatIsEthereumInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C774BCA281E4B077DBBFA /* WhatIsEthereumInfoViewController.swift */; }; 5E7C72C8A15397C5A40BFE76 /* WhatIsEthereumInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C774BCA281E4B077DBBFA /* WhatIsEthereumInfoViewController.swift */; };
5E7C72E1D4B4B4C8443F3DA1 /* SendHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7828BD821B6F04B71C00 /* SendHeaderView.swift */; };
5E7C731B88842C036A74A039 /* AlphaWalletSettingsButtonRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71EBD4C95AD4E11F3352 /* AlphaWalletSettingsButtonRow.swift */; }; 5E7C731B88842C036A74A039 /* AlphaWalletSettingsButtonRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71EBD4C95AD4E11F3352 /* AlphaWalletSettingsButtonRow.swift */; };
5E7C73305DF984B99E94D9F9 /* TransferModeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7717D829205D1E254AC1 /* TransferModeButton.swift */; }; 5E7C73305DF984B99E94D9F9 /* TransferModeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7717D829205D1E254AC1 /* TransferModeButton.swift */; };
5E7C733638D7596F93DEE2A9 /* OnboardingCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75CE3F1D6B7993E7A840 /* OnboardingCollectionViewController.swift */; }; 5E7C733638D7596F93DEE2A9 /* OnboardingCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75CE3F1D6B7993E7A840 /* OnboardingCollectionViewController.swift */; };
@ -307,6 +308,7 @@
5E7C745DACB5FCCEBCEB49CA /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C793E23E2364B73C4D813 /* WelcomeViewController.swift */; }; 5E7C745DACB5FCCEBCEB49CA /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C793E23E2364B73C4D813 /* WelcomeViewController.swift */; };
5E7C7499A8D6814F7950DA70 /* LockCreatePasscodeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AB3440C01136DF4F3E9 /* LockCreatePasscodeCoordinator.swift */; }; 5E7C7499A8D6814F7950DA70 /* LockCreatePasscodeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AB3440C01136DF4F3E9 /* LockCreatePasscodeCoordinator.swift */; };
5E7C74B99922D0CAB635970E /* PasscodeCharacterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B9220E616F82EDA956F /* PasscodeCharacterView.swift */; }; 5E7C74B99922D0CAB635970E /* PasscodeCharacterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B9220E616F82EDA956F /* PasscodeCharacterView.swift */; };
5E7C7567A690B6B8F889AE83 /* SendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C70088832B2D161EB4AAB /* SendViewController.swift */; };
5E7C75C99B9F595F26EDC405 /* LockPasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D5F3CAE69CF932AB236 /* LockPasscodeViewController.swift */; }; 5E7C75C99B9F595F26EDC405 /* LockPasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D5F3CAE69CF932AB236 /* LockPasscodeViewController.swift */; };
5E7C75D46140FACBD12333BF /* EthTokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7EE374A74F2B00013C18 /* EthTokenViewCell.swift */; }; 5E7C75D46140FACBD12333BF /* EthTokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7EE374A74F2B00013C18 /* EthTokenViewCell.swift */; };
5E7C75E3C4BAE885746BD1B3 /* TransferTicketViaWalletAddressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72B37551451352EBB9F9 /* TransferTicketViaWalletAddressViewController.swift */; }; 5E7C75E3C4BAE885746BD1B3 /* TransferTicketViaWalletAddressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72B37551451352EBB9F9 /* TransferTicketViaWalletAddressViewController.swift */; };
@ -540,7 +542,6 @@
2923D9B41FDA4E07000CF3F8 /* PasswordGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordGenerator.swift; sourceTree = "<group>"; }; 2923D9B41FDA4E07000CF3F8 /* PasswordGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordGenerator.swift; sourceTree = "<group>"; };
2923D9B61FDA5E51000CF3F8 /* PasswordGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordGeneratorTests.swift; sourceTree = "<group>"; }; 2923D9B61FDA5E51000CF3F8 /* PasswordGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordGeneratorTests.swift; sourceTree = "<group>"; };
29282B521F7630970067F88D /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = "<group>"; }; 29282B521F7630970067F88D /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = "<group>"; };
29285B411F6FB3E60044CF29 /* SendViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendViewController.swift; sourceTree = "<group>"; };
2931120F1FC4ADCB00966EEA /* InCoordinatorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InCoordinatorViewModel.swift; sourceTree = "<group>"; }; 2931120F1FC4ADCB00966EEA /* InCoordinatorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InCoordinatorViewModel.swift; sourceTree = "<group>"; };
293112111FC4F48400966EEA /* ServiceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceProvider.swift; sourceTree = "<group>"; }; 293112111FC4F48400966EEA /* ServiceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceProvider.swift; sourceTree = "<group>"; };
2931122D1FC94E4200966EEA /* SettingsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsError.swift; sourceTree = "<group>"; }; 2931122D1FC94E4200966EEA /* SettingsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsError.swift; sourceTree = "<group>"; };
@ -780,6 +781,7 @@
442FCFEB2D7443C4E0B889B0 /* TicketHolder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketHolder.swift; sourceTree = "<group>"; }; 442FCFEB2D7443C4E0B889B0 /* TicketHolder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketHolder.swift; sourceTree = "<group>"; };
477899BEAA4489DA423E8857 /* Pods-TrustUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TrustUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TrustUITests/Pods-TrustUITests.debug.xcconfig"; sourceTree = "<group>"; }; 477899BEAA4489DA423E8857 /* Pods-TrustUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TrustUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TrustUITests/Pods-TrustUITests.debug.xcconfig"; sourceTree = "<group>"; };
4DB8204016307EAFC079EA48 /* Pods-Trust.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Trust.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Trust/Pods-Trust.debug.xcconfig"; sourceTree = "<group>"; }; 4DB8204016307EAFC079EA48 /* Pods-Trust.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Trust.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Trust/Pods-Trust.debug.xcconfig"; sourceTree = "<group>"; };
5E7C70088832B2D161EB4AAB /* SendViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendViewController.swift; sourceTree = "<group>"; };
5E7C7011D8E5C9FFE0E59D55 /* TransferTicketsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsViewController.swift; sourceTree = "<group>"; }; 5E7C7011D8E5C9FFE0E59D55 /* TransferTicketsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsViewController.swift; sourceTree = "<group>"; };
5E7C703BA1D0E9ACB7399155 /* TransferTicketsQuantitySelectionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsQuantitySelectionViewModel.swift; sourceTree = "<group>"; }; 5E7C703BA1D0E9ACB7399155 /* TransferTicketsQuantitySelectionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsQuantitySelectionViewModel.swift; sourceTree = "<group>"; };
5E7C70CC85B337061151724E /* ImportWalletHelpBubbleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletHelpBubbleView.swift; sourceTree = "<group>"; }; 5E7C70CC85B337061151724E /* ImportWalletHelpBubbleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletHelpBubbleView.swift; sourceTree = "<group>"; };
@ -822,6 +824,7 @@
5E7C778F20D32B70D7FF2135 /* TicketRedemptionInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketRedemptionInfoViewController.swift; sourceTree = "<group>"; }; 5E7C778F20D32B70D7FF2135 /* TicketRedemptionInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketRedemptionInfoViewController.swift; sourceTree = "<group>"; };
5E7C77E1E6194F5A1DC8D645 /* ScreenChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenChecker.swift; sourceTree = "<group>"; }; 5E7C77E1E6194F5A1DC8D645 /* ScreenChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenChecker.swift; sourceTree = "<group>"; };
5E7C7821694C489D5114DB18 /* TicketsViewControllerTitleHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketsViewControllerTitleHeader.swift; sourceTree = "<group>"; }; 5E7C7821694C489D5114DB18 /* TicketsViewControllerTitleHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketsViewControllerTitleHeader.swift; sourceTree = "<group>"; };
5E7C7828BD821B6F04B71C00 /* SendHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendHeaderView.swift; sourceTree = "<group>"; };
5E7C783E3ADA4CF9554A0E7D /* TicketTokenViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketTokenViewCell.swift; sourceTree = "<group>"; }; 5E7C783E3ADA4CF9554A0E7D /* TicketTokenViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketTokenViewCell.swift; sourceTree = "<group>"; };
5E7C78581AA28CA5C3CBC468 /* AdvancedSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsViewController.swift; sourceTree = "<group>"; }; 5E7C78581AA28CA5C3CBC468 /* AdvancedSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsViewController.swift; sourceTree = "<group>"; };
5E7C78B001F9F95F404D5FEF /* HowDoIGetMyMoneyInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HowDoIGetMyMoneyInfoViewController.swift; sourceTree = "<group>"; }; 5E7C78B001F9F95F404D5FEF /* HowDoIGetMyMoneyInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HowDoIGetMyMoneyInfoViewController.swift; sourceTree = "<group>"; };
@ -841,6 +844,7 @@
5E7C7B0BE9EE3B198AE7D92D /* WhatIsASeedPhraseInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhatIsASeedPhraseInfoViewController.swift; sourceTree = "<group>"; }; 5E7C7B0BE9EE3B198AE7D92D /* WhatIsASeedPhraseInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhatIsASeedPhraseInfoViewController.swift; sourceTree = "<group>"; };
5E7C7B1FB2702A2A8A4EBD76 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; }; 5E7C7B1FB2702A2A8A4EBD76 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
5E7C7B3302309706CA0F972A /* TokensViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensViewController.swift; sourceTree = "<group>"; }; 5E7C7B3302309706CA0F972A /* TokensViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensViewController.swift; sourceTree = "<group>"; };
5E7C7B7A45EDFA8ED1E25863 /* SendHeaderViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendHeaderViewViewModel.swift; sourceTree = "<group>"; };
5E7C7B82CC07F290B9CAA4E4 /* StatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusViewController.swift; sourceTree = "<group>"; }; 5E7C7B82CC07F290B9CAA4E4 /* StatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusViewController.swift; sourceTree = "<group>"; };
5E7C7B9220E616F82EDA956F /* PasscodeCharacterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeCharacterView.swift; sourceTree = "<group>"; }; 5E7C7B9220E616F82EDA956F /* PasscodeCharacterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeCharacterView.swift; sourceTree = "<group>"; };
5E7C7BD9B4BDAFC2D9EBD741 /* StatusViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusViewControllerViewModel.swift; sourceTree = "<group>"; }; 5E7C7BD9B4BDAFC2D9EBD741 /* StatusViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusViewControllerViewModel.swift; sourceTree = "<group>"; };
@ -1854,7 +1858,6 @@
29B6AECC1F7C87E700EC6DE3 /* ViewControllers */ = { 29B6AECC1F7C87E700EC6DE3 /* ViewControllers */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
29285B411F6FB3E60044CF29 /* SendViewController.swift */,
291A1B661F98092F00ADEC80 /* ConfirmPaymentViewController.swift */, 291A1B661F98092F00ADEC80 /* ConfirmPaymentViewController.swift */,
299B5E411FD2298E0051361C /* ConfigureTransactionViewController.swift */, 299B5E411FD2298E0051361C /* ConfigureTransactionViewController.swift */,
5E7C74DCC21272EC231A20E2 /* RequestViewController.swift */, 5E7C74DCC21272EC231A20E2 /* RequestViewController.swift */,
@ -1862,6 +1865,7 @@
5E7C7419F47CC8B2996AA8F9 /* TransferTicketsQuantitySelectionViewController.swift */, 5E7C7419F47CC8B2996AA8F9 /* TransferTicketsQuantitySelectionViewController.swift */,
5E7C7D177F5297D4A3DB3F33 /* ChooseTicketTransferModeViewController.swift */, 5E7C7D177F5297D4A3DB3F33 /* ChooseTicketTransferModeViewController.swift */,
5E7C72B37551451352EBB9F9 /* TransferTicketViaWalletAddressViewController.swift */, 5E7C72B37551451352EBB9F9 /* TransferTicketViaWalletAddressViewController.swift */,
5E7C70088832B2D161EB4AAB /* SendViewController.swift */,
); );
path = ViewControllers; path = ViewControllers;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1878,6 +1882,7 @@
5E7C703BA1D0E9ACB7399155 /* TransferTicketsQuantitySelectionViewModel.swift */, 5E7C703BA1D0E9ACB7399155 /* TransferTicketsQuantitySelectionViewModel.swift */,
5E7C75BE23CDD9CD271EC30C /* ChooseTicketTransferModeViewControllerViewModel.swift */, 5E7C75BE23CDD9CD271EC30C /* ChooseTicketTransferModeViewControllerViewModel.swift */,
5E7C7610C8DD3223230E3951 /* TransferTicketViaWalletAddressViewControllerViewModel.swift */, 5E7C7610C8DD3223230E3951 /* TransferTicketViaWalletAddressViewControllerViewModel.swift */,
5E7C7B7A45EDFA8ED1E25863 /* SendHeaderViewViewModel.swift */,
); );
path = ViewModels; path = ViewModels;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2332,6 +2337,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5E7C7717D829205D1E254AC1 /* TransferModeButton.swift */, 5E7C7717D829205D1E254AC1 /* TransferModeButton.swift */,
5E7C7828BD821B6F04B71C00 /* SendHeaderView.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@ -3225,7 +3231,6 @@
296421951F70C1EC00EB363B /* LoadingView.swift in Sources */, 296421951F70C1EC00EB363B /* LoadingView.swift in Sources */,
2961BD071FB146EB00C4B840 /* ChainState.swift in Sources */, 2961BD071FB146EB00C4B840 /* ChainState.swift in Sources */,
73D26837202E827E009777A1 /* DecimalFormatter.swift in Sources */, 73D26837202E827E009777A1 /* DecimalFormatter.swift in Sources */,
29285B421F6FB3E60044CF29 /* SendViewController.swift in Sources */,
2996F14D1F6CA743005C33AE /* UIViewController.swift in Sources */, 2996F14D1F6CA743005C33AE /* UIViewController.swift in Sources */,
2959961F1FAE759700DB66A8 /* RawTransaction.swift in Sources */, 2959961F1FAE759700DB66A8 /* RawTransaction.swift in Sources */,
295B61D41FE7D5B500642E60 /* CurrencyFormatter.swift in Sources */, 295B61D41FE7D5B500642E60 /* CurrencyFormatter.swift in Sources */,
@ -3506,6 +3511,9 @@
5E7C7AD4DF6DFA6B3AF206E7 /* TransferTicketViaWalletAddressViewControllerViewModel.swift in Sources */, 5E7C7AD4DF6DFA6B3AF206E7 /* TransferTicketViaWalletAddressViewControllerViewModel.swift in Sources */,
5E7C7A41B07499B607476300 /* ScanQRCodeForWalletAddressToTransferTicketCoordinator.swift in Sources */, 5E7C7A41B07499B607476300 /* ScanQRCodeForWalletAddressToTransferTicketCoordinator.swift in Sources */,
5E7C7EEE563D81793CB96FA0 /* TransferTicketsCoordinator.swift in Sources */, 5E7C7EEE563D81793CB96FA0 /* TransferTicketsCoordinator.swift in Sources */,
5E7C7567A690B6B8F889AE83 /* SendViewController.swift in Sources */,
5E7C72E1D4B4B4C8443F3DA1 /* SendHeaderView.swift in Sources */,
5E7C713ACE8C72642B1C9F93 /* SendHeaderViewViewModel.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

@ -42,8 +42,6 @@ class InCoordinator: Coordinator {
$0 as? TransactionCoordinator $0 as? TransactionCoordinator
}.first }.first
} }
//In addition to `transactionCoordinator` which is shown as a tab, `nonTabTransactionCoordinator` is meant for presenting (in iOS terms)
var nonTabTransactionCoordinator: TransactionCoordinator?
var ticketsCoordinator: TicketsCoordinator? { var ticketsCoordinator: TicketsCoordinator? {
return self.coordinators.flatMap { return self.coordinators.flatMap {
@ -114,7 +112,7 @@ class InCoordinator: Coordinator {
keystore: keystore, keystore: keystore,
tokensStorage: tokensStorage tokensStorage: tokensStorage
) )
transactionCoordinator.rootViewController.tabBarItem = UITabBarItem(title: NSLocalizedString("transactions.tabbar.item.title", value: "Transactions", comment: ""), image: R.image.feed(), selectedImage: nil) transactionCoordinator.rootViewController.tabBarItem = UITabBarItem(title: NSLocalizedString("transactions.tabbar.item.title", value: "Transactions", comment: ""), image: R.image.feed()?.withRenderingMode(.alwaysOriginal), selectedImage: R.image.feed())
transactionCoordinator.delegate = self transactionCoordinator.delegate = self
transactionCoordinator.start() transactionCoordinator.start()
addCoordinator(transactionCoordinator) addCoordinator(transactionCoordinator)
@ -136,7 +134,7 @@ class InCoordinator: Coordinator {
keystore: keystore, keystore: keystore,
tokensStorage: alphaWalletTokensStorage tokensStorage: alphaWalletTokensStorage
) )
tokensCoordinator.rootViewController.tabBarItem = UITabBarItem(title: R.string.localizable.walletTokensTabbarItemTitle(), image: R.image.tab_wallet(), selectedImage: nil) tokensCoordinator.rootViewController.tabBarItem = UITabBarItem(title: R.string.localizable.walletTokensTabbarItemTitle(), image: R.image.tab_wallet()?.withRenderingMode(.alwaysOriginal), selectedImage: R.image.tab_wallet())
tokensCoordinator.delegate = self tokensCoordinator.delegate = self
tokensCoordinator.start() tokensCoordinator.start()
addCoordinator(tokensCoordinator) addCoordinator(tokensCoordinator)
@ -145,7 +143,7 @@ class InCoordinator: Coordinator {
let marketplaceController = MarketplaceViewController() let marketplaceController = MarketplaceViewController()
let marketplaceNavigationController = UINavigationController(rootViewController: marketplaceController) let marketplaceNavigationController = UINavigationController(rootViewController: marketplaceController)
marketplaceController.tabBarItem = UITabBarItem(title: R.string.localizable.aMarketplaceTabbarItemTitle(), image: R.image.tab_marketplace(), selectedImage: nil) marketplaceController.tabBarItem = UITabBarItem(title: R.string.localizable.aMarketplaceTabbarItemTitle(), image: R.image.tab_marketplace()?.withRenderingMode(.alwaysOriginal), selectedImage: R.image.tab_marketplace())
tabBarController.viewControllers?.append(marketplaceNavigationController) tabBarController.viewControllers?.append(marketplaceNavigationController)
let alphaSettingsCoordinator = SettingsCoordinator( let alphaSettingsCoordinator = SettingsCoordinator(
@ -156,8 +154,8 @@ class InCoordinator: Coordinator {
) )
alphaSettingsCoordinator.rootViewController.tabBarItem = UITabBarItem( alphaSettingsCoordinator.rootViewController.tabBarItem = UITabBarItem(
title: R.string.localizable.aSettingsNavigationTitle(), title: R.string.localizable.aSettingsNavigationTitle(),
image: R.image.tab_settings(), image: R.image.tab_settings()?.withRenderingMode(.alwaysOriginal),
selectedImage: nil selectedImage: R.image.tab_settings()
) )
alphaSettingsCoordinator.delegate = self alphaSettingsCoordinator.delegate = self
alphaSettingsCoordinator.start() alphaSettingsCoordinator.start()
@ -228,8 +226,6 @@ class InCoordinator: Coordinator {
func removeAllCoordinators() { func removeAllCoordinators() {
coordinators.removeAll() coordinators.removeAll()
//Manually remove nonTabTransactionCoordinator since we don't add it to coordinators because existing code assume there is only 1 TransactionCoordinator there
nonTabTransactionCoordinator = nil
} }
func checkDevice() { func checkDevice() {
@ -312,45 +308,6 @@ class InCoordinator: Coordinator {
coordinator.showRedeemViewController() coordinator.showRedeemViewController()
} }
private func showTransactions(for type: PaymentFlow) {
if nonTabTransactionCoordinator == nil {
if let wallet = keystore.recentlyUsedWallet {
let migration = MigrationInitializer(account: wallet, chainID: config.chainID)
let web3 = self.web3(for: config.server)
web3.start()
let realm = self.realm(for: migration.config)
let tokensStorage = TokensDataStore(realm: realm, account: wallet, config: config, web3: web3)
let balance = BalanceCoordinator(wallet: wallet, config: config, storage: tokensStorage)
let session = WalletSession(
account: wallet,
config: config,
web3: web3,
balanceCoordinator: balance
)
let transactionsStorage = TransactionsStorage(
realm: realm
)
nonTabTransactionCoordinator = TransactionCoordinator(
session: session,
storage: transactionsStorage,
keystore: keystore,
tokensStorage: tokensStorage
)
nonTabTransactionCoordinator?.delegate = self
nonTabTransactionCoordinator?.start()
nonTabTransactionCoordinator?.rootViewController.showActionButtons = true
nonTabTransactionCoordinator?.rootViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissTransactions))
}
}
guard let transactionCoordinator = nonTabTransactionCoordinator else {
return
}
transactionCoordinator.rootViewController.paymentType = type
navigationController.present(transactionCoordinator.navigationController, animated: true, completion: nil)
}
private func handlePendingTransaction(transaction: SentTransaction) { private func handlePendingTransaction(transaction: SentTransaction) {
transactionCoordinator?.dataCoordinator.addSentTransaction(transaction) transactionCoordinator?.dataCoordinator.addSentTransaction(transaction)
} }
@ -432,7 +389,7 @@ extension InCoordinator: SettingsCoordinatorDelegate {
extension InCoordinator: TokensCoordinatorDelegate { extension InCoordinator: TokensCoordinatorDelegate {
func didPress(for type: PaymentFlow, in coordinator: TokensCoordinator) { func didPress(for type: PaymentFlow, in coordinator: TokensCoordinator) {
showTransactions(for: type) showPaymentFlow(for: type)
} }
func didPressStormBird(for type: PaymentFlow, token: TokenObject, in coordinator: TokensCoordinator) { func didPressStormBird(for type: PaymentFlow, token: TokenObject, in coordinator: TokensCoordinator) {

@ -196,7 +196,7 @@
"a.wallet.ticketToken.transfer.mode.choose.email.title" = "Send Email"; "a.wallet.ticketToken.transfer.mode.choose.email.title" = "Send Email";
"a.wallet.ticketToken.transfer.mode.choose.inputWalletAddress.title" = "Input Wallet Address"; "a.wallet.ticketToken.transfer.mode.choose.inputWalletAddress.title" = "Input Wallet Address";
"a.wallet.ticketToken.transfer.mode.choose.walletAddressViaQRCodeScanner.title" = "Use QR Code Scanner"; "a.wallet.ticketToken.transfer.mode.choose.walletAddressViaQRCodeScanner.title" = "Use QR Code Scanner";
"a.wallet.ticketToken.transfer.mode.choose.other.title" = "Others"; "a.wallet.ticketToken.transfer.mode.choose.other.title" = "Share Link";
"a.wallet.ticketToken.transfer.mode.walletAddress.title" = "Input Wallet Address"; "a.wallet.ticketToken.transfer.mode.walletAddress.title" = "Input Wallet Address";
"a.wallet.ticketToken.transfer.mode.walletAddress.target.title" = "WALLET ADDRESS TO TRANSFER TO"; "a.wallet.ticketToken.transfer.mode.walletAddress.target.title" = "WALLET ADDRESS TO TRANSFER TO";
"a.wallet.ticketToken.transfer.mode.walletAddress.confirmation" = "Transfer to wallet address %@?"; "a.wallet.ticketToken.transfer.mode.walletAddress.confirmation" = "Transfer to wallet address %@?";
@ -248,10 +248,14 @@
"a.welcome.onboarding.createwallet.button.title" = "GET STARTED"; "a.welcome.onboarding.createwallet.button.title" = "GET STARTED";
"a.settings.advanced.label.title" = "Advanced"; "a.settings.advanced.label.title" = "Advanced";
"a.marketplace.tabbar.item.title" = "Marketplace"; "a.marketplace.tabbar.item.title" = "Marketplace";
"a.claim.ticket.success.title" = "Your ticket has been imported and will be available shortly"; "a.claim.ticket.success.title" = "Your ticket has been transferred and the balance will be updated shortly";
"a.claim.ticket.failed.title" = "Invalid ticket link"; "a.claim.ticket.failed.title" = "Invalid ticket link";
"a.claim.ticket.inProgress.title" = "Importing ticket..."; "a.claim.ticket.inProgress.title" = "Importing ticket...";
"a.claim.ticket.done.button.title" = "Done"; "a.claim.ticket.done.button.title" = "Done";
"a.claim.ticket.import.button.title" = "Import"; "a.claim.ticket.import.button.title" = "Import";
"a.setup.reminder.text.text" = "Please set up text messaging to send a text."; "a.setup.reminder.text.text" = "Please set up text messaging to send a text.";
"a.setup.reminder.email.text" = "Please set up a Mail account in order to send email."; "a.setup.reminder.email.text" = "Please set up a Mail account in order to send email.";
"a.send.receive.button.title" = "Send/Receive";
"a.send.recipient.address.title" = "ADDRESS";
"a.send.recipient.amount.title" = "AMOUNT TO TRANSFER";
"a.send.sender.address.title" = "MY ADDRESS";

@ -88,7 +88,7 @@ class TokensViewModel {
func canDelete(for row: Int, section: Int) -> Bool { func canDelete(for row: Int, section: Int) -> Bool {
let token = item(for: row, section: section) let token = item(for: row, section: section)
return token.isCustom return token.isCustom && token.contract.lowercased() != Constants.fifaContractAddress.lowercased()
} }
var footerTextColor: UIColor { var footerTextColor: UIColor {

@ -154,32 +154,6 @@ class TicketsCoordinator: NSObject, Coordinator {
return controller return controller
} }
private func transferViaText(ticketHolder: TicketHolder, paymentFlow: PaymentFlow) {
guard MFMessageComposeViewController.canSendText() else {
UIAlertController.alert(title: "", message: R.string.localizable.aSetupReminderTextText(), alertButtonTitles: [R.string.localizable.oK()], alertButtonStyles: [.cancel], viewController: navigationController, completion: nil)
return
}
let url = generateTransferLink(ticketHolder: ticketHolder, paymentFlow: paymentFlow)
let vc = MFMessageComposeViewController()
vc.messageComposeDelegate = self
vc.body = url
navigationController.present(vc, animated: true)
}
private func transferViaEmail(ticketHolder: TicketHolder, paymentFlow: PaymentFlow) {
guard MFMailComposeViewController.canSendMail() else {
UIAlertController.alert(title: "", message: R.string.localizable.aSetupReminderEmailText(), alertButtonTitles: [R.string.localizable.oK()], alertButtonStyles: [.cancel], viewController: navigationController, completion: nil)
return
}
let url = generateTransferLink(ticketHolder: ticketHolder, paymentFlow: paymentFlow)
let vc = MFMailComposeViewController()
vc.setMessageBody(url, isHTML: false)
vc.mailComposeDelegate = self
navigationController.present(vc, animated: true)
}
private func generateTransferLink(ticketHolder: TicketHolder, paymentFlow: PaymentFlow) -> String { private func generateTransferLink(ticketHolder: TicketHolder, paymentFlow: PaymentFlow) -> String {
let timestamp = Int(NSDate().timeIntervalSince1970) + 86400 let timestamp = Int(NSDate().timeIntervalSince1970) + 86400
let order = Order( let order = Order(
@ -303,10 +277,6 @@ extension TicketsCoordinator: ChooseTicketTransferModeViewControllerDelegate {
let ticketHolder = viewController.ticketHolder let ticketHolder = viewController.ticketHolder
switch transferMode { switch transferMode {
case .text:
transferViaText(ticketHolder: ticketHolder, paymentFlow: viewController.paymentFlow)
case .email:
transferViaEmail(ticketHolder: ticketHolder, paymentFlow: viewController.paymentFlow)
case .walletAddressTextEntry: case .walletAddressTextEntry:
transferViaWalletAddressTextEntry(ticketHolder: ticketHolder, paymentFlow: viewController.paymentFlow) transferViaWalletAddressTextEntry(ticketHolder: ticketHolder, paymentFlow: viewController.paymentFlow)
case .walletAddressFromQRCode: case .walletAddressFromQRCode:
@ -362,24 +332,3 @@ extension TicketsCoordinator: TransferTicketsCoordinatorDelegate {
} }
} }
extension TicketsCoordinator: MFMessageComposeViewControllerDelegate {
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
if result == .cancelled || result == .failed {
controller.dismiss(animated: true)
} else {
controller.dismiss(animated: false)
navigationController.dismiss(animated: true)
}
}
}
extension TicketsCoordinator: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
if result == .cancelled || result == .failed {
controller.dismiss(animated: true)
} else {
controller.dismiss(animated: false)
navigationController.dismiss(animated: true)
}
}
}

@ -131,10 +131,6 @@ extension TransactionCoordinator: TransactionsViewControllerDelegate {
} }
} }
func didPressRequest(in viewController: TransactionsViewController) {
delegate?.didPress(for: .request, in: self)
}
func didPressTransaction(transaction: Transaction, in viewController: TransactionsViewController) { func didPressTransaction(transaction: Transaction, in viewController: TransactionsViewController) {
showTransaction(transaction) showTransaction(transaction)
} }

@ -9,7 +9,6 @@ import TrustKeystore
protocol TransactionsViewControllerDelegate: class { protocol TransactionsViewControllerDelegate: class {
func didPressSend(in viewController: TransactionsViewController) func didPressSend(in viewController: TransactionsViewController)
func didPressRequest(in viewController: TransactionsViewController)
func didPressTransaction(transaction: Transaction, in viewController: TransactionsViewController) func didPressTransaction(transaction: Transaction, in viewController: TransactionsViewController)
func didPressDeposit(for account: Wallet, sender: UIView, in viewController: TransactionsViewController) func didPressDeposit(for account: Wallet, sender: UIView, in viewController: TransactionsViewController)
} }
@ -43,7 +42,6 @@ class TransactionsViewController: UIViewController {
lazy var footerView: TransactionsFooterView = { lazy var footerView: TransactionsFooterView = {
let footerView = TransactionsFooterView(frame: .zero) let footerView = TransactionsFooterView(frame: .zero)
footerView.translatesAutoresizingMaskIntoConstraints = false footerView.translatesAutoresizingMaskIntoConstraints = false
footerView.requestButton.addTarget(self, action: #selector(request), for: .touchUpInside)
footerView.sendButton.addTarget(self, action: #selector(send), for: .touchUpInside) footerView.sendButton.addTarget(self, action: #selector(send), for: .touchUpInside)
return footerView return footerView
}() }()
@ -54,8 +52,6 @@ class TransactionsViewController: UIViewController {
return footerBar return footerBar
}() }()
let insets = UIEdgeInsets(top: 130, left: 0, bottom: ButtonSize.extraLarge.height + 84, right: 0)
init( init(
account: Wallet, account: Wallet,
dataCoordinator: TransactionDataCoordinator, dataCoordinator: TransactionDataCoordinator,
@ -85,11 +81,6 @@ class TransactionsViewController: UIViewController {
let footerViewHeight = CGFloat(60) let footerViewHeight = CGFloat(60)
footerBar.addSubview(footerView) footerBar.addSubview(footerView)
let separator = UIView()
separator.translatesAutoresizingMaskIntoConstraints = false
separator.backgroundColor = Colors.appLightButtonSeparator
footerBar.addSubview(separator)
actionButtonsVisibleConstraint = footerBar.heightAnchor.constraint(equalToConstant: footerViewHeight) actionButtonsVisibleConstraint = footerBar.heightAnchor.constraint(equalToConstant: footerViewHeight)
actionButtonsInVisibleConstraint = footerBar.topAnchor.constraint(equalTo: footerBar.bottomAnchor) actionButtonsInVisibleConstraint = footerBar.topAnchor.constraint(equalTo: footerBar.bottomAnchor)
@ -107,11 +98,6 @@ class TransactionsViewController: UIViewController {
footerView.topAnchor.constraint(equalTo: footerBar.topAnchor), footerView.topAnchor.constraint(equalTo: footerBar.topAnchor),
footerView.heightAnchor.constraint(equalToConstant: footerViewHeight), footerView.heightAnchor.constraint(equalToConstant: footerViewHeight),
separator.leadingAnchor.constraint(equalTo: footerView.sendButton.trailingAnchor, constant: -separatorThickness / 2),
separator.trailingAnchor.constraint(equalTo: footerView.requestButton.leadingAnchor, constant: separatorThickness / 2),
separator.topAnchor.constraint(equalTo: footerView.topAnchor, constant: 8),
separator.bottomAnchor.constraint(equalTo: footerView.bottomAnchor, constant: -8),
footerBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), footerBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
footerBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), footerBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
footerBar.bottomAnchor.constraint(equalTo: view.bottomAnchor), footerBar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
@ -123,11 +109,11 @@ class TransactionsViewController: UIViewController {
refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged) refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged)
tableView.addSubview(refreshControl) tableView.addSubview(refreshControl)
errorView = ErrorView(insets: insets, onRetry: { [weak self] in errorView = ErrorView(onRetry: { [weak self] in
self?.startLoading() self?.startLoading()
self?.dataCoordinator.fetch() self?.dataCoordinator.fetch()
}) })
loadingView = LoadingView(insets: insets) loadingView = LoadingView()
//TODO move into StateViewModel once this change is global //TODO move into StateViewModel once this change is global
if let loadingView = loadingView as? LoadingView { if let loadingView = loadingView as? LoadingView {
loadingView.backgroundColor = Colors.appBackground loadingView.backgroundColor = Colors.appBackground
@ -137,7 +123,6 @@ class TransactionsViewController: UIViewController {
} }
emptyView = { emptyView = {
let view = TransactionsEmptyView( let view = TransactionsEmptyView(
insets: insets,
onDeposit: { [unowned self] sender in onDeposit: { [unowned self] sender in
self.showDeposit(sender) self.showDeposit(sender)
} }
@ -174,10 +159,6 @@ class TransactionsViewController: UIViewController {
delegate?.didPressSend(in: self) delegate?.didPressSend(in: self)
} }
@objc func request() {
delegate?.didPressRequest(in: self)
}
func showDeposit(_ sender: UIButton) { func showDeposit(_ sender: UIButton) {
delegate?.didPressDeposit(for: account, sender: sender, in: self) delegate?.didPressDeposit(for: account, sender: sender, in: self)
} }

@ -8,23 +8,16 @@ class TransactionsFooterView: UIView {
lazy var sendButton: UIButton = { lazy var sendButton: UIButton = {
let sendButton = UIButton(type: .system) let sendButton = UIButton(type: .system)
sendButton.translatesAutoresizingMaskIntoConstraints = false sendButton.translatesAutoresizingMaskIntoConstraints = false
sendButton.setTitle(NSLocalizedString("Send", value: "Send", comment: ""), for: .normal) sendButton.setTitle(R.string.localizable.aSendReceiveButtonTitle(), for: .normal)
return sendButton return sendButton
}() }()
lazy var requestButton: UIButton = {
let requestButton = UIButton(type: .system)
requestButton.translatesAutoresizingMaskIntoConstraints = false
requestButton.setTitle(R.string.localizable.transactionsReceiveButtonTitle(), for: .normal)
return requestButton
}()
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
let stackView = UIStackView(arrangedSubviews: [ let stackView = UIStackView(arrangedSubviews: [
sendButton, sendButton,
requestButton,
]) ])
stackView.translatesAutoresizingMaskIntoConstraints = false stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.distribution = .fillEqually stackView.distribution = .fillEqually
@ -51,9 +44,5 @@ class TransactionsFooterView: UIView {
sendButton.setTitleColor(Colors.appWhite, for: .normal) sendButton.setTitleColor(Colors.appWhite, for: .normal)
sendButton.backgroundColor = Colors.appHighlightGreen sendButton.backgroundColor = Colors.appHighlightGreen
sendButton.titleLabel?.font = Fonts.regular(size: 20)! sendButton.titleLabel?.font = Fonts.regular(size: 20)!
requestButton.setTitleColor(Colors.appWhite, for: .normal)
requestButton.backgroundColor = Colors.appHighlightGreen
requestButton.titleLabel?.font = Fonts.regular(size: 20)!
} }
} }

@ -21,15 +21,6 @@ class PaymentCoordinator: Coordinator {
let storage: TokensDataStore let storage: TokensDataStore
let ticketHolders: [TicketHolder]! let ticketHolders: [TicketHolder]!
lazy var transferType: TransferType = {
switch self.flow {
case .send(let type):
return type
case .request:
return .ether(destination: .none)
}
}()
init( init(
navigationController: UINavigationController = UINavigationController(), navigationController: UINavigationController = UINavigationController(),
flow: PaymentFlow, flow: PaymentFlow,

@ -46,6 +46,18 @@ class SendCoordinator: Coordinator {
} }
func start() { func start() {
let config = Config()
let symbol = sendViewController.transferType.symbol(server: config.server)
sendViewController.configure(viewModel:
.init(transferType: sendViewController.transferType,
session: session,
storage: sendViewController.storage,
config: config,
currentPair: SendViewController.Pair(left: symbol, right: session.config.currency.rawValue)
)
)
//Make sure the pop up, especially the height, is enough to fit the content in iPad
sendViewController.preferredContentSize = CGSize(width: 540, height: 700)
if navigationController.viewControllers.isEmpty { if navigationController.viewControllers.isEmpty {
navigationController.viewControllers = [sendViewController] navigationController.viewControllers = [sendViewController]
} else { } else {
@ -58,23 +70,15 @@ class SendCoordinator: Coordinator {
session: session, session: session,
storage: storage, storage: storage,
account: account, account: account,
transferType: transferType, transferType: transferType
ticketHolders: ticketHolders
) )
if navigationController.viewControllers.isEmpty { if navigationController.viewControllers.isEmpty {
controller.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismiss)) controller.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismiss))
} }
controller.navigationItem.rightBarButtonItem = UIBarButtonItem(
title: NSLocalizedString("Next", value: "Next", comment: ""),
style: .done,
target: controller,
action: #selector(SendViewController.send)
)
switch transferType { switch transferType {
case .ether(let destination): case .ether(let destination):
controller.addressRow?.value = destination?.description controller.targetAddressTextField.text = destination?.description
controller.addressRow?.cell.row.updateCell()
case .token: break case .token: break
case .stormBird: break case .stormBird: break
case .stormBirdOrder: break case .stormBirdOrder: break

@ -4,8 +4,6 @@ import UIKit
import MessageUI import MessageUI
enum TicketTransferMode { enum TicketTransferMode {
case text
case email
case walletAddressTextEntry case walletAddressTextEntry
case walletAddressFromQRCode case walletAddressFromQRCode
case other case other
@ -19,8 +17,6 @@ class ChooseTicketTransferModeViewController: UIViewController {
//roundedBackground is used to achieve the top 2 rounded corners-only effect since maskedCorners to not round bottom corners is not available in iOS 10 //roundedBackground is used to achieve the top 2 rounded corners-only effect since maskedCorners to not round bottom corners is not available in iOS 10
let roundedBackground = UIView() let roundedBackground = UIView()
let titleLabel = UILabel() let titleLabel = UILabel()
let textButton = TransferModeButton()
let emailButton = TransferModeButton()
let inputWalletAddressButton = TransferModeButton() let inputWalletAddressButton = TransferModeButton()
let qrCodeScannerButton = TransferModeButton() let qrCodeScannerButton = TransferModeButton()
let otherButton = TransferModeButton() let otherButton = TransferModeButton()
@ -39,16 +35,6 @@ class ChooseTicketTransferModeViewController: UIViewController {
roundedBackground.cornerRadius = 20 roundedBackground.cornerRadius = 20
view.addSubview(roundedBackground) view.addSubview(roundedBackground)
textButton.callback = {
self.delegate?.didChoose(transferMode: .text, in: self)
}
textButton.translatesAutoresizingMaskIntoConstraints = false
emailButton.callback = {
self.delegate?.didChoose(transferMode: .email, in: self)
}
emailButton.translatesAutoresizingMaskIntoConstraints = false
inputWalletAddressButton.callback = { inputWalletAddressButton.callback = {
self.delegate?.didChoose(transferMode: .walletAddressTextEntry, in: self) self.delegate?.didChoose(transferMode: .walletAddressTextEntry, in: self)
} }
@ -65,33 +51,24 @@ class ChooseTicketTransferModeViewController: UIViewController {
otherButton.translatesAutoresizingMaskIntoConstraints = false otherButton.translatesAutoresizingMaskIntoConstraints = false
let buttonRow1 = UIStackView(arrangedSubviews: [ let buttonRow1 = UIStackView(arrangedSubviews: [
textButton, inputWalletAddressButton,
emailButton, qrCodeScannerButton,
]) ])
buttonRow1.translatesAutoresizingMaskIntoConstraints = false buttonRow1.translatesAutoresizingMaskIntoConstraints = false
buttonRow1.axis = .horizontal buttonRow1.axis = .horizontal
buttonRow1.spacing = 12 buttonRow1.spacing = 12
buttonRow1.distribution = .fill buttonRow1.distribution = .fill
let buttonPlaceholder = UIView()
let buttonRow2 = UIStackView(arrangedSubviews: [ let buttonRow2 = UIStackView(arrangedSubviews: [
inputWalletAddressButton, otherButton,
qrCodeScannerButton, buttonPlaceholder,
]) ])
buttonRow2.translatesAutoresizingMaskIntoConstraints = false buttonRow2.translatesAutoresizingMaskIntoConstraints = false
buttonRow2.axis = .horizontal buttonRow2.axis = .horizontal
buttonRow2.spacing = 12 buttonRow2.spacing = 12
buttonRow2.distribution = .fill buttonRow2.distribution = .fill
let buttonPlaceholder = UIView()
let buttonRow3 = UIStackView(arrangedSubviews: [
otherButton,
buttonPlaceholder,
])
buttonRow3.translatesAutoresizingMaskIntoConstraints = false
buttonRow3.axis = .horizontal
buttonRow3.spacing = 12
buttonRow3.distribution = .fill
let stackView = UIStackView(arrangedSubviews: [ let stackView = UIStackView(arrangedSubviews: [
.spacer(height: 7), .spacer(height: 7),
titleLabel, titleLabel,
@ -99,8 +76,6 @@ class ChooseTicketTransferModeViewController: UIViewController {
buttonRow1, buttonRow1,
.spacer(height: 12), .spacer(height: 12),
buttonRow2, buttonRow2,
.spacer(height: 12),
buttonRow3,
]) ])
stackView.translatesAutoresizingMaskIntoConstraints = false stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical stackView.axis = .vertical
@ -140,12 +115,6 @@ class ChooseTicketTransferModeViewController: UIViewController {
titleLabel.textAlignment = .center titleLabel.textAlignment = .center
titleLabel.text = viewModel.titleLabelText titleLabel.text = viewModel.titleLabelText
textButton.title = viewModel.textButtonTitle
textButton.image = viewModel.textButtonImage
emailButton.title = viewModel.emailButtonTitle
emailButton.image = viewModel.emailButtonImage
inputWalletAddressButton.title = viewModel.inputWalletAddressButtonTitle inputWalletAddressButton.title = viewModel.inputWalletAddressButtonTitle
inputWalletAddressButton.image = viewModel.inputWalletAddressButtonImage inputWalletAddressButton.image = viewModel.inputWalletAddressButtonImage
@ -155,14 +124,10 @@ class ChooseTicketTransferModeViewController: UIViewController {
otherButton.title = viewModel.otherButtonTitle otherButton.title = viewModel.otherButtonTitle
otherButton.image = viewModel.otherButtonImage otherButton.image = viewModel.otherButtonImage
textButton.label.font = viewModel.buttonTitleFont
emailButton.label.font = viewModel.buttonTitleFont
inputWalletAddressButton.label.font = viewModel.buttonTitleFont inputWalletAddressButton.label.font = viewModel.buttonTitleFont
qrCodeScannerButton.label.font = viewModel.buttonTitleFont qrCodeScannerButton.label.font = viewModel.buttonTitleFont
otherButton.label.font = viewModel.buttonTitleFont otherButton.label.font = viewModel.buttonTitleFont
textButton.label.textColor = viewModel.buttonTitleColor
emailButton.label.textColor = viewModel.buttonTitleColor
inputWalletAddressButton.label.textColor = viewModel.buttonTitleColor inputWalletAddressButton.label.textColor = viewModel.buttonTitleColor
qrCodeScannerButton.label.textColor = viewModel.buttonTitleColor qrCodeScannerButton.label.textColor = viewModel.buttonTitleColor
otherButton.label.textColor = viewModel.buttonTitleColor otherButton.label.textColor = viewModel.buttonTitleColor
@ -170,8 +135,6 @@ class ChooseTicketTransferModeViewController: UIViewController {
override func viewDidLayoutSubviews() { override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews() super.viewDidLayoutSubviews()
textButton.layer.cornerRadius = textButton.frame.size.height / 2
emailButton.layer.cornerRadius = emailButton.frame.size.height / 2
inputWalletAddressButton.layer.cornerRadius = inputWalletAddressButton.frame.size.height / 2 inputWalletAddressButton.layer.cornerRadius = inputWalletAddressButton.frame.size.height / 2
qrCodeScannerButton.layer.cornerRadius = qrCodeScannerButton.frame.size.height / 2 qrCodeScannerButton.layer.cornerRadius = qrCodeScannerButton.frame.size.height / 2
} }

@ -1,4 +1,5 @@
// Copyright SIX DAY LLC. All rights reserved. // Copyright SIX DAY LLC. All rights reserved.
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation import Foundation
import UIKit import UIKit
@ -8,6 +9,7 @@ import APIKit
import QRCodeReaderViewController import QRCodeReaderViewController
import BigInt import BigInt
import TrustKeystore import TrustKeystore
import MBProgressHUD
protocol SendViewControllerDelegate: class { protocol SendViewControllerDelegate: class {
func didPressConfirm( func didPressConfirm(
@ -17,20 +19,43 @@ protocol SendViewControllerDelegate: class {
) )
} }
class SendViewController: FormViewController { class SendViewController: UIViewController {
private lazy var viewModel: SendViewModel = { //roundedBackground is used to achieve the top 2 rounded corners-only effect since maskedCorners to not round bottom corners is not available in iOS 10
return SendViewModel(transferType: self.transferType, let roundedBackground = UIView()
config: Config(), let header = SendHeaderView()
ticketHolders: self.ticketHolders) let targetAddressTextField = UITextField()
let amountTextField = UITextField()
let alternativeAmountLabel = UILabel()
let targetAddressLabel = UILabel()
let amountLabel = UILabel()
let myAddressContainer = UIView()
let myAddressLabelLabel = UILabel()
let myAddressLabel: UILabel = {
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .center
label.minimumScaleFactor = 0.5
label.adjustsFontSizeToFitWidth = true
return label
}() }()
weak var delegate: SendViewControllerDelegate? let copyButton: UIButton = {
let button = Button(size: .normal, style: .border)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(copyAddress), for: .touchUpInside)
return button
}()
let imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
let nextButton = UIButton(type: .system)
struct Values { var viewModel: SendViewModel!
static let address = "address" var headerViewModel = SendHeaderViewViewModel()
static let amount = "amount" var balanceViewModel: BalanceBaseViewModel?
static let existingTicketIds = "existingTicketIds" weak var delegate: SendViewControllerDelegate?
static let ticketIdsToSend = "ticketIdsToSend"
}
struct Pair { struct Pair {
let left: String let left: String
@ -46,17 +71,7 @@ class SendViewController: FormViewController {
let account: Account let account: Account
let transferType: TransferType let transferType: TransferType
let storage: TokensDataStore let storage: TokensDataStore
let ticketHolders: [TicketHolder]!
var addressRow: TextFloatLabelRow? {
return form.rowBy(tag: Values.address) as? TextFloatLabelRow
}
var amountRow: TextFloatLabelRow? {
return form.rowBy(tag: Values.amount) as? TextFloatLabelRow
}
var ticketIdsRow: TextFloatLabelRow? {
return form.rowBy(tag: Values.ticketIdsToSend) as? TextFloatLabelRow
}
private var allowedCharacters: String = { private var allowedCharacters: String = {
let decimalSeparator = Locale.current.decimalSeparator ?? "." let decimalSeparator = Locale.current.decimalSeparator ?? "."
return "0123456789" + decimalSeparator return "0123456789" + decimalSeparator
@ -69,122 +84,233 @@ class SendViewController: FormViewController {
lazy var decimalFormatter: DecimalFormatter = { lazy var decimalFormatter: DecimalFormatter = {
return DecimalFormatter() return DecimalFormatter()
}() }()
lazy var stringFormatter: StringFormatter = {
return StringFormatter()
}()
init( init(
session: WalletSession, session: WalletSession,
storage: TokensDataStore, storage: TokensDataStore,
account: Account, account: Account,
transferType: TransferType = .ether(destination: .none), transferType: TransferType = .ether(destination: .none)
ticketHolders: [TicketHolder] = []
) { ) {
self.session = session self.session = session
self.account = account self.account = account
self.transferType = transferType self.transferType = transferType
self.storage = storage self.storage = storage
self.ticketHolders = ticketHolders
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
configureBalanceViewModel()
roundedBackground.translatesAutoresizingMaskIntoConstraints = false
roundedBackground.backgroundColor = Colors.appWhite
roundedBackground.cornerRadius = 20
view.addSubview(roundedBackground)
targetAddressTextField.translatesAutoresizingMaskIntoConstraints = false
targetAddressTextField.delegate = self
targetAddressTextField.returnKeyType = .next
targetAddressTextField.leftViewMode = .always
targetAddressTextField.rightViewMode = .always
amountTextField.translatesAutoresizingMaskIntoConstraints = false
amountTextField.delegate = self
amountTextField.keyboardType = .decimalPad
amountTextField.leftViewMode = .always
amountTextField.rightViewMode = .always
amountTextField.inputAccessoryView = makeToolbarWithDoneButton()
myAddressContainer.translatesAutoresizingMaskIntoConstraints = false
let myAddressContainerCol0 = UIStackView(arrangedSubviews: [
myAddressLabelLabel,
.spacer(height: 10),
myAddressLabel,
.spacer(height: 10),
copyButton,
])
myAddressContainerCol0.translatesAutoresizingMaskIntoConstraints = false
myAddressContainerCol0.axis = .vertical
myAddressContainerCol0.spacing = 0
myAddressContainerCol0.distribution = .fill
myAddressContainerCol0.alignment = .center
let myAddressContainerStackView = UIStackView(arrangedSubviews: [
myAddressContainerCol0,
.spacerWidth(20),
imageView,
])
myAddressContainerStackView.translatesAutoresizingMaskIntoConstraints = false
myAddressContainerStackView.axis = .horizontal
myAddressContainerStackView.spacing = 0
myAddressContainerStackView.distribution = .fill
myAddressContainerStackView.alignment = .center
myAddressContainer.addSubview(myAddressContainerStackView)
nextButton.setTitle(R.string.localizable.aWalletTicketTokenTransferButtonTitle(), for: .normal)
nextButton.addTarget(self, action: #selector(send), for: .touchUpInside)
let buttonsStackView = UIStackView(arrangedSubviews: [nextButton])
buttonsStackView.translatesAutoresizingMaskIntoConstraints = false
buttonsStackView.axis = .horizontal
buttonsStackView.spacing = 0
buttonsStackView.distribution = .fillEqually
buttonsStackView.setContentHuggingPriority(.required, for: .horizontal)
let stackView = UIStackView(arrangedSubviews: [
header,
.spacer(height: ScreenChecker().isNarrowScreen() ? 7: 20),
targetAddressLabel,
.spacer(height: ScreenChecker().isNarrowScreen() ? 2 : 4),
targetAddressTextField,
.spacer(height: ScreenChecker().isNarrowScreen() ? 7 : 14),
amountLabel,
.spacer(height: ScreenChecker().isNarrowScreen() ? 2 : 4),
amountTextField,
alternativeAmountLabel,
.spacer(height: ScreenChecker().isNarrowScreen() ? 10: 20),
myAddressContainer,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 0
stackView.distribution = .fill
stackView.alignment = .center
roundedBackground.addSubview(stackView)
let marginToHideBottomRoundedCorners = CGFloat(30)
let footerBar = UIView()
footerBar.translatesAutoresizingMaskIntoConstraints = false
footerBar.backgroundColor = Colors.appHighlightGreen
roundedBackground.addSubview(footerBar)
let buttonsHeight = CGFloat(60)
footerBar.addSubview(buttonsStackView)
NSLayoutConstraint.activate([
header.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30),
header.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30),
targetAddressTextField.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30),
targetAddressTextField.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30),
targetAddressTextField.heightAnchor.constraint(equalToConstant: ScreenChecker().isNarrowScreen() ? 30 : 50),
amountTextField.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30),
amountTextField.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30),
amountTextField.heightAnchor.constraint(equalToConstant: ScreenChecker().isNarrowScreen() ? 30 : 50),
myAddressContainerStackView.leadingAnchor.constraint(equalTo: myAddressContainer.leadingAnchor, constant: 20),
myAddressContainerStackView.trailingAnchor.constraint(equalTo: myAddressContainer.trailingAnchor, constant: -20),
myAddressContainerStackView.topAnchor.constraint(equalTo: myAddressContainer.topAnchor, constant: ScreenChecker().isNarrowScreen() ? 10 : 20),
myAddressContainerStackView.bottomAnchor.constraint(equalTo: myAddressContainer.bottomAnchor, constant: ScreenChecker().isNarrowScreen() ? -10 : -20),
myAddressContainer.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30),
myAddressContainer.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30),
roundedBackground.leadingAnchor.constraint(equalTo: view.leadingAnchor),
roundedBackground.trailingAnchor.constraint(equalTo: view.trailingAnchor),
roundedBackground.topAnchor.constraint(equalTo: view.topAnchor),
roundedBackground.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: marginToHideBottomRoundedCorners),
imageView.widthAnchor.constraint(equalTo: myAddressContainerStackView.widthAnchor, multiplier: 0.5, constant: 10),
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor),
stackView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor),
stackView.topAnchor.constraint(equalTo: roundedBackground.topAnchor),
buttonsStackView.leadingAnchor.constraint(equalTo: footerBar.leadingAnchor),
buttonsStackView.trailingAnchor.constraint(equalTo: footerBar.trailingAnchor),
buttonsStackView.topAnchor.constraint(equalTo: footerBar.topAnchor),
buttonsStackView.heightAnchor.constraint(equalToConstant: buttonsHeight),
footerBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
footerBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
footerBar.heightAnchor.constraint(equalToConstant: buttonsHeight),
footerBar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
storage.updatePrices() storage.updatePrices()
getGasPrice() getGasPrice()
}
if viewModel.isStormBird { @objc func closeKeyboard() {
title = viewModel.title view.endEditing(true)
} else { }
navigationItem.titleView = BalanceTitleView.make(from: self.session, transferType)
}
view.backgroundColor = viewModel.backgroundColor
let recipientRightView = FieldAppereance.addressFieldRightView(
pasteAction: { [unowned self] in self.pasteAction() },
qrAction: { [unowned self] in self.openReader() }
)
let maxButton = Button(size: .normal, style: .borderless)
maxButton.translatesAutoresizingMaskIntoConstraints = false
maxButton.setTitle(NSLocalizedString("send.max.button.title", value: "Max", comment: ""), for: .normal)
maxButton.addTarget(self, action: #selector(useMaxAmount), for: .touchUpInside)
let fiatButton = Button(size: .normal, style: .borderless) func configure(viewModel: SendViewModel) {
fiatButton.translatesAutoresizingMaskIntoConstraints = false let firstConfigure = self.viewModel == nil
fiatButton.setTitle(currentPair.right, for: .normal) self.viewModel = viewModel
fiatButton.addTarget(self, action: #selector(fiatAction), for: .touchUpInside)
fiatButton.isHidden = isFiatViewHidden()
let amountRightView = UIStackView(arrangedSubviews: [ if firstConfigure {
fiatButton, //Not good to rely on viewModel here on firstConfigure, which means if we change the padding on subsequent calls (which will probably never happen), it wouldn't be reflected. Unfortunately this needs to be here, otherwise while typing in the amount text field, the left and right views will move out of the text field momentarily
]) amountTextField.leftView = .spacerWidth(viewModel.textFieldHorizontalPadding)
amountTextField.rightView = makeAmountRightView()
targetAddressTextField.leftView = .spacerWidth(viewModel.textFieldHorizontalPadding)
targetAddressTextField.rightView = makeTargetAddressRightView()
}
amountRightView.translatesAutoresizingMaskIntoConstraints = false changeQRCode(value: 0)
amountRightView.distribution = .equalSpacing
amountRightView.spacing = 1
amountRightView.axis = .horizontal
if viewModel.isStormBird { view.backgroundColor = viewModel.backgroundColor
form += [Section(viewModel.formHeaderTitle)
<<< TextAreaRow(Values.existingTicketIds) {
$0.textAreaHeight = .dynamic(initialTextViewHeight: 44)
$0.value = viewModel.ticketNumbers
}.cellUpdate { cell, _ in
cell.isUserInteractionEnabled = false
},
]
}
form += [Section(footer: formFooterText()) header.configure(viewModel: headerViewModel)
<<< AppFormAppearance.textFieldFloat(tag: Values.address) {
$0.add(rule: EthereumAddressRule()) targetAddressTextField.textColor = viewModel.textFieldTextColor
$0.validationOptions = .validatesOnDemand targetAddressTextField.font = viewModel.textFieldFont
}.cellUpdate { cell, _ in targetAddressTextField.layer.borderColor = viewModel.textFieldBorderColor.cgColor
cell.textField.textAlignment = .left targetAddressTextField.layer.borderWidth = viewModel.textFieldBorderWidth
cell.textField.placeholder = NSLocalizedString("send.recipientAddress.textField.placeholder", value: "Recipient Address", comment: "")
cell.textField.rightView = recipientRightView //targetAddressLabel.text = R.string.localizable.aSendRecipientAddressTitle()
cell.textField.rightViewMode = .always targetAddressLabel.font = viewModel.textFieldsLabelFont
cell.textField.accessibilityIdentifier = "amount-field" targetAddressLabel.textColor = viewModel.textFieldsLabelTextColor
}
<<< AppFormAppearance.textFieldFloat(tag: Values.amount) { //amountLabel.text = R.string.localizable.aSendRecipientAmountTitle()
$0.add(rule: RuleClosure<String> { [weak self] rowValue in amountLabel.font = viewModel.textFieldsLabelFont
return !(self?.viewModel.isStormBird)! && (rowValue == nil || rowValue!.isEmpty) ? ValidationError(msg: "Field required!") : nil amountLabel.textColor = viewModel.textFieldsLabelTextColor
})
$0.validationOptions = .validatesOnDemand amountTextField.textColor = viewModel.textFieldTextColor
$0.hidden = Condition(booleanLiteral: self.viewModel.isStormBird) amountTextField.font = viewModel.textFieldFont
}.cellUpdate { [weak self] cell, _ in amountTextField.layer.borderColor = viewModel.textFieldBorderColor.cgColor
cell.textField.isCopyPasteDisabled = true amountTextField.layer.borderWidth = viewModel.textFieldBorderWidth
cell.textField.textAlignment = .left
cell.textField.delegate = self alternativeAmountLabel.numberOfLines = 0
cell.textField.placeholder = "\(self?.currentPair.left ?? "") " + NSLocalizedString("send.amount.textField.placeholder", value: "Amount", comment: "") alternativeAmountLabel.textColor = viewModel.alternativeAmountColor
cell.textField.keyboardType = .decimalPad alternativeAmountLabel.font = viewModel.alternativeAmountFont
cell.textField.rightView = amountRightView alternativeAmountLabel.textAlignment = .center
cell.textField.rightViewMode = .always alternativeAmountLabel.text = viewModel.alternativeAmountText
} alternativeAmountLabel.isHidden = !viewModel.showAlternativeAmount
<<< AppFormAppearance.textFieldFloat(tag: Values.ticketIdsToSend) {
$0.add(rule: RuleClosure<String> { [weak self] rowValue in //myAddressLabelLabel.text = R.string.localizable.aSendSenderAddressTitle()
if (self?.viewModel.isStormBird)! { myAddressLabelLabel.font = viewModel.textFieldsLabelFont
if !(self?.ticketIdsValidated())! { myAddressLabelLabel.textColor = viewModel.textFieldsLabelTextColor
return ValidationError(msg: "Please enter valid ticket IDs!")
} myAddressLabel.textColor = viewModel.myAddressTextColor
} myAddressLabel.font = viewModel.addressFont
return nil myAddressLabel.text = viewModel.myAddressText
})
$0.validationOptions = .validatesOnDemand copyButton.titleLabel?.font = viewModel.copyAddressButtonFont
$0.hidden = Condition(booleanLiteral: !self.viewModel.isStormBird) copyButton.setTitle(" \(viewModel.copyAddressButtonTitle) ", for: .normal)
}.cellUpdate { cell, _ in copyButton.setTitleColor(viewModel.copyAddressButtonTitleColor, for: .normal)
cell.textField.isCopyPasteDisabled = true copyButton.backgroundColor = viewModel.copyAddressButtonBackgroundColor
cell.textField.textAlignment = .left
cell.textField.placeholder = NSLocalizedString("send.amount.textField.ticketids", value: "Enter Ticket IDs", comment: "") myAddressContainer.borderColor = viewModel.myAddressBorderColor
cell.textField.keyboardType = .numbersAndPunctuation myAddressContainer.borderWidth = viewModel.myAddressBorderWidth
}, myAddressContainer.cornerRadius = 20
]
nextButton.setTitleColor(viewModel.buttonTitleColor, for: .normal)
nextButton.backgroundColor = viewModel.buttonBackgroundColor
nextButton.titleLabel?.font = viewModel.buttonFont
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
roundCornersBasedOnHeight()
} }
override func viewWillAppear(_ animated: Bool) { private func roundCornersBasedOnHeight() {
super.viewWillAppear(animated) targetAddressTextField.layer.cornerRadius = targetAddressTextField.frame.size.height / 2
self.navigationController?.applyTintAdjustment() amountTextField.layer.cornerRadius = amountTextField.frame.size.height / 2
copyButton.cornerRadius = copyButton.frame.size.height / 2
} }
func getGasPrice() { func getGasPrice() {
@ -198,23 +324,11 @@ class SendViewController: FormViewController {
} }
} }
func clear() {
let fields = [addressRow, amountRow, ticketIdsRow]
for field in fields {
field?.value = ""
field?.reload()
}
}
@objc func send() { @objc func send() {
let errors = form.validate() let addressString = targetAddressTextField.text?.trimmed ?? ""
guard errors.isEmpty else {
return
}
let addressString = addressRow?.value?.trimmed ?? ""
var amountString = "" var amountString = ""
if self.currentPair.left == viewModel.symbol { if self.currentPair.left == viewModel.symbol {
amountString = amountRow?.value?.trimmed ?? "" amountString = amountTextField.text?.trimmed ?? ""
} else { } else {
guard let formatedValue = decimalFormatter.string(from: NSNumber(value: self.pairValue)) else { guard let formatedValue = decimalFormatter.string(from: NSNumber(value: self.pairValue)) else {
return displayError(error: SendInputErrors.wrongInput) return displayError(error: SendInputErrors.wrongInput)
@ -252,7 +366,7 @@ class SendViewController: FormViewController {
r: .none, r: .none,
s: .none, s: .none,
expiry: .none, expiry: .none,
indices: viewModel.isStormBird ? getIndiciesFromUI() : .none indices: .none
) )
self.delegate?.didPressConfirm(transaction: transaction, transferType: transferType, in: self) self.delegate?.didPressConfirm(transaction: transaction, transferType: transferType, in: self)
} }
@ -271,28 +385,24 @@ class SendViewController: FormViewController {
guard CryptoAddressValidator.isValidAddress(value) else { guard CryptoAddressValidator.isValidAddress(value) else {
return displayError(error: Errors.invalidAddress) return displayError(error: Errors.invalidAddress)
} }
addressRow?.value = "0x99f05a668119d8938d79f85add73c9ab8ff719b1" targetAddressTextField.text = value
addressRow?.reload()
activateAmountView() activateAmountView()
} }
@objc func useMaxAmount() {
guard let value = session.balance?.amountFull else {
return
}
amountRow?.value = value
amountRow?.reload()
}
@objc func fiatAction(sender: UIButton) { @objc func fiatAction(sender: UIButton) {
let swappedPair = currentPair.swapPair() let swappedPair = currentPair.swapPair()
//New pair for future calculation we should swap pair each time we press fiat button. //New pair for future calculation we should swap pair each time we press fiat button.
self.currentPair = swappedPair self.currentPair = swappedPair
if var viewModel = viewModel {
viewModel.currentPair = currentPair
viewModel.pairValue = 0
configure(viewModel: viewModel)
}
//Update button title. //Update button title.
sender.setTitle(currentPair.right, for: .normal) sender.setTitle(currentPair.left, for: .normal)
//Reset amountRow value. amountTextField.text = nil
amountRow?.value = nil
amountRow?.reload()
//Reset pair value. //Reset pair value.
pairValue = 0.0 pairValue = 0.0
//Update section. //Update section.
@ -301,8 +411,17 @@ class SendViewController: FormViewController {
activateAmountView() activateAmountView()
} }
@objc func copyAddress() {
UIPasteboard.general.string = viewModel.myAddressText
let hud = MBProgressHUD.showAdded(to: view, animated: true)
hud.mode = .text
hud.label.text = viewModel.addressCopiedText
hud.hide(animated: true, afterDelay: 1.5)
}
func activateAmountView() { func activateAmountView() {
amountRow?.cell.textField.becomeFirstResponder() amountTextField.becomeFirstResponder()
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
@ -310,87 +429,165 @@ class SendViewController: FormViewController {
} }
private func updatePriceSection() { private func updatePriceSection() {
//Update section only if fiat view is visible. guard viewModel.showAlternativeAmount else {
guard !isFiatViewHidden() else {
return return
} }
//We use this section update to prevent update of the all section including cells.
UIView.setAnimationsEnabled(false) if var viewModel = viewModel {
tableView.beginUpdates() viewModel.pairValue = pairValue
let footerSectionIndex: Int configure(viewModel: viewModel)
if viewModel.isStormBird {
footerSectionIndex = 1
} else {
footerSectionIndex = 0
}
if let containerView = tableView.footerView(forSection: footerSectionIndex) {
containerView.textLabel!.text = valueOfPairRepresantetion()
containerView.sizeToFit()
} }
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
} }
private func updatePairPrice(with amount: Double) { private func updatePairPrice(with amount: Double) {
guard let rates = storage.tickers, let currentTokenInfo = rates[viewModel.destinationAddress.description], let price = Double(currentTokenInfo.price) else { guard let rates = storage.tickers, let currentTokenInfo = rates[viewModel.destinationAddress.description], let price = Double(currentTokenInfo.price) else {
return return
} }
if self.currentPair.left == viewModel.symbol { if currentPair.left == viewModel.symbol {
pairValue = amount * price pairValue = amount * price
} else { } else {
pairValue = amount / price pairValue = amount / price
} }
self.updatePriceSection() updatePriceSection()
} }
private func isFiatViewHidden() -> Bool { private func addressTextFieldChanged(in range: NSRange, to string: String) -> Bool {
guard let currentTokenInfo = storage.tickers?[viewModel.destinationAddress.description], let price = Double(currentTokenInfo.price), price > 0 else { return true
return true
}
return false
} }
private func formFooterText() -> String { private func amountTextFieldChanged(in range: NSRange, to string: String) -> Bool {
return isFiatViewHidden() ? "" : valueOfPairRepresantetion() guard let input = amountTextField.text else {
return true
}
//In this step we validate only allowed characters it is because of the iPad keyboard.
let characterSet = NSCharacterSet(charactersIn: allowedCharacters).inverted
let separatedChars = string.components(separatedBy: characterSet)
let filteredNumbersAndSeparator = separatedChars.joined(separator: "")
if string != filteredNumbersAndSeparator {
return false
}
//This is required to prevent user from input of numbers like 1.000.25 or 1,000,25.
if string == "," || string == "." || string == "'" {
return !input.contains(string)
}
let text = (input as NSString).replacingCharacters(in: range, with: string)
guard let amount = decimalFormatter.number(from: text) else {
//Should be done in another way.
pairValue = 0.0
updatePriceSection()
return true
}
updatePairPrice(with: amount.doubleValue)
return true
} }
private func getTicket(for id: UInt16) -> Ticket? { private func changeQRCode(value: Int) {
let tickets = ticketHolders.flatMap { $0.tickets } if let viewModel = viewModel {
let filteredTickets = tickets.filter { UInt16($0.id, radix: 16)! == id } let string = viewModel.myAddressText
return filteredTickets[Int(id)] DispatchQueue.global(qos: .background).async {
// EIP67 format not being used much yet, use hex value for now
// let string = "ethereum:\(account.address.address)?value=\(value)"
let image = self.generateQRCode(from: string)
DispatchQueue.main.async {
self.imageView.image = image
}
}
}
} }
private func isTicketExisting(for id: UInt16) -> Bool {
return getTicket(for: id) != nil
}
private func getTicketIds() -> [String] { // private func getTicket(for id: UInt16) -> Ticket? {
return (ticketIdsRow?.value?.components(separatedBy: ","))! // let tickets = ticketHolders.flatMap { $0.tickets }
// let filteredTickets = tickets.filter { UInt16($0.id, radix: 16)! == id }
// return filteredTickets[Int(id)]
// }
private func generateQRCode(from string: String) -> UIImage? {
return string.toQRCode()
} }
private func ticketIdsValidated() -> Bool { private func configureBalanceViewModel() {
let rowValue = ticketIdsRow?.value switch transferType {
if rowValue == nil || rowValue!.isEmpty { case .ether:
return false session.balanceViewModel.subscribe { viewModel in
} guard let viewModel = viewModel else { return }
let ticketIds = getTicketIds() let amount = viewModel.amountShort
for id in ticketIds { self.headerViewModel.title = "\(amount) \(self.session.config.server.name) (\(viewModel.symbol))"
guard id.isNumeric() else { if let viewModel = self.viewModel {
return false self.configure(viewModel: viewModel)
} }
guard let intId = UInt16(id) else {
return false
} }
guard isTicketExisting(for: intId) else { session.refresh(.ethBalance)
return false case .token(let token):
let viewModel = BalanceTokenViewModel(token: token)
let amount = viewModel.amountShort
headerViewModel.title = "\(amount) \(viewModel.symbol)"
if let viewModel = self.viewModel {
configure(viewModel: self.viewModel)
} }
default:
break
} }
return true
} }
private func getIndiciesFromUI() -> [UInt16] { private func makeTargetAddressRightView() -> UIView {
let ticketIds = getTicketIds() let pasteButton = Button(size: .normal, style: .borderless)
return ticketIds.map { (getTicket(for: UInt16($0)!)?.index)! } pasteButton.translatesAutoresizingMaskIntoConstraints = false
pasteButton.setTitle(R.string.localizable.sendPasteButtonTitle(), for: .normal)
pasteButton.titleLabel?.font = Fonts.regular(size: 14)!
pasteButton.setTitleColor(UIColor(red: 155, green: 155, blue: 155), for: .normal)
pasteButton.addTarget(self, action: #selector(pasteAction), for: .touchUpInside)
let scanQRCodeButton = Button(size: .normal, style: .borderless)
scanQRCodeButton.translatesAutoresizingMaskIntoConstraints = false
scanQRCodeButton.setImage(R.image.qr_code_icon(), for: .normal)
scanQRCodeButton.setTitleColor(UIColor(red: 155, green: 155, blue: 155), for: .normal)
scanQRCodeButton.addTarget(self, action: #selector(openReader), for: .touchUpInside)
let targetAddressRightView = UIStackView(arrangedSubviews: [
pasteButton,
scanQRCodeButton,
])
targetAddressRightView.translatesAutoresizingMaskIntoConstraints = false
targetAddressRightView.distribution = .equalSpacing
targetAddressRightView.spacing = 0
targetAddressRightView.axis = .horizontal
return targetAddressRightView
}
private func makeAmountRightView() -> UIView {
let fiatButton = Button(size: .normal, style: .borderless)
fiatButton.translatesAutoresizingMaskIntoConstraints = false
fiatButton.setTitle(currentPair.left, for: .normal)
fiatButton.setTitleColor(UIColor(red: 155, green: 155, blue: 155), for: .normal)
fiatButton.addTarget(self, action: #selector(fiatAction), for: .touchUpInside)
fiatButton.isHidden = !viewModel.showAlternativeAmount
let amountRightView = UIStackView(arrangedSubviews: [
fiatButton,
])
amountRightView.translatesAutoresizingMaskIntoConstraints = false
amountRightView.distribution = .equalSpacing
amountRightView.spacing = 1
amountRightView.axis = .horizontal
return amountRightView
}
private func makeToolbarWithDoneButton() -> UIToolbar {
//Frame needed, but actual values aren't that important
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
toolbar.barStyle = .default
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let done = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(closeKeyboard))
toolbar.items = [flexSpace, done]
toolbar.sizeToFit()
return toolbar
} }
} }
@ -409,8 +606,7 @@ extension SendViewController: QRCodeReaderDelegate {
guard let result = QRURLParser.from(string: result) else { guard let result = QRURLParser.from(string: result) else {
return return
} }
addressRow?.value = result.address targetAddressTextField.text = result.address
addressRow?.reload()
if let dataString = result.params["data"] { if let dataString = result.params["data"] {
data = Data(hex: dataString.drop0x) data = Data(hex: dataString.drop0x)
@ -419,50 +615,32 @@ extension SendViewController: QRCodeReaderDelegate {
} }
if let value = result.params["amount"] { if let value = result.params["amount"] {
amountRow?.value = EtherNumberFormatter.full.string(from: BigInt(value) ?? BigInt(), units: .ether) amountTextField.text = EtherNumberFormatter.full.string(from: BigInt(value) ?? BigInt(), units: .ether)
} else { } else {
amountRow?.value = "" amountTextField.text = ""
} }
amountRow?.reload()
pairValue = 0.0 pairValue = 0.0
updatePriceSection() updatePriceSection()
} }
private func valueOfPairRepresantetion() -> String {
var formattedString = ""
if self.currentPair.left == viewModel.symbol {
formattedString = StringFormatter().currency(with: self.pairValue, and: self.session.config.currency.rawValue)
} else {
formattedString = stringFormatter.formatter(for: self.pairValue)
}
return "~ \(formattedString) " + "\(currentPair.right)"
}
} }
extension SendViewController: UITextFieldDelegate { extension SendViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let input = textField.text else { if textField == targetAddressTextField {
return addressTextFieldChanged(in: range, to: string)
} else if textField == amountTextField {
return amountTextFieldChanged(in: range, to: string)
} else {
return true return true
} }
//In this step we validate only allowed characters it is because of the iPad keyboard. }
let characterSet = NSCharacterSet(charactersIn: self.allowedCharacters).inverted
let separatedChars = string.components(separatedBy: characterSet) func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let filteredNumbersAndSeparator = separatedChars.joined(separator: "") if textField == targetAddressTextField {
if string != filteredNumbersAndSeparator { activateAmountView()
return false } else if textField == amountTextField {
} view.endEditing(true)
//This is required to prevent user from input of numbers like 1.000.25 or 1,000,25.
if string == "," || string == "." || string == "'" {
return !input.contains(string)
}
let text = (input as NSString).replacingCharacters(in: range, with: string)
guard let amount = decimalFormatter.number(from: text) else {
//Should be done in another way.
pairValue = 0.0
updatePriceSection()
return true
} }
self.updatePairPrice(with: amount.doubleValue)
return true return true
} }
} }

@ -0,0 +1,89 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
struct SendHeaderViewViewModel {
var title = ""
var issuer: String {
return ""
}
var blockChainName: String {
return "Ethereum Blockchain"
}
var backgroundColor: UIColor {
return Colors.appWhite
}
var contentsBackgroundColor: UIColor {
return Colors.appWhite
}
var titleColor: UIColor {
return Colors.appText
}
var subtitleColor: UIColor {
return Colors.appBackground
}
var titleFont: UIFont {
return Fonts.light(size: 25)!
}
var subtitleFont: UIFont {
return Fonts.semibold(size: 10)!
}
var borderColor: UIColor {
return UIColor(red: 236, green: 236, blue: 236)
}
var textColor: UIColor {
return UIColor(red: 155, green: 155, blue: 155)
}
var valuePercentageChangeColor: UIColor {
//TODO must have a different color when depreciate?
return Colors.appHighlightGreen
}
var textValueFont: UIFont {
return Fonts.semibold(size: 15)!
}
var textLabelFont: UIFont {
return Fonts.regular(size: 10)!
}
var valuePercentageChangeValue: String {
//TODO read from model
// return "+50%"
return "N/A"
}
var valuePercentageChangePeriod: String {
return R.string.localizable.aWalletContentsValuePeriodTitle()
}
var valueChange: String {
//TODO read from model
// return "$17,000"
return "N/A"
}
var valueChangeName: String {
return R.string.localizable.aWalletContentsValueAppreciationTitle()
}
var value: String {
//TODO read from model
return "N/A"
}
var valueName: String {
return R.string.localizable.aWalletContentsValueDollarTitle()
}
}

@ -5,26 +5,22 @@ import UIKit
import TrustKeystore import TrustKeystore
struct SendViewModel { struct SendViewModel {
typealias Pair = SendViewController.Pair
let transferType: TransferType let transferType: TransferType
let session: WalletSession
let storage: TokensDataStore
let config: Config let config: Config
let ticketHolders: [TicketHolder]! var currentPair: Pair
let stringFormatter = StringFormatter()
var pairValue = 0.0
var title: String { init(transferType: TransferType, session: WalletSession, storage: TokensDataStore, config: Config, currentPair: Pair) {
return isStormBird ? "Transfer Ticket" : "Send" + symbol self.transferType = transferType
} self.session = session
self.storage = storage
var formHeaderTitle: String { self.config = config
if let ticketHolder = ticketHolders.first { self.currentPair = currentPair
return ticketHolder.name
}
return ""
}
var ticketNumbers: String {
let tickets = ticketHolders.flatMap { $0.tickets }
let ids = tickets.map { String($0.id) }
return ids.joined(separator: ",")
} }
var symbol: String { var symbol: String {
@ -36,14 +32,7 @@ struct SendViewModel {
} }
var backgroundColor: UIColor { var backgroundColor: UIColor {
return .white return Colors.appBackground
}
var isStormBird: Bool {
if let token = self.token {
return token.isStormBird
}
return false
} }
var token: TokenObject? { var token: TokenObject? {
@ -59,4 +48,102 @@ struct SendViewModel {
} }
} }
var textFieldTextColor: UIColor {
return Colors.appText
}
var textFieldFont: UIFont {
if ScreenChecker().isNarrowScreen() {
return Fonts.light(size: 11)!
} else {
return Fonts.light(size: 15)!
}
}
var textFieldBorderColor: UIColor {
return Colors.appBackground
}
var textFieldBorderWidth: CGFloat {
return 1
}
var textFieldHorizontalPadding: CGFloat {
return 22
}
var alternativeAmountColor: UIColor {
return UIColor(red: 155, green: 155, blue: 155)
}
var alternativeAmountFont: UIFont {
return Fonts.regular(size: 10)!
}
var alternativeAmountText: String {
return valueOfPairRepresentation()
}
var showAlternativeAmount: Bool {
guard let currentTokenInfo = storage.tickers?[destinationAddress.description], let price = Double(currentTokenInfo.price), price > 0 else {
return false
}
return true
}
private func valueOfPairRepresentation() -> String {
var formattedString = ""
if currentPair.left == symbol {
formattedString = StringFormatter().currency(with: pairValue, and: session.config.currency.rawValue)
} else {
formattedString = stringFormatter.formatter(for: pairValue)
}
return "~ \(formattedString) " + "\(currentPair.right)"
}
var myAddressText: String {
return session.account.address.description
}
var addressColor: UIColor {
return Colors.appText
}
var addressFont: UIFont {
return Fonts.semibold(size: 14)!
}
var addressCopiedText: String {
return NSLocalizedString("request.addressCopied.title", value: "Address copied", comment: "")
}
var copyAddressButtonBackgroundColor: UIColor {
return Colors.appBackground
}
var copyAddressButtonTitleColor: UIColor {
return Colors.appWhite
}
var copyAddressButtonFont: UIFont {
return Fonts.regular(size: 14)!
}
var copyAddressButtonTitle: String {
return R.string.localizable.copy()
}
var textFieldsLabelTextColor: UIColor {
return UIColor(red: 155, green: 155, blue: 155)
}
var textFieldsLabelFont: UIFont {
return Fonts.regular(size: 10)!
}
var myAddressTextColor: UIColor {
return Colors.gray
}
var myAddressBorderColor: UIColor {
return UIColor(red: 235, green: 235, blue: 235)
}
var myAddressBorderWidth: CGFloat {
return 1
}
var buttonTitleColor: UIColor {
return Colors.appWhite
}
var buttonBackgroundColor: UIColor {
return Colors.appHighlightGreen
}
var buttonFont: UIFont {
return Fonts.regular(size: 20)!
}
} }

@ -0,0 +1,132 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
class SendHeaderView: UIView {
let background = UIView()
let titleLabel = UILabel()
let blockchainLabel = UILabel()
let issuerLabel = UILabel()
let middleBorder = UIView()
let valuePercentageChangeValueLabel = UILabel()
let valuePercentageChangePeriodLabel = UILabel()
let valueChangeLabel = UILabel()
let valueChangeNameLabel = UILabel()
let valueLabel = UILabel()
let valueNameLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
background.translatesAutoresizingMaskIntoConstraints = false
addSubview(background)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
valuePercentageChangeValueLabel.textAlignment = .center
valuePercentageChangePeriodLabel.textAlignment = .center
valueChangeLabel.textAlignment = .center
valueChangeNameLabel.textAlignment = .center
valueLabel.textAlignment = .center
valueNameLabel.textAlignment = .center
let bottomRowStack = UIStackView(arrangedSubviews: [blockchainLabel, issuerLabel])
bottomRowStack.axis = .horizontal
bottomRowStack.spacing = 15
bottomRowStack.distribution = .fill
let footerValuesStack = UIStackView(arrangedSubviews: [valuePercentageChangeValueLabel, valueChangeLabel, valueLabel])
footerValuesStack.axis = .horizontal
footerValuesStack.spacing = 15
footerValuesStack.distribution = .fillEqually
let footerNamesStack = UIStackView(arrangedSubviews: [valuePercentageChangePeriodLabel, valueChangeNameLabel, valueNameLabel])
footerNamesStack.axis = .horizontal
footerNamesStack.spacing = 15
footerNamesStack.distribution = .fillEqually
let footerStackView = UIStackView(arrangedSubviews: [middleBorder, .spacer(height: 14), footerValuesStack, footerNamesStack])
footerStackView.translatesAutoresizingMaskIntoConstraints = false
footerStackView.axis = .vertical
footerStackView.spacing = 0
footerStackView.distribution = .fill
footerValuesStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
bottomRowStack,
.spacer(height: 7),
footerStackView,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 0
stackView.distribution = .fill
background.addSubview(stackView)
let backgroundWidthConstraint = background.widthAnchor.constraint(equalTo: widthAnchor)
backgroundWidthConstraint.priority = .defaultHigh
// TODO extract constant. Maybe StyleLayout.sideMargin
NSLayoutConstraint.activate([
background.leadingAnchor.constraint(equalTo: leadingAnchor),
background.topAnchor.constraint(equalTo: topAnchor),
background.heightAnchor.constraint(equalTo: heightAnchor),
backgroundWidthConstraint,
middleBorder.heightAnchor.constraint(equalToConstant: 1),
stackView.leadingAnchor.constraint(equalTo: background.leadingAnchor, constant: 7),
stackView.trailingAnchor.constraint(equalTo: background.trailingAnchor, constant: -7),
stackView.topAnchor.constraint(equalTo: background.topAnchor, constant: 16),
stackView.bottomAnchor.constraint(lessThanOrEqualTo: background.bottomAnchor, constant: -16),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: SendHeaderViewViewModel) {
backgroundColor = viewModel.backgroundColor
titleLabel.textColor = viewModel.titleColor
titleLabel.font = viewModel.titleFont
titleLabel.text = viewModel.title
titleLabel.adjustsFontSizeToFitWidth = true
blockchainLabel.textColor = viewModel.subtitleColor
blockchainLabel.font = viewModel.subtitleFont
blockchainLabel.text = viewModel.blockChainName
issuerLabel.textColor = viewModel.subtitleColor
issuerLabel.font = viewModel.subtitleFont
let issuer = viewModel.issuer
if issuer.isEmpty {
issuerLabel.text = ""
} else {
issuerLabel.text = "\(R.string.localizable.aWalletContentsIssuerTitle()): \(issuer)"
}
middleBorder.backgroundColor = viewModel.borderColor
valuePercentageChangePeriodLabel.textColor = viewModel.textColor
valuePercentageChangePeriodLabel.font = viewModel.textLabelFont
valuePercentageChangePeriodLabel.text = viewModel.valuePercentageChangePeriod
valueChangeNameLabel.textColor = viewModel.textColor
valueChangeNameLabel.font = viewModel.textLabelFont
valueChangeNameLabel.text = viewModel.valueChangeName
valueNameLabel.textColor = viewModel.textColor
valueNameLabel.font = viewModel.textLabelFont
valueNameLabel.text = viewModel.valueName
valuePercentageChangeValueLabel.textColor = viewModel.valuePercentageChangeColor
valuePercentageChangeValueLabel.font = viewModel.textValueFont
valuePercentageChangeValueLabel.text = viewModel.valuePercentageChangeValue
valueChangeLabel.textColor = viewModel.textColor
valueChangeLabel.font = viewModel.textValueFont
valueChangeLabel.text = viewModel.valueChange
valueLabel.textColor = viewModel.textColor
valueLabel.font = viewModel.textValueFont
valueLabel.text = viewModel.value
}
}

@ -33,7 +33,7 @@ class SendCoordinatorTests: XCTestCase {
) )
coordinator.start() coordinator.start()
XCTAssertEqual(address.description, coordinator.sendViewController.addressRow?.value) XCTAssertEqual(address.description, coordinator.sendViewController.targetAddressTextField.text)
XCTAssertTrue(coordinator.navigationController.viewControllers[0] is SendViewController) XCTAssertTrue(coordinator.navigationController.viewControllers[0] is SendViewController)
} }

Loading…
Cancel
Save