UI for sell tickets flow

pull/185/head
Hwee-Boon Yar 7 years ago
parent 8ae63b12f8
commit 9df873320c
  1. 92
      Trust.xcodeproj/project.pbxproj
  2. 21
      Trust/Assets.xcassets/time.imageset/Contents.json
  3. BIN
      Trust/Assets.xcassets/time.imageset/time@3x.png
  4. 4
      Trust/Extensions/Date.swift
  5. 13
      Trust/InCoordinator.swift
  6. 26
      Trust/Localization/en.lproj/Localizable.strings
  7. 55
      Trust/Sell/Coordinators/ScanQRCodeForWalletAddressToSellTicketCoordinator.swift
  8. 151
      Trust/Sell/Coordinators/SellTicketsCoordinator.swift
  9. 149
      Trust/Sell/ViewControllers/ChooseTicketSellModeViewController.swift
  10. 437
      Trust/Sell/ViewControllers/EnterSellTicketsDetailsViewController.swift
  11. 165
      Trust/Sell/ViewControllers/SellTicketViaWalletAddressViewController.swift
  12. 154
      Trust/Sell/ViewControllers/SellTicketsViewController.swift
  13. 49
      Trust/Sell/ViewModels/ChooseTicketSellModeViewControllerViewModel.swift
  14. 81
      Trust/Sell/ViewModels/SellTicketViaWalletAddressViewControllerViewModel.swift
  15. 131
      Trust/Sell/ViewModels/SellTicketsQuantitySelectionViewModel.swift
  16. 43
      Trust/Sell/ViewModels/SellTicketsViewModel.swift
  17. 197
      Trust/Sell/Views/AmountTextField.swift
  18. 108
      Trust/Sell/Views/DateEntryField.swift
  19. 110
      Trust/Sell/Views/TimeEntryField.swift
  20. 4
      Trust/Tokens/ViewControllers/TicketsViewController.swift
  21. 174
      Trust/Transactions/Coordinators/TicketsCoordinator.swift
  22. 6
      Trust/Transfer/ViewControllers/ChooseTicketTransferModeViewController.swift
  23. 2
      Trust/Transfer/Views/ShareModeButton.swift

@ -287,6 +287,7 @@
5E7C70FF17622C0FFD45A542 /* AlphaWalletSettingPushRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D2AAB777BF35B8B56BD /* AlphaWalletSettingPushRow.swift */; };
5E7C710331196CD591B51785 /* LockCreatePasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C741196D9D9C9C3EE5E30 /* LockCreatePasscodeViewController.swift */; };
5E7C713ACE8C72642B1C9F93 /* SendHeaderViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B7A45EDFA8ED1E25863 /* SendHeaderViewViewModel.swift */; };
5E7C719652B282E6711086AC /* ChooseTicketSellModeViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7DC1065C7AE79389B544 /* ChooseTicketSellModeViewControllerViewModel.swift */; };
5E7C71A6B0BDF301747A49AE /* ScreenChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C77E1E6194F5A1DC8D645 /* ScreenChecker.swift */; };
5E7C71A7D2BD6FCE3980CC51 /* ImportWalletHelpBubbleViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7A16ABC8BD5D508AA641 /* ImportWalletHelpBubbleViewViewModel.swift */; };
5E7C71B52A77008694BFA5D1 /* TokensDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74B9EB81C51E956566E7 /* TokensDataStore.swift */; };
@ -301,7 +302,7 @@
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 */; };
5E7C73305DF984B99E94D9F9 /* TransferModeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7717D829205D1E254AC1 /* TransferModeButton.swift */; };
5E7C73305DF984B99E94D9F9 /* ShareModeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7717D829205D1E254AC1 /* ShareModeButton.swift */; };
5E7C733638D7596F93DEE2A9 /* OnboardingCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75CE3F1D6B7993E7A840 /* OnboardingCollectionViewController.swift */; };
5E7C7376B566E5A59CC8F463 /* ImportTicketViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72D0E7CA03ADE5CFAE7A /* ImportTicketViewControllerViewModel.swift */; };
5E7C73FC3990D110C474C3D6 /* WalletFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75CC640BAFFE0E789F44 /* WalletFilterViewModel.swift */; };
@ -313,9 +314,12 @@
5E7C75C99B9F595F26EDC405 /* LockPasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D5F3CAE69CF932AB236 /* LockPasscodeViewController.swift */; };
5E7C75D46140FACBD12333BF /* EthTokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7EE374A74F2B00013C18 /* EthTokenViewCell.swift */; };
5E7C75E3C4BAE885746BD1B3 /* TransferTicketViaWalletAddressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72B37551451352EBB9F9 /* TransferTicketViaWalletAddressViewController.swift */; };
5E7C75E81F85353844CACECC /* EnterSellTicketsDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7F610139D24D947B1625 /* EnterSellTicketsDetailsViewController.swift */; };
5E7C75F80A7E178B49830BCD /* TicketsViewControllerHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C796039C0F47CDCA236C0 /* TicketsViewControllerHeader.swift */; };
5E7C75FD5699EF14F126DBB4 /* SellTicketsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C765220E8DC2118FBA34B /* SellTicketsCoordinator.swift */; };
5E7C760C7D55C97424F55138 /* TicketTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75F877B2F2E24C7EF258 /* TicketTableViewCellViewModel.swift */; };
5E7C76605A5102FBD376F32A /* ImportTicketViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7535095323B035CA47C0 /* ImportTicketViewController.swift */; };
5E7C764D3C130AAB26E80EC1 /* AmountTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73617E3A4C0B9A90A5F8 /* AmountTextField.swift */; };
5E7C76A0365D128B7F19A0C2 /* ProtectionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74BEC095303B66FB4B1E /* ProtectionCoordinator.swift */; };
5E7C76A65C14D0F11AF7848F /* TicketRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7742709724B3BD0C2A0D /* TicketRowViewModel.swift */; };
5E7C76B917517C93D1E26B0A /* LockEnterPasscodeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7981AB6584B25C72D46B /* LockEnterPasscodeCoordinator.swift */; };
@ -333,12 +337,15 @@
5E7C78D6C94739B1ADDFBB5B /* WhyUseEthereumInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74FABE14B7B1BEEC4F5E /* WhyUseEthereumInfoViewController.swift */; };
5E7C797BE2C8DB7EF6F217B3 /* OnboardingPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7103135DCCCAB96EE5FC /* OnboardingPage.swift */; };
5E7C798E5F5EE00D405B91AE /* TicketRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7ACB94CEE493AC37487F /* TicketRowView.swift */; };
5E7C79F30A324D75DF42DDDE /* SellTicketsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7EB14E787BC019660389 /* SellTicketsViewModel.swift */; };
5E7C7A41B07499B607476300 /* ScanQRCodeForWalletAddressToTransferTicketCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7F4A5956D5C5C310ADB2 /* ScanQRCodeForWalletAddressToTransferTicketCoordinator.swift */; };
5E7C7AB2ECFB589632F2A26C /* WalletFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7E2DCCE0D775ECF83088 /* WalletFilter.swift */; };
5E7C7AB6950E43BD6E8D0CBE /* TokensViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B3302309706CA0F972A /* TokensViewController.swift */; };
5E7C7AD4DF6DFA6B3AF206E7 /* TransferTicketViaWalletAddressViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7610C8DD3223230E3951 /* TransferTicketViaWalletAddressViewControllerViewModel.swift */; };
5E7C7B3E08EEA63C5B68B9C4 /* TicketRedemptionInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C778F20D32B70D7FF2135 /* TicketRedemptionInfoViewController.swift */; };
5E7C7BF21012B107715AE2D3 /* SellTicketViaWalletAddressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D9D2A99B8812B6BD9B3 /* SellTicketViaWalletAddressViewController.swift */; };
5E7C7C0FAC500A6651E663FD /* TransferTicketsQuantitySelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C703BA1D0E9ACB7399155 /* TransferTicketsQuantitySelectionViewModel.swift */; };
5E7C7C1BA109C22706D4817E /* ScanQRCodeForWalletAddressToSellTicketCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7A1FDDC13D49B2DBC164 /* ScanQRCodeForWalletAddressToSellTicketCoordinator.swift */; };
5E7C7C21E5CAF122AA4F6617 /* HowDoIGetMyMoneyInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C78B001F9F95F404D5FEF /* HowDoIGetMyMoneyInfoViewController.swift */; };
5E7C7C658D619C70F1E3DE59 /* AdvancedSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C719B717E002583B1E2E9 /* AdvancedSettingsViewModel.swift */; };
5E7C7C98EAF40E8110241DBD /* TicketTokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C783E3ADA4CF9554A0E7D /* TicketTokenViewCell.swift */; };
@ -349,18 +356,24 @@
5E7C7CF3BB38045FA40F38AE /* PrivacyPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72142D5817EF8FA8CADA /* PrivacyPolicyViewController.swift */; };
5E7C7CF43176653FFCE86644 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B1FB2702A2A8A4EBD76 /* SettingsCoordinator.swift */; };
5E7C7D03D745BF5C202A2CD1 /* TokensCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7F932B48011A24C26733 /* TokensCoordinator.swift */; };
5E7C7D2948B4B9724F2E509E /* TimeEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7E3022F835109AB71A00 /* TimeEntryField.swift */; };
5E7C7D71D3184F44C397FFE7 /* HelpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C715F395B973FB61056CF /* HelpViewController.swift */; };
5E7C7D8173CB1089D622DA38 /* HelpViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7646352F10C96B5FC6F6 /* HelpViewCell.swift */; };
5E7C7D8AFC9BA1E8C1D05167 /* TicketSellInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74A2C738BF2412D412A7 /* TicketSellInfoViewController.swift */; };
5E7C7DD506747B6224C28721 /* TransferTicketsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7E2486CDE31871C98FC7 /* TransferTicketsViewModel.swift */; };
5E7C7E04D4DDD7D8881A2AB1 /* UniversalLinkCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C76AF81B8DFF605558499 /* UniversalLinkCoordinator.swift */; };
5E7C7E1B18EC7F7FD6D64439 /* SellTicketsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7FF84A4377FC395772C3 /* SellTicketsViewController.swift */; };
5E7C7E2C03099D78A9D911D7 /* WhatIsASeedPhraseInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B0BE9EE3B198AE7D92D /* WhatIsASeedPhraseInfoViewController.swift */; };
5E7C7E2F558A1DFF078B61F9 /* TransferTicketsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7011D8E5C9FFE0E59D55 /* TransferTicketsViewController.swift */; };
5E7C7E4B4054AAD41C5BE3EC /* SettingsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7564AF453BAB0BDAAA57 /* SettingsAction.swift */; };
5E7C7E5C30EFDC70DF1E00C1 /* TicketsViewControllerHeaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C77316522DF2B256F1F92 /* TicketsViewControllerHeaderViewModel.swift */; };
5E7C7E99E59C63309DD92865 /* ChooseTicketSellModeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7745F5E4699306B2C6FD /* ChooseTicketSellModeViewController.swift */; };
5E7C7EBCE95393763A2DF5F7 /* SellTicketViaWalletAddressViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74553071E5222FBF5939 /* SellTicketViaWalletAddressViewControllerViewModel.swift */; };
5E7C7EEE563D81793CB96FA0 /* TransferTicketsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C755132D9B6F95080A1BE /* TransferTicketsCoordinator.swift */; };
5E7C7FAF2A07E7AE21BF09AF /* AlphaWalletSettingsTextRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71684B93F60206992E10 /* AlphaWalletSettingsTextRow.swift */; };
5E7C7FC0770A411DB09F8C09 /* TokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7C077372C3F2A4349FA1 /* TokenViewCell.swift */; };
5E7C7FCC321493B41C1083C1 /* SellTicketsQuantitySelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C765E0DC0174E9788CCF9 /* SellTicketsQuantitySelectionViewModel.swift */; };
5E7C7FD0D4E0F77FEA2FCEC2 /* DateEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7A65F6033318F7C8AEB0 /* DateEntryField.swift */; };
5E7C7FDD73F658772181896B /* TermsOfServiceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7607B0EF9B8F1BC41073 /* TermsOfServiceViewController.swift */; };
5E7C7FE10C2FEA7316401F04 /* WelcomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71CE10548877F1124BF2 /* WelcomeViewModel.swift */; };
5E7C7FE8247F0E50BEF35D77 /* HowDoITransferETHIntoMyWalletInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D4F7C566EDD30EF1C19 /* HowDoITransferETHIntoMyWalletInfoViewController.swift */; };
@ -798,8 +811,10 @@
5E7C72D0E7CA03ADE5CFAE7A /* ImportTicketViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportTicketViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C731B6F01534683227123 /* TicketTokenViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketTokenViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C73495E0C0A207152EC25 /* LockEnterPasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LockEnterPasscodeViewController.swift; path = Trust/AlphaWalletLock/ViewControllers/LockEnterPasscodeViewController.swift; sourceTree = SOURCE_ROOT; };
5E7C73617E3A4C0B9A90A5F8 /* AmountTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmountTextField.swift; sourceTree = "<group>"; };
5E7C741196D9D9C9C3EE5E30 /* LockCreatePasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockCreatePasscodeViewController.swift; sourceTree = "<group>"; };
5E7C7419F47CC8B2996AA8F9 /* TransferTicketsQuantitySelectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsQuantitySelectionViewController.swift; sourceTree = "<group>"; };
5E7C74553071E5222FBF5939 /* SellTicketViaWalletAddressViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SellTicketViaWalletAddressViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C74A2C738BF2412D412A7 /* TicketSellInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketSellInfoViewController.swift; sourceTree = "<group>"; };
5E7C74B82783A94091A43470 /* EthTokenViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthTokenViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C74B9EB81C51E956566E7 /* TokensDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensDataStore.swift; sourceTree = "<group>"; };
@ -818,12 +833,15 @@
5E7C7610C8DD3223230E3951 /* TransferTicketViaWalletAddressViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketViaWalletAddressViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C7646352F10C96B5FC6F6 /* HelpViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelpViewCell.swift; sourceTree = "<group>"; };
5E7C764B98F526271E4C2A6A /* StaticHTMLViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticHTMLViewController.swift; sourceTree = "<group>"; };
5E7C765220E8DC2118FBA34B /* SellTicketsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SellTicketsCoordinator.swift; path = Coordinators/SellTicketsCoordinator.swift; sourceTree = "<group>"; };
5E7C765E0DC0174E9788CCF9 /* SellTicketsQuantitySelectionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SellTicketsQuantitySelectionViewModel.swift; sourceTree = "<group>"; };
5E7C767497AD8DEE83F384D7 /* RequestViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestViewModel.swift; sourceTree = "<group>"; };
5E7C76AF81B8DFF605558499 /* UniversalLinkCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalLinkCoordinator.swift; sourceTree = "<group>"; };
5E7C77061BEF269BCE358086 /* BaseTicketTableViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTicketTableViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C7717D829205D1E254AC1 /* TransferModeButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferModeButton.swift; sourceTree = "<group>"; };
5E7C7717D829205D1E254AC1 /* ShareModeButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareModeButton.swift; sourceTree = "<group>"; };
5E7C77316522DF2B256F1F92 /* TicketsViewControllerHeaderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketsViewControllerHeaderViewModel.swift; sourceTree = "<group>"; };
5E7C7742709724B3BD0C2A0D /* TicketRowViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketRowViewModel.swift; sourceTree = "<group>"; };
5E7C7745F5E4699306B2C6FD /* ChooseTicketSellModeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChooseTicketSellModeViewController.swift; sourceTree = "<group>"; };
5E7C774BCA281E4B077DBBFA /* WhatIsEthereumInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhatIsEthereumInfoViewController.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>"; };
@ -839,6 +857,8 @@
5E7C79D674D45A07E694CE31 /* LockView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockView.swift; sourceTree = "<group>"; };
5E7C79ED9F842D3FC102AC54 /* TokenViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C7A16ABC8BD5D508AA641 /* ImportWalletHelpBubbleViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletHelpBubbleViewViewModel.swift; sourceTree = "<group>"; };
5E7C7A1FDDC13D49B2DBC164 /* ScanQRCodeForWalletAddressToSellTicketCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ScanQRCodeForWalletAddressToSellTicketCoordinator.swift; path = Coordinators/ScanQRCodeForWalletAddressToSellTicketCoordinator.swift; sourceTree = "<group>"; };
5E7C7A65F6033318F7C8AEB0 /* DateEntryField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateEntryField.swift; sourceTree = "<group>"; };
5E7C7AB3440C01136DF4F3E9 /* LockCreatePasscodeCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockCreatePasscodeCoordinator.swift; sourceTree = "<group>"; };
5E7C7ACB94CEE493AC37487F /* TicketRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketRowView.swift; sourceTree = "<group>"; };
5E7C7AE6FAE0DF969B4F52E9 /* ContactUsBannerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactUsBannerView.swift; sourceTree = "<group>"; };
@ -858,14 +878,20 @@
5E7C7D2AAB777BF35B8B56BD /* AlphaWalletSettingPushRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlphaWalletSettingPushRow.swift; path = Views/AlphaWalletSettingPushRow.swift; sourceTree = "<group>"; };
5E7C7D4F7C566EDD30EF1C19 /* HowDoITransferETHIntoMyWalletInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HowDoITransferETHIntoMyWalletInfoViewController.swift; sourceTree = "<group>"; };
5E7C7D5F3CAE69CF932AB236 /* LockPasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockPasscodeViewController.swift; sourceTree = "<group>"; };
5E7C7D9D2A99B8812B6BD9B3 /* SellTicketViaWalletAddressViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SellTicketViaWalletAddressViewController.swift; sourceTree = "<group>"; };
5E7C7DC1065C7AE79389B544 /* ChooseTicketSellModeViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChooseTicketSellModeViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C7E2486CDE31871C98FC7 /* TransferTicketsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsViewModel.swift; sourceTree = "<group>"; };
5E7C7E24936CC2190D2A16C2 /* OnboardingPageViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPageViewModel.swift; sourceTree = "<group>"; };
5E7C7E2DCCE0D775ECF83088 /* WalletFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WalletFilter.swift; path = Models/WalletFilter.swift; sourceTree = "<group>"; };
5E7C7E3022F835109AB71A00 /* TimeEntryField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeEntryField.swift; sourceTree = "<group>"; };
5E7C7EB14E787BC019660389 /* SellTicketsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SellTicketsViewModel.swift; sourceTree = "<group>"; };
5E7C7EE374A74F2B00013C18 /* EthTokenViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthTokenViewCell.swift; sourceTree = "<group>"; };
5E7C7EE467A7F5F2E5B1F660 /* TokensViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensViewModel.swift; sourceTree = "<group>"; };
5E7C7F4A5956D5C5C310ADB2 /* ScanQRCodeForWalletAddressToTransferTicketCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanQRCodeForWalletAddressToTransferTicketCoordinator.swift; sourceTree = "<group>"; };
5E7C7F5C10E3895E805EA7E0 /* BaseTicketTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTicketTableViewCell.swift; sourceTree = "<group>"; };
5E7C7F610139D24D947B1625 /* EnterSellTicketsDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterSellTicketsDetailsViewController.swift; sourceTree = "<group>"; };
5E7C7F932B48011A24C26733 /* TokensCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensCoordinator.swift; sourceTree = "<group>"; };
5E7C7FF84A4377FC395772C3 /* SellTicketsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SellTicketsViewController.swift; sourceTree = "<group>"; };
613D04881FDE15F8008DE72E /* COMODO ECC Domain Validation Secure Server CA 2.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = "COMODO ECC Domain Validation Secure Server CA 2.cer"; sourceTree = "<group>"; };
613D048A1FDE162B008DE72E /* TrustProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustProvider.swift; sourceTree = "<group>"; };
61621995A39B7730239E6112 /* Pods-AlphaWalletTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AlphaWalletTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AlphaWalletTests/Pods-AlphaWalletTests.debug.xcconfig"; sourceTree = "<group>"; };
@ -1124,6 +1150,7 @@
76F1D47A29573DA8BD3E4979 /* Redeem */,
5E7C7ACB32FB112CD7D92977 /* AlphaWalletHelp */,
5E7C7C37B4E80BA2E0DC7FA4 /* Marketplace */,
5E7C71698FE1429F1AC0777D /* Sell */,
);
path = Trust;
sourceTree = "<group>";
@ -2281,6 +2308,29 @@
path = ViewControllers;
sourceTree = "<group>";
};
5E7C71698FE1429F1AC0777D /* Sell */ = {
isa = PBXGroup;
children = (
5E7C7A230A2B5F1AD0820F35 /* ViewControllers */,
5E7C717017C1CBA50A260024 /* ViewModels */,
5E7C79274D73196395BBA426 /* Views */,
5E7C7A1FDDC13D49B2DBC164 /* ScanQRCodeForWalletAddressToSellTicketCoordinator.swift */,
5E7C765220E8DC2118FBA34B /* SellTicketsCoordinator.swift */,
);
path = Sell;
sourceTree = "<group>";
};
5E7C717017C1CBA50A260024 /* ViewModels */ = {
isa = PBXGroup;
children = (
5E7C7EB14E787BC019660389 /* SellTicketsViewModel.swift */,
5E7C765E0DC0174E9788CCF9 /* SellTicketsQuantitySelectionViewModel.swift */,
5E7C7DC1065C7AE79389B544 /* ChooseTicketSellModeViewControllerViewModel.swift */,
5E7C74553071E5222FBF5939 /* SellTicketViaWalletAddressViewControllerViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
};
5E7C71D254F51DCFBA549722 /* Coordinators */ = {
isa = PBXGroup;
children = (
@ -2315,6 +2365,27 @@
path = ViewModels;
sourceTree = "<group>";
};
5E7C79274D73196395BBA426 /* Views */ = {
isa = PBXGroup;
children = (
5E7C73617E3A4C0B9A90A5F8 /* AmountTextField.swift */,
5E7C7A65F6033318F7C8AEB0 /* DateEntryField.swift */,
5E7C7E3022F835109AB71A00 /* TimeEntryField.swift */,
);
path = Views;
sourceTree = "<group>";
};
5E7C7A230A2B5F1AD0820F35 /* ViewControllers */ = {
isa = PBXGroup;
children = (
5E7C7FF84A4377FC395772C3 /* SellTicketsViewController.swift */,
5E7C7F610139D24D947B1625 /* EnterSellTicketsDetailsViewController.swift */,
5E7C7745F5E4699306B2C6FD /* ChooseTicketSellModeViewController.swift */,
5E7C7D9D2A99B8812B6BD9B3 /* SellTicketViaWalletAddressViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
};
5E7C7A5A98CE71365B6E80FF /* Views */ = {
isa = PBXGroup;
children = (
@ -2342,7 +2413,7 @@
5E7C7F64537949FBD3F77457 /* Views */ = {
isa = PBXGroup;
children = (
5E7C7717D829205D1E254AC1 /* TransferModeButton.swift */,
5E7C7717D829205D1E254AC1 /* ShareModeButton.swift */,
5E7C7828BD821B6F04B71C00 /* SendHeaderView.swift */,
);
path = Views;
@ -3512,7 +3583,7 @@
5E7C77AD9FAAC18211B6F355 /* TransferTicketsQuantitySelectionViewController.swift in Sources */,
5E7C70EEFB9D9745C6CF7578 /* ChooseTicketTransferModeViewController.swift in Sources */,
5E7C782C7894C44E9D6897F4 /* ChooseTicketTransferModeViewControllerViewModel.swift in Sources */,
5E7C73305DF984B99E94D9F9 /* TransferModeButton.swift in Sources */,
5E7C73305DF984B99E94D9F9 /* ShareModeButton.swift in Sources */,
5E7C75E3C4BAE885746BD1B3 /* TransferTicketViaWalletAddressViewController.swift in Sources */,
5E7C7AD4DF6DFA6B3AF206E7 /* TransferTicketViaWalletAddressViewControllerViewModel.swift in Sources */,
5E7C7A41B07499B607476300 /* ScanQRCodeForWalletAddressToTransferTicketCoordinator.swift in Sources */,
@ -3522,6 +3593,19 @@
5E7C713ACE8C72642B1C9F93 /* SendHeaderViewViewModel.swift in Sources */,
5E7C7376B566E5A59CC8F463 /* ImportTicketViewControllerViewModel.swift in Sources */,
5E7C76605A5102FBD376F32A /* ImportTicketViewController.swift in Sources */,
5E7C79F30A324D75DF42DDDE /* SellTicketsViewModel.swift in Sources */,
5E7C7E1B18EC7F7FD6D64439 /* SellTicketsViewController.swift in Sources */,
5E7C75E81F85353844CACECC /* EnterSellTicketsDetailsViewController.swift in Sources */,
5E7C7FCC321493B41C1083C1 /* SellTicketsQuantitySelectionViewModel.swift in Sources */,
5E7C764D3C130AAB26E80EC1 /* AmountTextField.swift in Sources */,
5E7C7FD0D4E0F77FEA2FCEC2 /* DateEntryField.swift in Sources */,
5E7C7D2948B4B9724F2E509E /* TimeEntryField.swift in Sources */,
5E7C7E99E59C63309DD92865 /* ChooseTicketSellModeViewController.swift in Sources */,
5E7C719652B282E6711086AC /* ChooseTicketSellModeViewControllerViewModel.swift in Sources */,
5E7C7C1BA109C22706D4817E /* ScanQRCodeForWalletAddressToSellTicketCoordinator.swift in Sources */,
5E7C7BF21012B107715AE2D3 /* SellTicketViaWalletAddressViewController.swift in Sources */,
5E7C7EBCE95393763A2DF5F7 /* SellTicketViaWalletAddressViewControllerViewModel.swift in Sources */,
5E7C75FD5699EF14F126DBB4 /* SellTicketsCoordinator.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "time@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -34,4 +34,8 @@ public extension Date {
}
return foundFormatter!
}
public static var yesterday: Date {
return Calendar.current.date(byAdding: .day, value: -1, to: Date())!
}
}

@ -308,6 +308,10 @@ class InCoordinator: Coordinator {
coordinator.showRedeemViewController()
}
func showTicketListToSell(for paymentFlow: PaymentFlow, coordinator: TicketsCoordinator) {
coordinator.showSellViewController(for: paymentFlow)
}
private func handlePendingTransaction(transaction: SentTransaction) {
transactionCoordinator?.dataCoordinator.addSentTransaction(transaction)
}
@ -340,6 +344,10 @@ extension InCoordinator: TicketsCoordinatorDelegate {
showTicketListToRedeem(for: token, coordinator: coordinator)
}
func didPressSell(for type: PaymentFlow, in coordinator: TicketsCoordinator) {
showTicketListToSell(for: type, coordinator: coordinator)
}
func didCancel(in coordinator: TicketsCoordinator) {
coordinator.navigationController.dismiss(animated: true, completion: nil)
removeCoordinator(coordinator)
@ -349,6 +357,11 @@ extension InCoordinator: TicketsCoordinatorDelegate {
let controller = TicketRedemptionInfoViewController()
viewController.navigationController?.pushViewController(controller, animated: true)
}
func didPressViewEthereumInfo(in viewController: UIViewController) {
let controller = WhatIsEthereumInfoViewController()
viewController.navigationController?.pushViewController(controller, animated: true)
}
}
extension InCoordinator: TransactionCoordinatorDelegate {

@ -185,6 +185,32 @@
"a.wallet.ticketToken.redeem.quantity.title" = "QUANTITY OF TICKETS";
"a.wallet.ticketToken.redeem.showQRCode.title" = "Show QR Code to Redemption Booth";
"a.wallet.ticketToken.sell.button.title" = "Sell";
"a.wallet.ticketToken.sell.generateLink.button.title" = "Generate Sale Link";
"a.wallet.ticketToken.sell.selectTickets.title" = "Select Tickets To Sell:";
"a.wallet.ticketToken.sell.ethHelp.title" = "Set Ethereum price for magic link:";
"a.wallet.ticketToken.sell.learnAboutEth.button.title" = "Learn more about Ethereum";
"a.wallet.ticketToken.sell.selectTicketQuantity.atLeastOne.title" = "Please select quantity of tickets";
"a.wallet.ticketToken.sell.price.provide.title" = "Please enter price of tickets";
"a.wallet.ticketToken.sell.selectTickets.atLeastOne.title" = "Please select a ticket to sell";
"a.wallet.ticketToken.sell.selectQuantity.title" = "Set a Price";
"a.wallet.ticketToken.sell.pricePerTicket.title" = "PRICE PER TICKET";
"a.wallet.ticketToken.sell.quantity.title" = "QUANTITY OF TICKETS";
"a.wallet.ticketToken.sell.linkExpiryDate.title" = "LINK EXPIRY DATE";
"a.wallet.ticketToken.sell.linkExpiryTime.title" = "LINK EXPIRY TIME";
"a.wallet.ticketToken.sell.totalCost.title" = "Total Cost:";
"a.wallet.ticketToken.sell.showQRCode.title" = "Show QR Code to Redemption Booth";
"a.wallet.ticketToken.sell.mode.choose.title" = "Send your sell link";
"a.wallet.ticketToken.sell.mode.choose.text.title" = "Send SMS/iMessage";
"a.wallet.ticketToken.sell.mode.choose.email.title" = "Send Email";
"a.wallet.ticketToken.sell.mode.choose.inputWalletAddress.title" = "Input Wallet Address";
"a.wallet.ticketToken.sell.mode.choose.walletAddressViaQRCodeScanner.title" = "Use QR Code Scanner";
"a.wallet.ticketToken.sell.mode.choose.other.title" = "Share Link";
"a.wallet.ticketToken.sell.mode.walletAddress.title" = "Input Wallet Address";
"a.wallet.ticketToken.sell.mode.walletAddress.target.title" = "WALLET ADDRESS TO SELL TO";
"a.wallet.ticketToken.sell.mode.walletAddress.confirmation" = "Sell to wallet address %@?";
"a.wallet.ticketToken.sell.success.title" = "Your ticket has been transferred";
"a.wallet.ticketToken.sell.failed.title" = "Your ticket was not transferred";
"a.wallet.ticketToken.sell.inProgress.title" = "Transferring ticket...";
"a.wallet.ticketToken.transfer.button.title" = "Transfer";
"a.wallet.ticketToken.transfer.selectTickets.title" = "Select Tickets To Transfer:";
"a.wallet.ticketToken.transfer.selectTicketQuantity.atLeastOne.title" = "Please select quantity of tickets";

@ -0,0 +1,55 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import QRCodeReaderViewController
protocol ScanQRCodeForWalletAddressToSellTicketCoordinatorDelegate: class {
func scanned(walletAddress: String, in coordinator: ScanQRCodeForWalletAddressToSellTicketCoordinator)
func cancelled(in coordinator: ScanQRCodeForWalletAddressToSellTicketCoordinator)
}
class ScanQRCodeForWalletAddressToSellTicketCoordinator: NSObject, Coordinator {
var coordinators: [Coordinator] = []
var ticketHolder: TicketHolder
var viewController: UIViewController
var linkExpiryDate: Date
var ethCost: String
var dollarCost: String
var paymentFlow: PaymentFlow
weak var delegate: ScanQRCodeForWalletAddressToSellTicketCoordinatorDelegate?
init(ticketHolder: TicketHolder, linkExpiryDate: Date, ethCost: String, dollarCost: String, paymentFlow: PaymentFlow, in viewController: UIViewController) {
self.ticketHolder = ticketHolder
self.linkExpiryDate = linkExpiryDate
self.ethCost = ethCost
self.dollarCost = dollarCost
self.paymentFlow = paymentFlow
self.viewController = viewController
}
func start() {
let controller = QRCodeReaderViewController()
controller.delegate = self
viewController.present(controller, animated: true, completion: nil)
}
}
extension ScanQRCodeForWalletAddressToSellTicketCoordinator: QRCodeReaderDelegate {
func readerDidCancel(_ reader: QRCodeReaderViewController!) {
reader.stopScanning()
reader.dismiss(animated: true) { [weak self] in
if let celf = self {
celf.delegate?.cancelled(in: celf)
}
}
}
func reader(_ reader: QRCodeReaderViewController!, didScanResult result: String!) {
reader.stopScanning()
reader.dismiss(animated: true) { [weak self] in
if let celf = self {
celf.delegate?.scanned(walletAddress: result, in: celf)
}
}
}
}

@ -0,0 +1,151 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import TrustKeystore
import BigInt
protocol SellTicketsCoordinatorDelegate: class {
func didClose(in coordinator: SellTicketsCoordinator)
func didFinishSuccessfully(in coordinator: SellTicketsCoordinator)
func didFail(in coordinator: SellTicketsCoordinator)
}
class SellTicketsCoordinator: Coordinator {
var coordinators: [Coordinator] = []
var ticketHolder: TicketHolder
var linkExpiryDate: Date
var ethCost: String
var dollarCost: String
var walletAddress: String
var paymentFlow: PaymentFlow
var keystore: Keystore
var session: WalletSession
var account: Account
var viewController: UIViewController
var statusViewController: StatusViewController?
weak var delegate: SellTicketsCoordinatorDelegate?
var status = StatusViewControllerViewModel.State.processing {
didSet {
statusViewController?.configure(viewModel: .init(
state: status,
inProgressText: R.string.localizable.aClaimTicketInProgressTitle(),
succeededTextText: R.string.localizable.aClaimTicketSuccessTitle(),
failedText: R.string.localizable.aClaimTicketFailedTitle()
))
}
}
init(ticketHolder: TicketHolder, linkExpiryDate: Date, ethCost: String, dollarCost: String, walletAddress: String, paymentFlow: PaymentFlow, keystore: Keystore, session: WalletSession, account: Account, on viewController: UIViewController) {
self.ticketHolder = ticketHolder
self.linkExpiryDate = linkExpiryDate
self.ethCost = ethCost
self.dollarCost = dollarCost
self.walletAddress = walletAddress
self.paymentFlow = paymentFlow
self.keystore = keystore
self.session = session
self.account = account
self.viewController = viewController
}
func start() {
guard let address = validateAddress() else { return }
showProgressViewController()
sell(address: address)
}
private func showProgressViewController() {
statusViewController = StatusViewController()
if let vc = statusViewController {
vc.delegate = self
vc.configure(viewModel: .init(
state: .processing,
inProgressText: R.string.localizable.aWalletTicketTokenSellInProgressTitle(),
succeededTextText: R.string.localizable.aWalletTicketTokenSellSuccessTitle(),
failedText: R.string.localizable.aWalletTicketTokenSellFailedTitle()
))
vc.modalPresentationStyle = .overCurrentContext
viewController.present(vc, animated: true)
}
}
//TODO code is for transfers. Convert to sell
private func sell(address: Address) {
if case .send(let transferType) = paymentFlow {
let transaction = UnconfirmedTransaction(
transferType: transferType,
value: BigInt(0),
to: address,
data: Data(),
gasLimit: .none,
gasPrice: nil,
nonce: .none,
v: .none,
r: .none,
s: .none,
expiry: .none,
indices: ticketHolder.ticketIndices
)
let configurator = TransactionConfigurator(
session: session,
account: account,
transaction: transaction
)
configurator.load { [weak self] result in
guard let `self` = self else { return }
switch result {
case .success:
self.sendTransaction(with: configurator)
case .failure(let error):
self.processFailed()
}
}
}
}
//TODO code is for transfers. Convert to sell (if necessary)
private func sendTransaction(with configurator: TransactionConfigurator) {
let unsignedTransaction = configurator.formUnsignedTransaction()
let sendTransactionCoordinator = SendTransactionCoordinator(
session: session,
keystore: keystore,
confirmType: .signThenSend)
sendTransactionCoordinator.send(transaction: unsignedTransaction) { [weak self] result in
if let celf = self {
switch result {
case .success(let type):
celf.processSuccessful()
case .failure(let error):
celf.processFailed()
}
}
}
}
private func processSuccessful() {
status = .succeeded
}
private func processFailed() {
status = .failed
}
private func validateAddress() -> Address? {
return Address(string: walletAddress)
}
}
extension SellTicketsCoordinator: StatusViewControllerDelegate {
func didPressDone(in viewController: StatusViewController) {
viewController.dismiss(animated: false)
switch status {
case .processing:
delegate?.didClose(in: self)
case .succeeded:
delegate?.didFinishSuccessfully(in: self)
case .failed:
delegate?.didFail(in: self)
}
}
}

@ -0,0 +1,149 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
import MessageUI
enum TicketSellMode {
case walletAddressTextEntry
case walletAddressFromQRCode
case other
}
protocol ChooseTicketSellModeViewControllerDelegate: class {
func didChoose(sellMode: TicketSellMode, in viewController: ChooseTicketSellModeViewController)
}
class ChooseTicketSellModeViewController: 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
let roundedBackground = UIView()
let titleLabel = UILabel()
let inputWalletAddressButton = ShareModeButton()
let qrCodeScannerButton = ShareModeButton()
let otherButton = ShareModeButton()
let ticketHolder: TicketHolder
var ethCost: String
var dollarCost: String
var linkExpiryDate: Date
var paymentFlow: PaymentFlow
weak var delegate: ChooseTicketSellModeViewControllerDelegate?
init(ticketHolder: TicketHolder, linkExpiryDate: Date, ethCost: String, dollarCost: String, paymentFlow: PaymentFlow) {
self.ticketHolder = ticketHolder
self.linkExpiryDate = linkExpiryDate
self.ethCost = ethCost
//TODO if we only need ethCost, remove dollarCost?
self.dollarCost = dollarCost
self.paymentFlow = paymentFlow
super.init(nibName: nil, bundle: nil)
view.backgroundColor = Colors.appBackground
roundedBackground.translatesAutoresizingMaskIntoConstraints = false
roundedBackground.cornerRadius = 20
view.addSubview(roundedBackground)
inputWalletAddressButton.callback = {
self.delegate?.didChoose(sellMode: .walletAddressTextEntry, in: self)
}
inputWalletAddressButton.translatesAutoresizingMaskIntoConstraints = false
qrCodeScannerButton.callback = {
self.delegate?.didChoose(sellMode: .walletAddressFromQRCode, in: self)
}
qrCodeScannerButton.translatesAutoresizingMaskIntoConstraints = false
otherButton.callback = {
self.delegate?.didChoose(sellMode: .other, in: self)
}
otherButton.translatesAutoresizingMaskIntoConstraints = false
let buttonRow1 = UIStackView(arrangedSubviews: [
inputWalletAddressButton,
qrCodeScannerButton,
])
buttonRow1.translatesAutoresizingMaskIntoConstraints = false
buttonRow1.axis = .horizontal
buttonRow1.spacing = 12
buttonRow1.distribution = .fill
let buttonPlaceholder = UIView()
let buttonRow2 = UIStackView(arrangedSubviews: [
otherButton,
buttonPlaceholder,
])
buttonRow2.translatesAutoresizingMaskIntoConstraints = false
buttonRow2.axis = .horizontal
buttonRow2.spacing = 12
buttonRow2.distribution = .fill
let stackView = UIStackView(arrangedSubviews: [
.spacer(height: 7),
titleLabel,
.spacer(height: 20),
buttonRow1,
.spacer(height: 12),
buttonRow2,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 0
stackView.distribution = .fill
roundedBackground.addSubview(stackView)
let marginToHideBottomRoundedCorners = CGFloat(30)
NSLayoutConstraint.activate([
otherButton.widthAnchor.constraint(equalTo: inputWalletAddressButton.widthAnchor),
otherButton.heightAnchor.constraint(equalTo: inputWalletAddressButton.heightAnchor),
otherButton.widthAnchor.constraint(equalTo: buttonPlaceholder.widthAnchor),
otherButton.heightAnchor.constraint(equalTo: buttonPlaceholder.heightAnchor),
stackView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30),
stackView.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30),
stackView.topAnchor.constraint(equalTo: roundedBackground.topAnchor, constant: 16),
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),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: ChooseTicketSellModeViewControllerViewModel) {
roundedBackground.backgroundColor = viewModel.contentsBackgroundColor
roundedBackground.layer.cornerRadius = 20
titleLabel.numberOfLines = 0
titleLabel.textColor = viewModel.titleColor
titleLabel.font = viewModel.titleFont
titleLabel.textAlignment = .center
titleLabel.text = viewModel.titleLabelText
inputWalletAddressButton.title = viewModel.inputWalletAddressButtonTitle
inputWalletAddressButton.image = viewModel.inputWalletAddressButtonImage
qrCodeScannerButton.title = viewModel.qrCodeScannerButtonTitle
qrCodeScannerButton.image = viewModel.qrCodeScannerButtonImage
otherButton.title = viewModel.otherButtonTitle
otherButton.image = viewModel.otherButtonImage
inputWalletAddressButton.label.font = viewModel.buttonTitleFont
qrCodeScannerButton.label.font = viewModel.buttonTitleFont
otherButton.label.font = viewModel.buttonTitleFont
inputWalletAddressButton.label.textColor = viewModel.buttonTitleColor
qrCodeScannerButton.label.textColor = viewModel.buttonTitleColor
otherButton.label.textColor = viewModel.buttonTitleColor
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
inputWalletAddressButton.layer.cornerRadius = inputWalletAddressButton.frame.size.height / 2
qrCodeScannerButton.layer.cornerRadius = qrCodeScannerButton.frame.size.height / 2
}
}

@ -0,0 +1,437 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
protocol EnterSellTicketsDetailsViewControllerDelegate: class {
func didEnterSellTicketDetails(ticketHolder: TicketHolder, linkExpiryDate: Date, ethCost: String, dollarCost: String, in viewController: EnterSellTicketsDetailsViewController)
func didPressViewInfo(in viewController: EnterSellTicketsDetailsViewController)
}
class EnterSellTicketsDetailsViewController: UIViewController {
let storage: TokensDataStore
//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 scrollView = UIScrollView()
let header = TicketsViewControllerTitleHeader()
let subtitleLabel = UILabel()
let ethHelpButton = UIButton(type: .system)
let pricePerTicketLabel = UILabel()
let pricePerTicketField = AmountTextField()
let quantityLabel = UILabel()
let quantityStepper = NumberStepper()
let linkExpiryDateLabel = UILabel()
let linkExpiryDateField = DateEntryField()
let linkExpiryTimeLabel = UILabel()
let linkExpiryTimeField = TimeEntryField()
let totalCostLabel = UILabel()
let costLabel = UILabel()
let ticketView = TicketRowView()
let nextButton = UIButton(type: .system)
var datePicker = UIDatePicker()
var timePicker = UIDatePicker()
var viewModel: SellTicketsQuantitySelectionViewModel!
var paymentFlow: PaymentFlow
weak var delegate: EnterSellTicketsDetailsViewControllerDelegate?
init(storage: TokensDataStore, paymentFlow: PaymentFlow) {
self.storage = storage
self.paymentFlow = paymentFlow
super.init(nibName: nil, bundle: nil)
navigationItem.rightBarButtonItem = UIBarButtonItem(image: R.image.location(), style: .plain, target: self, action: #selector(showInfo))
roundedBackground.translatesAutoresizingMaskIntoConstraints = false
roundedBackground.backgroundColor = Colors.appWhite
roundedBackground.cornerRadius = 20
view.addSubview(roundedBackground)
scrollView.translatesAutoresizingMaskIntoConstraints = false
roundedBackground.addSubview(scrollView)
subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
ethHelpButton.setTitle(R.string.localizable.aWalletTicketTokenSellLearnAboutEthButtonTitle(), for: .normal)
ethHelpButton.addTarget(self, action: #selector(learnMoreAboutEthereumTapped), for: .touchUpInside)
nextButton.setTitle(R.string.localizable.aWalletTicketTokenSellGenerateLinkButtonTitle(), for: .normal)
nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside)
ticketView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(ticketView)
pricePerTicketLabel.translatesAutoresizingMaskIntoConstraints = false
quantityLabel.translatesAutoresizingMaskIntoConstraints = false
linkExpiryDateLabel.translatesAutoresizingMaskIntoConstraints = false
linkExpiryTimeLabel.translatesAutoresizingMaskIntoConstraints = false
totalCostLabel.translatesAutoresizingMaskIntoConstraints = false
pricePerTicketField.translatesAutoresizingMaskIntoConstraints = false
//TODO is there a better way to get the price?
if let rates = storage.tickers, let ticker = rates.values.first(where: { $0.symbol == "ETH" }), let price = Double(ticker.price) {
pricePerTicketField.ethToDollarRate = price
}
pricePerTicketField.delegate = self
costLabel.translatesAutoresizingMaskIntoConstraints = false
linkExpiryDateField.translatesAutoresizingMaskIntoConstraints = false
linkExpiryDateField.value = Date.yesterday
linkExpiryDateField.delegate = self
linkExpiryTimeField.translatesAutoresizingMaskIntoConstraints = false
linkExpiryTimeField.delegate = self
quantityStepper.translatesAutoresizingMaskIntoConstraints = false
quantityStepper.minimumValue = 1
quantityStepper.value = 1
let col0 = UIStackView(arrangedSubviews: [
pricePerTicketLabel,
.spacer(height: 4),
pricePerTicketField,
pricePerTicketField.alternativeAmountLabel,
.spacer(height: 16),
linkExpiryDateLabel,
.spacer(height: 4),
linkExpiryDateField,
])
col0.translatesAutoresizingMaskIntoConstraints = false
col0.axis = .vertical
col0.spacing = 0
col0.distribution = .fill
let sameHeightAsPricePerTicketAlternativeAmountLabelPlaceholder = UIView()
sameHeightAsPricePerTicketAlternativeAmountLabelPlaceholder.translatesAutoresizingMaskIntoConstraints = false
let col1 = UIStackView(arrangedSubviews: [
quantityLabel,
.spacer(height: 4),
quantityStepper,
sameHeightAsPricePerTicketAlternativeAmountLabelPlaceholder,
.spacer(height: 16),
linkExpiryTimeLabel,
.spacer(height: 4),
linkExpiryTimeField,
])
col1.translatesAutoresizingMaskIntoConstraints = false
col1.axis = .vertical
col1.spacing = 0
col1.distribution = .fill
let choicesStackView = UIStackView(arrangedSubviews: [
col0,
.spacerWidth(10),
col1,
])
choicesStackView.translatesAutoresizingMaskIntoConstraints = false
choicesStackView.axis = .horizontal
choicesStackView.spacing = 0
choicesStackView.distribution = .fill
datePicker.datePickerMode = .date
datePicker.minimumDate = Date()
datePicker.addTarget(self, action: #selector(datePickerValueChanged), for: .valueChanged)
datePicker.isHidden = true
timePicker.datePickerMode = .time
timePicker.minimumDate = Date()
timePicker.addTarget(self, action: #selector(timePickerValueChanged), for: .valueChanged)
timePicker.isHidden = true
let separator = UIView()
separator.backgroundColor = UIColor(red: 230, green: 230, blue: 230)
let stackView = UIStackView(arrangedSubviews: [
header,
subtitleLabel,
.spacer(height: 16),
ethHelpButton,
.spacer(height: 30),
ticketView,
.spacer(height: 20),
choicesStackView,
datePicker,
timePicker,
.spacer(height: 18),
totalCostLabel,
.spacer(height: 10),
separator,
.spacer(height: 10),
costLabel,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 0
stackView.distribution = .fill
stackView.alignment = .center
scrollView.addSubview(stackView)
let buttonsStackView = UIStackView(arrangedSubviews: [nextButton])
buttonsStackView.translatesAutoresizingMaskIntoConstraints = false
buttonsStackView.axis = .horizontal
buttonsStackView.spacing = 0
buttonsStackView.distribution = .fillEqually
buttonsStackView.setContentHuggingPriority(.required, for: .horizontal)
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.heightAnchor.constraint(equalToConstant: 90),
//Strange repositioning of header horizontally while typing without this
header.leadingAnchor.constraint(equalTo: stackView.leadingAnchor),
header.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
quantityStepper.heightAnchor.constraint(equalToConstant: 50),
sameHeightAsPricePerTicketAlternativeAmountLabelPlaceholder.heightAnchor.constraint(equalTo: pricePerTicketField.alternativeAmountLabel.heightAnchor),
ticketView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
ticketView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
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),
separator.heightAnchor.constraint(equalToConstant: 1),
separator.leadingAnchor.constraint(equalTo: ticketView.background.leadingAnchor),
separator.trailingAnchor.constraint(equalTo: ticketView.background.trailingAnchor),
pricePerTicketField.leadingAnchor.constraint(equalTo: ticketView.background.leadingAnchor),
quantityStepper.rightAnchor.constraint(equalTo: ticketView.background.rightAnchor),
linkExpiryDateField.leadingAnchor.constraint(equalTo: ticketView.background.leadingAnchor),
linkExpiryTimeField.rightAnchor.constraint(equalTo: ticketView.background.rightAnchor),
datePicker.leadingAnchor.constraint(equalTo: ticketView.background.leadingAnchor),
datePicker.trailingAnchor.constraint(equalTo: ticketView.background.trailingAnchor),
timePicker.leadingAnchor.constraint(equalTo: ticketView.background.leadingAnchor),
timePicker.trailingAnchor.constraint(equalTo: ticketView.background.trailingAnchor),
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
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),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: footerBar.topAnchor),
pricePerTicketField.widthAnchor.constraint(equalTo: quantityStepper.widthAnchor),
pricePerTicketField.heightAnchor.constraint(equalTo: quantityStepper.heightAnchor),
linkExpiryDateField.widthAnchor.constraint(equalTo: quantityStepper.widthAnchor),
linkExpiryDateField.heightAnchor.constraint(equalTo: quantityStepper.heightAnchor),
linkExpiryTimeField.widthAnchor.constraint(equalTo: quantityStepper.widthAnchor),
linkExpiryTimeField.heightAnchor.constraint(equalTo: quantityStepper.heightAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc
func nextButtonTapped() {
guard quantityStepper.value > 0 else {
UIAlertController.alert(title: "",
message: R.string.localizable.aWalletTicketTokenSellSelectTicketQuantityAtLeastOneTitle(),
alertButtonTitles: [R.string.localizable.oK()],
alertButtonStyles: [.cancel],
viewController: self,
completion: nil)
return
}
let noPrice: Bool
if let price = Double(pricePerTicketField.ethCost) {
noPrice = price.isZero
} else {
noPrice = true
}
guard !noPrice else {
UIAlertController.alert(title: "",
message: R.string.localizable.aWalletTicketTokenSellPriceProvideTitle(),
alertButtonTitles: [R.string.localizable.oK()],
alertButtonStyles: [.cancel],
viewController: self,
completion: nil)
return
}
//TODO be good if we check if date chosen is not too far into the future. Example 1 year ahead. Common error?
delegate?.didEnterSellTicketDetails(ticketHolder: getTicketHolderFromQuantity(), linkExpiryDate: linkExpiryDate(), ethCost: pricePerTicketField.ethCost, dollarCost: pricePerTicketField.dollarCost, in: self)
}
private func linkExpiryDate() -> Date {
let hour = NSCalendar.current.component(.hour, from: linkExpiryTimeField.value)
let minutes = NSCalendar.current.component(.minute, from: linkExpiryTimeField.value)
let seconds = NSCalendar.current.component(.second, from: linkExpiryTimeField.value)
if let date = NSCalendar.current.date(bySettingHour: hour, minute: minutes, second: seconds, of: linkExpiryDateField.value) {
return date
} else {
return Date()
}
}
@objc func learnMoreAboutEthereumTapped() {
showInfo()
}
@objc func showInfo() {
delegate?.didPressViewInfo(in: self)
}
func configure(viewModel: SellTicketsQuantitySelectionViewModel) {
self.viewModel = viewModel
view.backgroundColor = viewModel.backgroundColor
header.configure(title: viewModel.headerTitle)
subtitleLabel.textAlignment = .center
subtitleLabel.textColor = viewModel.subtitleLabelColor
subtitleLabel.font = viewModel.subtitleLabelFont
subtitleLabel.text = viewModel.subtitleLabelText
ethHelpButton.titleLabel?.font = viewModel.ethHelpButtonFont
ticketView.configure(viewModel: .init())
pricePerTicketLabel.textAlignment = .center
pricePerTicketLabel.textColor = viewModel.subtitleLabelColor
pricePerTicketLabel.font = viewModel.choiceLabelFont
pricePerTicketLabel.text = viewModel.pricePerTicketLabelText
linkExpiryDateLabel.textAlignment = .center
linkExpiryDateLabel.textColor = viewModel.subtitleLabelColor
linkExpiryDateLabel.font = viewModel.choiceLabelFont
linkExpiryDateLabel.text = viewModel.linkExpiryDateLabelText
linkExpiryTimeLabel.textAlignment = .center
linkExpiryTimeLabel.textColor = viewModel.subtitleLabelColor
linkExpiryTimeLabel.font = viewModel.choiceLabelFont
linkExpiryTimeLabel.text = viewModel.linkExpiryTimeLabelText
totalCostLabel.textAlignment = .center
totalCostLabel.textColor = viewModel.totalCostLabelColor
totalCostLabel.font = viewModel.totalCostLabelFont
totalCostLabel.text = viewModel.totalCostLabelText
costLabel.textAlignment = .center
costLabel.textColor = viewModel.costLabelColor
costLabel.font = viewModel.costLabelFont
costLabel.text = viewModel.costLabelText
quantityLabel.textAlignment = .center
quantityLabel.textColor = viewModel.choiceLabelColor
quantityLabel.font = viewModel.choiceLabelFont
quantityLabel.text = viewModel.quantityLabelText
quantityStepper.borderWidth = 1
quantityStepper.clipsToBounds = true
quantityStepper.borderColor = viewModel.stepperBorderColor
quantityStepper.maximumValue = viewModel.maxValue
ticketView.stateLabel.isHidden = true
ticketView.ticketCountLabel.text = viewModel.ticketCount
ticketView.titleLabel.text = viewModel.title
ticketView.venueLabel.text = viewModel.venue
ticketView.dateLabel.text = viewModel.date
ticketView.seatRangeLabel.text = viewModel.seatRange
ticketView.zoneNameLabel.text = viewModel.zoneName
nextButton.setTitleColor(viewModel.buttonTitleColor, for: .normal)
nextButton.backgroundColor = viewModel.buttonBackgroundColor
nextButton.titleLabel?.font = viewModel.buttonFont
}
private func getTicketHolderFromQuantity() -> TicketHolder {
let quantity = quantityStepper.value
let ticketHolder = viewModel.ticketHolder
let tickets = Array(ticketHolder.tickets[..<quantity])
return TicketHolder(
tickets: tickets,
zone: ticketHolder.zone,
name: ticketHolder.name,
venue: ticketHolder.venue,
date: ticketHolder.date,
status: ticketHolder.status
)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
quantityStepper.layer.cornerRadius = quantityStepper.frame.size.height / 2
pricePerTicketField.layer.cornerRadius = quantityStepper.frame.size.height / 2
linkExpiryDateField.layer.cornerRadius = linkExpiryDateField.frame.size.height / 2
linkExpiryTimeField.layer.cornerRadius = linkExpiryTimeField.frame.size.height / 2
}
@objc func datePickerValueChanged() {
linkExpiryDateField.value = datePicker.date
}
@objc func timePickerValueChanged() {
linkExpiryTimeField.value = timePicker.date
}
}
extension EnterSellTicketsDetailsViewController: DateEntryFieldDelegate {
func didTap(in dateEntryField: DateEntryField) {
datePicker.isHidden = !datePicker.isHidden
if !datePicker.isHidden {
datePicker.date = linkExpiryDateField.value
timePicker.isHidden = true
}
}
}
extension EnterSellTicketsDetailsViewController: TimeEntryFieldDelegate {
func didTap(in timeEntryField: TimeEntryField) {
timePicker.isHidden = !timePicker.isHidden
if !timePicker.isHidden {
timePicker.date = linkExpiryTimeField.value
datePicker.isHidden = true
}
}
}
extension EnterSellTicketsDetailsViewController: AmountTextFieldDelegate {
func changeAmount(in textField: AmountTextField) {
viewModel.ethCost = textField.ethCost
configure(viewModel: viewModel)
}
func changeType(in textField: AmountTextField) {
viewModel.ethCost = textField.ethCost
configure(viewModel: viewModel)
}
}

@ -0,0 +1,165 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
import TrustKeystore
protocol SellTicketViaWalletAddressViewControllerDelegate: class {
func didChooseSell(to walletAddress: String, viewController: SellTicketViaWalletAddressViewController)
}
class SellTicketViaWalletAddressViewController: 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
let roundedBackground = UIView()
let titleLabel = UILabel()
let subtitleLabel = UILabel()
let textField = UITextField()
let ticketView = TicketRowView()
let actionButton = UIButton(type: .system)
var paymentFlow: PaymentFlow
weak var delegate: SellTicketViaWalletAddressViewControllerDelegate?
let ticketHolder: TicketHolder
var linkExpiryDate: Date
var ethCost: String
var dollarCost: String
init(ticketHolder: TicketHolder, linkExpiryDate: Date, ethCost: String, dollarCost: String, paymentFlow: PaymentFlow) {
self.ticketHolder = ticketHolder
self.linkExpiryDate = linkExpiryDate
self.ethCost = ethCost
self.dollarCost = dollarCost
self.paymentFlow = paymentFlow
super.init(nibName: nil, bundle: nil)
view.backgroundColor = Colors.appBackground
roundedBackground.translatesAutoresizingMaskIntoConstraints = false
roundedBackground.cornerRadius = 20
view.addSubview(roundedBackground)
textField.translatesAutoresizingMaskIntoConstraints = false
textField.delegate = self
textField.returnKeyType = .done
actionButton.translatesAutoresizingMaskIntoConstraints = false
actionButton.addTarget(self, action: #selector(sell), for: .touchUpInside)
roundedBackground.addSubview(actionButton)
ticketView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(ticketView)
let stackView = UIStackView(arrangedSubviews: [
.spacer(height: 7),
titleLabel,
.spacer(height: 20),
subtitleLabel,
.spacer(height: 10),
textField,
.spacer(height: 40),
ticketView,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 0
stackView.distribution = .fill
stackView.alignment = .center
roundedBackground.addSubview(stackView)
let marginToHideBottomRoundedCorners = CGFloat(30)
NSLayoutConstraint.activate([
textField.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30),
textField.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30),
textField.heightAnchor.constraint(equalToConstant: 50),
ticketView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
ticketView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
stackView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor),
stackView.topAnchor.constraint(equalTo: roundedBackground.topAnchor, constant: 16),
actionButton.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor),
actionButton.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor),
actionButton.heightAnchor.constraint(equalToConstant: 60),
actionButton.bottomAnchor.constraint(equalTo: roundedBackground.bottomAnchor, constant: -marginToHideBottomRoundedCorners),
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),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: SellTicketViaWalletAddressViewControllerViewModel) {
roundedBackground.backgroundColor = viewModel.contentsBackgroundColor
roundedBackground.layer.cornerRadius = 20
titleLabel.numberOfLines = 0
titleLabel.textColor = viewModel.titleColor
titleLabel.font = viewModel.titleFont
titleLabel.textAlignment = .center
titleLabel.text = viewModel.titleLabelText
subtitleLabel.textColor = viewModel.subtitleColor
subtitleLabel.font = viewModel.subtitleFont
subtitleLabel.textAlignment = .center
subtitleLabel.text = viewModel.subtitleLabelText
textField.textColor = viewModel.textFieldTextColor
textField.font = viewModel.textFieldFont
textField.layer.borderColor = viewModel.textFieldBorderColor.cgColor
textField.layer.borderWidth = viewModel.textFieldBorderWidth
textField.leftView = .spacerWidth(viewModel.textFieldHorizontalPadding)
textField.leftViewMode = .always
textField.rightView = .spacerWidth(viewModel.textFieldHorizontalPadding)
textField.rightViewMode = .always
ticketView.configure(viewModel: .init())
ticketView.stateLabel.isHidden = true
ticketView.ticketCountLabel.text = viewModel.ticketCount
ticketView.titleLabel.text = viewModel.title
ticketView.venueLabel.text = viewModel.venue
ticketView.dateLabel.text = viewModel.date
ticketView.seatRangeLabel.text = viewModel.seatRange
ticketView.zoneNameLabel.text = viewModel.zoneName
actionButton.setTitle(viewModel.actionButtonTitle, for: .normal)
actionButton.setTitleColor(viewModel.actionButtonTitleColor, for: .normal)
actionButton.setBackgroundColor(viewModel.actionButtonBackgroundColor, forState: .normal)
actionButton.titleLabel?.font = viewModel.actionButtonTitleFont
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
actionButton.layer.cornerRadius = actionButton.frame.size.height / 2
textField.layer.cornerRadius = textField.frame.size.height / 2
}
@objc func sell() {
if let address = textField.text, !address.isEmpty {
guard let _ = Address(string: address) else {
displayError(error: Errors.invalidAddress)
return
}
delegate?.didChooseSell(to: address, viewController: self)
}
}
}
extension SellTicketViaWalletAddressViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}

@ -0,0 +1,154 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
protocol SellTicketsViewControllerDelegate: class {
func didSelectTicketHolder(ticketHolder: TicketHolder, in viewController: SellTicketsViewController)
func didPressViewInfo(in viewController: SellTicketsViewController)
}
class SellTicketsViewController: 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
let roundedBackground = UIView()
let header = TicketsViewControllerTitleHeader()
let tableView = UITableView(frame: .zero, style: .plain)
let nextButton = UIButton(type: .system)
var viewModel: SellTicketsViewModel!
var paymentFlow: PaymentFlow
weak var delegate: SellTicketsViewControllerDelegate?
init(paymentFlow: PaymentFlow) {
self.paymentFlow = paymentFlow
super.init(nibName: nil, bundle: nil)
navigationItem.rightBarButtonItem = UIBarButtonItem(image: R.image.location(), style: .plain, target: self, action: #selector(showInfo))
view.backgroundColor = Colors.appBackground
roundedBackground.translatesAutoresizingMaskIntoConstraints = false
roundedBackground.backgroundColor = Colors.appWhite
roundedBackground.cornerRadius = 20
view.addSubview(roundedBackground)
tableView.register(TicketTableViewCellWithCheckbox.self, forCellReuseIdentifier: TicketTableViewCellWithCheckbox.identifier)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.separatorStyle = .none
tableView.backgroundColor = Colors.appWhite
tableView.tableHeaderView = header
roundedBackground.addSubview(tableView)
nextButton.setTitle(R.string.localizable.aWalletNextButtonTitle(), for: .normal)
nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside)
let buttonsStackView = UIStackView(arrangedSubviews: [nextButton])
buttonsStackView.translatesAutoresizingMaskIntoConstraints = false
buttonsStackView.axis = .horizontal
buttonsStackView.spacing = 0
buttonsStackView.distribution = .fillEqually
buttonsStackView.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
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([
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),
tableView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor),
tableView.topAnchor.constraint(equalTo: roundedBackground.topAnchor),
tableView.bottomAnchor.constraint(equalTo: footerBar.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),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: SellTicketsViewModel) {
self.viewModel = viewModel
tableView.dataSource = self
header.configure(title: viewModel.title)
tableView.tableHeaderView = header
nextButton.setTitleColor(viewModel.buttonTitleColor, for: .normal)
nextButton.backgroundColor = viewModel.buttonBackgroundColor
nextButton.titleLabel?.font = viewModel.buttonFont
}
private func resetSelection(for ticketHolder: TicketHolder) {
let selected = ticketHolder.isSelected
viewModel.ticketHolders?.forEach { $0.isSelected = false }
ticketHolder.isSelected = !selected
tableView.reloadData()
}
@objc
func nextButtonTapped() {
let selectedTicketHolders = viewModel.ticketHolders?.filter { $0.isSelected }
if selectedTicketHolders!.isEmpty {
UIAlertController.alert(title: "",
message: R.string.localizable.aWalletTicketTokenSellSelectTicketsAtLeastOneTitle(),
alertButtonTitles: [R.string.localizable.oK()],
alertButtonStyles: [.cancel],
viewController: self,
completion: nil)
} else {
self.delegate?.didSelectTicketHolder(ticketHolder: selectedTicketHolders!.first!, in: self)
}
}
@objc func showInfo() {
delegate?.didPressViewInfo(in: self)
}
}
extension SellTicketsViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.numberOfItems(for: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: TicketTableViewCellWithCheckbox.identifier, for: indexPath) as! TicketTableViewCellWithCheckbox
let ticketHolder = viewModel.item(for: indexPath)
cell.configure(viewModel: .init(ticketHolder: ticketHolder))
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let ticketHolder = viewModel.item(for: indexPath)
let cellViewModel = BaseTicketTableViewCellViewModel(ticketHolder: ticketHolder)
return cellViewModel.cellHeight
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let ticketHolder = viewModel.item(for: indexPath)
resetSelection(for: ticketHolder)
tableView.deselectRow(at: indexPath, animated: true)
}
}

@ -0,0 +1,49 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
class ChooseTicketSellModeViewControllerViewModel {
var contentsBackgroundColor: UIColor {
return Colors.appWhite
}
var titleColor: UIColor {
return Colors.appText
}
var titleFont: UIFont {
return Fonts.light(size: 25)!
}
var titleLabelText: String {
return R.string.localizable.aWalletTicketTokenSellModeChooseTitle()
}
var textButtonTitle: String {
return R.string.localizable.aWalletTicketTokenSellModeChooseTextTitle()
}
var inputWalletAddressButtonTitle: String {
return R.string.localizable.aWalletTicketTokenSellModeChooseInputWalletAddressTitle()
}
var inputWalletAddressButtonImage: UIImage? {
return R.image.transfer_wallet_address()
}
var qrCodeScannerButtonTitle: String {
return R.string.localizable.aWalletTicketTokenSellModeChooseWalletAddressViaQRCodeScannerTitle()
}
var qrCodeScannerButtonImage: UIImage? {
return R.image.transfer_qr_code()
}
var otherButtonTitle: String {
return R.string.localizable.aWalletTicketTokenSellModeChooseOtherTitle()
}
var otherButtonImage: UIImage? {
return R.image.transfer_others()
}
var buttonTitleFont: UIFont {
if ScreenChecker().isNarrowScreen() {
return Fonts.light(size: 18)!
} else {
return Fonts.light(size: 21)!
}
}
var buttonTitleColor: UIColor {
return Colors.appText
}
}

@ -0,0 +1,81 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
struct SellTicketViaWalletAddressViewControllerViewModel {
var ticketHolder: TicketHolder
var contentsBackgroundColor: UIColor {
return Colors.appWhite
}
var titleColor: UIColor {
return Colors.appText
}
var titleFont: UIFont {
return Fonts.light(size: 25)!
}
var subtitleColor: UIColor {
return UIColor(red: 155, green: 155, blue: 155)
}
var subtitleFont: UIFont {
return Fonts.regular(size: 10)!
}
var actionButtonTitleColor: UIColor {
return Colors.appWhite
}
var actionButtonBackgroundColor: UIColor {
return Colors.appHighlightGreen
}
var actionButtonTitleFont: UIFont {
return Fonts.regular(size: 20)!
}
var titleLabelText: String {
return R.string.localizable.aWalletTicketTokenSellModeWalletAddressTitle()
}
var subtitleLabelText: String {
return R.string.localizable.aWalletTicketTokenSellModeWalletAddressTargetTitle()
}
var actionButtonTitle: String {
return R.string.localizable.aWalletTicketTokenSellButtonTitle()
}
var textFieldTextColor: UIColor {
return Colors.appText
}
var textFieldFont: UIFont {
return Fonts.light(size: 15)!
}
var textFieldBorderColor: UIColor {
return Colors.appBackground
}
var textFieldBorderWidth: CGFloat {
return 1
}
var textFieldHorizontalPadding: CGFloat {
return 22
}
var ticketCount: String {
return "x\(ticketHolder.tickets.count)"
}
var title: String {
return ticketHolder.name
}
var seatRange: String {
return ticketHolder.seatRange
}
var zoneName: String {
return ticketHolder.zone
}
var venue: String {
return ticketHolder.venue
}
var date: String {
//TODO Should format be localized?
return ticketHolder.date.format("dd MMM yyyy")
}
}

@ -0,0 +1,131 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
struct SellTicketsQuantitySelectionViewModel {
var ticketHolder: TicketHolder
var ethCost: String = "0"
var headerTitle: String {
return R.string.localizable.aWalletTicketTokenSellSelectQuantityTitle()
}
var maxValue: Int {
return ticketHolder.tickets.count
}
var backgroundColor: UIColor {
return Colors.appBackground
}
var buttonTitleColor: UIColor {
return Colors.appWhite
}
var buttonBackgroundColor: UIColor {
return Colors.appHighlightGreen
}
var buttonFont: UIFont {
return Fonts.regular(size: 20)!
}
var subtitleLabelColor: UIColor {
return UIColor(red: 155, green: 155, blue: 155)
}
var subtitleLabelFont: UIFont {
return Fonts.light(size: 18)!
}
var subtitleLabelText: String {
return R.string.localizable.aWalletTicketTokenSellEthHelpTitle()
}
var ethHelpButtonFont: UIFont {
return Fonts.semibold(size: 18)!
}
var choiceLabelColor: UIColor {
return UIColor(red: 155, green: 155, blue: 155)
}
var choiceLabelFont: UIFont {
return Fonts.regular(size: 10)!
}
var stepperBorderColor: UIColor {
return Colors.appBackground
}
var ticketCount: String {
return "x\(ticketHolder.tickets.count)"
}
var title: String {
return ticketHolder.name
}
var seatRange: String {
return ticketHolder.seatRange
}
var zoneName: String {
return ticketHolder.zone
}
var venue: String {
return ticketHolder.venue
}
var quantityLabelText: String {
return R.string.localizable.aWalletTicketTokenSellQuantityTitle()
}
var date: String {
//TODO Should format be localized?
return ticketHolder.date.format("dd MMM yyyy")
}
var pricePerTicketLabelText: String {
return R.string.localizable.aWalletTicketTokenSellPricePerTicketTitle()
}
var linkExpiryDateLabelText: String {
return R.string.localizable.aWalletTicketTokenSellLinkExpiryDateTitle()
}
var linkExpiryTimeLabelText: String {
return R.string.localizable.aWalletTicketTokenSellLinkExpiryTimeTitle()
}
var totalCostLabelText: String {
return R.string.localizable.aWalletTicketTokenSellTotalCostTitle()
}
var totalCostLabelFont: UIFont {
return Fonts.light(size: 21)!
}
var totalCostLabelColor: UIColor {
return Colors.appText
}
var costLabelText: String {
return "\(ethCost) ETH"
}
var costLabelColor: UIColor {
return Colors.appBackground
}
var costLabelFont: UIFont {
return Fonts.semibold(size: 21)!
}
init(ticketHolder: TicketHolder) {
self.ticketHolder = ticketHolder
}
}

@ -0,0 +1,43 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
struct SellTicketsViewModel {
var token: TokenObject
var ticketHolders: [TicketHolder]?
init(token: TokenObject) {
self.token = token
self.ticketHolders = TicketAdaptor.getTicketHolders(for: token)
}
func item(for indexPath: IndexPath) -> TicketHolder {
return ticketHolders![indexPath.row]
}
func numberOfItems(for section: Int) -> Int {
return ticketHolders!.count
}
func height(for section: Int) -> CGFloat {
return 90
}
var title: String {
return R.string.localizable.aWalletTicketTokenSellSelectTicketsTitle ()
}
var buttonTitleColor: UIColor {
return Colors.appWhite
}
var buttonBackgroundColor: UIColor {
return Colors.appHighlightGreen
}
var buttonFont: UIFont {
return Fonts.regular(size: 20)!
}
}

@ -0,0 +1,197 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
protocol AmountTextFieldDelegate: class {
func changeAmount(in textField: AmountTextField)
func changeType(in textField: AmountTextField)
}
class AmountTextField: UIControl {
struct Pair {
let left: String
let right: String
func swapPair() -> Pair {
return Pair(left: right, right: left)
}
}
var ethToDollarRate: Double? = nil
var ethCost: String {
if currentPair.left == "ETH" {
return textField.text ?? "0"
} else {
return String(convertToAlternateAmount())
}
}
var dollarCost: String {
if currentPair.left == "ETH" {
return String(convertToAlternateAmount())
} else {
return textField.text ?? "0"
}
}
var currentPair: Pair
let textField = UITextField()
let alternativeAmountLabel = UILabel()
let fiatButton = Button(size: .normal, style: .borderless)
weak var delegate: AmountTextFieldDelegate?
private var allowedCharacters: String = {
let decimalSeparator = Locale.current.decimalSeparator ?? "."
return "0123456789" + decimalSeparator
}()
lazy var decimalFormatter: DecimalFormatter = {
return DecimalFormatter()
}()
init() {
currentPair = Pair(left: "ETH", right: "USD")
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
layer.borderColor = Colors.appBackground.cgColor
layer.borderWidth = 1
textField.translatesAutoresizingMaskIntoConstraints = false
textField.delegate = self
textField.keyboardType = .decimalPad
textField.leftViewMode = .always
textField.rightViewMode = .always
textField.inputAccessoryView = makeToolbarWithDoneButton()
textField.leftView = .spacerWidth(22)
textField.rightView = makeAmountRightView()
textField.textColor = Colors.appBackground
textField.font = Fonts.bold(size: 21)
addSubview(textField)
alternativeAmountLabel.translatesAutoresizingMaskIntoConstraints = false
alternativeAmountLabel.numberOfLines = 0
alternativeAmountLabel.textColor = UIColor(red: 155, green: 155, blue: 155)
alternativeAmountLabel.font = Fonts.regular(size: 10)!
alternativeAmountLabel.textAlignment = .center
computeAlternateAmount()
NSLayoutConstraint.activate([
textField.leadingAnchor.constraint(equalTo: leadingAnchor),
textField.trailingAnchor.constraint(equalTo: trailingAnchor),
textField.topAnchor.constraint(equalTo: topAnchor),
textField.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
private func makeAmountRightView() -> UIView {
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)
let amountRightView = UIStackView(arrangedSubviews: [
fiatButton,
])
amountRightView.translatesAutoresizingMaskIntoConstraints = false
amountRightView.distribution = .equalSpacing
amountRightView.spacing = 1
amountRightView.axis = .horizontal
return amountRightView
}
@objc func fiatAction(button: UIButton) {
let swappedPair = currentPair.swapPair()
//New pair for future calculation we should swap pair each time we press fiat button.
self.currentPair = swappedPair
fiatButton.setTitle(currentPair.left, for: .normal)
button.setTitle(currentPair.left, for: .normal)
textField.text = nil
computeAlternateAmount()
activateAmountView()
delegate?.changeType(in: self)
}
private func activateAmountView() {
becomeFirstResponder()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
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
}
@objc func closeKeyboard() {
endEditing(true)
}
private func amountChanged(in range: NSRange, to string: String) -> Bool {
guard let input = textField.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)
}
return true
}
private func computeAlternateAmount() {
if currentPair.left == "ETH" {
alternativeAmountLabel.text = "~ \(convertToAlternateAmount()) USD"
} else {
alternativeAmountLabel.text = "~ \(convertToAlternateAmount()) ETH"
}
}
private func convertToAlternateAmount() -> String {
if let ethToDollarRate = ethToDollarRate, let string = textField.text, let amount = Double(string) {
if currentPair.left == "ETH" {
return String(amount * ethToDollarRate)
} else {
return String(amount / ethToDollarRate)
}
} else {
if currentPair.left == "ETH" {
return "0.000000"
} else {
return "0.00"
}
}
}
}
extension AmountTextField: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let allowChange = amountChanged(in: range, to: string)
if allowChange {
//We have to allow the text field the chance to update, so we have to use asyncAfter..
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.computeAlternateAmount()
self.delegate?.changeAmount(in: self)
}
}
return allowChange
}
}

@ -0,0 +1,108 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
protocol DateEntryFieldDelegate: class {
func didTap(in dateEntryField: DateEntryField)
}
class DateEntryField: UIControl {
var leftButton = UIButton(type: .custom)
var value = Date() {
didSet {
displayDateString()
}
}
weak var delegate: DateEntryFieldDelegate?
init() {
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
leftButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
displayDateString()
let rightView = makeRightView()
let stackView = UIStackView(arrangedSubviews: [
.spacerWidth(22),
leftButton,
rightView,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.spacing = 0
stackView.distribution = .fill
stackView.alignment = .center
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
configure()
}
private func configure() {
layer.borderColor = Colors.appBackground.cgColor
layer.borderWidth = 1
leftButton.setTitleColor(Colors.appBackground, for: .normal)
leftButton.titleLabel?.font = Fonts.regular(size: 18)
}
private func makeRightView() -> UIView {
let rightButton = UIButton(type: .system)
rightButton.translatesAutoresizingMaskIntoConstraints = false
rightButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10)
rightButton.imageView?.contentMode = .scaleAspectFit
rightButton.setImage(R.image.calendar(), for: .normal)
rightButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
let rightView = UIStackView(arrangedSubviews: [
rightButton,
])
rightView.translatesAutoresizingMaskIntoConstraints = false
rightView.distribution = .equalSpacing
rightView.spacing = 1
rightView.axis = .horizontal
return rightView
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
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
}
@objc func closeKeyboard() {
delegate?.didTap(in: self)
}
@objc func buttonTapped() {
delegate?.didTap(in: self)
}
private func displayDateString() {
//TODO Should format be localized?
let dateString = value.format("dd MMM yyyy")
leftButton.setTitle(dateString, for: .normal)
}
}

@ -0,0 +1,110 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
protocol TimeEntryFieldDelegate: class {
func didTap(in timeEntryField: TimeEntryField)
}
class TimeEntryField: UIControl {
var leftButton = UIButton(type: .custom)
var value = Date() {
didSet {
displayTimeString()
}
}
weak var delegate: TimeEntryFieldDelegate?
init() {
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
leftButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
displayTimeString()
let rightView = makeRightView()
let stackView = UIStackView(arrangedSubviews: [
.spacerWidth(22),
leftButton,
rightView,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.spacing = 0
stackView.distribution = .fill
stackView.alignment = .center
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
configure()
}
private func configure() {
layer.borderColor = Colors.appBackground.cgColor
layer.borderWidth = 1
leftButton.setTitleColor(Colors.appBackground, for: .normal)
leftButton.titleLabel?.font = Fonts.regular(size: 18)
}
private func makeRightView() -> UIView {
let rightButton = UIButton(type: .system)
rightButton.translatesAutoresizingMaskIntoConstraints = false
rightButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10)
rightButton.imageView?.contentMode = .scaleAspectFit
rightButton.setImage(R.image.time(), for: .normal)
rightButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
let rightView = UIStackView(arrangedSubviews: [
rightButton,
])
rightView.translatesAutoresizingMaskIntoConstraints = false
rightView.distribution = .equalSpacing
rightView.spacing = 1
rightView.axis = .horizontal
return rightView
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
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
}
@objc func closeKeyboard() {
delegate?.didTap(in: self)
}
@objc func buttonTapped() {
delegate?.didTap(in: self)
}
private func displayTimeString() {
//TODO Should format be localized?
let formatter = DateFormatter()
formatter.timeStyle = .short
let timeString = formatter.string(from: value)
leftButton.setTitle(timeString, for: .normal)
}
}

@ -13,7 +13,7 @@ import TrustKeystore
protocol TicketsViewControllerDelegate: class {
func didPressRedeem(token: TokenObject, in viewController: TicketsViewController)
func didPressSell(token: TokenObject, in viewController: TicketsViewController)
func didPressSell(for type: PaymentFlow, in viewController: TicketsViewController)
func didPressTransfer(for type: PaymentFlow, ticketHolders: [TicketHolder], in viewController: TicketsViewController)
func didCancel(in viewController: TicketsViewController)
func didPressViewRedemptionInfo(in viewController: TicketsViewController)
@ -171,7 +171,7 @@ class TicketsViewController: UIViewController {
}
@objc func sell() {
delegate?.didPressSell(token: viewModel.token, in: self)
delegate?.didPressSell(for: .send(type: .stormBird(viewModel.token)), in: self)
}
@objc func transfer() {

@ -18,8 +18,11 @@ protocol TicketsCoordinatorDelegate: class {
in coordinator: TicketsCoordinator)
func didPressRedeem(for token: TokenObject,
in coordinator: TicketsCoordinator)
func didPressSell(for type: PaymentFlow,
in coordinator: TicketsCoordinator)
func didCancel(in coordinator: TicketsCoordinator)
func didPressViewRedemptionInfo(in: UIViewController)
func didPressViewEthereumInfo(in: UIViewController)
}
class TicketsCoordinator: NSObject, Coordinator {
@ -38,6 +41,7 @@ class TicketsCoordinator: NSObject, Coordinator {
let navigationController: UINavigationController
var coordinators: [Coordinator] = []
var scanQRCodeForWalletAddressToTransferTicketCoordinator: ScanQRCodeForWalletAddressToTransferTicketCoordinator?
var scanQRCodeForWalletAddressToSellTicketCoordinator: ScanQRCodeForWalletAddressToSellTicketCoordinator?
init(
session: WalletSession,
@ -78,6 +82,11 @@ class TicketsCoordinator: NSObject, Coordinator {
navigationController.pushViewController(redeemViewController, animated: true)
}
func showSellViewController(for paymentFlow: PaymentFlow) {
let sellViewController = makeSellTicketsViewController(paymentFlow: paymentFlow)
navigationController.pushViewController(sellViewController, animated: true)
}
func showTransferViewController(for paymentFlow: PaymentFlow, ticketHolders: [TicketHolder]) {
let transferViewController = makeTransferTicketsViewController(paymentFlow: paymentFlow)
navigationController.pushViewController(transferViewController, animated: true)
@ -89,12 +98,27 @@ class TicketsCoordinator: NSObject, Coordinator {
viewController.navigationController?.pushViewController(vc, animated: true)
}
private func showChooseTicketSellModeViewController(for ticketHolder: TicketHolder,
linkExpiryDate: Date,
ethCost: String,
dollarCost: String,
in viewController: EnterSellTicketsDetailsViewController) {
let vc = makeChooseTicketSellModeViewController(for: ticketHolder, linkExpiryDate: linkExpiryDate, ethCost: ethCost, dollarCost: dollarCost, paymentFlow: viewController.paymentFlow)
viewController.navigationController?.pushViewController(vc, animated: true)
}
private func showQuantityViewController(for ticketHolder: TicketHolder,
in viewController: RedeemTicketsViewController) {
let quantityViewController = makeRedeemTicketsQuantitySelectionViewController(for: ticketHolder)
viewController.navigationController?.pushViewController(quantityViewController, animated: true)
}
private func showQuantityViewController(for ticketHolder: TicketHolder,
in viewController: SellTicketsViewController) {
let quantityViewController = makeSellTicketsQuantitySelectionViewController(for: ticketHolder, paymentFlow: viewController.paymentFlow)
viewController.navigationController?.pushViewController(quantityViewController, animated: true)
}
private func showTicketRedemptionViewController(for ticketHolder: TicketHolder,
in viewController: UIViewController) {
let quantityViewController = makeTicketRedemptionViewController(for: ticketHolder)
@ -109,6 +133,14 @@ class TicketsCoordinator: NSObject, Coordinator {
return controller
}
private func makeSellTicketsViewController(paymentFlow: PaymentFlow) -> SellTicketsViewController {
let controller = SellTicketsViewController(paymentFlow: paymentFlow)
let viewModel = SellTicketsViewModel(token: token)
controller.configure(viewModel: viewModel)
controller.delegate = self
return controller
}
private func makeRedeemTicketsQuantitySelectionViewController(for ticketHolder: TicketHolder) -> RedeemTicketsQuantitySelectionViewController {
let controller = RedeemTicketsQuantitySelectionViewController()
let viewModel = RedeemTicketsQuantitySelectionViewModel(ticketHolder: ticketHolder)
@ -117,6 +149,14 @@ class TicketsCoordinator: NSObject, Coordinator {
return controller
}
private func makeSellTicketsQuantitySelectionViewController(for ticketHolder: TicketHolder, paymentFlow: PaymentFlow) -> EnterSellTicketsDetailsViewController {
let controller = EnterSellTicketsDetailsViewController(storage: tokensStorage, paymentFlow: paymentFlow)
let viewModel = SellTicketsQuantitySelectionViewModel(ticketHolder: ticketHolder)
controller.configure(viewModel: viewModel)
controller.delegate = self
return controller
}
private func makeTicketRedemptionViewController(for ticketHolder: TicketHolder) -> TicketRedemptionViewController {
let controller = TicketRedemptionViewController(session: session)
let viewModel = TicketRedemptionViewModel(ticketHolder: ticketHolder)
@ -154,6 +194,14 @@ class TicketsCoordinator: NSObject, Coordinator {
return controller
}
private func makeChooseTicketSellModeViewController(for ticketHolder: TicketHolder, linkExpiryDate: Date, ethCost: String, dollarCost: String, paymentFlow: PaymentFlow) -> ChooseTicketSellModeViewController {
let controller = ChooseTicketSellModeViewController(ticketHolder: ticketHolder, linkExpiryDate: linkExpiryDate, ethCost: ethCost, dollarCost: dollarCost, paymentFlow: paymentFlow)
let viewModel = ChooseTicketSellModeViewControllerViewModel()
controller.configure(viewModel: viewModel)
controller.delegate = self
return controller
}
private func generateTransferLink(ticketHolder: TicketHolder, paymentFlow: PaymentFlow) -> String {
let timestamp = Int(NSDate().timeIntervalSince1970) + 86400
let order = Order(
@ -171,6 +219,11 @@ class TicketsCoordinator: NSObject, Coordinator {
return UniversalLinkHandler().createUniversalLink(signedOrder: signedOrders[0])
}
private func generateSellLink(ticketHolder: TicketHolder, linkExpiryDate: Date, ethCost: String, dollarCost: String, paymentFlow: PaymentFlow) -> String {
//TODO Generate sell link
return "https://app.awallet.com/a_sell_link"
}
private func transferViaWalletAddressTextEntry(ticketHolder: TicketHolder, paymentFlow: PaymentFlow) {
let controller = TransferTicketViaWalletAddressViewController(ticketHolder: ticketHolder, paymentFlow: paymentFlow)
controller.delegate = self
@ -197,6 +250,31 @@ class TicketsCoordinator: NSObject, Coordinator {
navigationController.present(vc, animated: true)
}
private func sellViaWalletAddressTextEntry(ticketHolder: TicketHolder, linkExpiryDate: Date, ethCost: String, dollarCost: String, paymentFlow: PaymentFlow) {
let controller = SellTicketViaWalletAddressViewController(ticketHolder: ticketHolder, linkExpiryDate: linkExpiryDate, ethCost: ethCost, dollarCost: dollarCost, paymentFlow: paymentFlow)
controller.delegate = self
controller.configure(viewModel: .init(ticketHolder: ticketHolder))
navigationController.pushViewController(controller, animated: true)
}
private func sellViaReadingWalletAddressFromQRCode(ticketHolder: TicketHolder, linkExpiryDate: Date, ethCost: String, dollarCost: String, paymentFlow: PaymentFlow) {
scanQRCodeForWalletAddressToSellTicketCoordinator = ScanQRCodeForWalletAddressToSellTicketCoordinator(ticketHolder: ticketHolder, linkExpiryDate: linkExpiryDate, ethCost: ethCost, dollarCost: dollarCost, paymentFlow: paymentFlow, in: navigationController)
scanQRCodeForWalletAddressToSellTicketCoordinator?.delegate = self
scanQRCodeForWalletAddressToSellTicketCoordinator?.start()
}
private func sellViaActivitySheet(ticketHolder: TicketHolder, linkExpiryDate: Date, ethCost: String, dollarCost: String, paymentFlow: PaymentFlow) {
let url = generateSellLink(ticketHolder: ticketHolder, linkExpiryDate: linkExpiryDate, ethCost: ethCost, dollarCost: dollarCost, paymentFlow: paymentFlow)
let vc = UIActivityViewController(activityItems: [url], applicationActivities: nil)
vc.completionWithItemsHandler = { activityType, completed, returnedItems, error in
//Be annoying if user copies and we close the sell process
if completed && activityType != UIActivityType.copyToPasteboard {
self.navigationController.dismiss(animated: true)
}
}
navigationController.present(vc, animated: true)
}
private func transfer(ticketHolder: TicketHolder, to walletAddress: String, paymentFlow: PaymentFlow) {
UIAlertController.alert(title: "", message: R.string.localizable.aWalletTicketTokenTransferModeWalletAddressConfirmation(walletAddress), alertButtonTitles: [R.string.localizable.aWalletTicketTokenTransferButtonTitle(), R.string.localizable.cancel()], alertButtonStyles: [.default, .cancel], viewController: navigationController) {
guard $0 == 0 else { return }
@ -214,6 +292,24 @@ class TicketsCoordinator: NSObject, Coordinator {
}
}
}
private func sell(ticketHolder: TicketHolder, linkExpiryDate: Date, ethCost: String, dollarCost: String, to walletAddress: String, paymentFlow: PaymentFlow) {
UIAlertController.alert(title: "", message: R.string.localizable.aWalletTicketTokenSellModeWalletAddressConfirmation(walletAddress), alertButtonTitles: [R.string.localizable.aWalletTicketTokenSellButtonTitle(), R.string.localizable.cancel()], alertButtonStyles: [.default, .cancel], viewController: navigationController) {
guard $0 == 0 else { return }
//Defensive. Should already be checked before this
guard let address = Address(string: walletAddress) else {
return self.navigationController.displayError(error: Errors.invalidAddress)
}
if case .real(let account) = self.session.account.type {
let coordinator = SellTicketsCoordinator(ticketHolder: ticketHolder, linkExpiryDate: linkExpiryDate, ethCost: ethCost, dollarCost: dollarCost, walletAddress: walletAddress, paymentFlow: paymentFlow, keystore: self.keystore, session: self.session, account: account, on: self.navigationController)
coordinator.delegate = self
coordinator.start()
self.addCoordinator(coordinator)
}
}
}
}
extension TicketsCoordinator: TicketsViewControllerDelegate {
@ -221,13 +317,8 @@ extension TicketsCoordinator: TicketsViewControllerDelegate {
delegate?.didPressRedeem(for: token, in: self)
}
func didPressSell(token: TokenObject, in viewController: TicketsViewController) {
UIAlertController.alert(title: "",
message: "This feature is not yet implemented",
alertButtonTitles: ["OK"],
alertButtonStyles: [.cancel],
viewController: viewController,
completion: nil)
func didPressSell(for type: PaymentFlow, in viewController: TicketsViewController) {
delegate?.didPressSell(for: type, in: self)
}
func didPressTransfer(for type: PaymentFlow, ticketHolders: [TicketHolder], in viewController: TicketsViewController) {
@ -263,6 +354,16 @@ extension TicketsCoordinator: RedeemTicketsQuantitySelectionViewControllerDelega
}
}
extension TicketsCoordinator: SellTicketsViewControllerDelegate {
func didSelectTicketHolder(ticketHolder: TicketHolder, in viewController: SellTicketsViewController) {
showQuantityViewController(for: ticketHolder, in: viewController)
}
func didPressViewInfo(in viewController: SellTicketsViewController) {
delegate?.didPressViewEthereumInfo(in: viewController)
}
}
extension TicketsCoordinator: TransferTicketsQuantitySelectionViewControllerDelegate {
func didSelectQuantity(ticketHolder: TicketHolder, in viewController: TransferTicketsQuantitySelectionViewController) {
showChooseTicketTransferModeViewController(for: ticketHolder, in: viewController)
@ -273,6 +374,16 @@ extension TicketsCoordinator: TransferTicketsQuantitySelectionViewControllerDele
}
}
extension TicketsCoordinator: EnterSellTicketsDetailsViewControllerDelegate {
func didEnterSellTicketDetails(ticketHolder: TicketHolder, linkExpiryDate: Date, ethCost: String, dollarCost: String, in viewController: EnterSellTicketsDetailsViewController) {
showChooseTicketSellModeViewController(for: ticketHolder, linkExpiryDate: linkExpiryDate, ethCost: ethCost, dollarCost: dollarCost, in: viewController)
}
func didPressViewInfo(in viewController: EnterSellTicketsDetailsViewController) {
delegate?.didPressViewEthereumInfo(in: viewController)
}
}
extension TicketsCoordinator: ChooseTicketTransferModeViewControllerDelegate {
func didChoose(transferMode: TicketTransferMode, in viewController: ChooseTicketTransferModeViewController, sender: UIView) {
let ticketHolder = viewController.ticketHolder
@ -288,6 +399,21 @@ extension TicketsCoordinator: ChooseTicketTransferModeViewControllerDelegate {
}
}
extension TicketsCoordinator: ChooseTicketSellModeViewControllerDelegate {
func didChoose(sellMode: TicketSellMode, in viewController: ChooseTicketSellModeViewController) {
let ticketHolder = viewController.ticketHolder
switch sellMode {
case .walletAddressTextEntry:
sellViaWalletAddressTextEntry(ticketHolder: ticketHolder, linkExpiryDate: viewController.linkExpiryDate, ethCost: viewController.ethCost, dollarCost: viewController.dollarCost, paymentFlow: viewController.paymentFlow)
case .walletAddressFromQRCode:
sellViaReadingWalletAddressFromQRCode(ticketHolder: ticketHolder, linkExpiryDate: viewController.linkExpiryDate, ethCost: viewController.ethCost, dollarCost: viewController.dollarCost, paymentFlow: viewController.paymentFlow)
case .other:
sellViaActivitySheet(ticketHolder: ticketHolder, linkExpiryDate: viewController.linkExpiryDate, ethCost: viewController.ethCost, dollarCost: viewController.dollarCost, paymentFlow: viewController.paymentFlow)
}
}
}
extension TicketsCoordinator: TransferTicketsViewControllerDelegate {
func didSelectTicketHolder(ticketHolder: TicketHolder, in viewController: TransferTicketsViewController) {
showQuantityViewController(for: ticketHolder, in: viewController)
@ -333,3 +459,37 @@ extension TicketsCoordinator: TransferTicketsCoordinatorDelegate {
}
}
extension TicketsCoordinator: SellTicketsCoordinatorDelegate {
private func cleanUpAfterSell(coordinator: SellTicketsCoordinator) {
navigationController.dismiss(animated: true)
removeCoordinator(coordinator)
}
func didClose(in coordinator: SellTicketsCoordinator) {
cleanUpAfterSell(coordinator: coordinator)
}
func didFinishSuccessfully(in coordinator: SellTicketsCoordinator) {
cleanUpAfterSell(coordinator: coordinator)
}
func didFail(in coordinator: SellTicketsCoordinator) {
cleanUpAfterSell(coordinator: coordinator)
}
}
extension TicketsCoordinator: SellTicketViaWalletAddressViewControllerDelegate {
func didChooseSell(to walletAddress: String, viewController: SellTicketViaWalletAddressViewController) {
sell(ticketHolder: viewController.ticketHolder, linkExpiryDate: viewController.linkExpiryDate, ethCost: viewController.ethCost, dollarCost: viewController.dollarCost, to: walletAddress, paymentFlow: viewController.paymentFlow)
}
}
extension TicketsCoordinator: ScanQRCodeForWalletAddressToSellTicketCoordinatorDelegate {
func scanned(walletAddress: String, in coordinator: ScanQRCodeForWalletAddressToSellTicketCoordinator) {
sell(ticketHolder: coordinator.ticketHolder, linkExpiryDate: coordinator.linkExpiryDate, ethCost: coordinator.ethCost, dollarCost: coordinator.dollarCost, to: walletAddress, paymentFlow: coordinator.paymentFlow)
}
func cancelled(in coordinator: ScanQRCodeForWalletAddressToSellTicketCoordinator) {
//no-op
}
}

@ -17,9 +17,9 @@ 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
let roundedBackground = UIView()
let titleLabel = UILabel()
let inputWalletAddressButton = TransferModeButton()
let qrCodeScannerButton = TransferModeButton()
let otherButton = TransferModeButton()
let inputWalletAddressButton = ShareModeButton()
let qrCodeScannerButton = ShareModeButton()
let otherButton = ShareModeButton()
let ticketHolder: TicketHolder
var paymentFlow: PaymentFlow
weak var delegate: ChooseTicketTransferModeViewControllerDelegate?

@ -2,7 +2,7 @@
import UIKit
class TransferModeButton: UIControl {
class ShareModeButton: UIControl {
var title: String = "" {
didSet {
label.text = title
Loading…
Cancel
Save