Implement redesigned Dapp browser and new Dapp home screens

pull/999/head
Hwee-Boon Yar 6 years ago
parent 6f0c682336
commit 8f51f4f0d9
  1. 184
      AlphaWallet.xcodeproj/project.pbxproj
  2. 23
      AlphaWallet/Assets.xcassets/discoverDapps.imageset/Contents.json
  3. BIN
      AlphaWallet/Assets.xcassets/discoverDapps.imageset/discoverDapps.png
  4. BIN
      AlphaWallet/Assets.xcassets/discoverDapps.imageset/discoverDapps@2x.png
  5. BIN
      AlphaWallet/Assets.xcassets/discoverDapps.imageset/discoverDapps@3x.png
  6. 23
      AlphaWallet/Assets.xcassets/history.imageset/Contents.json
  7. BIN
      AlphaWallet/Assets.xcassets/history.imageset/history.png
  8. BIN
      AlphaWallet/Assets.xcassets/history.imageset/history@2x.png
  9. BIN
      AlphaWallet/Assets.xcassets/history.imageset/history@3x.png
  10. 23
      AlphaWallet/Assets.xcassets/myDapps.imageset/Contents.json
  11. BIN
      AlphaWallet/Assets.xcassets/myDapps.imageset/myDapps.png
  12. BIN
      AlphaWallet/Assets.xcassets/myDapps.imageset/myDapps@2x.png
  13. BIN
      AlphaWallet/Assets.xcassets/myDapps.imageset/myDapps@3x.png
  14. 429
      AlphaWallet/Browser/Coordinators/BrowserCoordinator.swift
  15. 574
      AlphaWallet/Browser/Coordinators/DappBrowserCoordinator.swift
  16. 39
      AlphaWallet/Browser/Storage/BookmarksStore.swift
  17. 13
      AlphaWallet/Browser/Types/BrowserAction.swift
  18. 4
      AlphaWallet/Browser/Types/Favicon.swift
  19. 114
      AlphaWallet/Browser/ViewControllers/BookmarkViewController.swift
  20. 171
      AlphaWallet/Browser/ViewControllers/BrowserHistoryViewController.swift
  21. 121
      AlphaWallet/Browser/ViewControllers/BrowserViewController.swift
  22. 85
      AlphaWallet/Browser/ViewControllers/DappsAutoCompletionViewController.swift
  23. 192
      AlphaWallet/Browser/ViewControllers/DappsHomeViewController.swift
  24. 174
      AlphaWallet/Browser/ViewControllers/DiscoverDappsViewController.swift
  25. 207
      AlphaWallet/Browser/ViewControllers/EditMyDappViewController.swift
  26. 113
      AlphaWallet/Browser/ViewControllers/HistoryViewController.swift
  27. 136
      AlphaWallet/Browser/ViewControllers/MasterBrowserViewController.swift
  28. 165
      AlphaWallet/Browser/ViewControllers/MyDappsViewController.swift
  29. 26
      AlphaWallet/Browser/ViewModel/BookmarkViewModel.swift
  30. 31
      AlphaWallet/Browser/ViewModel/BookmarksViewModel.swift
  31. 60
      AlphaWallet/Browser/ViewModel/BrowserHistoryCellViewModel.swift
  32. 10
      AlphaWallet/Browser/ViewModel/Dapp.swift
  33. 17
      AlphaWallet/Browser/ViewModel/DappButtonViewModel.swift
  34. 61
      AlphaWallet/Browser/ViewModel/DappViewCellViewModel.swift
  35. 35
      AlphaWallet/Browser/ViewModel/Dapps.swift
  36. 44
      AlphaWallet/Browser/ViewModel/DappsAutoCompletionCellViewModel.swift
  37. 18
      AlphaWallet/Browser/ViewModel/DappsAutoCompletionViewControllerViewModel.swift
  38. 8
      AlphaWallet/Browser/ViewModel/DappsHomeEmptyViewViewModel.swift
  39. 20
      AlphaWallet/Browser/ViewModel/DappsHomeHeaderViewViewModel.swift
  40. 38
      AlphaWallet/Browser/ViewModel/DappsHomeViewControllerHeaderViewViewModel.swift
  41. 20
      AlphaWallet/Browser/ViewModel/DappsHomeViewControllerViewModel.swift
  42. 99
      AlphaWallet/Browser/ViewModel/DiscoverDappCellViewModel.swift
  43. 15
      AlphaWallet/Browser/ViewModel/DiscoverDappsViewControllerViewModel.swift
  44. 152
      AlphaWallet/Browser/ViewModel/EditMyDappViewControllerViewModel.swift
  45. 5
      AlphaWallet/Browser/ViewModel/HistoriesViewModel.swift
  46. 27
      AlphaWallet/Browser/ViewModel/HistoryViewModel.swift
  47. 9
      AlphaWallet/Browser/ViewModel/MarketplaceViewModel.swift
  48. 80
      AlphaWallet/Browser/ViewModel/MyDappCellViewModel.swift
  49. 19
      AlphaWallet/Browser/ViewModel/MyDappsViewControllerViewModel.swift
  50. 82
      AlphaWallet/Browser/Views/BrowserHistoryCell.swift
  51. 52
      AlphaWallet/Browser/Views/BrowserHistoryViewControllerHeaderView.swift
  52. 120
      AlphaWallet/Browser/Views/BrowserNavigationBar.swift
  53. 298
      AlphaWallet/Browser/Views/DappBrowserNavigationBar.swift
  54. 43
      AlphaWallet/Browser/Views/DappButton.swift
  55. 158
      AlphaWallet/Browser/Views/DappViewCell.swift
  56. 44
      AlphaWallet/Browser/Views/DappsAutoCompletionCell.swift
  57. 43
      AlphaWallet/Browser/Views/DappsHomeEmptyView.swift
  58. 47
      AlphaWallet/Browser/Views/DappsHomeHeaderView.swift
  59. 59
      AlphaWallet/Browser/Views/DappsHomeViewControllerHeaderView.swift
  60. 121
      AlphaWallet/Browser/Views/DiscoverDappCell.swift
  61. 79
      AlphaWallet/Browser/Views/MyDappCell.swift
  62. 75
      AlphaWallet/Browser/Views/MyDappsViewControllerHeaderView.swift
  63. 4
      AlphaWallet/Core/Helpers/ScreenChecker.swift
  64. 4
      AlphaWallet/Extensions/UIViewController.swift
  65. 17
      AlphaWallet/InCoordinator.swift
  66. 20
      AlphaWallet/Localization/en.lproj/Localizable.strings
  67. 20
      AlphaWallet/Localization/es.lproj/Localizable.strings
  68. 20
      AlphaWallet/Localization/ja.lproj/Localizable.strings
  69. 20
      AlphaWallet/Localization/ko.lproj/Localizable.strings
  70. 18
      AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings
  71. 9
      AlphaWallet/Style/AppStyle.swift
  72. 47
      AlphaWallet/UI/BoxView.swift
  73. 2
      AlphaWalletTests/Coordinators/InCoordinatorTests.swift

@ -165,7 +165,6 @@
29E14FDB1F7F4F3D00185568 /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E14FDA1F7F4F3D00185568 /* Transaction.swift */; };
29E2E33A1F7A008C000CF94A /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E2E3391F7A008C000CF94A /* UIView.swift */; };
29E2E33E1F7A2423000CF94A /* TransactionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E2E33D1F7A2423000CF94A /* TransactionHeaderView.swift */; };
29E6E06C1FE897D90079265A /* BrowserCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E6E06B1FE897D90079265A /* BrowserCoordinator.swift */; };
29E6E06E1FE897EE0079265A /* BrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E6E06D1FE897EE0079265A /* BrowserViewController.swift */; };
29E6E0701FEA12910079265A /* TransactionConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E6E06F1FEA12910079265A /* TransactionConfigurator.swift */; };
29E6E0721FEA200D0079265A /* ConfirmPaymentDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E6E0711FEA200D0079265A /* ConfirmPaymentDetailsViewModel.swift */; };
@ -236,9 +235,10 @@
442FCE2BEE8D475C7DEB39C1 /* RedeemTokenCardQuantitySelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FC54DA900FA2F9BB73A63 /* RedeemTokenCardQuantitySelectionViewModel.swift */; };
5E7C700A0B11504AC44718DD /* CanScanQRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74159ED115D14384A1CB /* CanScanQRCode.swift */; };
5E7C701BFF4469B35A074EB9 /* RequestViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C767497AD8DEE83F384D7 /* RequestViewModel.swift */; };
5E7C702C4B29AF2B8D61CCA4 /* DappsAutoCompletionViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D19E3CF96929FB8CEA3 /* DappsAutoCompletionViewControllerViewModel.swift */; };
5E7C705B311BB36C950ECE1D /* AssetDefinitionsOverridesViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73CAB804322C4A631C67 /* AssetDefinitionsOverridesViewCell.swift */; };
5E7C70861A499FF79B7DA903 /* DiscoverDappCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B54826BFDD53DF3E5BF /* DiscoverDappCell.swift */; };
5E7C708D05DBC08C8304F274 /* GetIsERC875Encode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7DCB0BDDD30D10130AE7 /* GetIsERC875Encode.swift */; };
5E7C709168AFA09B5D2926FB /* BookmarkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C729B595A03A5CF3EC9F4 /* BookmarkViewModel.swift */; };
5E7C70AE62DBB193399C7F5E /* ServerViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7CDB0BAD5D27D2F24F57 /* ServerViewCell.swift */; };
5E7C70BE9AE35408038E1971 /* HelpContentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B089FD4C96810DD10FD /* HelpContentsViewController.swift */; };
5E7C70CF1C732CE07D074A8B /* BookmarksStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AB4464F82391AAD68C1 /* BookmarksStore.swift */; };
@ -252,12 +252,13 @@
5E7C716B59DA18ECEBF9D7D8 /* ContractERC875Transfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C70A496987CAB2D76D7F5 /* ContractERC875Transfer.swift */; };
5E7C718043636901114BF76C /* LocalesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7FB99843529061368DA1 /* LocalesViewModel.swift */; };
5E7C7180F07239D2132F31A4 /* DirectoryContentsWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C70B7E1F621657184ABD0 /* DirectoryContentsWatcher.swift */; };
5E7C7186B20660F2C1462AA9 /* DiscoverDappsViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7F840AFFD4459FD3DBD6 /* DiscoverDappsViewControllerViewModel.swift */; };
5E7C71A2EAA5124E07AA54B6 /* Favicon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7BAC4E511FE8446D212F /* Favicon.swift */; };
5E7C71A6B0BDF301747A49AE /* ScreenChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C77E1E6194F5A1DC8D645 /* ScreenChecker.swift */; };
5E7C71A7D2BD6FCE3980CC51 /* ImportWalletHelpBubbleViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7A16ABC8BD5D508AA641 /* ImportWalletHelpBubbleViewViewModel.swift */; };
5E7C71AE5E97BDA5BB685A9F /* Dapp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D8D618A8A8D55479CDF /* Dapp.swift */; };
5E7C71B412DAE4BD7920436B /* ABIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7558286761EF1ADD2988 /* ABIError.swift */; };
5E7C71B52A77008694BFA5D1 /* TokensDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74B9EB81C51E956566E7 /* TokensDataStore.swift */; };
5E7C71CA7E0B33EEB08D551D /* HistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7A794D1437F435CBB780 /* HistoryViewController.swift */; };
5E7C71D1D16FE09032EB4B7E /* TokenObjectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C702A684DF27DC8ED4E42 /* TokenObjectTest.swift */; };
5E7C71D40F530871D95170C6 /* BookmarkViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5E7C72CEFD7E32ACE303AB1F /* BookmarkViewCell.xib */; };
5E7C71DAA5DAFF764F92587D /* SetTransferTokensCardExpiryDateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C727433F7B8E322B3C68A /* SetTransferTokensCardExpiryDateViewController.swift */; };
@ -272,8 +273,11 @@
5E7C729FA0EC60113B031391 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C772DC28C5110021894E3 /* ImageCache.swift */; };
5E7C72AF95DCE8BC65490BCA /* StatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B82CC07F290B9CAA4E4 /* StatusViewController.swift */; };
5E7C72B0A10A92E591696E48 /* ContactUsBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AE6FAE0DF969B4F52E9 /* ContactUsBannerView.swift */; };
5E7C72B359B220D0CBA7DCF1 /* DappsAutoCompletionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C76EE22984D66A3C18E70 /* DappsAutoCompletionViewController.swift */; };
5E7C72B3D5F75999937FCFA1 /* TokenListFormatRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74A1A13A1A6CB9E61BAC /* TokenListFormatRowViewModel.swift */; };
5E7C72C8A15397C5A40BFE76 /* WhatIsEthereumInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C774BCA281E4B077DBBFA /* WhatIsEthereumInfoViewController.swift */; };
5E7C72CEFE98436DB8EC0E05 /* BrowserHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73D55C366BCC53208686 /* BrowserHistoryCell.swift */; };
5E7C72CF145240C816BB12E2 /* DappsHomeViewControllerHeaderViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7375430F36C549EA8748 /* DappsHomeViewControllerHeaderViewViewModel.swift */; };
5E7C72D93255EF86E54331CB /* AssetDefinitionsOverridesViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7070B0A3FFF303822DF4 /* AssetDefinitionsOverridesViewCellViewModel.swift */; };
5E7C72E1D4B4B4C8443F3DA1 /* SendHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7828BD821B6F04B71C00 /* SendHeaderView.swift */; };
5E7C730C6AEF556AFB9A4B2C /* LocalesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7BF09AD68C113D58344C /* LocalesViewController.swift */; };
@ -287,14 +291,15 @@
5E7C73CA81FB2CE9BCAFC992 /* CallForAssetAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C787CA216AFED8023A35F /* CallForAssetAttribute.swift */; };
5E7C73FC3990D110C474C3D6 /* WalletFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75CC640BAFFE0E789F44 /* WalletFilterViewModel.swift */; };
5E7C73FD5BD75D90C8D0EF3C /* WalletFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7C58586099F082973073 /* WalletFilterView.swift */; };
5E7C73FDC874F983EC83B820 /* HistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72BCB25F6212B3029E34 /* HistoryViewModel.swift */; };
5E7C7402B29A987B0AF7061D /* VerifiableStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7CC48CA7A1EA7D539C87 /* VerifiableStatusViewController.swift */; };
5E7C741353DDF87133054FCC /* DeletedContract.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72BEB789700C49FF64A6 /* DeletedContract.swift */; };
5E7C741CA88EFAC66756DE7F /* EditMyDappViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C776B129861728FFB8CC8 /* EditMyDappViewControllerViewModel.swift */; };
5E7C74438E1FBF28ADFAFAD1 /* BaseTokenCardTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AD33AC8BE19F5C66489 /* BaseTokenCardTableViewCellViewModel.swift */; };
5E7C744B0282FD7B3A29AA7B /* ScriptMessageProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C708DE897B6677EAD769B /* ScriptMessageProxy.swift */; };
5E7C745A423BD10CFDED9A81 /* ServersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7CBEBF984CFCA29D6866 /* ServersViewModel.swift */; };
5E7C745C725F3F34037DCC68 /* SetTransferTokensCardExpiryDateViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C754C0E2E57F32A61F9A3 /* SetTransferTokensCardExpiryDateViewControllerViewModel.swift */; };
5E7C745DACB5FCCEBCEB49CA /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C793E23E2364B73C4D813 /* WelcomeViewController.swift */; };
5E7C746219CE5A1139756869 /* MyDappsViewControllerHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7171B802C0C2718EEED0 /* MyDappsViewControllerHeaderView.swift */; };
5E7C7488D5CAE24B7462815A /* LiveLocaleSwitcherBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7CD7ABB18C1121D5776F /* LiveLocaleSwitcherBundle.swift */; };
5E7C7499A8D6814F7950DA70 /* LockCreatePasscodeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AB3440C01136DF4F3E9 /* LockCreatePasscodeCoordinator.swift */; };
5E7C749B7C5CBC729B7E256F /* OpenSeaNonFungibleTokenTraitCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C765A9FA64E4CC1B6C726 /* OpenSeaNonFungibleTokenTraitCell.swift */; };
@ -306,6 +311,7 @@
5E7C74DBAE43954C185057B3 /* ChooseTokenCardTransferModeViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7BA578BE5FB0E613A6D6 /* ChooseTokenCardTransferModeViewControllerViewModel.swift */; };
5E7C74E7DC2D79785240D757 /* GetERC875Balance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7251DB61EB9468910C81 /* GetERC875Balance.swift */; };
5E7C7507CC97BDE973FD4F0E /* GetENSOwnerCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7E9A5E7D36AA3BC108A4 /* GetENSOwnerCoordinatorTests.swift */; };
5E7C7521CA17E5C2A4506A20 /* DappsHomeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7FE5EEC96A7CDF62213F /* DappsHomeHeaderView.swift */; };
5E7C75450126F3BAA726EC50 /* web3.min.js in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7F7C7E37B5CD147EF784 /* web3.min.js */; };
5E7C754ACE4C4305F9E5365A /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = 5E7C784984649DD8E02B50B8 /* index.html */; };
5E7C7567A690B6B8F889AE83 /* SendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C70088832B2D161EB4AAB /* SendViewController.swift */; };
@ -333,9 +339,10 @@
5E7C769D2BFC2809F0EA5AA3 /* GeneralisedTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7142F1598ECC93F3A673 /* GeneralisedTime.swift */; };
5E7C76A0365D128B7F19A0C2 /* ProtectionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74BEC095303B66FB4B1E /* ProtectionCoordinator.swift */; };
5E7C76A50F321F8A0A0005EB /* ABIValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7318B6059C18BE87ECAE /* ABIValue.swift */; };
5E7C76B7DDF7BB124B12A738 /* DappButtonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C754BF8B4CC2DA82B1025 /* DappButtonViewModel.swift */; };
5E7C76B917517C93D1E26B0A /* LockEnterPasscodeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7981AB6584B25C72D46B /* LockEnterPasscodeCoordinator.swift */; };
5E7C76D09352C069C140B6CF /* PreferencesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74D5CEC9A6C4483B1C77 /* PreferencesController.swift */; };
5E7C76D4F2D67016E279756F /* MasterBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C79DCBECE3385649FAFDF /* MasterBrowserViewController.swift */; };
5E7C76D28BB14C7685296BEF /* DappsHomeEmptyViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74B424FB5DE3A4D6A2F4 /* DappsHomeEmptyViewViewModel.swift */; };
5E7C76E816E216D5C69D3D7B /* AssetDefinitionBackingStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7C963C42DE81F82732E5 /* AssetDefinitionBackingStore.swift */; };
5E7C76F8CB67466725C590CE /* TokenViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C79ED9F842D3FC102AC54 /* TokenViewCellViewModel.swift */; };
5E7C7705B09D780E84E2FDA5 /* XMLHandlerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C702300BB7DB0FD7788EF /* XMLHandlerTest.swift */; };
@ -344,6 +351,7 @@
5E7C773E3E3BBEB65C51DF2A /* UIStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D2C43C15D0762C7F374 /* UIStackView.swift */; };
5E7C774B5332AC0DC19C5B1B /* EthTokenViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74B82783A94091A43470 /* EthTokenViewCellViewModel.swift */; };
5E7C77552A957D1B144D9209 /* CallForAssetAttributeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7322ADC54452545C345A /* CallForAssetAttributeCoordinator.swift */; };
5E7C77649E432A905B836E95 /* DappViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D8C3613A9BD9F147B3C /* DappViewCellViewModel.swift */; };
5E7C776BE1B19F824954962D /* BaseTokenCardTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7F5C10E3895E805EA7E0 /* BaseTokenCardTableViewCell.swift */; };
5E7C777A50A02B7EC9DBEB0A /* FetchAssetDefinitionsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C736169A5251141791726 /* FetchAssetDefinitionsCoordinator.swift */; };
5E7C7788984F7ADCFE5B4DE0 /* AddressTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75B5AF76279A71395FC7 /* AddressTextField.swift */; };
@ -351,12 +359,15 @@
5E7C7793AB6B577906F2BCA3 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AFE9AF9FE6B58C925D4 /* SettingsViewController.swift */; };
5E7C77A8425E0AFAB11F1FCD /* PromptBackupCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7ADD0FBE8708A6E98AF8 /* PromptBackupCoordinator.swift */; };
5E7C77AD9FAAC18211B6F355 /* TransferTokensCardQuantitySelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7419F47CC8B2996AA8F9 /* TransferTokensCardQuantitySelectionViewController.swift */; };
5E7C77B397BCB0E254F359A8 /* DappsAutoCompletionCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74BE9900543A755CB76A /* DappsAutoCompletionCellViewModel.swift */; };
5E7C77BFA252C7AA63BA5B90 /* TokenCardRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D931F68BFB5E1DCE001 /* TokenCardRowView.swift */; };
5E7C77D12D8DE42157790BDC /* DappsAutoCompletionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74D2C599646C65B95E2F /* DappsAutoCompletionCell.swift */; };
5E7C77D5AD018763345D8DD5 /* CanOpenContractWebPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D674F6B2415FB5552B0 /* CanOpenContractWebPage.swift */; };
5E7C77E844D710D7AFBC58D4 /* RequestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74DCC21272EC231A20E2 /* RequestViewController.swift */; };
5E7C780795A7B3088AB8DAE6 /* OpenSeaNonFungibleTokenCardTableViewCellWithoutCheckbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C791BD7AFEA4A419BAE24 /* OpenSeaNonFungibleTokenCardTableViewCellWithoutCheckbox.swift */; };
5E7C7813019A111443A542CA /* TokenListFormatTableViewCellWithoutCheckbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7C781CCE43B6451671B9 /* TokenListFormatTableViewCellWithoutCheckbox.swift */; };
5E7C782410321CE6CEE68275 /* AssetDefinitionDiskBackingStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7BF5551BF64D2AE8AD66 /* AssetDefinitionDiskBackingStore.swift */; };
5E7C78265EECE083C3EB1845 /* DappsHomeViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C78B61907C2C1E2BCD478 /* DappsHomeViewControllerViewModel.swift */; };
5E7C783B4784DE76971EEBB4 /* StatusViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7BD9B4BDAFC2D9EBD741 /* StatusViewControllerViewModel.swift */; };
5E7C78407F6DCB0EDD562DF6 /* NonFungibleTokenViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C731B6F01534683227123 /* NonFungibleTokenViewCellViewModel.swift */; };
5E7C78504ED8AAF6F0BA675C /* ClaimERC875Order.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75850BDF6D7B9993C562 /* ClaimERC875Order.swift */; };
@ -371,6 +382,7 @@
5E7C797BE2C8DB7EF6F217B3 /* OnboardingPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7103135DCCCAB96EE5FC /* OnboardingPage.swift */; };
5E7C79D78AA5E774119BE49B /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7CFDE7DEA8C06C4100AF /* TextField.swift */; };
5E7C79DE8864702C51C0A7CC /* ResultResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C79FE0C70AC4198F2AEB7 /* ResultResult.swift */; };
5E7C79E9A6A3DFB7FA680752 /* DappViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C793CDFA907BFDFECB6CB /* DappViewCell.swift */; };
5E7C79F30A324D75DF42DDDE /* SellTokensCardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7EB14E787BC019660389 /* SellTokensCardViewModel.swift */; };
5E7C7A0B5FDADC60DC01F060 /* CallSmartContractFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C77C2844B3579A59C3F2F /* CallSmartContractFunction.swift */; };
5E7C7A205F8F66D8486FAD49 /* OpenSeaNonFungibleTokenCardTableViewCellWithCheckbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7F66EE7899E4573C64AE /* OpenSeaNonFungibleTokenCardTableViewCellWithCheckbox.swift */; };
@ -378,6 +390,7 @@
5E7C7A4384A8E3F22D3F8249 /* SetSellTokensCardExpiryDateViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C700CD3E43689E88FBE9B /* SetSellTokensCardExpiryDateViewControllerViewModel.swift */; };
5E7C7A67B6143DFB9B1CF02B /* ConfirmSignMessageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7DD9C564F2C7DE435894 /* ConfirmSignMessageTableViewCell.swift */; };
5E7C7A6C28C59DB62DEE2D63 /* BookmarkViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C706658D72CC1C8BB698C /* BookmarkViewCell.swift */; };
5E7C7A6CC21C553CD4008F14 /* DappButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75E7D995ABE0E6B7AD55 /* DappButton.swift */; };
5E7C7A91D0F6CBDA3C89DEAC /* LocaleViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C79E3BC4CACB123840A42 /* LocaleViewCell.swift */; };
5E7C7A928412AF3E16CDA038 /* AmountTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73D0DCE61EA2DE2DA21D /* AmountTextField.swift */; };
5E7C7A9D757269A9386B9F1F /* ContractERC20Transfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7E81029EED4B560DA523 /* ContractERC20Transfer.swift */; };
@ -392,12 +405,14 @@
5E7C7B129C55A8458AEF3F61 /* URLViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D69938F484C2A186FAE /* URLViewModel.swift */; };
5E7C7B3E08EEA63C5B68B9C4 /* TokenCardRedemptionInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C778F20D32B70D7FF2135 /* TokenCardRedemptionInfoViewController.swift */; };
5E7C7B414FA8A428798D73EF /* OpenSeaNonFungibleTokenHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C758EEBD945A3451C96C8 /* OpenSeaNonFungibleTokenHandling.swift */; };
5E7C7B4778FF36371701242E /* BrowserHistoryViewControllerHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C79FA3E6A05845ECFCDCF /* BrowserHistoryViewControllerHeaderView.swift */; };
5E7C7B4E3DEA90147A5A9E0A /* TokensDataStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71E355BD14E975AF7491 /* TokensDataStoreTest.swift */; };
5E7C7B8242E31B4DA0263BE5 /* EthTypedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7FC30FF22C3EA71451BC /* EthTypedData.swift */; };
5E7C7BC8F2E31F4E2BA534D9 /* Ether.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C727CF8549291E71C1640 /* Ether.swift */; };
5E7C7BCAB0CD58ACA37ED6A4 /* Dapps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C77A400E5145C04083FEB /* Dapps.swift */; };
5E7C7BDCB6A8279E1B8ADB59 /* TokenCardTableViewCellWithCheckbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7ECCB155D49910973F05 /* TokenCardTableViewCellWithCheckbox.swift */; };
5E7C7BFE9C8CAA3E204B1FAA /* TokenRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7F89E3480D3680750EA9 /* TokenRowView.swift */; };
5E7C7C026C7E0DD0458850B3 /* BookmarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7752CDF1417A704035C3 /* BookmarkViewController.swift */; };
5E7C7C0D3181CD31A581AEBE /* EditMyDappViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7F55495A6095B3E86248 /* EditMyDappViewController.swift */; };
5E7C7C0FAC500A6651E663FD /* TransferTokensCardQuantitySelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C703BA1D0E9ACB7399155 /* TransferTokensCardQuantitySelectionViewModel.swift */; };
5E7C7C21E5CAF122AA4F6617 /* HowDoIGetMyMoneyInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C78B001F9F95F404D5FEF /* HowDoIGetMyMoneyInfoViewController.swift */; };
5E7C7C7142C4519873B2BB4E /* ImportWalletTabBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7C2872E213BBB05D55BA /* ImportWalletTabBarViewModel.swift */; };
@ -423,6 +438,7 @@
5E7C7D71D3184F44C397FFE7 /* HelpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C715F395B973FB61056CF /* HelpViewController.swift */; };
5E7C7D8173CB1089D622DA38 /* HelpViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7646352F10C96B5FC6F6 /* HelpViewCell.swift */; };
5E7C7DCE5242D2AC0A8DA65C /* TokenCardRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7CAA3D0C19444005EA83 /* TokenCardRowViewModel.swift */; };
5E7C7DCF09B84E5675D58CED /* DiscoverDappCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74294562EB79EFCD3559 /* DiscoverDappCellViewModel.swift */; };
5E7C7DD4D2EAA036961F18F0 /* DAppRequster.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C778A54D7D3E196BC5542 /* DAppRequster.swift */; };
5E7C7DD506747B6224C28721 /* TransferTokensCardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7E2486CDE31871C98FC7 /* TransferTokensCardViewModel.swift */; };
5E7C7E02785866606FF298F3 /* OpenSeaNonFungibleTokenViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72FBC0D2787AAA804098 /* OpenSeaNonFungibleTokenViewCellViewModel.swift */; };
@ -437,7 +453,8 @@
5E7C7E68425E20834B898D06 /* AppLocale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B29A9E728402D144C05 /* AppLocale.swift */; };
5E7C7E747797C72C67BBDFF4 /* ABIEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7185AA9F93D4F0B67AF7 /* ABIEncoder.swift */; };
5E7C7E7AEF01B9D170228342 /* TimeEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73EFA9494B31C683A287 /* TimeEntryField.swift */; };
5E7C7E7C719E9B11A402D059 /* BookmarksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71CAD32891B5DC651054 /* BookmarksViewModel.swift */; };
5E7C7E83AD74AFB0C717EAC0 /* DappsHomeViewControllerHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C799D2B7D91072FC0050B /* DappsHomeViewControllerHeaderView.swift */; };
5E7C7EAAF2BD4D12987968E4 /* MyDappCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7324C9AC776E3A7B43D1 /* MyDappCellViewModel.swift */; };
5E7C7EAEBB435F3909DA36FB /* TransferTokensCardViaWalletAddressViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C76D3CFA12C2236E73E10 /* TransferTokensCardViaWalletAddressViewControllerViewModel.swift */; };
5E7C7EAED92E4AE8B99217AB /* TransferTokensCardQuantitySelectionViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7021EE19C4B81CAAF3C0 /* TransferTokensCardQuantitySelectionViewControllerTests.swift */; };
5E7C7EB80D32A2F366E79140 /* SettingsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75D384C0D727BB43305E /* SettingsHeaderView.swift */; };
@ -447,11 +464,14 @@
5E7C7ECE164289A89734B4EF /* LocalesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C76C895E7BFA47233068C /* LocalesCoordinator.swift */; };
5E7C7ED4612686DAD9B9D093 /* TokensCardViewControllerTitleHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71C2C110B621EFDE336F /* TokensCardViewControllerTitleHeader.swift */; };
5E7C7EDA1BB781A45C1C19CD /* ImportWalletTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73D26F24C4AAE981E2F2 /* ImportWalletTab.swift */; };
5E7C7EE445B044CA15171BD5 /* DappsHomeHeaderViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C799836611BEE66000EE1 /* DappsHomeHeaderViewViewModel.swift */; };
5E7C7EEA1041AA1BBDFEE155 /* AssetDefinitionsOverridesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7E5C3B750BDEEB2F3FC3 /* AssetDefinitionsOverridesViewController.swift */; };
5E7C7EEE563D81793CB96FA0 /* TransferNFTCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C755132D9B6F95080A1BE /* TransferNFTCoordinator.swift */; };
5E7C7EF1F2CDFA52BBF1C620 /* BrowserHistoryCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C712F42374C0B8DF8C64F /* BrowserHistoryCellViewModel.swift */; };
5E7C7F1B297CE042114EF095 /* LockEnterPasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75CBBFF0273EF476F95B /* LockEnterPasscodeViewController.swift */; };
5E7C7F287415575EDF33DDEB /* XMLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7633741EA2029D541466 /* XMLHandler.swift */; };
5E7C7F60056FDD6ACC390400 /* UniversalLinkInPasteboardCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7F3DD81D44A996789FC4 /* UniversalLinkInPasteboardCoordinator.swift */; };
5E7C7F67945615E242B61CC3 /* BoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7C5454600A70DCFD7C0E /* BoxView.swift */; };
5E7C7F72926D84CF741C0D18 /* ABIType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7CBCC0A74A084AC2F053 /* ABIType.swift */; };
5E7C7F95F75D39673B88D774 /* GetERC721BalanceEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72CD0C22247A6AF7C95E /* GetERC721BalanceEncode.swift */; };
5E7C7FAF2A07E7AE21BF09AF /* AlphaWalletSettingsTextRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71684B93F60206992E10 /* AlphaWalletSettingsTextRow.swift */; };
@ -461,6 +481,7 @@
5E7C7FDCAE5ED2EEE02CE661 /* OpenSeaNonFungibleTokenCardRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73E57ADDF29E0A5FB87E /* OpenSeaNonFungibleTokenCardRowView.swift */; };
5E7C7FDD73F658772181896B /* TermsOfServiceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7607B0EF9B8F1BC41073 /* TermsOfServiceViewController.swift */; };
5E7C7FE10C2FEA7316401F04 /* WelcomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71CE10548877F1124BF2 /* WelcomeViewModel.swift */; };
5E7C7FE5F70D5777FD7258B2 /* DappsHomeEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C790B6371A5BCD733A4BE /* DappsHomeEmptyView.swift */; };
5E7C7FE8247F0E50BEF35D77 /* HowDoITransferETHIntoMyWalletInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D4F7C566EDD30EF1C19 /* HowDoITransferETHIntoMyWalletInfoViewController.swift */; };
5E7C7FF4210029C482BCE4C0 /* AssetDefinitionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7982FA14CBFDFD93B3D0 /* AssetDefinitionStore.swift */; };
613D04891FDE15F8008DE72E /* COMODO ECC Domain Validation Secure Server CA 2.cer in Resources */ = {isa = PBXBuildFile; fileRef = 613D04881FDE15F8008DE72E /* COMODO ECC Domain Validation Secure Server CA 2.cer */; };
@ -490,11 +511,17 @@
73ED85A520349BE400593BF3 /* StringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73ED85A420349BE400593BF3 /* StringFormatter.swift */; };
73ED85A72034BFEF00593BF3 /* UITextFieldAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73ED85A62034BFEF00593BF3 /* UITextFieldAdditions.swift */; };
73ED85A92034C42D00593BF3 /* StringFormatterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73ED85A82034C42D00593BF3 /* StringFormatterTest.swift */; };
76F1D0A410FCFEB38307F7F9 /* DappsHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DD09B44FD653C1500DA8 /* DappsHomeViewController.swift */; };
76F1D12A7ED6E35000B1B020 /* DappBrowserCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D4E689C3ECFD38CBBC47 /* DappBrowserCoordinator.swift */; };
76F1D137B10D8309E513BBDD /* OrderSigningTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DE8ADA3176D0277EDF20 /* OrderSigningTests.swift */; };
76F1D13FC8A41AD967C59947 /* ClaimOrderCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DACA9404AD6740BEADBB /* ClaimOrderCoordinatorTests.swift */; };
76F1D24B6244EEA49F0FDE33 /* DiscoverDappsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D8A3F2F0F86B632BFBCE /* DiscoverDappsViewController.swift */; };
76F1D3B0E2CDBE82B764C0C7 /* MyDappCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D948016AAE8470099748 /* MyDappCell.swift */; };
76F1D5AF727A83205BBCF0EC /* OrderHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DADFD07E2941897FD2E1 /* OrderHandler.swift */; };
76F1D5B10569F2351CA98A93 /* GasLimitConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DE9EF8E265016BF02659 /* GasLimitConfiguration.swift */; };
76F1D5DFFA5652DC16EF713D /* DappBrowserNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DF357BEAC88C6AEB6D58 /* DappBrowserNavigationBar.swift */; };
76F1D5ECC391A932C96CAC13 /* GetENSOwnerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DE7BEE799DDFB68D0F54 /* GetENSOwnerCoordinator.swift */; };
76F1D6B78F541D723F4E6D7B /* MyDappsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DB8034ACC2FC91F818F9 /* MyDappsViewController.swift */; };
76F1D74912F5D8CDA72363BD /* GetContractInteractions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DF5CF4A922E6FFCB7B2A /* GetContractInteractions.swift */; };
76F1D7F08263A663C3A67926 /* GetIsERC721ContractCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D4F77311FBF3A442E4B5 /* GetIsERC721ContractCoordinator.swift */; };
76F1D850F4F2E968CF8D9C86 /* MonkeyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B138ABCF208C2C93000FC28A /* MonkeyTest.swift */; };
@ -502,8 +529,10 @@
76F1D9BBB4ACAA00C8391172 /* GetENSNameEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DC4B9964504DA12D8D3C /* GetENSNameEncode.swift */; };
76F1DB9E1443DCFC36228B08 /* ClaimOrderCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D419EE36261E50ABAFAE /* ClaimOrderCoordinator.swift */; };
76F1DBCA8BAAA42BAEB14719 /* GetERC721BalanceCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D473FF303828D93C95EB /* GetERC721BalanceCoordinator.swift */; };
76F1DBF9F5359160063625B2 /* BrowserHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DAFCBB43B6639472A229 /* BrowserHistoryViewController.swift */; };
76F1DC92CDEB695115DBC47C /* UniversalLinkHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D96298E216CBFC3DD78B /* UniversalLinkHandlerTests.swift */; };
76F1DD10DF9A6C844E5F57D6 /* CreateRedeemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D8877226D5DD086B135D /* CreateRedeemTests.swift */; };
76F1DD3DF34BCCFE9BE3CEBB /* MyDappsViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D3AB33F1310B03B18E37 /* MyDappsViewControllerViewModel.swift */; };
76F1DEFF94F9A1F67BDF2735 /* UniversalLinkHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DCD54618349AC91C6DF8 /* UniversalLinkHandler.swift */; };
771A8485203242B400528D28 /* InCoordinatorViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771A8484203242B400528D28 /* InCoordinatorViewModelTests.swift */; };
771AA94E1FF971CD00D25403 /* DappAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771AA94D1FF971CD00D25403 /* DappAction.swift */; };
@ -513,7 +542,6 @@
771AA966200D5F1900D25403 /* WordCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 771AA965200D5F1900D25403 /* WordCollectionViewCell.xib */; };
7721A6BE202A5677004DB16C /* DecryptError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7721A6BD202A5677004DB16C /* DecryptError.swift */; };
7721A6C8202EF81B004DB16C /* CustomRPC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7721A6C7202EF81B004DB16C /* CustomRPC.swift */; };
775C00B520195BFB001B5EBC /* BrowserAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 775C00B420195BFB001B5EBC /* BrowserAction.swift */; };
77872D232023F43B0032D687 /* TransactionsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77872D222023F43B0032D687 /* TransactionsTracker.swift */; };
77872D25202505B70032D687 /* EnterPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77872D24202505B70032D687 /* EnterPasswordViewController.swift */; };
77872D27202505C00032D687 /* EnterPasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77872D26202505C00032D687 /* EnterPasswordViewModel.swift */; };
@ -522,9 +550,7 @@
77872D302026DC570032D687 /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77872D2F2026DC570032D687 /* SplashViewController.swift */; };
77872D322027AA4A0032D687 /* SliderTextFieldRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77872D312027AA4A0032D687 /* SliderTextFieldRow.swift */; };
778EAF7D1FF10AF400C8E2AB /* SettingsCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778EAF7C1FF10AF400C8E2AB /* SettingsCoordinatorTests.swift */; };
77B3BF352017D0D000EEC15A /* MarketplaceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77B3BF342017D0D000EEC15A /* MarketplaceViewModel.swift */; };
77B3BF3C201908ED00EEC15A /* ConfirmCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77B3BF3B201908ED00EEC15A /* ConfirmCoordinator.swift */; };
77B3BF4A2019247200EEC15A /* BrowserNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77B3BF492019247200EEC15A /* BrowserNavigationBar.swift */; };
77E0E773201FAD06009B4B31 /* BrowserURLParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E0E772201FAD05009B4B31 /* BrowserURLParser.swift */; };
AA26C61F20412A1E00318B9B /* TokensCardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA26C61D20412A1D00318B9B /* TokensCardViewController.swift */; };
AA26C62320412A4100318B9B /* UIViewInspectableEnhancements.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA26C62120412A4100318B9B /* UIViewInspectableEnhancements.swift */; };
@ -748,7 +774,6 @@
29E14FDA1F7F4F3D00185568 /* Transaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transaction.swift; sourceTree = "<group>"; };
29E2E3391F7A008C000CF94A /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
29E2E33D1F7A2423000CF94A /* TransactionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHeaderView.swift; sourceTree = "<group>"; };
29E6E06B1FE897D90079265A /* BrowserCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserCoordinator.swift; sourceTree = "<group>"; };
29E6E06D1FE897EE0079265A /* BrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = "<group>"; };
29E6E06F1FEA12910079265A /* TransactionConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionConfigurator.swift; sourceTree = "<group>"; };
29E6E0711FEA200D0079265A /* ConfirmPaymentDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmPaymentDetailsViewModel.swift; sourceTree = "<group>"; };
@ -833,14 +858,15 @@
5E7C70FB40612BB02594EC00 /* ChooseTokenCardTransferModeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChooseTokenCardTransferModeViewController.swift; sourceTree = "<group>"; };
5E7C7103135DCCCAB96EE5FC /* OnboardingPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPage.swift; sourceTree = "<group>"; };
5E7C7117B1DF438E213B406A /* History.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = History.swift; sourceTree = "<group>"; };
5E7C712F42374C0B8DF8C64F /* BrowserHistoryCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserHistoryCellViewModel.swift; sourceTree = "<group>"; };
5E7C7142F1598ECC93F3A673 /* GeneralisedTime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralisedTime.swift; sourceTree = "<group>"; };
5E7C715BBA7416942FDA8516 /* HistoriesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoriesViewModel.swift; sourceTree = "<group>"; };
5E7C715F395B973FB61056CF /* HelpViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelpViewController.swift; sourceTree = "<group>"; };
5E7C71684B93F60206992E10 /* AlphaWalletSettingsTextRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlphaWalletSettingsTextRow.swift; path = Views/AlphaWalletSettingsTextRow.swift; sourceTree = "<group>"; };
5E7C7171B802C0C2718EEED0 /* MyDappsViewControllerHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDappsViewControllerHeaderView.swift; sourceTree = "<group>"; };
5E7C7185AA9F93D4F0B67AF7 /* ABIEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ABIEncoder.swift; sourceTree = "<group>"; };
5E7C719FC57B89C2F24B9BA3 /* ContractERC721Transfer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContractERC721Transfer.swift; sourceTree = "<group>"; };
5E7C71C2C110B621EFDE336F /* TokensCardViewControllerTitleHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensCardViewControllerTitleHeader.swift; sourceTree = "<group>"; };
5E7C71CAD32891B5DC651054 /* BookmarksViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksViewModel.swift; sourceTree = "<group>"; };
5E7C71CE10548877F1124BF2 /* WelcomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewModel.swift; sourceTree = "<group>"; };
5E7C71E355BD14E975AF7491 /* TokensDataStoreTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensDataStoreTest.swift; sourceTree = "<group>"; };
5E7C71EBD4C95AD4E11F3352 /* AlphaWalletSettingsButtonRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlphaWalletSettingsButtonRow.swift; path = Views/AlphaWalletSettingsButtonRow.swift; sourceTree = "<group>"; };
@ -850,8 +876,6 @@
5E7C727433F7B8E322B3C68A /* SetTransferTokensCardExpiryDateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetTransferTokensCardExpiryDateViewController.swift; sourceTree = "<group>"; };
5E7C727CF8549291E71C1640 /* Ether.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Ether.swift; sourceTree = "<group>"; };
5E7C7287B9288EAA0D66BAC4 /* PreferenceOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceOption.swift; sourceTree = "<group>"; };
5E7C729B595A03A5CF3EC9F4 /* BookmarkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkViewModel.swift; sourceTree = "<group>"; };
5E7C72BCB25F6212B3029E34 /* HistoryViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewModel.swift; sourceTree = "<group>"; };
5E7C72BEB789700C49FF64A6 /* DeletedContract.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeletedContract.swift; sourceTree = "<group>"; };
5E7C72CD0C22247A6AF7C95E /* GetERC721BalanceEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetERC721BalanceEncode.swift; sourceTree = "<group>"; };
5E7C72CEFD7E32ACE303AB1F /* BookmarkViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BookmarkViewCell.xib; sourceTree = "<group>"; };
@ -861,12 +885,15 @@
5E7C7318B6059C18BE87ECAE /* ABIValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ABIValue.swift; sourceTree = "<group>"; };
5E7C731B6F01534683227123 /* NonFungibleTokenViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NonFungibleTokenViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C7322ADC54452545C345A /* CallForAssetAttributeCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallForAssetAttributeCoordinator.swift; sourceTree = "<group>"; };
5E7C7324C9AC776E3A7B43D1 /* MyDappCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDappCellViewModel.swift; sourceTree = "<group>"; };
5E7C734D61C0347C1638A1F7 /* BaseTokenListFormatTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTokenListFormatTableViewCell.swift; sourceTree = "<group>"; };
5E7C736169A5251141791726 /* FetchAssetDefinitionsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchAssetDefinitionsCoordinator.swift; sourceTree = "<group>"; };
5E7C7375430F36C549EA8748 /* DappsHomeViewControllerHeaderViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeViewControllerHeaderViewViewModel.swift; sourceTree = "<group>"; };
5E7C7382EAC8B9CE5EE0668D /* OpenSeaNonFungible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungible.swift; sourceTree = "<group>"; };
5E7C73CAB804322C4A631C67 /* AssetDefinitionsOverridesViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionsOverridesViewCell.swift; sourceTree = "<group>"; };
5E7C73D0DCE61EA2DE2DA21D /* AmountTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AmountTextField.swift; path = Views/AmountTextField.swift; sourceTree = "<group>"; };
5E7C73D26F24C4AAE981E2F2 /* ImportWalletTab.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletTab.swift; sourceTree = "<group>"; };
5E7C73D55C366BCC53208686 /* BrowserHistoryCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserHistoryCell.swift; sourceTree = "<group>"; };
5E7C73DF5FBFE756097D32B1 /* EthCurrencyHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthCurrencyHelper.swift; sourceTree = "<group>"; };
5E7C73E57ADDF29E0A5FB87E /* OpenSeaNonFungibleTokenCardRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungibleTokenCardRowView.swift; sourceTree = "<group>"; };
5E7C73E8500C2573331D800D /* Function.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Function.swift; sourceTree = "<group>"; };
@ -875,18 +902,23 @@
5E7C741196D9D9C9C3EE5E30 /* LockCreatePasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockCreatePasscodeViewController.swift; sourceTree = "<group>"; };
5E7C74159ED115D14384A1CB /* CanScanQRCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CanScanQRCode.swift; sourceTree = "<group>"; };
5E7C7419F47CC8B2996AA8F9 /* TransferTokensCardQuantitySelectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTokensCardQuantitySelectionViewController.swift; sourceTree = "<group>"; };
5E7C74294562EB79EFCD3559 /* DiscoverDappCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverDappCellViewModel.swift; sourceTree = "<group>"; };
5E7C743172FCBDCD362C03A6 /* ImportWalletTabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletTabBar.swift; sourceTree = "<group>"; };
5E7C7477E69BEDF0C4950D5A /* TokenListFormatTableViewCellWithCheckbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenListFormatTableViewCellWithCheckbox.swift; sourceTree = "<group>"; };
5E7C7487BDF72352446E1266 /* ImportTokenViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportTokenViewControllerTests.swift; sourceTree = "<group>"; };
5E7C74A1A13A1A6CB9E61BAC /* TokenListFormatRowViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenListFormatRowViewModel.swift; sourceTree = "<group>"; };
5E7C74B424FB5DE3A4D6A2F4 /* DappsHomeEmptyViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeEmptyViewViewModel.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>"; };
5E7C74BE9900543A755CB76A /* DappsAutoCompletionCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsAutoCompletionCellViewModel.swift; sourceTree = "<group>"; };
5E7C74BEC095303B66FB4B1E /* ProtectionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProtectionCoordinator.swift; sourceTree = "<group>"; };
5E7C74C0C1803DD17FE9EBA7 /* ServersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServersViewController.swift; sourceTree = "<group>"; };
5E7C74D2C599646C65B95E2F /* DappsAutoCompletionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsAutoCompletionCell.swift; sourceTree = "<group>"; };
5E7C74D5CEC9A6C4483B1C77 /* PreferencesController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesController.swift; sourceTree = "<group>"; };
5E7C74DCC21272EC231A20E2 /* RequestViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestViewController.swift; sourceTree = "<group>"; };
5E7C7534FB6BF4D199643246 /* AlphaWalletSettingsSwitchRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlphaWalletSettingsSwitchRow.swift; path = Views/AlphaWalletSettingsSwitchRow.swift; sourceTree = "<group>"; };
5E7C7535095323B035CA47C0 /* ImportMagicTokenViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportMagicTokenViewController.swift; sourceTree = "<group>"; };
5E7C754BF8B4CC2DA82B1025 /* DappButtonViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappButtonViewModel.swift; sourceTree = "<group>"; };
5E7C754C0E2E57F32A61F9A3 /* SetTransferTokensCardExpiryDateViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetTransferTokensCardExpiryDateViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C755132D9B6F95080A1BE /* TransferNFTCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferNFTCoordinator.swift; sourceTree = "<group>"; };
5E7C7558286761EF1ADD2988 /* ABIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ABIError.swift; sourceTree = "<group>"; };
@ -901,6 +933,7 @@
5E7C75CC640BAFFE0E789F44 /* WalletFilterViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletFilterViewModel.swift; sourceTree = "<group>"; };
5E7C75CE3F1D6B7993E7A840 /* OnboardingCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingCollectionViewController.swift; sourceTree = "<group>"; };
5E7C75D384C0D727BB43305E /* SettingsHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsHeaderView.swift; sourceTree = "<group>"; };
5E7C75E7D995ABE0E6B7AD55 /* DappButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappButton.swift; sourceTree = "<group>"; };
5E7C75F65E8C1E20EBA6A5F4 /* Scrollable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scrollable.swift; sourceTree = "<group>"; };
5E7C7607B0EF9B8F1BC41073 /* TermsOfServiceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TermsOfServiceViewController.swift; sourceTree = "<group>"; };
5E7C761E148B46943FC38979 /* BaseOpenSeaNonFungibleTokenCardTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseOpenSeaNonFungibleTokenCardTableViewCell.swift; sourceTree = "<group>"; };
@ -916,14 +949,16 @@
5E7C76C895E7BFA47233068C /* LocalesCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalesCoordinator.swift; sourceTree = "<group>"; };
5E7C76D132F4BEA5CE4FFD0A /* StringExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensionTests.swift; sourceTree = "<group>"; };
5E7C76D3CFA12C2236E73E10 /* TransferTokensCardViaWalletAddressViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTokensCardViaWalletAddressViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C76EE22984D66A3C18E70 /* DappsAutoCompletionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsAutoCompletionViewController.swift; sourceTree = "<group>"; };
5E7C7721E0E4D4EFDD35E196 /* ScanQRCodeCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanQRCodeCoordinator.swift; sourceTree = "<group>"; };
5E7C772DC28C5110021894E3 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
5E7C77316522DF2B256F1F92 /* TokensCardViewControllerHeaderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensCardViewControllerHeaderViewModel.swift; sourceTree = "<group>"; };
5E7C774BCA281E4B077DBBFA /* WhatIsEthereumInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhatIsEthereumInfoViewController.swift; sourceTree = "<group>"; };
5E7C7752CDF1417A704035C3 /* BookmarkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkViewController.swift; sourceTree = "<group>"; };
5E7C775FD95FE80B0F1CEA33 /* TokenAdaptorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenAdaptorTest.swift; sourceTree = "<group>"; };
5E7C776B129861728FFB8CC8 /* EditMyDappViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditMyDappViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C778A54D7D3E196BC5542 /* DAppRequster.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DAppRequster.swift; sourceTree = "<group>"; };
5E7C778F20D32B70D7FF2135 /* TokenCardRedemptionInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenCardRedemptionInfoViewController.swift; sourceTree = "<group>"; };
5E7C77A400E5145C04083FEB /* Dapps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dapps.swift; sourceTree = "<group>"; };
5E7C77B790551456E111ED4F /* PeekOpenSeaNonFungibleTokenViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeekOpenSeaNonFungibleTokenViewController.swift; sourceTree = "<group>"; };
5E7C77C2844B3579A59C3F2F /* CallSmartContractFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallSmartContractFunction.swift; sourceTree = "<group>"; };
5E7C77E1E6194F5A1DC8D645 /* ScreenChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenChecker.swift; sourceTree = "<group>"; };
@ -935,9 +970,12 @@
5E7C787CA216AFED8023A35F /* CallForAssetAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallForAssetAttribute.swift; sourceTree = "<group>"; };
5E7C7892A9FC3F53B13498D9 /* GenerateSellMagicLinkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateSellMagicLinkViewController.swift; sourceTree = "<group>"; };
5E7C78B001F9F95F404D5FEF /* HowDoIGetMyMoneyInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HowDoIGetMyMoneyInfoViewController.swift; sourceTree = "<group>"; };
5E7C78B61907C2C1E2BCD478 /* DappsHomeViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C78B63FDE2FAF25389260 /* TransferTokensCardViaWalletAddressViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTokensCardViaWalletAddressViewController.swift; sourceTree = "<group>"; };
5E7C78E5C8FAEA752B32626D /* UIActivityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIActivityViewController.swift; sourceTree = "<group>"; };
5E7C790B6371A5BCD733A4BE /* DappsHomeEmptyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeEmptyView.swift; sourceTree = "<group>"; };
5E7C791BD7AFEA4A419BAE24 /* OpenSeaNonFungibleTokenCardTableViewCellWithoutCheckbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungibleTokenCardTableViewCellWithoutCheckbox.swift; sourceTree = "<group>"; };
5E7C793CDFA907BFDFECB6CB /* DappViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappViewCell.swift; sourceTree = "<group>"; };
5E7C793E23E2364B73C4D813 /* WelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = "<group>"; };
5E7C794F8EBAEE5E8F2821C2 /* MarketplaceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketplaceViewController.swift; sourceTree = "<group>"; };
5E7C796039C0F47CDCA236C0 /* TokenCardsViewControllerHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenCardsViewControllerHeader.swift; sourceTree = "<group>"; };
@ -945,15 +983,16 @@
5E7C79778E4BFE1322711EA6 /* LocaleViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocaleViewModel.swift; sourceTree = "<group>"; };
5E7C7981AB6584B25C72D46B /* LockEnterPasscodeCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockEnterPasscodeCoordinator.swift; sourceTree = "<group>"; };
5E7C7982FA14CBFDFD93B3D0 /* AssetDefinitionStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionStore.swift; sourceTree = "<group>"; };
5E7C799836611BEE66000EE1 /* DappsHomeHeaderViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeHeaderViewViewModel.swift; sourceTree = "<group>"; };
5E7C799D2B7D91072FC0050B /* DappsHomeViewControllerHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeViewControllerHeaderView.swift; sourceTree = "<group>"; };
5E7C79D674D45A07E694CE31 /* LockView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockView.swift; sourceTree = "<group>"; };
5E7C79DCBECE3385649FAFDF /* MasterBrowserViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterBrowserViewController.swift; sourceTree = "<group>"; };
5E7C79E3BC4CACB123840A42 /* LocaleViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocaleViewCell.swift; sourceTree = "<group>"; };
5E7C79ED9F842D3FC102AC54 /* TokenViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C79EF9D2C12F396364B92 /* AssetDefinitionDiskBackingStoreWithOverridesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionDiskBackingStoreWithOverridesTests.swift; sourceTree = "<group>"; };
5E7C79FA3E6A05845ECFCDCF /* BrowserHistoryViewControllerHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserHistoryViewControllerHeaderView.swift; sourceTree = "<group>"; };
5E7C79FE0C70AC4198F2AEB7 /* ResultResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultResult.swift; sourceTree = "<group>"; };
5E7C7A16ABC8BD5D508AA641 /* ImportWalletHelpBubbleViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletHelpBubbleViewViewModel.swift; sourceTree = "<group>"; };
5E7C7A40B418A8F13AA16C29 /* AssetDefinitionStoreCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionStoreCoordinator.swift; sourceTree = "<group>"; };
5E7C7A794D1437F435CBB780 /* HistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewController.swift; sourceTree = "<group>"; };
5E7C7A9876B43B1D9D17A9A9 /* OpenSeaNonFungibleTokenDisplayHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungibleTokenDisplayHelper.swift; sourceTree = "<group>"; };
5E7C7AB3440C01136DF4F3E9 /* LockCreatePasscodeCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockCreatePasscodeCoordinator.swift; sourceTree = "<group>"; };
5E7C7AB4464F82391AAD68C1 /* BookmarksStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksStore.swift; sourceTree = "<group>"; };
@ -970,6 +1009,7 @@
5E7C7B1FB2702A2A8A4EBD76 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
5E7C7B29A9E728402D144C05 /* AppLocale.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppLocale.swift; sourceTree = "<group>"; };
5E7C7B3302309706CA0F972A /* TokensViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensViewController.swift; sourceTree = "<group>"; };
5E7C7B54826BFDD53DF3E5BF /* DiscoverDappCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverDappCell.swift; sourceTree = "<group>"; };
5E7C7B6380E6EB88AF8810CD /* ConfirmSignMessageViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfirmSignMessageViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C7B6FAFE62FBAADB85228 /* Web3Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Web3Error.swift; sourceTree = "<group>"; };
5E7C7B7A45EDFA8ED1E25863 /* SendHeaderViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendHeaderViewViewModel.swift; sourceTree = "<group>"; };
@ -989,6 +1029,7 @@
5E7C7C12E88EB0B73AA1E562 /* TokenCardRowViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenCardRowViewModelProtocol.swift; sourceTree = "<group>"; };
5E7C7C2872E213BBB05D55BA /* ImportWalletTabBarViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletTabBarViewModel.swift; sourceTree = "<group>"; };
5E7C7C34A7BDCFE17CEF8F79 /* OpenSeaNonFungibleTokenAttributeCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungibleTokenAttributeCellViewModel.swift; sourceTree = "<group>"; };
5E7C7C5454600A70DCFD7C0E /* BoxView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoxView.swift; sourceTree = "<group>"; };
5E7C7C58586099F082973073 /* WalletFilterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletFilterView.swift; sourceTree = "<group>"; };
5E7C7C781CCE43B6451671B9 /* TokenListFormatTableViewCellWithoutCheckbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenListFormatTableViewCellWithoutCheckbox.swift; sourceTree = "<group>"; };
5E7C7C8CA3706DC14167786C /* BrowserURLParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserURLParserTests.swift; sourceTree = "<group>"; };
@ -1003,6 +1044,7 @@
5E7C7CDB0BAD5D27D2F24F57 /* ServerViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerViewCell.swift; sourceTree = "<group>"; };
5E7C7CFDE7DEA8C06C4100AF /* TextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
5E7C7D07B7D0738A1832AB58 /* AssetDefinitionInMemoryBackingStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionInMemoryBackingStore.swift; sourceTree = "<group>"; };
5E7C7D19E3CF96929FB8CEA3 /* DappsAutoCompletionViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsAutoCompletionViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C7D27B0DA47F340CEA70C /* TokenListFormatRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenListFormatRowView.swift; sourceTree = "<group>"; };
5E7C7D2C43C15D0762C7F374 /* UIStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = "<group>"; };
5E7C7D46C7CABC31A7477F37 /* GenerateTransferMagicLinkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateTransferMagicLinkViewController.swift; sourceTree = "<group>"; };
@ -1010,6 +1052,8 @@
5E7C7D5F3CAE69CF932AB236 /* LockPasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockPasscodeViewController.swift; sourceTree = "<group>"; };
5E7C7D674F6B2415FB5552B0 /* CanOpenContractWebPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CanOpenContractWebPage.swift; sourceTree = "<group>"; };
5E7C7D69938F484C2A186FAE /* URLViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = URLViewModel.swift; path = Protocols/URLViewModel.swift; sourceTree = "<group>"; };
5E7C7D8C3613A9BD9F147B3C /* DappViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C7D8D618A8A8D55479CDF /* Dapp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dapp.swift; sourceTree = "<group>"; };
5E7C7D913DAA3322F1C7DD46 /* OpenSeaNonFungibleTokenCardRowViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungibleTokenCardRowViewModel.swift; sourceTree = "<group>"; };
5E7C7D931F68BFB5E1DCE001 /* TokenCardRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TokenCardRowView.swift; path = Views/TokenCardRowView.swift; sourceTree = "<group>"; };
5E7C7DBF46316B08D3905C8E /* Web3Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Web3Request.swift; sourceTree = "<group>"; };
@ -1032,11 +1076,13 @@
5E7C7F19B130116A0B495B96 /* Web3RequestType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Web3RequestType.swift; sourceTree = "<group>"; };
5E7C7F1B66DB15E6167416F8 /* SearchEngine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchEngine.swift; sourceTree = "<group>"; };
5E7C7F3DD81D44A996789FC4 /* UniversalLinkInPasteboardCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalLinkInPasteboardCoordinator.swift; sourceTree = "<group>"; };
5E7C7F55495A6095B3E86248 /* EditMyDappViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditMyDappViewController.swift; sourceTree = "<group>"; };
5E7C7F5C10E3895E805EA7E0 /* BaseTokenCardTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTokenCardTableViewCell.swift; sourceTree = "<group>"; };
5E7C7F610139D24D947B1625 /* EnterSellTokensCardPriceQuantityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterSellTokensCardPriceQuantityViewController.swift; sourceTree = "<group>"; };
5E7C7F66EE7899E4573C64AE /* OpenSeaNonFungibleTokenCardTableViewCellWithCheckbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungibleTokenCardTableViewCellWithCheckbox.swift; sourceTree = "<group>"; };
5E7C7F718714A0EA529664E7 /* BrowserErrorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserErrorView.swift; sourceTree = "<group>"; };
5E7C7F7C7E37B5CD147EF784 /* web3.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = web3.min.js; sourceTree = "<group>"; };
5E7C7F840AFFD4459FD3DBD6 /* DiscoverDappsViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverDappsViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C7F89E3480D3680750EA9 /* TokenRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenRowView.swift; sourceTree = "<group>"; };
5E7C7F932B48011A24C26733 /* TokensCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensCoordinator.swift; sourceTree = "<group>"; };
5E7C7FB99843529061368DA1 /* LocalesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalesViewModel.swift; sourceTree = "<group>"; };
@ -1044,6 +1090,7 @@
5E7C7FC75FF544B1DF0B0D8B /* PushNotificationsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotificationsCoordinator.swift; sourceTree = "<group>"; };
5E7C7FCE2427A30ACD860DF8 /* ServerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerViewModel.swift; sourceTree = "<group>"; };
5E7C7FE30D58E4022AF04E48 /* AssetDefinitionStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionStoreTests.swift; sourceTree = "<group>"; };
5E7C7FE5EEC96A7CDF62213F /* DappsHomeHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeHeaderView.swift; sourceTree = "<group>"; };
5E7C7FF84A4377FC395772C3 /* SellTokensCardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SellTokensCardViewController.swift; sourceTree = "<group>"; };
613D04881FDE15F8008DE72E /* COMODO ECC Domain Validation Secure Server CA 2.cer */ = {isa = PBXFileReference; lastKnownFileType = file.cer; path = "COMODO ECC Domain Validation Secure Server CA 2.cer"; sourceTree = "<group>"; };
613D048A1FDE162B008DE72E /* AlphaWalletProviderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlphaWalletProviderFactory.swift; sourceTree = "<group>"; };
@ -1071,18 +1118,26 @@
73ED85A420349BE400593BF3 /* StringFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringFormatter.swift; sourceTree = "<group>"; };
73ED85A62034BFEF00593BF3 /* UITextFieldAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextFieldAdditions.swift; sourceTree = "<group>"; };
73ED85A82034C42D00593BF3 /* StringFormatterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringFormatterTest.swift; sourceTree = "<group>"; };
76F1D3AB33F1310B03B18E37 /* MyDappsViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDappsViewControllerViewModel.swift; sourceTree = "<group>"; };
76F1D419EE36261E50ABAFAE /* ClaimOrderCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaimOrderCoordinator.swift; sourceTree = "<group>"; };
76F1D473FF303828D93C95EB /* GetERC721BalanceCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetERC721BalanceCoordinator.swift; sourceTree = "<group>"; };
76F1D4E689C3ECFD38CBBC47 /* DappBrowserCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappBrowserCoordinator.swift; sourceTree = "<group>"; };
76F1D4F77311FBF3A442E4B5 /* GetIsERC721ContractCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetIsERC721ContractCoordinator.swift; sourceTree = "<group>"; };
76F1D8877226D5DD086B135D /* CreateRedeemTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateRedeemTests.swift; sourceTree = "<group>"; };
76F1D8A3F2F0F86B632BFBCE /* DiscoverDappsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverDappsViewController.swift; sourceTree = "<group>"; };
76F1D948016AAE8470099748 /* MyDappCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDappCell.swift; sourceTree = "<group>"; };
76F1D96298E216CBFC3DD78B /* UniversalLinkHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalLinkHandlerTests.swift; sourceTree = "<group>"; };
76F1DACA9404AD6740BEADBB /* ClaimOrderCoordinatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaimOrderCoordinatorTests.swift; sourceTree = "<group>"; };
76F1DADFD07E2941897FD2E1 /* OrderHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderHandler.swift; sourceTree = "<group>"; };
76F1DAFCBB43B6639472A229 /* BrowserHistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserHistoryViewController.swift; sourceTree = "<group>"; };
76F1DB8034ACC2FC91F818F9 /* MyDappsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDappsViewController.swift; sourceTree = "<group>"; };
76F1DC4B9964504DA12D8D3C /* GetENSNameEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetENSNameEncode.swift; sourceTree = "<group>"; };
76F1DCD54618349AC91C6DF8 /* UniversalLinkHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalLinkHandler.swift; sourceTree = "<group>"; };
76F1DD09B44FD653C1500DA8 /* DappsHomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeViewController.swift; sourceTree = "<group>"; };
76F1DE7BEE799DDFB68D0F54 /* GetENSOwnerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetENSOwnerCoordinator.swift; sourceTree = "<group>"; };
76F1DE8ADA3176D0277EDF20 /* OrderSigningTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderSigningTests.swift; sourceTree = "<group>"; };
76F1DE9EF8E265016BF02659 /* GasLimitConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GasLimitConfiguration.swift; sourceTree = "<group>"; };
76F1DF357BEAC88C6AEB6D58 /* DappBrowserNavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappBrowserNavigationBar.swift; sourceTree = "<group>"; };
76F1DF5CF4A922E6FFCB7B2A /* GetContractInteractions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetContractInteractions.swift; sourceTree = "<group>"; };
76F1DF80932454E9F58B7830 /* CreateRedeem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateRedeem.swift; sourceTree = "<group>"; };
771A8484203242B400528D28 /* InCoordinatorViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InCoordinatorViewModelTests.swift; sourceTree = "<group>"; };
@ -1094,7 +1149,6 @@
7721A6BD202A5677004DB16C /* DecryptError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecryptError.swift; sourceTree = "<group>"; };
7721A6BF202B1D3E004DB16C /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
7721A6C7202EF81B004DB16C /* CustomRPC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomRPC.swift; sourceTree = "<group>"; };
775C00B420195BFB001B5EBC /* BrowserAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserAction.swift; sourceTree = "<group>"; };
77872D222023F43B0032D687 /* TransactionsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionsTracker.swift; sourceTree = "<group>"; };
77872D24202505B70032D687 /* EnterPasswordViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterPasswordViewController.swift; sourceTree = "<group>"; };
77872D26202505C00032D687 /* EnterPasswordViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterPasswordViewModel.swift; sourceTree = "<group>"; };
@ -1103,9 +1157,7 @@
77872D2F2026DC570032D687 /* SplashViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = "<group>"; };
77872D312027AA4A0032D687 /* SliderTextFieldRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderTextFieldRow.swift; sourceTree = "<group>"; };
778EAF7C1FF10AF400C8E2AB /* SettingsCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinatorTests.swift; sourceTree = "<group>"; };
77B3BF342017D0D000EEC15A /* MarketplaceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketplaceViewModel.swift; sourceTree = "<group>"; };
77B3BF3B201908ED00EEC15A /* ConfirmCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmCoordinator.swift; sourceTree = "<group>"; };
77B3BF492019247200EEC15A /* BrowserNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserNavigationBar.swift; sourceTree = "<group>"; };
77E0E772201FAD05009B4B31 /* BrowserURLParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserURLParser.swift; sourceTree = "<group>"; };
7BEDA1C4253B796085A0E66C /* Pods_AlphaWalletUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AlphaWalletUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AA26C61D20412A1D00318B9B /* TokensCardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensCardViewController.swift; sourceTree = "<group>"; };
@ -1392,6 +1444,7 @@
5E7C73D0DCE61EA2DE2DA21D /* AmountTextField.swift */,
5E7C7CD1FB7D353704EF3389 /* DateEntryField.swift */,
5E7C73EFA9494B31C683A287 /* TimeEntryField.swift */,
5E7C7C5454600A70DCFD7C0E /* BoxView.swift */,
);
path = UI;
sourceTree = "<group>";
@ -2093,8 +2146,8 @@
29E6E0691FE897C70079265A /* Coordinators */ = {
isa = PBXGroup;
children = (
29E6E06B1FE897D90079265A /* BrowserCoordinator.swift */,
5E7C7721E0E4D4EFDD35E196 /* ScanQRCodeCoordinator.swift */,
76F1D4E689C3ECFD38CBBC47 /* DappBrowserCoordinator.swift */,
);
path = Coordinators;
sourceTree = "<group>";
@ -2103,9 +2156,12 @@
isa = PBXGroup;
children = (
29E6E06D1FE897EE0079265A /* BrowserViewController.swift */,
5E7C7752CDF1417A704035C3 /* BookmarkViewController.swift */,
5E7C7A794D1437F435CBB780 /* HistoryViewController.swift */,
5E7C79DCBECE3385649FAFDF /* MasterBrowserViewController.swift */,
76F1DD09B44FD653C1500DA8 /* DappsHomeViewController.swift */,
76F1DB8034ACC2FC91F818F9 /* MyDappsViewController.swift */,
76F1D8A3F2F0F86B632BFBCE /* DiscoverDappsViewController.swift */,
76F1DAFCBB43B6639472A229 /* BrowserHistoryViewController.swift */,
5E7C76EE22984D66A3C18E70 /* DappsAutoCompletionViewController.swift */,
5E7C7F55495A6095B3E86248 /* EditMyDappViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
@ -2917,7 +2973,6 @@
isa = PBXGroup;
children = (
771AA94D1FF971CD00D25403 /* DappAction.swift */,
775C00B420195BFB001B5EBC /* BrowserAction.swift */,
29FA00CB201CA63C002F7DC5 /* Method.swift */,
29FA00CD201CA64E002F7DC5 /* DappCommand.swift */,
29FA00CF201CA66A002F7DC5 /* DAppError.swift */,
@ -2975,10 +3030,21 @@
77B3BF2C2017CD1A00EEC15A /* Views */ = {
isa = PBXGroup;
children = (
77B3BF492019247200EEC15A /* BrowserNavigationBar.swift */,
5E7C706658D72CC1C8BB698C /* BookmarkViewCell.swift */,
5E7C7F718714A0EA529664E7 /* BrowserErrorView.swift */,
5E7C72CEFD7E32ACE303AB1F /* BookmarkViewCell.xib */,
76F1DF357BEAC88C6AEB6D58 /* DappBrowserNavigationBar.swift */,
5E7C74D2C599646C65B95E2F /* DappsAutoCompletionCell.swift */,
5E7C7B54826BFDD53DF3E5BF /* DiscoverDappCell.swift */,
76F1D948016AAE8470099748 /* MyDappCell.swift */,
5E7C793CDFA907BFDFECB6CB /* DappViewCell.swift */,
5E7C75E7D995ABE0E6B7AD55 /* DappButton.swift */,
5E7C799D2B7D91072FC0050B /* DappsHomeViewControllerHeaderView.swift */,
5E7C7FE5EEC96A7CDF62213F /* DappsHomeHeaderView.swift */,
5E7C73D55C366BCC53208686 /* BrowserHistoryCell.swift */,
5E7C79FA3E6A05845ECFCDCF /* BrowserHistoryViewControllerHeaderView.swift */,
5E7C790B6371A5BCD733A4BE /* DappsHomeEmptyView.swift */,
5E7C7171B802C0C2718EEED0 /* MyDappsViewControllerHeaderView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -2986,11 +3052,23 @@
77B3BF332017D0C400EEC15A /* ViewModel */ = {
isa = PBXGroup;
children = (
77B3BF342017D0D000EEC15A /* MarketplaceViewModel.swift */,
5E7C71CAD32891B5DC651054 /* BookmarksViewModel.swift */,
5E7C729B595A03A5CF3EC9F4 /* BookmarkViewModel.swift */,
5E7C715BBA7416942FDA8516 /* HistoriesViewModel.swift */,
5E7C72BCB25F6212B3029E34 /* HistoryViewModel.swift */,
5E7C78B61907C2C1E2BCD478 /* DappsHomeViewControllerViewModel.swift */,
5E7C7F840AFFD4459FD3DBD6 /* DiscoverDappsViewControllerViewModel.swift */,
5E7C7D19E3CF96929FB8CEA3 /* DappsAutoCompletionViewControllerViewModel.swift */,
76F1D3AB33F1310B03B18E37 /* MyDappsViewControllerViewModel.swift */,
5E7C7D8C3613A9BD9F147B3C /* DappViewCellViewModel.swift */,
5E7C77A400E5145C04083FEB /* Dapps.swift */,
5E7C7375430F36C549EA8748 /* DappsHomeViewControllerHeaderViewViewModel.swift */,
5E7C754BF8B4CC2DA82B1025 /* DappButtonViewModel.swift */,
5E7C799836611BEE66000EE1 /* DappsHomeHeaderViewViewModel.swift */,
5E7C74294562EB79EFCD3559 /* DiscoverDappCellViewModel.swift */,
5E7C712F42374C0B8DF8C64F /* BrowserHistoryCellViewModel.swift */,
5E7C74BE9900543A755CB76A /* DappsAutoCompletionCellViewModel.swift */,
5E7C7324C9AC776E3A7B43D1 /* MyDappCellViewModel.swift */,
5E7C74B424FB5DE3A4D6A2F4 /* DappsHomeEmptyViewViewModel.swift */,
5E7C776B129861728FFB8CC8 /* EditMyDappViewControllerViewModel.swift */,
5E7C7D8D618A8A8D55479CDF /* Dapp.swift */,
);
path = ViewModel;
sourceTree = "<group>";
@ -3553,7 +3631,6 @@
299B5E341FCBC5180051361C /* ConfirmPaymentViewModel.swift in Sources */,
771AA94E1FF971CD00D25403 /* DappAction.swift in Sources */,
29FF6D6B2011D2AF00A3011C /* InCoordinatorError.swift in Sources */,
77B3BF352017D0D000EEC15A /* MarketplaceViewModel.swift in Sources */,
29F114F01FA6D53700114A29 /* ImportSelectionType.swift in Sources */,
AA893ED5203C3E5400CDCED1 /* TokenBalance.swift in Sources */,
29850D251F6B27A800791A49 /* R.generated.swift in Sources */,
@ -3639,7 +3716,6 @@
CCA4FE361FD4282400749AE4 /* DeviceChecker.swift in Sources */,
732086B9201508690047F605 /* SplashCoordinator.swift in Sources */,
AA26C62420412A4100318B9B /* Double.swift in Sources */,
29E6E06C1FE897D90079265A /* BrowserCoordinator.swift in Sources */,
77872D292025116E0032D687 /* EnterPasswordCoordinator.swift in Sources */,
298542FB1FBEA03300CB5081 /* SendInputErrors.swift in Sources */,
29EB102A1F6CBD23000907A4 /* UIAlertController.swift in Sources */,
@ -3650,7 +3726,6 @@
299B5E421FD2298E0051361C /* ConfigureTransactionViewController.swift in Sources */,
294DFBA31FE0E2EA004CEB56 /* TransactionValue.swift in Sources */,
2912CD2B1F6A833E00C6CBE3 /* TransactionsViewController.swift in Sources */,
775C00B520195BFB001B5EBC /* BrowserAction.swift in Sources */,
BB5D6A9E20232EE8000FC5AB /* CurrencyRate+Fee.swift in Sources */,
77E0E773201FAD06009B4B31 /* BrowserURLParser.swift in Sources */,
29F1C863200375D2003780D8 /* Wallet.swift in Sources */,
@ -3675,7 +3750,6 @@
29B6AED61F7CA4A700EC6DE3 /* TransactionConfiguration.swift in Sources */,
29F114F41FA8117C00114A29 /* SendCoordinator.swift in Sources */,
293B8B411F707F4600356286 /* TransactionViewModel.swift in Sources */,
77B3BF4A2019247200EEC15A /* BrowserNavigationBar.swift in Sources */,
29BB94951F6FC54C009B09CC /* EthereumUnit.swift in Sources */,
29C70C712016C7780072E454 /* SentTransaction.swift in Sources */,
AAEF2CAB2050A68A0038BE0D /* SignatureHelper.swift in Sources */,
@ -3860,7 +3934,6 @@
5E7C78A31A16600FBA5C9956 /* ScanQRCodeCoordinator.swift in Sources */,
5E7C7B129C55A8458AEF3F61 /* URLViewModel.swift in Sources */,
5E7C716AB8079EA1283B2317 /* Bookmark.swift in Sources */,
5E7C7C026C7E0DD0458850B3 /* BookmarkViewController.swift in Sources */,
5E7C7B8242E31B4DA0263BE5 /* EthTypedData.swift in Sources */,
5E7C76D09352C069C140B6CF /* PreferencesController.swift in Sources */,
5E7C7569C4D26E565F3E4F56 /* PreferenceOption.swift in Sources */,
@ -3877,14 +3950,9 @@
5E7C7BC8F2E31F4E2BA534D9 /* Ether.swift in Sources */,
5E7C70CF1C732CE07D074A8B /* BookmarksStore.swift in Sources */,
5E7C7E2E47ED7EDD5C127D1D /* HistoryStore.swift in Sources */,
5E7C7E7C719E9B11A402D059 /* BookmarksViewModel.swift in Sources */,
5E7C709168AFA09B5D2926FB /* BookmarkViewModel.swift in Sources */,
5E7C7FC76A025AD91D57B960 /* HistoriesViewModel.swift in Sources */,
5E7C73FDC874F983EC83B820 /* HistoryViewModel.swift in Sources */,
5E7C7A6C28C59DB62DEE2D63 /* BookmarkViewCell.swift in Sources */,
5E7C7855E46A6604B2028C9D /* BrowserErrorView.swift in Sources */,
5E7C71CA7E0B33EEB08D551D /* HistoryViewController.swift in Sources */,
5E7C76D4F2D67016E279756F /* MasterBrowserViewController.swift in Sources */,
5E7C71A2EAA5124E07AA54B6 /* Favicon.swift in Sources */,
5E7C7EC725C711F5EA12CB1C /* History.swift in Sources */,
5E7C767C9166726E96ED4C07 /* DelegateContract.swift in Sources */,
@ -3951,6 +4019,42 @@
76F1D5B10569F2351CA98A93 /* GasLimitConfiguration.swift in Sources */,
5E7C7EB80D32A2F366E79140 /* SettingsHeaderView.swift in Sources */,
5E7C7A0B5FDADC60DC01F060 /* CallSmartContractFunction.swift in Sources */,
76F1D12A7ED6E35000B1B020 /* DappBrowserCoordinator.swift in Sources */,
76F1D0A410FCFEB38307F7F9 /* DappsHomeViewController.swift in Sources */,
76F1D6B78F541D723F4E6D7B /* MyDappsViewController.swift in Sources */,
76F1D24B6244EEA49F0FDE33 /* DiscoverDappsViewController.swift in Sources */,
76F1DBF9F5359160063625B2 /* BrowserHistoryViewController.swift in Sources */,
76F1D5DFFA5652DC16EF713D /* DappBrowserNavigationBar.swift in Sources */,
5E7C78265EECE083C3EB1845 /* DappsHomeViewControllerViewModel.swift in Sources */,
5E7C77D12D8DE42157790BDC /* DappsAutoCompletionCell.swift in Sources */,
5E7C7186B20660F2C1462AA9 /* DiscoverDappsViewControllerViewModel.swift in Sources */,
5E7C70861A499FF79B7DA903 /* DiscoverDappCell.swift in Sources */,
5E7C72B359B220D0CBA7DCF1 /* DappsAutoCompletionViewController.swift in Sources */,
5E7C702C4B29AF2B8D61CCA4 /* DappsAutoCompletionViewControllerViewModel.swift in Sources */,
76F1DD3DF34BCCFE9BE3CEBB /* MyDappsViewControllerViewModel.swift in Sources */,
76F1D3B0E2CDBE82B764C0C7 /* MyDappCell.swift in Sources */,
5E7C79E9A6A3DFB7FA680752 /* DappViewCell.swift in Sources */,
5E7C77649E432A905B836E95 /* DappViewCellViewModel.swift in Sources */,
5E7C7A6CC21C553CD4008F14 /* DappButton.swift in Sources */,
5E7C7E83AD74AFB0C717EAC0 /* DappsHomeViewControllerHeaderView.swift in Sources */,
5E7C7BCAB0CD58ACA37ED6A4 /* Dapps.swift in Sources */,
5E7C72CF145240C816BB12E2 /* DappsHomeViewControllerHeaderViewViewModel.swift in Sources */,
5E7C76B7DDF7BB124B12A738 /* DappButtonViewModel.swift in Sources */,
5E7C7521CA17E5C2A4506A20 /* DappsHomeHeaderView.swift in Sources */,
5E7C7EE445B044CA15171BD5 /* DappsHomeHeaderViewViewModel.swift in Sources */,
5E7C7F67945615E242B61CC3 /* BoxView.swift in Sources */,
5E7C7DCF09B84E5675D58CED /* DiscoverDappCellViewModel.swift in Sources */,
5E7C72CEFE98436DB8EC0E05 /* BrowserHistoryCell.swift in Sources */,
5E7C7EF1F2CDFA52BBF1C620 /* BrowserHistoryCellViewModel.swift in Sources */,
5E7C77B397BCB0E254F359A8 /* DappsAutoCompletionCellViewModel.swift in Sources */,
5E7C7B4778FF36371701242E /* BrowserHistoryViewControllerHeaderView.swift in Sources */,
5E7C7FE5F70D5777FD7258B2 /* DappsHomeEmptyView.swift in Sources */,
5E7C7EAAF2BD4D12987968E4 /* MyDappCellViewModel.swift in Sources */,
5E7C746219CE5A1139756869 /* MyDappsViewControllerHeaderView.swift in Sources */,
5E7C7C0D3181CD31A581AEBE /* EditMyDappViewController.swift in Sources */,
5E7C76D28BB14C7685296BEF /* DappsHomeEmptyViewViewModel.swift in Sources */,
5E7C741CA88EFAC66756DE7F /* EditMyDappViewControllerViewModel.swift in Sources */,
5E7C71AE5E97BDA5BB685A9F /* Dapp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

@ -1,429 +0,0 @@
// Copyright DApps Platform Inc. All rights reserved.
import Foundation
import UIKit
import BigInt
import TrustKeystore
import RealmSwift
import WebKit
protocol BrowserCoordinatorDelegate: class {
func didSentTransaction(transaction: SentTransaction, in coordinator: BrowserCoordinator)
func didPressCloseButton(in coordinator: BrowserCoordinator)
}
final class BrowserCoordinator: NSObject, Coordinator {
private let session: WalletSession
private let keystore: Keystore
private lazy var bookmarksViewController: BookmarkViewController = {
let controller = BookmarkViewController(bookmarksStore: bookmarksStore)
controller.delegate = self
return controller
}()
private lazy var historyViewController: HistoryViewController = {
let controller = HistoryViewController(store: historyStore)
controller.delegate = self
return controller
}()
private lazy var browserViewController: BrowserViewController = {
let controller = BrowserViewController(account: session.account, config: session.config, server: server)
controller.delegate = self
controller.webView.uiDelegate = self
return controller
}()
private let sharedRealm: Realm
private lazy var bookmarksStore: BookmarksStore = {
return BookmarksStore(realm: sharedRealm)
}()
private lazy var historyStore: HistoryStore = {
return HistoryStore(realm: sharedRealm)
}()
private lazy var preferences: PreferencesController = {
return PreferencesController()
}()
private var urlParser: BrowserURLParser {
let engine = SearchEngine(rawValue: preferences.get(for: .browserSearchEngine)) ?? .default
return BrowserURLParser(engine: engine)
}
private var server: RPCServer {
return session.config.server
}
private var enableToolbar: Bool = true {
didSet {
navigationController.isToolbarHidden = !enableToolbar
}
}
var coordinators: [Coordinator] = []
let navigationController: NavigationController
lazy var rootViewController: MasterBrowserViewController = {
let controller = MasterBrowserViewController(
bookmarksViewController: bookmarksViewController,
historyViewController: historyViewController,
browserViewController: browserViewController,
type: .browser
)
controller.delegate = self
return controller
}()
weak var delegate: BrowserCoordinatorDelegate?
init(
session: WalletSession,
keystore: Keystore,
sharedRealm: Realm
) {
self.navigationController = NavigationController(navigationBarClass: BrowserNavigationBar.self, toolbarClass: nil)
self.session = session
self.keystore = keystore
self.sharedRealm = sharedRealm
}
func start() {
navigationController.viewControllers = [rootViewController]
rootViewController.browserViewController.goHome()
}
@objc func dismiss() {
navigationController.dismiss(animated: true, completion: nil)
}
private func executeTransaction(account: Account, action: DappAction, callbackID: Int, transaction: UnconfirmedTransaction, type: ConfirmType, server: RPCServer) {
let configurator = TransactionConfigurator(
session: session,
account: account,
transaction: transaction
)
let coordinator = ConfirmCoordinator(
session: session,
configurator: configurator,
keystore: keystore,
account: account,
type: type
)
addCoordinator(coordinator)
coordinator.didCompleted = { [unowned self] result in
switch result {
case .success(let type):
switch type {
case .signedTransaction(let data):
let callback = DappCallback(id: callbackID, value: .signTransaction(data))
self.rootViewController.browserViewController.notifyFinish(callbackID: callbackID, value: .success(callback))
//TODO do we need to do this for a pending transaction?
// self.delegate?.didSentTransaction(transaction: transaction, in: self)
case .sentTransaction(let transaction):
// on send transaction we pass transaction ID only.
let data = Data(hex: transaction.id)
let callback = DappCallback(id: callbackID, value: .sentTransaction(data))
self.rootViewController.browserViewController.notifyFinish(callbackID: callbackID, value: .success(callback))
self.delegate?.didSentTransaction(transaction: transaction, in: self)
}
case .failure:
self.rootViewController.browserViewController.notifyFinish(
callbackID: callbackID,
value: .failure(DAppError.cancelled)
)
}
coordinator.didCompleted = nil
self.removeCoordinator(coordinator)
self.navigationController.dismiss(animated: true, completion: nil)
}
coordinator.start()
navigationController.present(coordinator.navigationController, animated: true, completion: nil)
}
func openURL(_ url: URL) {
rootViewController.browserViewController.goTo(url: url)
handleToolbar(for: url)
}
func handleToolbar(for url: URL) {
let isToolbarHidden = false
navigationController.isToolbarHidden = isToolbarHidden
rootViewController.select(viewType: .browser)
}
func signMessage(with type: SignMessageType, account: Account, callbackID: Int) {
let coordinator = SignMessageCoordinator(
navigationController: navigationController,
keystore: keystore,
account: account
)
coordinator.didComplete = { [weak self] result in
guard let strongSelf = self else { return }
switch result {
case .success(let data):
let callback: DappCallback
switch type {
case .message:
callback = DappCallback(id: callbackID, value: .signMessage(data))
case .personalMessage:
callback = DappCallback(id: callbackID, value: .signPersonalMessage(data))
case .typedMessage:
callback = DappCallback(id: callbackID, value: .signTypedMessage(data))
}
strongSelf.rootViewController.browserViewController.notifyFinish(callbackID: callbackID, value: .success(callback))
case .failure:
strongSelf.rootViewController.browserViewController.notifyFinish(callbackID: callbackID, value: .failure(DAppError.cancelled))
}
coordinator.didComplete = nil
strongSelf.removeCoordinator(coordinator)
}
coordinator.delegate = self
addCoordinator(coordinator)
coordinator.start(with: type)
}
func presentQRCodeReader() {
let coordinator = ScanQRCodeCoordinator(
navigationController: NavigationController()
)
coordinator.delegate = self
addCoordinator(coordinator)
navigationController.present(coordinator.qrcodeController, animated: true, completion: nil)
}
private func presentMoreOptions(sender: UIView) {
let alertController = makeMoreAlertSheet(sender: sender)
navigationController.present(alertController, animated: true, completion: nil)
}
private func makeMoreAlertSheet(sender: UIView) -> UIAlertController {
let alertController = UIAlertController(
title: nil,
message: nil,
preferredStyle: .actionSheet
)
alertController.popoverPresentationController?.sourceView = sender
alertController.popoverPresentationController?.sourceRect = sender.centerRect
let reloadAction = UIAlertAction(title: R.string.localizable.reload(), style: .default) { [unowned self] _ in
self.rootViewController.browserViewController.reload()
}
let shareAction = UIAlertAction(title: R.string.localizable.share(), style: .default) { [unowned self] _ in
self.share()
}
let cancelAction = UIAlertAction(title: R.string.localizable.cancel(), style: .cancel) { _ in }
let addBookmarkAction = UIAlertAction(title: R.string.localizable.browserAddbookmarkButtonTitle(), style: .default) { [unowned self] _ in
self.rootViewController.browserViewController.addBookmark()
}
alertController.addAction(reloadAction)
alertController.addAction(shareAction)
alertController.addAction(addBookmarkAction)
alertController.addAction(cancelAction)
return alertController
}
private func share() {
guard let url = rootViewController.browserViewController.webView.url else { return }
rootViewController.displayLoading()
rootViewController.showShareActivity(from: UIView(), with: [url]) { [weak self] in
self?.rootViewController.hideLoading()
}
}
}
extension BrowserCoordinator: BrowserViewControllerDelegate {
func runAction(action: BrowserAction) {
switch action {
case .bookmarks:
rootViewController.select(viewType: .bookmarks)
case .addBookmark(let bookmark):
bookmarksStore.add(bookmarks: [bookmark])
case .qrCode:
presentQRCodeReader()
case .history:
rootViewController.select(viewType: .history)
case .navigationAction(let navAction):
switch navAction {
case .home:
enableToolbar = true
rootViewController.select(viewType: .browser)
rootViewController.browserViewController.goHome()
case .close:
delegate?.didPressCloseButton(in: self)
case .more(let sender):
presentMoreOptions(sender: sender)
case .enter(let string):
guard let url = urlParser.url(from: string) else { return }
openURL(url)
case .goBack:
rootViewController.browserViewController.webView.goBack()
case .beginEditing:
break
}
case .changeURL(let url):
handleToolbar(for: url)
}
}
func didCall(action: DappAction, callbackID: Int) {
guard case .real(let account) = session.account.type else {
rootViewController.browserViewController.notifyFinish(callbackID: callbackID, value: .failure(DAppError.cancelled))
navigationController.topViewController?.displayError(error: InCoordinatorError.onlyWatchAccount)
return
}
switch action {
case .signTransaction(let unconfirmedTransaction):
executeTransaction(account: account, action: action, callbackID: callbackID, transaction: unconfirmedTransaction, type: .signThenSend, server: browserViewController.server)
case .sendTransaction(let unconfirmedTransaction):
executeTransaction(account: account, action: action, callbackID: callbackID, transaction: unconfirmedTransaction, type: .signThenSend, server: browserViewController.server)
case .signMessage(let hexMessage):
let msg = convertMessageToHex(msg: hexMessage)
signMessage(with: .message(Data(hex: msg)), account: account, callbackID: callbackID)
case .signPersonalMessage(let hexMessage):
let msg = convertMessageToHex(msg: hexMessage)
signMessage(with: .personalMessage(Data(hex: msg)), account: account, callbackID: callbackID)
case .signTypedMessage(let typedData):
signMessage(with: .typedMessage(typedData), account: account, callbackID: callbackID)
case .unknown:
break
}
}
//allow the message to be passed in as a pure string, if it is then we convert it to hex
private func convertMessageToHex(msg: String) -> String {
if msg.hasPrefix("0x") {
return msg
} else {
return msg.hex
}
}
func didVisitURL(url: URL, title: String) {
historyStore.record(url: url, title: title)
}
}
extension BrowserCoordinator: SignMessageCoordinatorDelegate {
func didCancel(in coordinator: SignMessageCoordinator) {
coordinator.didComplete = nil
removeCoordinator(coordinator)
}
}
extension BrowserCoordinator: ConfirmCoordinatorDelegate {
func didCancel(in coordinator: ConfirmCoordinator) {
navigationController.dismiss(animated: true, completion: nil)
coordinator.didCompleted = nil
removeCoordinator(coordinator)
}
}
extension BrowserCoordinator: ScanQRCodeCoordinatorDelegate {
func didCancel(in coordinator: ScanQRCodeCoordinator) {
coordinator.navigationController.dismiss(animated: true, completion: nil)
removeCoordinator(coordinator)
}
func didScan(result: String, in coordinator: ScanQRCodeCoordinator) {
coordinator.navigationController.dismiss(animated: true, completion: nil)
removeCoordinator(coordinator)
guard let url = URL(string: result) else {
return
}
openURL(url)
}
}
extension BrowserCoordinator: BookmarkViewControllerDelegate {
func didSelectBookmark(_ bookmark: Bookmark, in viewController: BookmarkViewController) {
guard let url = bookmark.linkURL else {
return
}
openURL(url)
}
}
extension BrowserCoordinator: HistoryViewControllerDelegate {
func didSelect(history: History, in controller: HistoryViewController) {
guard let url = history.URL else {
return
}
openURL(url)
}
}
extension BrowserCoordinator: WKUIDelegate {
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
browserViewController.webView.load(navigationAction.request)
}
return nil
}
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alertController = UIAlertController.alertController(
title: .none,
message: message,
style: .alert,
in: navigationController
)
alertController.addAction(UIAlertAction(title: R.string.localizable.oK(), style: .default, handler: { _ in
completionHandler()
}))
navigationController.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
let alertController = UIAlertController.alertController(
title: .none,
message: message,
style: .alert,
in: navigationController
)
alertController.addAction(UIAlertAction(title: R.string.localizable.oK(), style: .default, handler: { _ in
completionHandler(true)
}))
alertController.addAction(UIAlertAction(title: R.string.localizable.cancel(), style: .default, handler: { _ in
completionHandler(false)
}))
navigationController.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
let alertController = UIAlertController.alertController(
title: .none,
message: prompt,
style: .alert,
in: navigationController
)
alertController.addTextField { (textField) in
textField.text = defaultText
}
alertController.addAction(UIAlertAction(title: R.string.localizable.oK(), style: .default, handler: { _ in
if let text = alertController.textFields?.first?.text {
completionHandler(text)
} else {
completionHandler(defaultText)
}
}))
alertController.addAction(UIAlertAction(title: R.string.localizable.cancel(), style: .default, handler: { _ in
completionHandler(nil)
}))
navigationController.present(alertController, animated: true, completion: nil)
}
}
extension BrowserCoordinator: MasterBrowserViewControllerDelegate {
func didPressAction(_ action: BrowserToolbarAction) {
switch action {
case .view(let viewType):
switch viewType {
case .bookmarks:
break
case .history:
break
case .browser:
break
}
case .qrCode:
presentQRCodeReader()
}
}
}

@ -0,0 +1,574 @@
// Copyright DApps Platform Inc. All rights reserved.
import Foundation
import UIKit
import BigInt
import TrustKeystore
import RealmSwift
import WebKit
protocol DappBrowserCoordinatorDelegate: class {
func didSentTransaction(transaction: SentTransaction, inCoordinator coordinator: DappBrowserCoordinator)
}
final class DappBrowserCoordinator: NSObject, Coordinator {
private let session: WalletSession
private let keystore: Keystore
private var browserNavBar: DappBrowserNavigationBar? {
return navigationController.navigationBar as? DappBrowserNavigationBar
}
private lazy var historyViewController: BrowserHistoryViewController = {
let controller = BrowserHistoryViewController(store: historyStore)
controller.configure(viewModel: HistoriesViewModel(store: historyStore))
controller.delegate = self
return controller
}()
private lazy var browserViewController: BrowserViewController = {
let controller = BrowserViewController(account: session.account, config: session.config, server: server)
controller.delegate = self
controller.webView.uiDelegate = self
return controller
}()
private let sharedRealm: Realm
private lazy var bookmarksStore: BookmarksStore = {
return BookmarksStore(realm: sharedRealm)
}()
private lazy var historyStore: HistoryStore = {
return HistoryStore(realm: sharedRealm)
}()
private lazy var preferences: PreferencesController = {
return PreferencesController()
}()
private var urlParser: BrowserURLParser {
let engine = SearchEngine(rawValue: preferences.get(for: .browserSearchEngine)) ?? .default
return BrowserURLParser(engine: engine)
}
private var server: RPCServer {
return session.config.server
}
private var enableToolbar: Bool = true {
didSet {
navigationController.isToolbarHidden = !enableToolbar
}
}
var coordinators: [Coordinator] = []
let navigationController: NavigationController
lazy var rootViewController: DappsHomeViewController = {
let vc = DappsHomeViewController(bookmarksStore: bookmarksStore)
vc.delegate = self
return vc
}()
weak var delegate: DappBrowserCoordinatorDelegate?
init(
session: WalletSession,
keystore: Keystore,
sharedRealm: Realm
) {
self.navigationController = NavigationController(navigationBarClass: DappBrowserNavigationBar.self, toolbarClass: nil)
self.session = session
self.keystore = keystore
self.sharedRealm = sharedRealm
super.init()
(navigationController.navigationBar as? DappBrowserNavigationBar)?.navigationBarDelegate = self
}
func start() {
navigationController.viewControllers = [rootViewController]
}
@objc func dismiss() {
navigationController.dismiss(animated: true, completion: nil)
}
private func executeTransaction(account: Account, action: DappAction, callbackID: Int, transaction: UnconfirmedTransaction, type: ConfirmType, server: RPCServer) {
let configurator = TransactionConfigurator(
session: session,
account: account,
transaction: transaction
)
let coordinator = ConfirmCoordinator(
session: session,
configurator: configurator,
keystore: keystore,
account: account,
type: type
)
addCoordinator(coordinator)
coordinator.didCompleted = { [weak self] result in
guard let strongSelf = self else { return }
switch result {
case .success(let type):
switch type {
case .signedTransaction(let data):
let callback = DappCallback(id: callbackID, value: .signTransaction(data))
strongSelf.browserViewController.notifyFinish(callbackID: callbackID, value: .success(callback))
//TODO do we need to do this for a pending transaction?
// strongSelf.delegate?.didSentTransaction(transaction: transaction, inCoordinator: strongSelf)
case .sentTransaction(let transaction):
// on send transaction we pass transaction ID only.
let data = Data(hex: transaction.id)
let callback = DappCallback(id: callbackID, value: .sentTransaction(data))
strongSelf.browserViewController.notifyFinish(callbackID: callbackID, value: .success(callback))
strongSelf.delegate?.didSentTransaction(transaction: transaction, inCoordinator: strongSelf)
}
case .failure:
strongSelf.browserViewController.notifyFinish(
callbackID: callbackID,
value: .failure(DAppError.cancelled)
)
}
coordinator.didCompleted = nil
strongSelf.removeCoordinator(coordinator)
strongSelf.navigationController.dismiss(animated: true, completion: nil)
}
coordinator.start()
navigationController.present(coordinator.navigationController, animated: true, completion: nil)
}
func open(url: URL, browserOnly: Bool = false, animated: Bool = true) {
//TODO maybe not the best idea to check like this. Because it will always create the browserViewController twice the first time (or maybe it's ok. Just once)
if navigationController.topViewController != browserViewController {
browserViewController = BrowserViewController(account: session.account, config: session.config, server: server)
browserViewController.delegate = self
browserViewController.webView.uiDelegate = self
pushOntoNavigationController(viewController: browserViewController, animated: animated)
}
browserNavBar?.display(url: url)
if browserOnly {
browserNavBar?.makeBrowserOnly()
}
browserViewController.goTo(url: url)
}
func signMessage(with type: SignMessageType, account: Account, callbackID: Int) {
let coordinator = SignMessageCoordinator(
navigationController: navigationController,
keystore: keystore,
account: account
)
coordinator.didComplete = { [weak self] result in
guard let strongSelf = self else { return }
switch result {
case .success(let data):
let callback: DappCallback
switch type {
case .message:
callback = DappCallback(id: callbackID, value: .signMessage(data))
case .personalMessage:
callback = DappCallback(id: callbackID, value: .signPersonalMessage(data))
case .typedMessage:
callback = DappCallback(id: callbackID, value: .signTypedMessage(data))
}
strongSelf.browserViewController.notifyFinish(callbackID: callbackID, value: .success(callback))
case .failure:
strongSelf.browserViewController.notifyFinish(callbackID: callbackID, value: .failure(DAppError.cancelled))
}
coordinator.didComplete = nil
strongSelf.removeCoordinator(coordinator)
}
coordinator.delegate = self
addCoordinator(coordinator)
coordinator.start(with: type)
}
private func makeMoreAlertSheet(sender: UIView) -> UIAlertController {
let alertController = UIAlertController(
title: nil,
message: nil,
preferredStyle: .actionSheet
)
alertController.popoverPresentationController?.sourceView = sender
alertController.popoverPresentationController?.sourceRect = sender.centerRect
let reloadAction = UIAlertAction(title: R.string.localizable.reload(), style: .default) { [weak self] _ in
self?.browserViewController.reload()
}
let shareAction = UIAlertAction(title: R.string.localizable.share(), style: .default) { [weak self] _ in
self?.share()
}
let cancelAction = UIAlertAction(title: R.string.localizable.cancel(), style: .cancel) { _ in }
let addBookmarkAction = UIAlertAction(title: R.string.localizable.browserAddbookmarkButtonTitle(), style: .default) { [weak self] _ in
self?.addCurrentPageAsBookmark()
}
alertController.addAction(reloadAction)
alertController.addAction(shareAction)
alertController.addAction(addBookmarkAction)
alertController.addAction(cancelAction)
return alertController
}
private func share() {
guard let url = browserViewController.webView.url else { return }
rootViewController.displayLoading()
rootViewController.showShareActivity(from: UIView(), with: [url]) { [weak self] in
self?.rootViewController.hideLoading()
}
}
private func openDappInBrowser(_ dapp: Dapp) {
guard let url = URL(string: dapp.url) else { return }
open(url: url, animated: false)
}
private func openDappInBrowser(_ dapp: Bookmark) {
guard let url = URL(string: dapp.url) else { return }
open(url: url, animated: false)
}
private func showDappSuggestions(forText text: String) {
if let viewController = navigationController.topViewController as? DappsAutoCompletionViewController {
let hasResults = viewController.filter(withText: text)
if !hasResults {
navigationController.popViewController(animated: false)
}
} else {
let viewController = DappsAutoCompletionViewController()
viewController.delegate = self
let hasResults = viewController.filter(withText: text)
if hasResults {
pushOntoNavigationController(viewController: viewController, animated: false)
}
}
}
private func pushOntoNavigationController(viewController: UIViewController, animated: Bool) {
viewController.navigationItem.setHidesBackButton(true, animated: false)
navigationController.pushViewController(viewController, animated: animated)
}
private func deleteDappFromMyDapp(_ dapp: Bookmark) {
bookmarksStore.delete(bookmarks: [dapp])
refreshDapps()
}
//TODO can we animate changes better?
func refreshDapps() {
rootViewController.configure(viewModel: .init(bookmarksStore: bookmarksStore))
for each in navigationController.viewControllers {
guard let vc = each as? MyDappsViewController else { continue }
vc.configure(viewModel: .init(bookmarksStore: bookmarksStore))
}
}
private func addCurrentPageAsBookmark() {
guard let url = browserViewController.webView.url?.absoluteString else { return }
guard let title = browserViewController.webView.title else { return }
let bookmark = Bookmark(url: url, title: title)
bookmarksStore.add(bookmarks: [bookmark])
refreshDapps()
}
}
extension DappBrowserCoordinator: BrowserViewControllerDelegate {
func didCall(action: DappAction, callbackID: Int, inBrowserViewController viewController: BrowserViewController) {
guard case .real(let account) = session.account.type else {
browserViewController.notifyFinish(callbackID: callbackID, value: .failure(DAppError.cancelled))
navigationController.topViewController?.displayError(error: InCoordinatorError.onlyWatchAccount)
return
}
switch action {
case .signTransaction(let unconfirmedTransaction):
executeTransaction(account: account, action: action, callbackID: callbackID, transaction: unconfirmedTransaction, type: .signThenSend, server: browserViewController.server)
case .sendTransaction(let unconfirmedTransaction):
executeTransaction(account: account, action: action, callbackID: callbackID, transaction: unconfirmedTransaction, type: .signThenSend, server: browserViewController.server)
case .signMessage(let hexMessage):
let msg = convertMessageToHex(msg: hexMessage)
signMessage(with: .message(Data(hex: msg)), account: account, callbackID: callbackID)
case .signPersonalMessage(let hexMessage):
let msg = convertMessageToHex(msg: hexMessage)
signMessage(with: .personalMessage(Data(hex: msg)), account: account, callbackID: callbackID)
case .signTypedMessage(let typedData):
signMessage(with: .typedMessage(typedData), account: account, callbackID: callbackID)
case .unknown:
break
}
}
//allow the message to be passed in as a pure string, if it is then we convert it to hex
private func convertMessageToHex(msg: String) -> String {
if msg.hasPrefix("0x") {
return msg
} else {
return msg.hex
}
}
func didVisitURL(url: URL, title: String, inBrowserViewController viewController: BrowserViewController) {
browserNavBar?.display(url: url)
if let mostRecentUrl = historyStore.histories.first?.url, mostRecentUrl == url.absoluteString {
} else {
historyStore.record(url: url, title: title)
}
}
func dismissKeyboard(inBrowserViewController viewController: BrowserViewController) {
browserNavBar?.cancelEditing()
}
func forceUpdate(url: URL, inBrowserViewController viewController: BrowserViewController) {
browserNavBar?.display(url: url)
}
}
extension DappBrowserCoordinator: SignMessageCoordinatorDelegate {
func didCancel(in coordinator: SignMessageCoordinator) {
coordinator.didComplete = nil
removeCoordinator(coordinator)
}
}
extension DappBrowserCoordinator: ConfirmCoordinatorDelegate {
func didCancel(in coordinator: ConfirmCoordinator) {
navigationController.dismiss(animated: true, completion: nil)
coordinator.didCompleted = nil
removeCoordinator(coordinator)
}
}
extension DappBrowserCoordinator: BrowserHistoryViewControllerDelegate {
func didSelect(history: History, inViewController controller: BrowserHistoryViewController) {
guard let url = history.URL else { return }
open(url: url)
}
func clearHistory(inViewController viewController: BrowserHistoryViewController) {
historyStore.clearAll()
viewController.configure(viewModel: HistoriesViewModel(store: historyStore))
}
func dismissKeyboard(inViewController viewController: BrowserHistoryViewController) {
browserNavBar?.cancelEditing()
}
}
extension DappBrowserCoordinator: WKUIDelegate {
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
browserViewController.webView.load(navigationAction.request)
}
return nil
}
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alertController = UIAlertController.alertController(
title: .none,
message: message,
style: .alert,
in: navigationController
)
alertController.addAction(UIAlertAction(title: R.string.localizable.oK(), style: .default, handler: { _ in
completionHandler()
}))
navigationController.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
let alertController = UIAlertController.alertController(
title: .none,
message: message,
style: .alert,
in: navigationController
)
alertController.addAction(UIAlertAction(title: R.string.localizable.oK(), style: .default, handler: { _ in
completionHandler(true)
}))
alertController.addAction(UIAlertAction(title: R.string.localizable.cancel(), style: .default, handler: { _ in
completionHandler(false)
}))
navigationController.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
let alertController = UIAlertController.alertController(
title: .none,
message: prompt,
style: .alert,
in: navigationController
)
alertController.addTextField { (textField) in
textField.text = defaultText
}
alertController.addAction(UIAlertAction(title: R.string.localizable.oK(), style: .default, handler: { _ in
if let text = alertController.textFields?.first?.text {
completionHandler(text)
} else {
completionHandler(defaultText)
}
}))
alertController.addAction(UIAlertAction(title: R.string.localizable.cancel(), style: .default, handler: { _ in
completionHandler(nil)
}))
navigationController.present(alertController, animated: true, completion: nil)
}
}
extension DappBrowserCoordinator: DappsHomeViewControllerDelegate {
func didTapShowMyDappsViewController(inViewController viewController: DappsHomeViewController) {
let viewController = MyDappsViewController(bookmarksStore: bookmarksStore)
viewController.configure(viewModel: .init(bookmarksStore: bookmarksStore))
viewController.delegate = self
pushOntoNavigationController(viewController: viewController, animated: true)
}
func didTapShowBrowserHistoryViewController(inViewController viewController: DappsHomeViewController) {
pushOntoNavigationController(viewController: historyViewController, animated: true)
}
func didTapShowDiscoverDappsViewController(inViewController viewController: DappsHomeViewController) {
let viewController = DiscoverDappsViewController(bookmarksStore: bookmarksStore)
viewController.configure(viewModel: .init())
viewController.delegate = self
pushOntoNavigationController(viewController: viewController, animated: true)
}
func didTap(dapp: Bookmark, inViewController viewController: DappsHomeViewController) {
openDappInBrowser(dapp)
}
func delete(dapp: Bookmark, inViewController viewController: DappsHomeViewController) {
deleteDappFromMyDapp(dapp)
}
func viewControllerWillAppear(_ viewController: DappsHomeViewController) {
browserNavBar?.enableButtons()
}
func dismissKeyboard(inViewController viewController: DappsHomeViewController) {
browserNavBar?.cancelEditing()
}
}
extension DappBrowserCoordinator: DiscoverDappsViewControllerDelegate {
func didTap(dapp: Dapp, inViewController viewController: DiscoverDappsViewController) {
openDappInBrowser(dapp)
}
func didAdd(dapp: Dapp, inViewController viewController: DiscoverDappsViewController) {
refreshDapps()
}
func didRemove(dapp: Dapp, inViewController viewController: DiscoverDappsViewController) {
refreshDapps()
}
func dismissKeyboard(inViewController viewController: DiscoverDappsViewController) {
browserNavBar?.cancelEditing()
}
}
extension DappBrowserCoordinator: MyDappsViewControllerDelegate {
func didTapToEdit(dapp: Bookmark, inViewController viewController: MyDappsViewController) {
let vc = EditMyDappViewController()
vc.delegate = self
vc.configure(viewModel: .init(dapp: dapp))
vc.hidesBottomBarWhenPushed = true
navigationController.present(vc, animated: true)
}
func didTapToSelect(dapp: Bookmark, inViewController viewController: MyDappsViewController) {
openDappInBrowser(dapp)
}
func delete(dapp: Bookmark, inViewController viewController: MyDappsViewController) {
deleteDappFromMyDapp(dapp)
viewController.configure(viewModel: .init(bookmarksStore: bookmarksStore))
}
func dismissKeyboard(inViewController viewController: MyDappsViewController) {
browserNavBar?.cancelEditing()
}
}
extension DappBrowserCoordinator: DappsAutoCompletionViewControllerDelegate {
func didTap(dapp: Dapp, inViewController viewController: DappsAutoCompletionViewController) {
openDappInBrowser(dapp)
}
func dismissKeyboard(inViewController viewController: DappsAutoCompletionViewController) {
browserNavBar?.cancelEditing()
}
}
extension DappBrowserCoordinator: DappBrowserNavigationBarDelegate {
func didTapBack(inNavigationBar navigationBar: DappBrowserNavigationBar) {
if let browserVC = navigationController.topViewController as? BrowserViewController, browserVC.webView.canGoBack {
browserViewController.webView.goBack()
} else if !(browserNavBar?.isBrowserOnly ?? false) {
navigationController.popViewController(animated: true)
if let viewController = navigationController.topViewController as? DappsAutoCompletionViewController {
browserNavBar?.display(string: viewController.text)
} else if navigationController.topViewController is DappsHomeViewController {
browserNavBar?.clearDisplay()
}
}
}
func didTapForward(inNavigationBar navigationBar: DappBrowserNavigationBar) {
guard let browserVC = navigationController.topViewController as? BrowserViewController, browserVC.webView.canGoForward else { return }
browserViewController.webView.goForward()
}
func didTapMore(sender: UIView, inNavigationBar navigationBar: DappBrowserNavigationBar) {
let alertController = makeMoreAlertSheet(sender: sender)
navigationController.present(alertController, animated: true, completion: nil)
}
func didTapClose(inNavigationBar navigationBar: DappBrowserNavigationBar) {
dismiss()
}
func didTapHome(inNavigationBar navigationBar: DappBrowserNavigationBar) {
navigationController.popToRootViewController(animated: true)
browserNavBar?.clearDisplay()
}
func didTyped(text: String, inNavigationBar navigationBar: DappBrowserNavigationBar) {
let text = text.trimmed
if text.isEmpty {
if navigationController.topViewController as? DappsAutoCompletionViewController != nil {
navigationController.popViewController(animated: false)
}
} else {
showDappSuggestions(forText: text)
}
}
func didEnter(text: String, inNavigationBar navigationBar: DappBrowserNavigationBar) {
guard let url = urlParser.url(from: text) else { return }
open(url: url, animated: false)
}
}
extension DappBrowserCoordinator: EditMyDappViewControllerDelegate {
func didTapSave(dapp: Bookmark, withTitle title: String, url: String, inViewController viewController: EditMyDappViewController) {
try? sharedRealm.write {
dapp.title = title
dapp.url = url
}
viewController.dismiss(animated: true)
refreshDapps()
}
func didTapCancel(inViewController viewController: EditMyDappViewController) {
viewController.dismiss(animated: true)
}
}

@ -8,20 +8,47 @@ final class BookmarksStore {
return realm.objects(Bookmark.self)
.sorted(byKeyPath: "createdAt", ascending: false)
}
let realm: Realm
init(
realm: Realm
) {
private let realm: Realm
init(realm: Realm) {
self.realm = realm
}
private func findOriginalBookmarks(matchingBookmarks bookmarksToFind: [Bookmark]) -> [Bookmark] {
var originals = [Bookmark]()
for toDelete in bookmarksToFind {
var found = false
for original in bookmarks {
if original.id == toDelete.id {
originals.append(original)
found = true
break
}
}
if !found {
for original in bookmarks {
if original.url == toDelete.url {
originals.append(original)
break
}
}
}
}
return originals
}
func add(bookmarks: [Bookmark]) {
realm.beginWrite()
realm.add(bookmarks, update: true)
try! realm.commitWrite()
}
func delete(bookmarks: [Bookmark]) {
func delete(bookmarks bookmarksToDelete: [Bookmark]) {
//We may not receive the original Bookmark object(s), hence the lookup
let originalsToDelete = findOriginalBookmarks(matchingBookmarks: bookmarksToDelete)
realm.beginWrite()
realm.delete(bookmarks)
realm.delete(originalsToDelete)
try! realm.commitWrite()
}
}

@ -1,13 +0,0 @@
// Copyright DApps Platform Inc. All rights reserved.
import Foundation
import UIKit
enum BrowserNavigation {
case goBack
case more(sender: UIView)
case close
case home
case enter(String)
case beginEditing
}

@ -4,9 +4,7 @@ import Foundation
struct Favicon {
static func get(for url: URL?) -> URL? {
guard let host = url?.host else {
return .none
}
guard let host = url?.host else { return nil }
return URL(string: "https://api.faviconkit.com/\(host)/64")
}
}

@ -1,114 +0,0 @@
// Copyright DApps Platform Inc. All rights reserved.
import Foundation
import UIKit
import StatefulViewController
protocol BookmarkViewControllerDelegate: class {
func didSelectBookmark(_ bookmark: Bookmark, in viewController: BookmarkViewController)
}
final class BookmarkViewController: UIViewController {
private let tableView = UITableView(frame: .zero, style: .plain)
private let bookmarksStore: BookmarksStore
weak var delegate: BookmarkViewControllerDelegate?
lazy var viewModel: BookmarksViewModel = {
return BookmarksViewModel(bookmarksStore: bookmarksStore)
}()
init(bookmarksStore: BookmarksStore) {
self.bookmarksStore = bookmarksStore
super.init(nibName: nil, bundle: nil)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .singleLine
tableView.rowHeight = 60
tableView.register(R.nib.bookmarkViewCell(), forCellReuseIdentifier: R.nib.bookmarkViewCell.name)
view.addSubview(tableView)
emptyView = EmptyView(title: R.string.localizable.browserNoBookmarksLabelTitle())
NSLayoutConstraint.activate([
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupInitialViewState()
fetch()
}
func fetch() {
tableView.reloadData()
}
func confirmDelete(bookmark: Bookmark, index: IndexPath) {
confirm(title: R.string.localizable.browserBookmarksConfirmDeleteTitle(),
okTitle: R.string.localizable.delete(),
okStyle: .destructive) { [weak self] result in
guard let strongSelf = self else { return }
switch result {
case .success:
strongSelf.delete(bookmark: bookmark, index: index)
case .failure: break
}
}
}
func delete(bookmark: Bookmark, index: IndexPath) {
viewModel.delete(bookmark: bookmark)
tableView.deleteRows(at: [index], with: .automatic)
transitionViewStates()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension BookmarkViewController: StatefulViewController {
func hasContent() -> Bool {
return viewModel.hasBookmarks
}
}
extension BookmarkViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.numberOfRows
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: R.nib.bookmarkViewCell.name, for: indexPath) as! BookmarkViewCell
cell.viewModel = BookmarkViewModel(bookmark: viewModel.bookmark(for: indexPath))
return cell
}
}
extension BookmarkViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let bookmark = viewModel.bookmark(for: indexPath)
confirmDelete(bookmark: bookmark, index: indexPath)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let bookmark = viewModel.bookmark(for: indexPath)
delegate?.didSelectBookmark(bookmark, in: self)
}
}

@ -0,0 +1,171 @@
//
// Created by James Sangalli on 8/12/18.
//
import Foundation
import UIKit
import StatefulViewController
protocol BrowserHistoryViewControllerDelegate: class {
func didSelect(history: History, inViewController controller: BrowserHistoryViewController)
func clearHistory(inViewController viewController: BrowserHistoryViewController)
func dismissKeyboard(inViewController viewController: BrowserHistoryViewController)
}
final class BrowserHistoryViewController: UIViewController {
private let store: HistoryStore
private let tableView = UITableView(frame: .zero, style: .plain)
private var viewModel: HistoriesViewModel
lazy private var headerView = BrowserHistoryViewControllerHeaderView()
weak var delegate: BrowserHistoryViewControllerDelegate?
init(store: HistoryStore) {
self.store = store
self.viewModel = HistoriesViewModel(store: store)
super.init(nibName: nil, bundle: nil)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
tableView.tableHeaderView = headerView
tableView.separatorStyle = .none
tableView.register(BrowserHistoryCell.self, forCellReuseIdentifier: BrowserHistoryCell.identifier)
view.addSubview(tableView)
emptyView = {
let emptyView = DappsHomeEmptyView()
let headerViewModel = DappsHomeHeaderViewViewModel(title: R.string.localizable.dappBrowserBrowserHistory())
emptyView.configure(viewModel: .init(headerViewViewModel: headerViewModel, title: R.string.localizable.browserNoHistoryLabelTitle()))
return emptyView
}()
NSLayoutConstraint.activate([
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func keyboardWillShow(notification: NSNotification) {
if let keyboardEndFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardBeginFrame = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardEndFrame.size.height
tableView.contentInset.bottom = keyboardEndFrame.size.height
}
}
@objc private func keyboardWillHide(notification: NSNotification) {
if let keyboardEndFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardBeginFrame = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
tableView.contentInset.bottom = 0
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupInitialViewState()
fetch()
}
func fetch() {
tableView.reloadData()
}
func configure(viewModel: HistoriesViewModel) {
tableView.backgroundColor = Colors.appWhite
resizeTableViewHeader()
tableView.reloadData()
endLoading()
}
private func resizeTableViewHeader() {
let headerViewModel = DappsHomeHeaderViewViewModel(title: R.string.localizable.dappBrowserBrowserHistory())
headerView.delegate = self
headerView.configure(viewModel: headerViewModel)
let fittingSize = headerView.systemLayoutSizeFitting(.init(width: tableView.frame.size.width, height: 1000))
headerView.frame = .init(x: 0, y: 0, width: 0, height: fittingSize.height)
tableView.tableHeaderView = headerView
}
}
extension BrowserHistoryViewController: StatefulViewController {
func hasContent() -> Bool {
return viewModel.hasContent
}
}
extension BrowserHistoryViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.numberOfRows
}
func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.numberOfSections
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: BrowserHistoryCell.identifier, for: indexPath) as! BrowserHistoryCell
cell.configure(viewModel: .init(history: viewModel.item(for: indexPath)))
return cell
}
}
extension BrowserHistoryViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
delegate?.dismissKeyboard(inViewController: self)
let history = viewModel.item(for: indexPath)
delegate?.didSelect(history: history, inViewController: self)
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let history = viewModel.item(for: indexPath)
confirm(
title: R.string.localizable.browserHistoryConfirmDeleteTitle(),
message: history.url,
okTitle: R.string.localizable.removeButtonTitle(),
okStyle: .destructive
) { [weak self] result in
switch result {
case .success:
self?.store.delete(histories: [history])
//TODO improve animation
self?.tableView.reloadData()
self?.endLoading()
case .failure: break
}
}
}
}
}
extension BrowserHistoryViewController: BrowserHistoryViewControllerHeaderViewDelegate {
func didTapClearAll(inHeaderView headerView: BrowserHistoryViewControllerHeaderView) {
UIAlertController.alert(
title: R.string.localizable.dappBrowserClearHistory(),
message: R.string.localizable.dappBrowserClearHistoryPrompt(),
alertButtonTitles: [R.string.localizable.clearButtonTitle(), R.string.localizable.cancel()],
alertButtonStyles: [.destructive, .cancel],
viewController: self,
completion: { [weak self] buttonIndex in
guard let strongSelf = self else { return }
if buttonIndex == 0 {
strongSelf.delegate?.clearHistory(inViewController: strongSelf)
}
})
}
}

@ -6,19 +6,11 @@ import WebKit
import JavaScriptCore
import Result
enum BrowserAction {
case history
case addBookmark(bookmark: Bookmark)
case bookmarks
case qrCode
case changeURL(URL)
case navigationAction(BrowserNavigation)
}
protocol BrowserViewControllerDelegate: class {
func didCall(action: DappAction, callbackID: Int)
func runAction(action: BrowserAction)
func didVisitURL(url: URL, title: String)
func didCall(action: DappAction, callbackID: Int, inBrowserViewController viewController: BrowserViewController)
func didVisitURL(url: URL, title: String, inBrowserViewController viewController: BrowserViewController)
func dismissKeyboard(inBrowserViewController viewController: BrowserViewController)
func forceUpdate(url: URL, inBrowserViewController viewController: BrowserViewController)
}
final class BrowserViewController: UIViewController {
@ -44,10 +36,6 @@ final class BrowserViewController: UIViewController {
return errorView
}()
private var browserNavBar: BrowserNavigationBar? {
return navigationController?.navigationBar as? BrowserNavigationBar
}
weak var delegate: BrowserViewControllerDelegate?
lazy var webView: WKWebView = {
@ -80,8 +68,6 @@ final class BrowserViewController: UIViewController {
let server: RPCServer
var isCloseButtonVisibilityConfigured = false
init(
account: Wallet,
config: Config,
@ -118,18 +104,28 @@ final class BrowserViewController: UIViewController {
])
view.backgroundColor = .white
webView.addObserver(self, forKeyPath: Keys.estimatedProgress, options: .new, context: &myContext)
webView.addObserver(self, forKeyPath: Keys.URL, options: [.new, .initial], context: &myContext)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
@objc private func keyboardWillShow(notification: NSNotification) {
if let keyboardEndFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardBeginFrame = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardEndFrame.size.height
webView.scrollView.contentInset.bottom = keyboardEndFrame.size.height
}
}
browserNavBar?.browserDelegate = self
refreshURL()
@objc private func keyboardWillHide(notification: NSNotification) {
if let keyboardEndFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardBeginFrame = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
webView.scrollView.contentInset.bottom = 0
//Must exit editing more explicitly (and update the nav bar buttons) because tapping on the web view can hide keyboard
delegate?.dismissKeyboard(inBrowserViewController: self)
}
}
private func injectUserAgent() {
@ -140,6 +136,7 @@ final class BrowserViewController: UIViewController {
}
func goTo(url: URL) {
hideErrorView()
webView.load(URLRequest(url: url))
}
@ -155,15 +152,6 @@ final class BrowserViewController: UIViewController {
webView.evaluateJavaScript(script, completionHandler: nil)
}
func goHome() {
guard let url = URL(string: Constants.dappsBrowserURL) else { return }
var request = URLRequest(url: url)
request.cachePolicy = .returnCacheDataElseLoad
hideErrorView()
webView.load(request)
browserNavBar?.textField.text = url.absoluteString
}
func reload() {
hideErrorView()
webView.reload()
@ -173,22 +161,9 @@ final class BrowserViewController: UIViewController {
webView.stopLoading()
}
private func refreshURL() {
browserNavBar?.textField.text = webView.url?.absoluteString
browserNavBar?.backButton.isHidden = !webView.canGoBack
}
private func recordURL() {
guard let url = webView.url else {
return
}
delegate?.didVisitURL(url: url, title: webView.title ?? "")
}
private func changeURL(_ url: URL) {
delegate?.runAction(action: .changeURL(url))
refreshURL()
guard let url = webView.url else { return }
delegate?.didVisitURL(url: url, title: webView.title ?? "", inBrowserViewController: self)
}
private func hideErrorView() {
@ -206,31 +181,11 @@ final class BrowserViewController: UIViewController {
progressView.progress = progress
progressView.isHidden = progress == 1
}
} else if keyPath == Keys.URL {
if let url = webView.url {
browserNavBar?.textField.text = url.absoluteString
changeURL(url)
}
}
}
deinit {
webView.removeObserver(self, forKeyPath: Keys.estimatedProgress)
webView.removeObserver(self, forKeyPath: Keys.URL)
}
func addBookmark() {
guard let url = webView.url?.absoluteString else { return }
guard let title = webView.title else { return }
delegate?.runAction(action: .addBookmark(bookmark: Bookmark(url: url, title: title)))
}
@objc private func showBookmarks() {
delegate?.runAction(action: .bookmarks)
}
@objc private func history() {
delegate?.runAction(action: .history)
}
func handleError(error: Error) {
@ -239,45 +194,17 @@ final class BrowserViewController: UIViewController {
} else {
if error.domain == NSURLErrorDomain,
let failedURL = (error as NSError).userInfo[NSURLErrorFailingURLErrorKey] as? URL {
changeURL(failedURL)
delegate?.forceUpdate(url: failedURL, inBrowserViewController: self)
}
errorView.show(error: error)
}
}
/// This function is expected to be called at most once (hence "setup"), subsequent calls will be ignored. This is important because it's designed to be called from a viewWillAppear(), checking if the browser is being presented and we decide if we want to show the close button then. It shouldn't be changed, e.g. when we present another view controller over the browser and close it (this time the browser wouldn't be presented anymore and hiding the close button will be wrong)
func setUpCloseButtonAs(hidden: Bool) {
guard !isCloseButtonVisibilityConfigured else { return }
isCloseButtonVisibilityConfigured = true
browserNavBar?.closeButton.isHidden = hidden
}
}
extension BrowserViewController: BrowserNavigationBarDelegate {
func did(action: BrowserNavigation) {
delegate?.runAction(action: .navigationAction(action))
switch action {
case .goBack:
break
case .more:
break
case .close:
stopLoading()
case .home:
break
case .enter:
break
case .beginEditing:
stopLoading()
}
}
}
extension BrowserViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
recordURL()
hideErrorView()
refreshURL()
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
@ -301,7 +228,7 @@ extension BrowserViewController: WKScriptMessageHandler {
let transfer = Transfer(server: server, type: .dapp(token, requester))
let action = DappAction.fromCommand(command, transfer: transfer)
delegate?.didCall(action: action, callbackID: command.id)
delegate?.didCall(action: action, callbackID: command.id, inBrowserViewController: self)
}
}

@ -0,0 +1,85 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
protocol DappsAutoCompletionViewControllerDelegate: class {
func didTap(dapp: Dapp, inViewController viewController: DappsAutoCompletionViewController)
func dismissKeyboard(inViewController viewController: DappsAutoCompletionViewController)
}
class DappsAutoCompletionViewController: UIViewController {
private var viewModel: DappsAutoCompletionViewControllerViewModel
private let tableView = UITableView(frame: .zero, style: .plain)
weak var delegate: DappsAutoCompletionViewControllerDelegate?
var text: String {
return viewModel.keyword
}
init() {
self.viewModel = .init()
super.init(nibName: nil, bundle: nil)
tableView.register(DappsAutoCompletionCell.self, forCellReuseIdentifier: DappsAutoCompletionCell.identifier)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
tableView.backgroundColor = Colors.appBackground
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func keyboardWillShow(notification: NSNotification) {
if let keyboardEndFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardBeginFrame = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardEndFrame.size.height
tableView.contentInset.bottom = keyboardEndFrame.size.height
}
}
@objc private func keyboardWillHide(notification: NSNotification) {
if let keyboardEndFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardBeginFrame = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
tableView.contentInset.bottom = 0
}
}
func filter(withText text: String) -> Bool {
viewModel.keyword = text
tableView.reloadData()
return viewModel.dappSuggestionsCount > 0
}
}
extension DappsAutoCompletionViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: DappsAutoCompletionCell.identifier, for: indexPath) as! DappsAutoCompletionCell
let dapp = viewModel.dappSuggestions[indexPath.row]
cell.configure(viewModel: .init(dapp: dapp, keyword: viewModel.keyword))
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.dappSuggestionsCount
}
}
extension DappsAutoCompletionViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
delegate?.dismissKeyboard(inViewController: self)
let dapp = viewModel.dappSuggestions[indexPath.row]
delegate?.didTap(dapp: dapp, inViewController: self)
}
}

@ -0,0 +1,192 @@
//
// Created by James Sangalli on 8/12/18.
//
import Foundation
import UIKit
protocol DappsHomeViewControllerDelegate: class {
func didTapShowMyDappsViewController(inViewController viewController: DappsHomeViewController)
func didTapShowBrowserHistoryViewController(inViewController viewController: DappsHomeViewController)
func didTapShowDiscoverDappsViewController(inViewController viewController: DappsHomeViewController)
func didTap(dapp: Bookmark, inViewController viewController: DappsHomeViewController)
func delete(dapp: Bookmark, inViewController viewController: DappsHomeViewController)
func viewControllerWillAppear(_ viewController: DappsHomeViewController)
func dismissKeyboard(inViewController viewController: DappsHomeViewController)
}
class DappsHomeViewController: UIViewController {
private static let headerIdentifier = "header"
private var isEditingDapps = false {
didSet {
dismissKeyboard()
if isEditingDapps {
if !oldValue {
let vibration = UIImpactFeedbackGenerator()
vibration.prepare()
vibration.impactOccurred()
}
guard timerToCheckIfStillEditing == nil else { return }
timerToCheckIfStillEditing = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] timer in
guard let strongSelf = self else { return }
if strongSelf.isTopViewController {
} else {
strongSelf.isEditingDapps = false
}
}
} else {
timerToCheckIfStillEditing?.invalidate()
timerToCheckIfStillEditing = nil
}
dappsCollectionView.reloadData()
}
}
private var timerToCheckIfStillEditing: Timer?
private var viewModel: DappsHomeViewControllerViewModel
lazy private var dappsCollectionView = { () -> UICollectionView in
let layout = UICollectionViewFlowLayout()
let fixedGutter = CGFloat(24)
let availableWidth = UIScreen.main.bounds.size.width - (2 * fixedGutter)
let numberOfColumns: CGFloat
if ScreenChecker().isBigScreen() {
numberOfColumns = 6
} else {
numberOfColumns = 3
}
let dimension = (availableWidth / numberOfColumns).rounded(.down)
//Using a sizing cell doesn't get the same reason after we change network. Resorting to hardcoding the width and height difference
let itemSize = CGSize(width: dimension, height: dimension + 30)
let additionalGutter = (availableWidth - itemSize.width * numberOfColumns) / (numberOfColumns + 1)
layout.itemSize = itemSize
layout.minimumInteritemSpacing = 0
layout.sectionInset = .init(top: 0, left: additionalGutter + fixedGutter, bottom: 0, right: additionalGutter + fixedGutter)
return UICollectionView(frame: .zero, collectionViewLayout: layout)
}()
private let bookmarksStore: BookmarksStore
weak var delegate: DappsHomeViewControllerDelegate?
init(bookmarksStore: BookmarksStore) {
self.bookmarksStore = bookmarksStore
self.viewModel = .init(bookmarksStore: bookmarksStore)
super.init(nibName: nil, bundle: nil)
dappsCollectionView.translatesAutoresizingMaskIntoConstraints = false
dappsCollectionView.alwaysBounceVertical = true
dappsCollectionView.register(DappsHomeViewControllerHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: DappsHomeViewController.headerIdentifier)
dappsCollectionView.register(DappViewCell.self, forCellWithReuseIdentifier: DappViewCell.identifier)
dappsCollectionView.dataSource = self
dappsCollectionView.delegate = self
view.addSubview(dappsCollectionView)
NSLayoutConstraint.activate([
dappsCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
dappsCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
dappsCollectionView.topAnchor.constraint(equalTo: view.topAnchor),
dappsCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
configure(viewModel: viewModel)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
delegate?.viewControllerWillAppear(self)
}
@objc private func keyboardWillShow(notification: NSNotification) {
if let keyboardEndFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardBeginFrame = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardEndFrame.size.height
dappsCollectionView.contentInset.bottom = keyboardEndFrame.size.height
}
}
@objc private func keyboardWillHide(notification: NSNotification) {
if let keyboardEndFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardBeginFrame = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
dappsCollectionView.contentInset.bottom = 0
}
}
func configure(viewModel: DappsHomeViewControllerViewModel) {
self.viewModel = viewModel
view.backgroundColor = viewModel.backgroundColor
dappsCollectionView.backgroundColor = viewModel.backgroundColor
dappsCollectionView.reloadData()
}
@objc private func showMyDappsViewController() {
dismissKeyboard()
delegate?.didTapShowMyDappsViewController(inViewController: self)
}
@objc private func showBrowserHistoryViewController() {
dismissKeyboard()
delegate?.didTapShowBrowserHistoryViewController(inViewController: self)
}
@objc private func showDiscoverPageViewController() {
dismissKeyboard()
delegate?.didTapShowDiscoverDappsViewController(inViewController: self)
}
private func dismissKeyboard() {
delegate?.dismissKeyboard(inViewController: self)
}
}
extension DappsHomeViewController: UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModel.dappsCount
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let dapp = viewModel.dapp(atIndex: indexPath.item)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DappViewCell.identifier, for: indexPath) as! DappViewCell
cell.delegate = self
cell.configure(viewModel: .init(dapp: dapp))
cell.isEditing = isEditingDapps
return cell
}
public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: DappsHomeViewController.headerIdentifier, for: indexPath) as! DappsHomeViewControllerHeaderView
headerView.configure()
headerView.myDappsButton.addTarget(self, action: #selector(showMyDappsViewController), for: .touchUpInside)
headerView.discoverDappsButton.addTarget(self, action: #selector(showDiscoverPageViewController), for: .touchUpInside)
headerView.historyButton.addTarget(self, action: #selector(showBrowserHistoryViewController), for: .touchUpInside)
return headerView
}
}
extension DappsHomeViewController: UICollectionViewDelegateFlowLayout {
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
dappsCollectionView.deselectItem(at: indexPath, animated: true)
dismissKeyboard()
let dapp = viewModel.dapp(atIndex: indexPath.item)
delegate?.didTap(dapp: dapp, inViewController: self)
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let headerView = DappsHomeViewControllerHeaderView()
headerView.configure()
let size = headerView.systemLayoutSizeFitting(.init(width: collectionView.frame.size.width, height: 1000))
return size
}
}
extension DappsHomeViewController: DappViewCellDelegate {
func didTapDelete(dapp: Bookmark, inCell cell: DappViewCell) {
delegate?.delete(dapp: dapp, inViewController: self)
}
func didLongPressed(dapp: Bookmark, onCell cell: DappViewCell) {
isEditingDapps = true
}
}

@ -0,0 +1,174 @@
//
// Created by James Sangalli on 8/12/18.
//
import Foundation
import UIKit
protocol DiscoverDappsViewControllerDelegate: class {
func didTap(dapp: Dapp, inViewController viewController: DiscoverDappsViewController)
func didAdd(dapp: Dapp, inViewController viewController: DiscoverDappsViewController)
func didRemove(dapp: Dapp, inViewController viewController: DiscoverDappsViewController)
func dismissKeyboard(inViewController viewController: DiscoverDappsViewController)
}
class DiscoverDappsViewController: UIViewController {
lazy private var headerBoxView = BoxView(view: DappsHomeHeaderView())
private let tableView = UITableView(frame: .zero, style: .plain)
private var viewModel = DiscoverDappsViewControllerViewModel()
private var bookmarksStore: BookmarksStore
weak var delegate: DiscoverDappsViewControllerDelegate?
init(bookmarksStore: BookmarksStore) {
self.bookmarksStore = bookmarksStore
super.init(nibName: nil, bundle: nil)
tableView.register(DiscoverDappCell.self, forCellReuseIdentifier: DiscoverDappCell.identifier)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.tableHeaderView = headerBoxView
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func keyboardWillShow(notification: NSNotification) {
if let keyboardEndFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardBeginFrame = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardEndFrame.size.height
tableView.contentInset.bottom = keyboardEndFrame.size.height
}
}
@objc private func keyboardWillHide(notification: NSNotification) {
if let keyboardEndFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardBeginFrame = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
tableView.contentInset.bottom = 0
}
}
func configure(viewModel: DiscoverDappsViewControllerViewModel) {
self.viewModel = viewModel
tableView.backgroundColor = viewModel.backgroundColor
resizeTableViewHeader()
tableView.reloadData()
}
private func resizeTableViewHeader() {
let headerViewModel = DappsHomeHeaderViewViewModel(title: R.string.localizable.discoverDappsButtonImageLabel())
headerBoxView.view.configure(viewModel: headerViewModel)
headerBoxView.backgroundColor = headerViewModel.backgroundColor
headerBoxView.insets = .init(top: 50, left: 0, bottom: 50, right: 0)
let fittingSize = headerBoxView.systemLayoutSizeFitting(.init(width: tableView.frame.size.width, height: 1000))
headerBoxView.frame = .init(x: 0, y: 0, width: 0, height: fittingSize.height)
tableView.tableHeaderView = headerBoxView
}
private func dismissKeyboard() {
delegate?.dismissKeyboard(inViewController: self)
}
}
extension DiscoverDappsViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.dappCategories.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: DiscoverDappCell.identifier, for: indexPath) as! DiscoverDappCell
let dapp = viewModel.dappCategories[indexPath.section].dapps[indexPath.row]
cell.configure(viewModel: .init(bookmarksStore: bookmarksStore, dapp: dapp))
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.dappCategories[section].dapps.count
}
}
extension DiscoverDappsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
dismissKeyboard()
let dapp = viewModel.dappCategories[indexPath.section].dapps[indexPath.row]
delegate?.didTap(dapp: dapp, inViewController: self)
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let title = viewModel.dappCategories[section].name
return SectionHeaderView(title: title)
}
}
extension DiscoverDappsViewController: DiscoverDappCellDelegate {
func onAdd(dapp: Dapp, inCell cell: DiscoverDappCell) {
bookmarksStore.add(bookmarks: [.init(url: dapp.url, title: dapp.name)])
tableView.reloadData()
delegate?.didAdd(dapp: dapp, inViewController: self)
}
func onRemove(dapp: Dapp, inCell cell: DiscoverDappCell) {
bookmarksStore.delete(bookmarks: [.init(url: dapp.url, title: dapp.name)])
tableView.reloadData()
delegate?.didRemove(dapp: dapp, inViewController: self)
}
}
fileprivate class SectionHeaderView: UIView {
private let label = UILabel()
var title: String {
didSet {
update(title: title)
}
}
init(title: String) {
self.title = title
super.init(frame: .zero)
update(title: title)
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 31),
label.trailingAnchor.constraint(equalTo: trailingAnchor),
label.topAnchor.constraint(equalTo: topAnchor, constant: 12),
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10),
])
configure()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure() {
backgroundColor = Colors.appWhite
label.textColor = UIColor(red: 77, green: 77, blue: 77)
label.font = Fonts.regular(size: 10)
}
private func update(title: String) {
label.text = title.localizedUppercase
}
}

@ -0,0 +1,207 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
protocol EditMyDappViewControllerDelegate: class {
func didTapSave(dapp: Bookmark, withTitle title: String, url: String, inViewController viewController: EditMyDappViewController)
func didTapCancel(inViewController viewController: EditMyDappViewController)
}
class EditMyDappViewController: UIViewController {
private let roundedBackground = RoundedBackground()
private let screenTitleLabel = UILabel()
private let iconImageView = UIImageView()
//Holder to show the shadow around the image because the UIImageView is clipsToBounds=true
private let imageHolder = UIView()
private let titleLabel = UILabel()
private let titleTextField = UITextField()
private let urlLabel = UILabel()
private let urlTextField = UITextField()
private let cancelButton = UIButton(type: .system)
private let saveButton = UIButton(type: .system)
private var viewModel: EditMyDappViewControllerViewModel?
weak var delegate: EditMyDappViewControllerDelegate?
init() {
super.init(nibName: nil, bundle: nil)
roundedBackground.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(roundedBackground)
iconImageView.translatesAutoresizingMaskIntoConstraints = false
imageHolder.addSubview(iconImageView)
titleTextField.delegate = self
urlTextField.delegate = self
let stackView = [
UIView.spacer(height: 34),
screenTitleLabel,
UIView.spacer(height: 28),
imageHolder,
UIView.spacer(height: 28),
titleLabel,
UIView.spacer(height: 7),
titleTextField,
UIView.spacer(height: 18),
urlLabel,
UIView.spacer(height: 7),
urlTextField
].asStackView(axis: .vertical, alignment: .center)
stackView.translatesAutoresizingMaskIntoConstraints = false
roundedBackground.addSubview(stackView)
let footerBar = UIView()
footerBar.translatesAutoresizingMaskIntoConstraints = false
roundedBackground.addSubview(footerBar)
let buttonsHeight = Metrics.greenButtonHeight
saveButton.translatesAutoresizingMaskIntoConstraints = false
saveButton.setContentCompressionResistancePriority(.required, for: .horizontal)
saveButton.setContentHuggingPriority(.defaultLow, for: .horizontal)
saveButton.addTarget(self, action: #selector(save), for: .touchUpInside)
footerBar.addSubview(saveButton)
cancelButton.addTarget(self, action: #selector(cancel), for: .touchUpInside)
cancelButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cancelButton)
let marginToHideBottomRoundedCorners = CGFloat(30)
NSLayoutConstraint.activate([
iconImageView.leadingAnchor.constraint(equalTo: imageHolder.leadingAnchor),
iconImageView.trailingAnchor.constraint(equalTo: imageHolder.trailingAnchor),
iconImageView.topAnchor.constraint(equalTo: imageHolder.topAnchor),
iconImageView.bottomAnchor.constraint(equalTo: imageHolder.bottomAnchor),
imageHolder.widthAnchor.constraint(equalToConstant: 80),
imageHolder.widthAnchor.constraint(equalTo: imageHolder.heightAnchor),
titleLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor),
urlLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor),
titleTextField.widthAnchor.constraint(equalTo: stackView.widthAnchor),
urlTextField.widthAnchor.constraint(equalTo: stackView.widthAnchor),
footerBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
footerBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
//Additional allowance so there's a margin below the buttons for non-iPhone X devices
footerBar.topAnchor.constraint(equalTo: view.layoutGuide.bottomAnchor, constant: -buttonsHeight - 3),
footerBar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
saveButton.leadingAnchor.constraint(equalTo: footerBar.leadingAnchor, constant: 15),
saveButton.trailingAnchor.constraint(equalTo: footerBar.trailingAnchor, constant: -15),
saveButton.topAnchor.constraint(equalTo: footerBar.topAnchor),
saveButton.heightAnchor.constraint(equalToConstant: buttonsHeight),
stackView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 37),
stackView.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -37),
stackView.topAnchor.constraint(equalTo: roundedBackground.topAnchor),
cancelButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 14),
cancelButton.topAnchor.constraint(equalTo: view.layoutGuide.topAnchor),
//We don't use createConstraintsWithContainer() because the top rounded corners need to be lower
roundedBackground.leadingAnchor.constraint(equalTo: view.leadingAnchor),
roundedBackground.trailingAnchor.constraint(equalTo: view.trailingAnchor),
roundedBackground.topAnchor.constraint(equalTo: cancelButton.bottomAnchor, constant: 10),
roundedBackground.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: marginToHideBottomRoundedCorners),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: EditMyDappViewControllerViewModel) {
self.viewModel = viewModel
view.backgroundColor = viewModel.backgroundColor
imageHolder.layer.shadowColor = viewModel.imageShadowColor.cgColor
imageHolder.layer.shadowOffset = viewModel.imageShadowOffset
imageHolder.layer.shadowOpacity = viewModel.imageShadowOpacity
imageHolder.layer.shadowRadius = viewModel.imageShadowRadius
iconImageView.backgroundColor = viewModel.imageBackgroundColor
iconImageView.contentMode = .scaleAspectFill
iconImageView.clipsToBounds = true
iconImageView.kf.setImage(with: viewModel.imageUrl, placeholder: viewModel.imagePlaceholder)
screenTitleLabel.text = viewModel.screenTitle
screenTitleLabel.textAlignment = .center
screenTitleLabel.font = viewModel.screenFont
titleLabel.textColor = viewModel.titleColor
titleLabel.font = viewModel.titleFont
titleLabel.text = viewModel.titleText
urlLabel.textColor = viewModel.urlColor
urlLabel.font = viewModel.urlFont
urlLabel.text = viewModel.urlText
titleTextField.borderStyle = viewModel.titleTextFieldBorderStyle
titleTextField.borderWidth = viewModel.titleTextFieldBorderWidth
titleTextField.borderColor = viewModel.titleTextFieldBorderColor
titleTextField.cornerRadius = viewModel.titleTextFieldCornerRadius
titleTextField.font = viewModel.titleTextFieldFont
titleTextField.returnKeyType = .next
titleTextField.text = viewModel.titleTextFieldText
urlTextField.borderStyle = viewModel.urlTextFieldBorderStyle
urlTextField.borderWidth = viewModel.urlTextFieldBorderWidth
urlTextField.borderColor = viewModel.urlTextFieldBorderColor
urlTextField.cornerRadius = viewModel.urlTextFieldCornerRadius
urlTextField.font = viewModel.urlTextFieldFont
urlTextField.returnKeyType = .done
urlTextField.text = viewModel.urlTextFieldText
saveButton.setTitleColor(viewModel.saveButtonTitleColor, for: .normal)
saveButton.backgroundColor = viewModel.saveButtonBackgroundColor
saveButton.titleLabel?.font = viewModel.saveButtonFont
saveButton.setTitle(viewModel.saveButtonTitle, for: .normal)
saveButton.cornerRadius = viewModel.saveButtonCornerRadius
cancelButton.setTitleColor(viewModel.cancelButtonTitleColor, for: .normal)
cancelButton.titleLabel?.font = viewModel.cancelButtonFont
cancelButton.setTitle(viewModel.cancelButtonTitle, for: .normal)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
imageHolder.layer.cornerRadius = imageHolder.frame.size.width / 2
iconImageView.layer.cornerRadius = iconImageView.frame.size.width / 2
imageHolder.layer.shadowPath = UIBezierPath(roundedRect: imageHolder.bounds, cornerRadius: imageHolder.layer.cornerRadius).cgPath
}
@objc private func save() {
guard let dapp = viewModel?.dapp else { return }
guard let url = urlTextField.text?.trimmed else { return }
guard !url.isEmpty else { return }
let title = titleTextField.text?.trimmed ?? ""
delegate?.didTapSave(dapp: dapp, withTitle: title, url: url, inViewController: self)
}
@objc private func cancel() {
delegate?.didTapCancel(inViewController: self)
}
}
extension EditMyDappViewController: UITextFieldDelegate {
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
switch textField {
case titleTextField:
urlTextField.becomeFirstResponder()
case urlTextField:
urlTextField.endEditing(true)
default:
break
}
return true
}
}

@ -1,113 +0,0 @@
// Copyright DApps Platform Inc. All rights reserved.
import UIKit
import StatefulViewController
protocol HistoryViewControllerDelegate: class {
func didSelect(history: History, in controller: HistoryViewController)
}
final class HistoryViewController: UIViewController {
private let store: HistoryStore
private let tableView = UITableView(frame: .zero, style: .plain)
private lazy var viewModel: HistoriesViewModel = {
return HistoriesViewModel(store: store)
}()
weak var delegate: HistoryViewControllerDelegate?
init(store: HistoryStore) {
self.store = store
super.init(nibName: nil, bundle: nil)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .singleLine
tableView.rowHeight = 60
tableView.register(R.nib.bookmarkViewCell(), forCellReuseIdentifier: R.nib.bookmarkViewCell.name)
view.addSubview(tableView)
emptyView = EmptyView(title: R.string.localizable.browserNoHistoryLabelTitle())
NSLayoutConstraint.activate([
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupInitialViewState()
fetch()
}
func fetch() {
tableView.reloadData()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension HistoryViewController: StatefulViewController {
func hasContent() -> Bool {
return viewModel.hasContent
}
}
extension HistoryViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.numberOfRows
}
func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.numberOfSections
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: R.nib.bookmarkViewCell.name, for: indexPath) as! BookmarkViewCell
cell.viewModel = HistoryViewModel(history: viewModel.item(for: indexPath))
return cell
}
}
extension HistoryViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let history = viewModel.item(for: indexPath)
delegate?.didSelect(history: history, in: self)
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let history = viewModel.item(for: indexPath)
confirm(
title: R.string.localizable.browserHistoryConfirmDeleteTitle(),
okTitle: R.string.localizable.delete(),
okStyle: .destructive
) { [weak self] result in
switch result {
case .success:
self?.store.delete(histories: [history])
self?.tableView.reloadData()
case .failure: break
}
}
}
}
}

@ -1,136 +0,0 @@
// Copyright DApps Platform Inc. All rights reserved.
import Foundation
import UIKit
protocol MasterBrowserViewControllerDelegate: class {
func didPressAction(_ action: BrowserToolbarAction)
}
enum BrowserToolbarAction {
case view(BookmarksViewType)
case qrCode
}
enum BookmarksViewType: Int {
case browser
case bookmarks
case history
}
final class MasterBrowserViewController: UIViewController {
private lazy var segmentController: UISegmentedControl = {
let items = [
R.string.localizable.new(),
R.string.localizable.bookmarks(),
R.string.localizable.history(),
]
let segmentedControl = UISegmentedControl(items: items)
segmentedControl.addTarget(self, action: #selector(selectionDidChange(_:)), for: .valueChanged)
segmentedControl.style()
return segmentedControl
}()
private lazy var qrcodeButton: UIButton = {
let button = Button(size: .normal, style: .borderless)
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(R.image.browser_scan()?.withRenderingMode(.alwaysTemplate), for: .normal)
button.imageView?.tintColor = Colors.appBackground
button.addTarget(self, action: #selector(qrReader), for: .touchUpInside)
return button
}()
private let bookmarksViewController: BookmarkViewController
private let historyViewController: HistoryViewController
weak var delegate: MasterBrowserViewControllerDelegate?
let browserViewController: BrowserViewController
init(
bookmarksViewController: BookmarkViewController,
historyViewController: HistoryViewController,
browserViewController: BrowserViewController,
type: BookmarksViewType
) {
self.bookmarksViewController = bookmarksViewController
self.historyViewController = historyViewController
self.browserViewController = browserViewController
super.init(nibName: nil, bundle: nil)
segmentController.selectedSegmentIndex = type.rawValue
}
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let navigationController = navigationController, navigationController.isBeingPresented {
browserViewController.setUpCloseButtonAs(hidden: false)
} else if isBeingPresented {
browserViewController.setUpCloseButtonAs(hidden: false)
} else {
browserViewController.setUpCloseButtonAs(hidden: true)
}
}
func select(viewType: BookmarksViewType) {
segmentController.selectedSegmentIndex = viewType.rawValue
updateView()
}
private func setupView() {
let items: [UIBarButtonItem] = [
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil),
UIBarButtonItem(customView: segmentController),
UIBarButtonItem(customView: qrcodeButton),
UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil),
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil),
]
toolbarItems = items
navigationController?.isToolbarHidden = false
navigationController?.toolbar.isTranslucent = false
updateView()
}
private func updateView() {
if segmentController.selectedSegmentIndex == BookmarksViewType.bookmarks.rawValue {
remove(asChildViewController: browserViewController)
remove(asChildViewController: historyViewController)
add(asChildViewController: bookmarksViewController)
} else if segmentController.selectedSegmentIndex == BookmarksViewType.history.rawValue {
remove(asChildViewController: browserViewController)
remove(asChildViewController: bookmarksViewController)
add(asChildViewController: historyViewController)
} else {
remove(asChildViewController: bookmarksViewController)
remove(asChildViewController: historyViewController)
add(asChildViewController: browserViewController)
}
}
@objc func selectionDidChange(_ sender: UISegmentedControl) {
updateView()
guard let viewType = BookmarksViewType(rawValue: sender.selectedSegmentIndex) else {
return
}
delegate?.didPressAction(.view(viewType))
}
@objc func qrReader() {
delegate?.didPressAction(.qrCode)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension MasterBrowserViewController: Scrollable {
func scrollOnTop() {
browserViewController.goHome()
}
}

@ -0,0 +1,165 @@
//
// Created by James Sangalli on 8/12/18.
//
import Foundation
import UIKit
import StatefulViewController
protocol MyDappsViewControllerDelegate: class {
func didTapToEdit(dapp: Bookmark, inViewController viewController: MyDappsViewController)
func didTapToSelect(dapp: Bookmark, inViewController viewController: MyDappsViewController)
func delete(dapp: Bookmark, inViewController viewController: MyDappsViewController)
func dismissKeyboard(inViewController viewController: MyDappsViewController)
}
class MyDappsViewController: UIViewController {
private let tableView = UITableView(frame: .zero, style: .plain)
lazy private var headerView = MyDappsViewControllerHeaderView()
private var viewModel: MyDappsViewControllerViewModel
private var browserNavBar: DappBrowserNavigationBar? {
return navigationController?.navigationBar as? DappBrowserNavigationBar
}
private let bookmarksStore: BookmarksStore
weak var delegate: MyDappsViewControllerDelegate?
init(bookmarksStore: BookmarksStore) {
self.bookmarksStore = bookmarksStore
self.viewModel = .init(bookmarksStore: bookmarksStore)
super.init(nibName: nil, bundle: nil)
tableView.register(MyDappCell.self, forCellReuseIdentifier: MyDappCell.identifier)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.tableHeaderView = headerView
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
tableView.backgroundColor = Colors.appBackground
tableView.allowsSelectionDuringEditing = true
emptyView = {
let emptyView = DappsHomeEmptyView()
let headerViewModel = DappsHomeHeaderViewViewModel(title: R.string.localizable.myDappsButtonImageLabel())
emptyView.configure(viewModel: .init(headerViewViewModel: headerViewModel, title: R.string.localizable.dappBrowserMyDappsEmpty()))
return emptyView
}()
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func keyboardWillShow(notification: NSNotification) {
if let keyboardEndFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardBeginFrame = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardEndFrame.size.height
tableView.contentInset.bottom = keyboardEndFrame.size.height
}
}
@objc private func keyboardWillHide(notification: NSNotification) {
if let keyboardEndFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let keyboardBeginFrame = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
tableView.contentInset.bottom = 0
}
}
func configure(viewModel: MyDappsViewControllerViewModel) {
self.viewModel = viewModel
resizeTableViewHeader()
tableView.reloadData()
endLoading()
}
private func resizeTableViewHeader() {
headerView.delegate = self
let headerViewModel = DappsHomeHeaderViewViewModel(title: R.string.localizable.myDappsButtonImageLabel())
headerView.configure(viewModel: headerViewModel)
headerView.backgroundColor = headerViewModel.backgroundColor
let fittingSize = headerView.systemLayoutSizeFitting(.init(width: tableView.frame.size.width, height: 1000))
headerView.frame = .init(x: 0, y: 0, width: 0, height: fittingSize.height)
tableView.tableHeaderView = headerView
}
private func dismissKeyboard() {
delegate?.dismissKeyboard(inViewController: self)
}
}
extension MyDappsViewController: StatefulViewController {
func hasContent() -> Bool {
return viewModel.hasContent
}
}
extension MyDappsViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MyDappCell.identifier, for: indexPath) as! MyDappCell
let dapp = viewModel.dapp(atIndex: indexPath.row)
cell.configure(viewModel: .init(dapp: dapp))
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.dappsCount
}
}
extension MyDappsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let dapp = viewModel.dapp(atIndex: indexPath.row)
if tableView.isEditing {
delegate?.didTapToEdit(dapp: dapp, inViewController: self)
} else {
dismissKeyboard()
delegate?.didTapToSelect(dapp: dapp, inViewController: self)
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let dapp = viewModel.dapp(atIndex: indexPath.row)
confirm(
title: R.string.localizable.dappBrowserClearMyDapps(),
message: dapp.title,
okTitle: R.string.localizable.removeButtonTitle(),
okStyle: .destructive
) { [weak self] result in
switch result {
case .success:
guard let strongSelf = self else { return }
strongSelf.delegate?.delete(dapp: dapp, inViewController: strongSelf)
if !strongSelf.viewModel.hasContent {
strongSelf.headerView.exitEditMode()
}
case .failure:
break
}
}
}
}
}
extension MyDappsViewController: MyDappsViewControllerHeaderViewDelegate {
func didEnterEditMode(inHeaderView headerView: MyDappsViewControllerHeaderView) {
//TODO should this be a state case in the nav bar, but with a flag (associated value?) whether to disable the buttons?
browserNavBar?.disableButtons()
tableView.setEditing(true, animated: true)
}
func didExitEditMode(inHeaderView headerView: MyDappsViewControllerHeaderView) {
//TODO should this be a state case in the nav bar, but with a flag (associated value?) whether to disable the buttons?
browserNavBar?.enableButtons()
tableView.setEditing(false, animated: true)
}
}

@ -1,26 +0,0 @@
// Copyright DApps Platform Inc. All rights reserved.
import Foundation
import UIKit
struct BookmarkViewModel: URLViewModel {
private let bookmark: Bookmark
init(
bookmark: Bookmark
) {
self.bookmark = bookmark
}
var urlText: String? {
return bookmark.linkURL?.absoluteString
}
var title: String {
return bookmark.title
}
var imageURL: URL? {
return Favicon.get(for: bookmark.linkURL)
}
}

@ -1,31 +0,0 @@
// Copyright DApps Platform Inc. All rights reserved.
import Foundation
import RealmSwift
struct BookmarksViewModel {
private let bookmarksStore: BookmarksStore
init(
bookmarksStore: BookmarksStore
) {
self.bookmarksStore = bookmarksStore
}
var hasBookmarks: Bool {
return !bookmarksStore.bookmarks.isEmpty
}
var numberOfRows: Int {
return bookmarksStore.bookmarks.count
}
func bookmark(for indexPath: IndexPath) -> Bookmark {
return bookmarksStore.bookmarks[indexPath.row]
}
func delete(bookmark: Bookmark) {
bookmarksStore.delete(bookmarks: [bookmark])
}
}

@ -0,0 +1,60 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
struct BrowserHistoryCellViewModel {
let history: History
var backgroundColor: UIColor {
return Colors.appWhite
}
var imageUrl: URL? {
return Favicon.get(for: URL(string: history.url))
}
var fallbackImage: UIImage? {
return R.image.launch_icon()
}
var name: String {
return history.title
}
var url: String {
return history.url
}
var nameFont: UIFont {
return Fonts.semibold(size: 12)!
}
var urlFont: UIFont {
return Fonts.semibold(size: 10)!
}
var nameColor: UIColor? {
return UIColor(red: 77, green: 77, blue: 77)
}
var urlColor: UIColor? {
return Colors.appBackground
}
var imageViewShadowColor: UIColor {
return .black
}
var imageViewShadowOffset: CGSize {
return Metrics.DappsHome.Icon.shadowOffset
}
var imageViewShadowOpacity: Float {
return Metrics.DappsHome.Icon.shadowOpacity
}
var imageViewShadowRadius: CGFloat {
return Metrics.DappsHome.Icon.shadowRadius
}
}

@ -0,0 +1,10 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
struct Dapp {
let name: String
let description: String
let url: String
let cat: String
}

@ -0,0 +1,17 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
struct DappButtonViewModel {
var font: UIFont? {
return Fonts.semibold(size: 10)
}
var textColor: UIColor? {
return .init(red: 77, green: 77, blue: 77)
}
let image: UIImage?
let title: String
}

@ -0,0 +1,61 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
class DappViewCellViewModel {
let dapp: Bookmark
var imageUrl: URL?
var title: String {
return dapp.title
}
var fallbackImage: UIImage? {
return R.image.launch_icon()
}
var domainName: String {
return URL(string: dapp.url)?.host ?? ""
}
init(dapp: Bookmark) {
self.dapp = dapp
self.imageUrl = Favicon.get(for: URL(string: dapp.url))
}
var backgroundColor: UIColor {
return Colors.appWhite
}
var imageViewShadowColor: UIColor {
return .black
}
var imageViewShadowOffset: CGSize {
return Metrics.DappsHome.Icon.shadowOffset
}
var imageViewShadowOpacity: Float {
return Metrics.DappsHome.Icon.shadowOpacity
}
var imageViewShadowRadius: CGFloat {
return Metrics.DappsHome.Icon.shadowRadius
}
var titleColor: UIColor {
return UIColor(red: 77, green: 77, blue: 77)
}
var titleFont: UIFont {
return Fonts.regular(size: 12)!
}
var domainNameColor: UIColor {
return Colors.appBackground
}
var domainNameFont: UIFont {
return Fonts.bold(size: 10)!
}
}

@ -0,0 +1,35 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
enum Dapps {
static let masterList = [
Dapp(name: "AirSwap", description: "Peer-to-Peer trading on Ethereum", url: "https://airswap.io", cat: "Games"),
Dapp(name: "Chibi Fighters", description: "Chibi Fighters are fierce little warriors that know no mercy", url: "https://chibifighters.io", cat: "Games"),
Dapp(name: "CryptoKitties", description: "Collect and breed digital cats!", url: "https://cryptokitties.co", cat: "Misc"),
Dapp(name: "Multitoken Protocol", description: "Protect Crypto Investments from Volatility", url: "https://multitoken.com", cat: "Misc"),
]
struct Category {
let name: String
var dapps: [Dapp]
}
static let categorisedDapps: [Category] = {
var results = [String: Category]()
for each in masterList {
let catName = each.cat
if var cat = results[catName] {
var dapps = cat.dapps
dapps.append(each)
cat.dapps = dapps
results[catName] = cat
} else {
var cat = Category(name: catName, dapps: [each])
results[catName] = cat
}
}
//TODO sort categories by hand
return Array(results.values)
}()
}

@ -0,0 +1,44 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
struct DappsAutoCompletionCellViewModel {
let dapp: Dapp
let keyword: String
var backgroundColor: UIColor {
return UIColor(red: 244, green: 244, blue: 244)
}
var name: NSAttributedString {
let text = NSMutableAttributedString(string: dapp.name)
text.setAttributes([NSAttributedString.Key.foregroundColor: nameColor], range: .init(location: 0, length: dapp.name.count))
if let range = dapp.name.lowercased().range(of: keyword.lowercased()) {
let location = dapp.name.distance(from: dapp.name.startIndex, to: range.lowerBound)
let length = keyword.characters.count
text.setAttributes([NSAttributedString.Key.foregroundColor: Colors.appBackground], range: .init(location: location, length: length))
}
return text
}
var description: String {
return dapp.description
}
var nameFont: UIFont {
return Fonts.regular(size: 16)!
}
var descriptionFont: UIFont {
return Fonts.light(size: 12)!
}
private var nameColor: UIColor? {
return UIColor(red: 55, green: 55, blue: 55)
}
var descriptionColor: UIColor? {
return UIColor(red: 77, green: 77, blue: 77)
}
}

@ -0,0 +1,18 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
struct DappsAutoCompletionViewControllerViewModel {
var dappSuggestions = [Dapp]()
var dappSuggestionsCount: Int {
return dappSuggestions.count
}
var keyword: String = "" {
didSet {
let lowercased = keyword.lowercased().trimmed
dappSuggestions = Dapps.masterList.filter { $0.name.lowercased().contains(lowercased) || $0.url.lowercased().contains(lowercased) }
}
}
}

@ -0,0 +1,8 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
struct DappsHomeEmptyViewViewModel {
let headerViewViewModel: DappsHomeHeaderViewViewModel
let title: String
}

@ -0,0 +1,20 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
struct DappsHomeHeaderViewViewModel {
var title: String
var backgroundColor: UIColor {
return Colors.appWhite
}
var logo: UIImage? {
return R.image.launch_icon()
}
var titleFont: UIFont? {
return Fonts.light(size: 20)
}
}

@ -0,0 +1,38 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
struct DappsHomeViewControllerHeaderViewViewModel {
var backgroundColor: UIColor {
return Colors.appWhite
}
var title: String {
return R.string.localizable.dappBrowserTitle()
}
var myDappsButtonImage: UIImage? {
return R.image.myDapps()
}
var myDappsButtonTitle: String {
return R.string.localizable.myDappsButtonImageLabel()
}
var discoverButtonImage: UIImage? {
return R.image.discoverDapps()
}
var discoverButtonTitle: String {
return R.string.localizable.discoverDappsButtonImageLabel()
}
var historyButtonImage: UIImage? {
return R.image.history()
}
var historyButtonTitle: String {
return R.string.localizable.historyButtonImageLabel()
}
}

@ -0,0 +1,20 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
struct DappsHomeViewControllerViewModel {
var bookmarksStore: BookmarksStore
var dappsCount: Int {
return bookmarksStore.bookmarks.count
}
func dapp(atIndex index: Int) -> Bookmark {
return bookmarksStore.bookmarks[index]
}
var backgroundColor: UIColor {
return Colors.appWhite
}
}

@ -0,0 +1,99 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
struct DiscoverDappCellViewModel {
let bookmarksStore: BookmarksStore
let dapp: Dapp
private var containsDapp: Bool {
//TODO can we not loop? Or at least we can cache this value, no need to be a computed var
for each in bookmarksStore.bookmarks {
if each.url == dapp.url {
return true
}
}
return false
}
var isAddButtonHidden: Bool {
return containsDapp
}
var isRemoveButtonHidden: Bool {
return !containsDapp
}
var backgroundColor: UIColor {
return Colors.appWhite
}
var imageUrl: URL? {
return Favicon.get(for: URL(string: dapp.url))
}
var fallbackImage: UIImage? {
return R.image.launch_icon()
}
var name: String {
return dapp.name
}
var description: String {
return dapp.description
}
var nameFont: UIFont {
return Fonts.semibold(size: 12)!
}
var descriptionFont: UIFont {
return Fonts.regular(size: 10)!
}
var nameColor: UIColor? {
return UIColor(red: 77, green: 77, blue: 77)
}
var descriptionColor: UIColor? {
return UIColor(red: 77, green: 77, blue: 77)
}
var addRemoveButtonFont: UIFont {
return Fonts.semibold(size: 12)!
}
var addRemoveButtonContentEdgeInsets: UIEdgeInsets {
return .init(top: 7, left: 14, bottom: 7, right: 14)
}
var addRemoveButtonBorderColor: UIColor {
return Colors.appBackground
}
var addRemoveButtonBorderWidth: CGFloat {
return 1
}
var addRemoveButtonBorderCornerRadius: CGFloat {
return 9
}
var imageViewShadowColor: UIColor {
return .black
}
var imageViewShadowOffset: CGSize {
return Metrics.DappsHome.Icon.shadowOffset
}
var imageViewShadowOpacity: Float {
return Metrics.DappsHome.Icon.shadowOpacity
}
var imageViewShadowRadius: CGFloat {
return Metrics.DappsHome.Icon.shadowRadius
}
}

@ -0,0 +1,15 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
struct DiscoverDappsViewControllerViewModel {
var dappCategories = Dapps.categorisedDapps
var dapps = Dapps.masterList
var backgroundColor: UIColor {
return Colors.appWhite
}
}

@ -0,0 +1,152 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
struct EditMyDappViewControllerViewModel {
let dapp: Bookmark
var backgroundColor: UIColor {
return Colors.appBackground
}
var imageShadowColor: UIColor {
return .black
}
var imageShadowOffset: CGSize {
return Metrics.DappsHome.Icon.shadowOffset
}
var imageShadowOpacity: Float {
return Metrics.DappsHome.Icon.shadowOpacity
}
var imageShadowRadius: CGFloat {
return Metrics.DappsHome.Icon.shadowRadius
}
var imageBackgroundColor: UIColor {
return Colors.appWhite
}
var imagePlaceholder: UIImage {
return R.image.launch_icon()!
}
var imageUrl: URL? {
return Favicon.get(for: URL(string: dapp.url))
}
var screenTitle: String {
return R.string.localizable.dappBrowserMyDappsEdit()
}
var screenFont: UIFont {
return Fonts.semibold(size: 20)!
}
var titleColor: UIColor {
return .init(red: 71, green: 71, blue: 71)
}
var titleFont: UIFont {
return Fonts.semibold(size: 16)!
}
var titleText: String {
return R.string.localizable.dappBrowserMyDappsEditTitleLabel()
}
var urlColor: UIColor {
return .init(red: 71, green: 71, blue: 71)
}
var urlFont: UIFont {
return Fonts.semibold(size: 16)!
}
var urlText: String {
return R.string.localizable.dappBrowserMyDappsEditUrlLabel()
}
var titleTextFieldBorderStyle: UITextField.BorderStyle {
return .roundedRect
}
var titleTextFieldBorderWidth: CGFloat {
return 0.5
}
var titleTextFieldBorderColor: UIColor {
return .init(red: 112, green: 112, blue: 112)
}
var titleTextFieldCornerRadius: CGFloat {
return 7
}
var titleTextFieldFont: UIFont {
return Fonts.light(size: 16)!
}
var titleTextFieldText: String {
return dapp.title
}
var urlTextFieldBorderStyle: UITextField.BorderStyle {
return .roundedRect
}
var urlTextFieldBorderWidth: CGFloat {
return 0.5
}
var urlTextFieldBorderColor: UIColor {
return .init(red: 112, green: 112, blue: 112)
}
var urlTextFieldCornerRadius: CGFloat {
return 7
}
var urlTextFieldFont: UIFont {
return Fonts.light(size: 16)!
}
var urlTextFieldText: String {
return dapp.url
}
var saveButtonTitleColor: UIColor {
return Colors.appWhite
}
var saveButtonBackgroundColor: UIColor {
return Colors.appHighlightGreen
}
var saveButtonFont: UIFont {
return Fonts.regular(size: 20)!
}
var saveButtonTitle: String {
return R.string.localizable.save()
}
var saveButtonCornerRadius: CGFloat {
return 16
}
var cancelButtonTitleColor: UIColor {
return Colors.appWhite
}
var cancelButtonFont: UIFont {
return Fonts.regular(size: 20)!
}
var cancelButtonTitle: String {
return R.string.localizable.cancel()
}
}

@ -3,12 +3,9 @@
import Foundation
struct HistoriesViewModel {
private let store: HistoryStore
init(
store: HistoryStore
) {
init(store: HistoryStore) {
self.store = store
}

@ -1,27 +0,0 @@
// Copyright DApps Platform Inc. All rights reserved.
import Foundation
import UIKit
struct HistoryViewModel: URLViewModel {
private let history: History
init(
history: History
) {
self.history = history
}
var urlText: String? {
return history.URL?.absoluteString
}
var title: String {
return history.title
}
var imageURL: URL? {
return Favicon.get(for: history.URL)
}
}

@ -1,9 +0,0 @@
// Copyright SIX DAY LLC. All rights reserved.
import Foundation
struct MarketplaceViewModel {
var title: String {
return R.string.localizable.aMarketplaceTabbarItemTitle()
}
}

@ -0,0 +1,80 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
struct MyDappCellViewModel {
let dapp: Bookmark
var backgroundColor: UIColor {
return Colors.appWhite
}
var imageUrl: URL? {
return Favicon.get(for: URL(string: dapp.url))
}
var fallbackImage: UIImage? {
return R.image.launch_icon()
}
var name: String {
return dapp.title
}
var domainName: String {
return URL(string: dapp.url)?.host ?? ""
}
var nameFont: UIFont {
return Fonts.semibold(size: 12)!
}
var domainNameFont: UIFont {
return Fonts.bold(size: 10)!
}
var nameColor: UIColor? {
return UIColor(red: 77, green: 77, blue: 77)
}
var domainNameColor: UIColor? {
return Colors.appBackground
}
var addRemoveButtonFont: UIFont {
return Fonts.semibold(size: 12)!
}
var addRemoveButtonContentEdgeInsets: UIEdgeInsets {
return .init(top: 7, left: 14, bottom: 7, right: 14)
}
var addRemoveButtonBorderColor: UIColor {
return Colors.appBackground
}
var addRemoveButtonBorderWidth: CGFloat {
return 1
}
var addRemoveButtonBorderCornerRadius: CGFloat {
return 9
}
var imageViewShadowColor: UIColor {
return .black
}
var imageViewShadowOffset: CGSize {
return Metrics.DappsHome.Icon.shadowOffset
}
var imageViewShadowOpacity: Float {
return Metrics.DappsHome.Icon.shadowOpacity
}
var imageViewShadowRadius: CGFloat {
return Metrics.DappsHome.Icon.shadowRadius
}
}

@ -0,0 +1,19 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
struct MyDappsViewControllerViewModel {
var bookmarksStore: BookmarksStore
var dappsCount: Int {
return bookmarksStore.bookmarks.count
}
var hasContent: Bool {
return !bookmarksStore.bookmarks.isEmpty
}
func dapp(atIndex index: Int) -> Bookmark {
return bookmarksStore.bookmarks[index]
}
}

@ -0,0 +1,82 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
class BrowserHistoryCell: UITableViewCell {
static let identifier = "BrowserHistoryCell"
private var viewModel: BrowserHistoryCellViewModel?
private let iconImageViewHolder = UIView()
let iconImageView = UIImageView()
let titleLabel = UILabel()
let urlLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
let labelsVerticalStackView = [
titleLabel,
urlLabel
].asStackView(axis: .vertical)
iconImageView.translatesAutoresizingMaskIntoConstraints = false
iconImageViewHolder.addSubview(iconImageView)
let mainStackView = [.spacerWidth(29), iconImageViewHolder, .spacerWidth(26), labelsVerticalStackView, .spacerWidth(29)].asStackView(axis: .horizontal, alignment: .center)
mainStackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(mainStackView)
NSLayoutConstraint.activate([
mainStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
mainStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
mainStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 7),
mainStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -7),
iconImageView.widthAnchor.constraint(equalToConstant: 44),
iconImageView.widthAnchor.constraint(equalTo: iconImageView.heightAnchor),
iconImageView.leadingAnchor.constraint(equalTo: iconImageViewHolder.leadingAnchor),
iconImageView.trailingAnchor.constraint(equalTo: iconImageViewHolder.trailingAnchor),
iconImageView.topAnchor.constraint(equalTo: iconImageViewHolder.topAnchor),
iconImageView.bottomAnchor.constraint(equalTo: iconImageViewHolder.bottomAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: BrowserHistoryCellViewModel) {
self.viewModel = viewModel
backgroundColor = viewModel.backgroundColor
contentView.backgroundColor = viewModel.backgroundColor
iconImageViewHolder.layer.shadowColor = viewModel.imageViewShadowColor.cgColor
iconImageViewHolder.layer.shadowOffset = viewModel.imageViewShadowOffset
iconImageViewHolder.layer.shadowOpacity = viewModel.imageViewShadowOpacity
iconImageViewHolder.layer.shadowRadius = viewModel.imageViewShadowRadius
iconImageView.backgroundColor = viewModel.backgroundColor
iconImageView.contentMode = .scaleAspectFill
iconImageView.clipsToBounds = true
iconImageView.kf.setImage(with: viewModel.imageUrl, placeholder: viewModel.fallbackImage)
titleLabel.font = viewModel.nameFont
titleLabel.textColor = viewModel.nameColor
titleLabel.text = viewModel.name
urlLabel.font = viewModel.urlFont
urlLabel.textColor = viewModel.urlColor
urlLabel.text = viewModel.url
//TODO ugly hack to get the image view's frame. Can't figure out a good point to retrieve the correct frame otherwise
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
self.iconImageView.layer.cornerRadius = self.iconImageView.frame.size.width / 2
self.iconImageViewHolder.layer.cornerRadius = self.iconImageViewHolder.frame.size.width / 2
self.iconImageViewHolder.layer.shadowPath = UIBezierPath(roundedRect: self.iconImageViewHolder.bounds, cornerRadius: self.iconImageViewHolder.layer.cornerRadius).cgPath
}
}
}

@ -0,0 +1,52 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
protocol BrowserHistoryViewControllerHeaderViewDelegate: class {
func didTapClearAll(inHeaderView headerView: BrowserHistoryViewControllerHeaderView)
}
class BrowserHistoryViewControllerHeaderView: UIView {
private let header = DappsHomeHeaderView()
private let clearButton = UIButton(type: .system)
weak var delegate: BrowserHistoryViewControllerHeaderViewDelegate?
init() {
super.init(frame: .zero)
header.translatesAutoresizingMaskIntoConstraints = false
addSubview(header)
clearButton.addTarget(self, action: #selector(clearHistory), for: .touchUpInside)
clearButton.translatesAutoresizingMaskIntoConstraints = false
addSubview(clearButton)
NSLayoutConstraint.activate([
header.trailingAnchor.constraint(equalTo: trailingAnchor),
header.leadingAnchor.constraint(equalTo: leadingAnchor),
header.topAnchor.constraint(equalTo: topAnchor, constant: 50),
header.bottomAnchor.constraint(equalTo: clearButton.topAnchor, constant: -30),
clearButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
clearButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: DappsHomeHeaderViewViewModel) {
backgroundColor = viewModel.backgroundColor
header.configure(viewModel: viewModel)
clearButton.setTitle(R.string.localizable.clearButtonTitle().localizedUppercase, for: .normal)
clearButton.titleLabel?.font = Fonts.bold(size: 12)
}
@objc private func clearHistory() {
delegate?.didTapClearAll(inHeaderView: self)
}
}

@ -1,120 +0,0 @@
// Copyright DApps Platform Inc. All rights reserved.
import UIKit
protocol BrowserNavigationBarDelegate: class {
func did(action: BrowserNavigation)
}
final class BrowserNavigationBar: UINavigationBar {
private let moreButton = UIButton()
private let homeButton = UIButton()
private struct Layout {
static let width: CGFloat = 34
static let moreButtonWidth: CGFloat = 24
}
let textField = UITextField()
let closeButton = UIButton()
let backButton = UIButton()
weak var browserDelegate: BrowserNavigationBarDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
textField.translatesAutoresizingMaskIntoConstraints = false
textField.backgroundColor = .white
textField.layer.cornerRadius = 5
textField.layer.borderWidth = 0.5
textField.layer.borderColor = Colors.lightGray.cgColor
textField.autocapitalizationType = .none
textField.autoresizingMask = .flexibleWidth
textField.delegate = self
textField.autocorrectionType = .no
textField.returnKeyType = .go
textField.clearButtonMode = .whileEditing
textField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 6, height: 30))
textField.leftViewMode = .always
textField.placeholder = R.string.localizable.browserUrlTextfieldPlaceholder()
textField.keyboardType = .webSearch
moreButton.translatesAutoresizingMaskIntoConstraints = false
moreButton.setImage(R.image.toolbarMenu(), for: .normal)
moreButton.addTarget(self, action: #selector(moreAction(_:)), for: .touchUpInside)
closeButton.translatesAutoresizingMaskIntoConstraints = false
closeButton.isHidden = true
closeButton.setTitle(R.string.localizable.done(), for: .normal)
closeButton.addTarget(self, action: #selector(closeAction(_:)), for: .touchUpInside)
closeButton.setContentCompressionResistancePriority(.required, for: .horizontal)
closeButton.setContentHuggingPriority(.required, for: .horizontal)
homeButton.translatesAutoresizingMaskIntoConstraints = false
homeButton.setImage(R.image.browserHome()?.withRenderingMode(.alwaysTemplate), for: .normal)
homeButton.addTarget(self, action: #selector(homeAction(_:)), for: .touchUpInside)
backButton.translatesAutoresizingMaskIntoConstraints = false
backButton.setImage(R.image.toolbarBack(), for: .normal)
backButton.addTarget(self, action: #selector(goBackAction), for: .touchUpInside)
let stackView = UIStackView(arrangedSubviews: [
closeButton,
homeButton,
.spacerWidth(),
backButton,
textField,
.spacerWidth(),
moreButton,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.distribution = .fill
stackView.spacing = 4
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor, constant: 4),
stackView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor, constant: 10),
stackView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor, constant: -10),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -6),
homeButton.widthAnchor.constraint(equalToConstant: Layout.width),
backButton.widthAnchor.constraint(equalToConstant: Layout.width),
moreButton.widthAnchor.constraint(equalToConstant: Layout.moreButtonWidth),
])
}
@objc private func goBackAction() {
browserDelegate?.did(action: .goBack)
}
@objc private func moreAction(_ sender: UIView) {
browserDelegate?.did(action: .more(sender: sender))
}
@objc private func homeAction(_ sender: UIView) {
browserDelegate?.did(action: .home)
}
@objc private func closeAction(_ sender: UIView) {
browserDelegate?.did(action: .close)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension BrowserNavigationBar: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
browserDelegate?.did(action: .enter(textField.text ?? ""))
textField.resignFirstResponder()
return true
}
func textFieldDidBeginEditing(_ textField: UITextField) {
browserDelegate?.did(action: .beginEditing)
}
}

@ -0,0 +1,298 @@
// Copyright DApps Platform Inc. All rights reserved.
import UIKit
protocol DappBrowserNavigationBarDelegate: class {
func didTyped(text: String, inNavigationBar navigationBar: DappBrowserNavigationBar)
func didEnter(text: String, inNavigationBar navigationBar: DappBrowserNavigationBar)
func didTapHome(inNavigationBar navigationBar: DappBrowserNavigationBar)
func didTapBack(inNavigationBar navigationBar: DappBrowserNavigationBar)
func didTapForward(inNavigationBar navigationBar: DappBrowserNavigationBar)
func didTapMore(sender: UIView, inNavigationBar navigationBar: DappBrowserNavigationBar)
func didTapClose(inNavigationBar navigationBar: DappBrowserNavigationBar)
}
fileprivate enum State {
case editingURLTextField
case notEditingURLTextField
case browserOnly
}
fileprivate struct Layout {
static let width: CGFloat = 34
static let moreButtonWidth: CGFloat = 24
}
final class DappBrowserNavigationBar: UINavigationBar {
private let moreButton = UIButton()
private let homeButton = UIButton()
private let cancelEditingButton = UIButton()
private let closeButton = UIButton()
private let textField = UITextField()
private let domainNameLabel = UILabel()
private let backButton = UIButton()
private let forwardButton = UIButton()
private var viewsToShowWhenNotEditing = [UIView]()
private var viewsToShowWhenEditing = [UIView]()
private var viewsToShowWhenBrowserOnly = [UIView]()
private var state = State.notEditingURLTextField {
didSet {
var show: [UIView]
var hide: [UIView]
switch state {
case .editingURLTextField:
hide = viewsToShowWhenNotEditing + viewsToShowWhenBrowserOnly - viewsToShowWhenEditing
show = viewsToShowWhenEditing
case .notEditingURLTextField:
hide = viewsToShowWhenEditing + viewsToShowWhenBrowserOnly - viewsToShowWhenNotEditing
show = viewsToShowWhenNotEditing
case .browserOnly:
hide = viewsToShowWhenEditing + viewsToShowWhenNotEditing - viewsToShowWhenBrowserOnly
show = viewsToShowWhenBrowserOnly
}
hide.hideAll()
show.showAll()
}
}
var isBrowserOnly: Bool {
return state == .browserOnly
}
weak var navigationBarDelegate: DappBrowserNavigationBarDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
textField.backgroundColor = .white
textField.layer.cornerRadius = 5
textField.layer.borderWidth = 0.5
textField.layer.borderColor = Colors.lightGray.cgColor
textField.autocapitalizationType = .none
textField.autoresizingMask = .flexibleWidth
textField.delegate = self
textField.autocorrectionType = .no
textField.returnKeyType = .go
textField.clearButtonMode = .whileEditing
textField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 6, height: 30))
textField.leftViewMode = .always
textField.placeholder = R.string.localizable.browserUrlTextfieldPlaceholder()
textField.keyboardType = .webSearch
domainNameLabel.isHidden = true
moreButton.setImage(R.image.toolbarMenu(), for: .normal)
moreButton.addTarget(self, action: #selector(moreAction(_:)), for: .touchUpInside)
closeButton.isHidden = true
closeButton.setTitle(R.string.localizable.done(), for: .normal)
closeButton.addTarget(self, action: #selector(closeAction(_:)), for: .touchUpInside)
closeButton.setContentCompressionResistancePriority(.required, for: .horizontal)
closeButton.setContentHuggingPriority(.required, for: .horizontal)
homeButton.setImage(R.image.browserHome()?.withRenderingMode(.alwaysTemplate), for: .normal)
homeButton.addTarget(self, action: #selector(homeAction(_:)), for: .touchUpInside)
backButton.setImage(R.image.toolbarBack(), for: .normal)
backButton.addTarget(self, action: #selector(goBackAction), for: .touchUpInside)
forwardButton.setImage(R.image.toolbarForward(), for: .normal)
forwardButton.addTarget(self, action: #selector(goForwardAction), for: .touchUpInside)
//compression and hugging priority required to make cancel button appear reliably yet not be too wide
cancelEditingButton.setContentCompressionResistancePriority(.required, for: .horizontal)
cancelEditingButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
cancelEditingButton.addTarget(self, action: #selector(cancelEditing), for: .touchUpInside)
let spacer0 = UIView.spacerWidth()
let spacer1 = UIView.spacerWidth()
let spacer2 = UIView.spacerWidth()
viewsToShowWhenNotEditing.append(contentsOf: [spacer0, spacer1, backButton, forwardButton, textField, homeButton, spacer2, moreButton])
viewsToShowWhenEditing.append(contentsOf: [textField, cancelEditingButton])
viewsToShowWhenBrowserOnly.append(contentsOf: [spacer0, backButton, forwardButton, domainNameLabel, spacer1, closeButton, spacer2, moreButton])
cancelEditingButton.isHidden = true
let stackView = UIStackView(arrangedSubviews: [
spacer0,
backButton,
forwardButton,
textField,
domainNameLabel,
spacer1,
homeButton,
closeButton,
spacer2,
moreButton,
cancelEditingButton,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.distribution = .fill
stackView.spacing = 4
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor, constant: 4),
stackView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor, constant: 10),
stackView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor, constant: -10),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -6),
homeButton.widthAnchor.constraint(equalToConstant: Layout.width),
backButton.widthAnchor.constraint(equalToConstant: Layout.width),
forwardButton.widthAnchor.constraint(equalToConstant: Layout.width),
moreButton.widthAnchor.constraint(equalToConstant: Layout.moreButtonWidth),
])
configure()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configure() {
let color = Colors.appWhite
backButton.imageView?.tintColor = color
forwardButton.imageView?.tintColor = color
homeButton.imageView?.tintColor = color
moreButton.imageView?.tintColor = color
domainNameLabel.textColor = color
domainNameLabel.textAlignment = .center
cancelEditingButton.setTitle(R.string.localizable.cancel(), for: .normal)
}
@objc private func goBackAction() {
cancelEditing()
navigationBarDelegate?.didTapBack(inNavigationBar: self)
}
@objc private func goForwardAction() {
cancelEditing()
navigationBarDelegate?.didTapForward(inNavigationBar: self)
}
@objc private func moreAction(_ sender: UIView) {
cancelEditing()
navigationBarDelegate?.didTapMore(sender: sender, inNavigationBar: self)
}
@objc private func homeAction(_ sender: UIView) {
cancelEditing()
navigationBarDelegate?.didTapHome(inNavigationBar: self)
}
@objc private func closeAction(_ sender: UIView) {
cancelEditing()
navigationBarDelegate?.didTapClose(inNavigationBar: self)
}
//TODO this might get triggered immediately if we use a physical keyboard. Verify
@objc func cancelEditing() {
dismissKeyboard()
switch state {
case .editingURLTextField:
UIView.animate(withDuration: 0.3) {
self.state = .notEditingURLTextField
}
case .notEditingURLTextField, .browserOnly:
//We especially don't want to switch (and animate) to .notEditingURLTextField when we are closing .browserOnly mode
break
}
}
func display(url: URL) {
textField.text = url.absoluteString
domainNameLabel.text = URL(string: url.absoluteString)?.host ?? ""
}
func display(string: String) {
textField.text = string
}
func clearDisplay() {
display(string: "")
}
private func dismissKeyboard() {
endEditing(true)
}
func makeBrowserOnly() {
state = .browserOnly
}
func disableButtons() {
backButton.isEnabled = false
forwardButton.isEnabled = false
homeButton.isEnabled = false
moreButton.isEnabled = false
textField.isEnabled = false
cancelEditingButton.isEnabled = false
closeButton.isEnabled = false
}
func enableButtons() {
backButton.isEnabled = true
forwardButton.isEnabled = true
homeButton.isEnabled = true
moreButton.isEnabled = true
textField.isEnabled = true
cancelEditingButton.isEnabled = true
closeButton.isEnabled = true
}
}
extension DappBrowserNavigationBar: UITextFieldDelegate {
private func queue(typedText text: String) {
navigationBarDelegate?.didTyped(text: text, inNavigationBar: self)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
navigationBarDelegate?.didEnter(text: textField.text ?? "", inNavigationBar: self)
textField.resignFirstResponder()
return true
}
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String
) -> Bool {
if let text = textField.text, let range = Range(range, in: text) {
queue(typedText: textField.text?.replacingCharacters(in: range, with: string) ?? "")
} else {
queue(typedText: "")
}
return true
}
func textFieldShouldClear(_ textField: UITextField) -> Bool {
queue(typedText: "")
return true
}
public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
UIView.animate(withDuration: 0.3) {
self.state = .editingURLTextField
}
return true
}
}
//TODO move when we use this more
fileprivate extension Collection where Element == UIView {
func hideAll() {
for each in self {
each.isHidden = true
}
}
func showAll() {
for each in self {
each.isHidden = false
}
}
}

@ -0,0 +1,43 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
class DappButton: UIControl {
private let imageView = UIImageView()
private let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
let stackView = [imageView, label].asStackView(
axis: .vertical,
contentHuggingPriority: .required,
alignment: .center
)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.isUserInteractionEnabled = false
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
imageView.widthAnchor.constraint(equalToConstant: 50),
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: DappButtonViewModel) {
imageView.image = viewModel.image
label.font = viewModel.font
label.textColor = viewModel.textColor
label.text = viewModel.title
}
}

@ -0,0 +1,158 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
protocol DappViewCellDelegate: class {
func didTapDelete(dapp: Bookmark, inCell cell: DappViewCell)
func didLongPressed(dapp: Bookmark, onCell cell: DappViewCell)
}
//hboon because of how we implemented shadows parallex doesn't work anymore. Fix it again by adding another wrapper around imageHolder? Maybe shadow should just be implemented with a sublayer
class DappViewCell: UICollectionViewCell {
static let identifier = "DappViewCell"
private let marginAroundImage = CGFloat(7)
private let jiggleAnimationKey = "jiggle"
private var viewModel: DappViewCellViewModel?
private var currentDisplayedImageUrl: URL?
private let background = UIView()
private let imageView = UIImageView()
//Holder to show the shadow around the image because the UIImageView is clipsToBounds=true
private let imageHolder = UIView()
private let titleLabel = UILabel()
private let domainLabel = UILabel()
private let deleteButton = UIButton(type: .system)
var isEditing: Bool = false {
didSet {
if isEditing {
let randomNumber = CGFloat(arc4random_uniform(500)) / 500 + 0.5
let angle = CGFloat(0.06 * randomNumber)
let left = CATransform3DMakeRotation(angle, 0, 0, 1)
let right = CATransform3DMakeRotation(-angle, 0, 0, 1)
let animation = CAKeyframeAnimation(keyPath: "transform")
animation.values = [left, right]
animation.autoreverses = true
animation.duration = 0.125
animation.repeatCount = Float(Int.max)
contentView.layer.add(animation, forKey: jiggleAnimationKey)
} else {
contentView.layer.removeAnimation(forKey: jiggleAnimationKey)
}
deleteButton.isHidden = !isEditing
}
}
weak var delegate: DappViewCellDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
background.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(background)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageHolder.addSubview(imageView)
let stackView = [
.spacer(height: marginAroundImage),
imageHolder,
.spacer(height: 9),
titleLabel,
domainLabel,
].asStackView(axis: .vertical, spacing: 0, alignment: .center)
stackView.translatesAutoresizingMaskIntoConstraints = false
background.addSubview(stackView)
deleteButton.addTarget(self, action: #selector(deleteDapp), for: .touchUpInside)
deleteButton.isHidden = true
deleteButton.translatesAutoresizingMaskIntoConstraints = false
background.addSubview(deleteButton)
let xMargin = CGFloat(0)
let yMargin = CGFloat(0)
NSLayoutConstraint.activate([
background.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: xMargin),
background.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -xMargin),
background.topAnchor.constraint(equalTo: contentView.topAnchor, constant: yMargin),
background.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -yMargin),
stackView.leadingAnchor.constraint(equalTo: background.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: background.trailingAnchor),
stackView.topAnchor.constraint(equalTo: background.topAnchor),
stackView.bottomAnchor.constraint(equalTo: background.bottomAnchor),
imageHolder.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: marginAroundImage),
imageHolder.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -marginAroundImage),
imageHolder.widthAnchor.constraint(equalTo: imageHolder.heightAnchor),
imageView.leadingAnchor.constraint(equalTo: imageHolder.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: imageHolder.trailingAnchor),
imageView.topAnchor.constraint(equalTo: imageHolder.topAnchor),
imageView.bottomAnchor.constraint(equalTo: imageHolder.bottomAnchor),
deleteButton.rightAnchor.constraint(equalTo: contentView.rightAnchor),
//Some allowance so the delete button is not clipped while jiggling
deleteButton.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5),
deleteButton.widthAnchor.constraint(equalToConstant: 22),
deleteButton.widthAnchor.constraint(equalTo: deleteButton.heightAnchor),
])
addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(longPressedDappCell)))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
imageHolder.layer.cornerRadius = imageHolder.frame.size.width / 2
imageView.layer.cornerRadius = imageView.frame.size.width / 2
imageHolder.layer.shadowPath = UIBezierPath(roundedRect: imageHolder.bounds, cornerRadius: imageHolder.layer.cornerRadius).cgPath
}
func configure(viewModel: DappViewCellViewModel) {
self.viewModel = viewModel
contentView.backgroundColor = viewModel.backgroundColor
background.backgroundColor = viewModel.backgroundColor
background.clipsToBounds = true
imageHolder.layer.shadowColor = viewModel.imageViewShadowColor.cgColor
imageHolder.layer.shadowOffset = viewModel.imageViewShadowOffset
imageHolder.layer.shadowOpacity = viewModel.imageViewShadowOpacity
imageHolder.layer.shadowRadius = viewModel.imageViewShadowRadius
imageView.backgroundColor = viewModel.backgroundColor
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.kf.setImage(with: viewModel.imageUrl, placeholder: viewModel.fallbackImage)
deleteButton.tintColor = Colors.appRed
deleteButton.imageView?.tintColor = Colors.appRed
deleteButton.setImage(R.image.onboarding_failed(), for: .normal)
titleLabel.textAlignment = .center
titleLabel.textColor = viewModel.titleColor
titleLabel.font = viewModel.titleFont
titleLabel.text = viewModel.title
domainLabel.textAlignment = .center
domainLabel.textColor = viewModel.domainNameColor
domainLabel.font = viewModel.domainNameFont
domainLabel.text = viewModel.domainName
}
@objc func deleteDapp() {
guard let dapp = viewModel?.dapp else { return }
delegate?.didTapDelete(dapp: dapp, inCell: self)
}
@objc private func longPressedDappCell() {
guard let dapp = viewModel?.dapp else { return }
delegate?.didLongPressed(dapp: dapp, onCell: self)
}
}

@ -0,0 +1,44 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
class DappsAutoCompletionCell: UITableViewCell {
static let identifier = "DappsAutoCompletionCell"
let titleLabel = UILabel()
let descriptionLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
let stackView = [
titleLabel,
descriptionLabel
].asStackView(axis: .vertical)
stackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 28),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -28),
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: DappsAutoCompletionCellViewModel) {
backgroundColor = viewModel.backgroundColor
contentView.backgroundColor = viewModel.backgroundColor
titleLabel.font = viewModel.nameFont
titleLabel.attributedText = viewModel.name
descriptionLabel.font = viewModel.descriptionFont
descriptionLabel.textColor = viewModel.descriptionColor
descriptionLabel.text = viewModel.description
}
}

@ -0,0 +1,43 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
class DappsHomeEmptyView: UIView {
private let header = DappsHomeHeaderView()
private let label = UILabel()
init() {
super.init(frame: .zero)
header.translatesAutoresizingMaskIntoConstraints = false
addSubview(header)
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate([
header.trailingAnchor.constraint(equalTo: trailingAnchor),
header.leadingAnchor.constraint(equalTo: leadingAnchor),
header.topAnchor.constraint(equalTo: topAnchor, constant: 50),
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 50),
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -50),
label.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: DappsHomeEmptyViewViewModel) {
backgroundColor = viewModel.headerViewViewModel.backgroundColor
header.configure(viewModel: viewModel.headerViewViewModel)
label.font = Fonts.light(size: 18)
label.textAlignment = .center
label.numberOfLines = 0
label.text = viewModel.title
}
}

@ -0,0 +1,47 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
class DappsHomeHeaderView: UIView {
private let stackView = [].asStackView(axis: .vertical, contentHuggingPriority: .required, alignment: .center)
private let buttonsStackView = [].asStackView(distribution: .equalSpacing, contentHuggingPriority: .required)
private let logoImage = UIImageView()
private let titleLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubviews([
logoImage,
.spacer(height: 20),
titleLabel,
])
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
logoImage.widthAnchor.constraint(equalToConstant: 80),
logoImage.widthAnchor.constraint(equalTo: logoImage.heightAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: DappsHomeHeaderViewViewModel) {
backgroundColor = viewModel.backgroundColor
logoImage.contentMode = .scaleAspectFit
logoImage.image = viewModel.logo
titleLabel.font = viewModel.titleFont
titleLabel.text = viewModel.title
}
}

@ -0,0 +1,59 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
class DappsHomeViewControllerHeaderView: UICollectionReusableView {
private let stackView = [].asStackView(axis: .vertical, contentHuggingPriority: .required, alignment: .center)
private let headerView = DappsHomeHeaderView()
let myDappsButton = DappButton()
let discoverDappsButton = DappButton()
let historyButton = DappButton()
override init(frame: CGRect) {
super.init(frame: frame)
let buttonsStackView = [
myDappsButton,
.spacerWidth(40),
discoverDappsButton,
.spacerWidth(40),
historyButton
].asStackView(distribution: .equalSpacing, contentHuggingPriority: .required)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubviews([
headerView,
.spacer(height: 30),
buttonsStackView,
])
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor, constant: 50),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -50),
myDappsButton.widthAnchor.constraint(equalTo: discoverDappsButton.widthAnchor),
myDappsButton.widthAnchor.constraint(equalTo: historyButton.widthAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: DappsHomeViewControllerHeaderViewViewModel = .init()) {
backgroundColor = viewModel.backgroundColor
headerView.configure(viewModel: .init(title: viewModel.title))
myDappsButton.configure(viewModel: .init(image: viewModel.myDappsButtonImage, title: viewModel.myDappsButtonTitle))
discoverDappsButton.configure(viewModel: .init(image: viewModel.discoverButtonImage, title: viewModel.discoverButtonTitle))
historyButton.configure(viewModel: .init(image: viewModel.historyButtonImage, title: viewModel.historyButtonTitle))
}
}

@ -0,0 +1,121 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
protocol DiscoverDappCellDelegate: class {
func onAdd(dapp: Dapp, inCell cell: DiscoverDappCell)
func onRemove(dapp: Dapp, inCell cell: DiscoverDappCell)
}
class DiscoverDappCell: UITableViewCell {
static let identifier = "DiscoverDappCell"
private let addButton = UIButton(type: .system)
private let removeButton = UIButton(type: .system)
private var viewModel: DiscoverDappCellViewModel?
private let iconImageViewHolder = UIView()
let iconImageView = UIImageView()
let titleLabel = UILabel()
let descriptionLabel = UILabel()
weak var delegate: DiscoverDappCellDelegate?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
let labelsVerticalStackView = [
titleLabel,
descriptionLabel
].asStackView(axis: .vertical)
addButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
removeButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
iconImageView.translatesAutoresizingMaskIntoConstraints = false
iconImageViewHolder.addSubview(iconImageView)
let mainStackView = [.spacerWidth(29), iconImageViewHolder, .spacerWidth(26), labelsVerticalStackView, .spacerWidth(26), addButton, removeButton, .spacerWidth(29)].asStackView(axis: .horizontal, alignment: .center)
mainStackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(mainStackView)
NSLayoutConstraint.activate([
mainStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
mainStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
mainStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 7),
mainStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -7),
iconImageView.widthAnchor.constraint(equalToConstant: 44),
iconImageView.widthAnchor.constraint(equalTo: iconImageView.heightAnchor),
iconImageView.leadingAnchor.constraint(equalTo: iconImageViewHolder.leadingAnchor),
iconImageView.trailingAnchor.constraint(equalTo: iconImageViewHolder.trailingAnchor),
iconImageView.topAnchor.constraint(equalTo: iconImageViewHolder.topAnchor),
iconImageView.bottomAnchor.constraint(equalTo: iconImageViewHolder.bottomAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: DiscoverDappCellViewModel) {
self.viewModel = viewModel
backgroundColor = viewModel.backgroundColor
contentView.backgroundColor = viewModel.backgroundColor
addButton.addTarget(self, action: #selector(onTappedAdd), for: .touchUpInside)
addButton.setTitle(R.string.localizable.addButtonTitle().localizedUppercase, for: .normal)
addButton.isHidden = viewModel.isAddButtonHidden
addButton.titleLabel?.font = viewModel.addRemoveButtonFont
addButton.contentEdgeInsets = viewModel.addRemoveButtonContentEdgeInsets
addButton.borderColor = viewModel.addRemoveButtonBorderColor
addButton.borderWidth = viewModel.addRemoveButtonBorderWidth
addButton.cornerRadius = viewModel.addRemoveButtonBorderCornerRadius
removeButton.addTarget(self, action: #selector(onTappedRemove), for: .touchUpInside)
removeButton.setTitle(R.string.localizable.removeButtonTitle().localizedUppercase, for: .normal)
removeButton.isHidden = viewModel.isRemoveButtonHidden
removeButton.titleLabel?.font = viewModel.addRemoveButtonFont
removeButton.contentEdgeInsets = viewModel.addRemoveButtonContentEdgeInsets
removeButton.borderColor = viewModel.addRemoveButtonBorderColor
removeButton.borderWidth = viewModel.addRemoveButtonBorderWidth
removeButton.cornerRadius = viewModel.addRemoveButtonBorderCornerRadius
iconImageViewHolder.layer.shadowColor = viewModel.imageViewShadowColor.cgColor
iconImageViewHolder.layer.shadowOffset = viewModel.imageViewShadowOffset
iconImageViewHolder.layer.shadowOpacity = viewModel.imageViewShadowOpacity
iconImageViewHolder.layer.shadowRadius = viewModel.imageViewShadowRadius
iconImageView.backgroundColor = viewModel.backgroundColor
iconImageView.contentMode = .scaleAspectFill
iconImageView.clipsToBounds = true
iconImageView.kf.setImage(with: viewModel.imageUrl, placeholder: viewModel.fallbackImage)
titleLabel.font = viewModel.nameFont
titleLabel.textColor = viewModel.nameColor
titleLabel.text = viewModel.name
descriptionLabel.font = viewModel.descriptionFont
descriptionLabel.textColor = viewModel.descriptionColor
descriptionLabel.text = viewModel.description
//TODO ugly hack to get the image view's frame. Can't figure out a good point to retrieve the correct frame otherwise
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
self.iconImageView.layer.cornerRadius = self.iconImageView.frame.size.width / 2
self.iconImageViewHolder.layer.cornerRadius = self.iconImageViewHolder.frame.size.width / 2
self.iconImageViewHolder.layer.shadowPath = UIBezierPath(roundedRect: self.iconImageViewHolder.bounds, cornerRadius: self.iconImageViewHolder.layer.cornerRadius).cgPath
}
}
@objc private func onTappedAdd() {
guard let dapp = viewModel?.dapp else { return }
delegate?.onAdd(dapp: dapp, inCell: self)
}
@objc private func onTappedRemove() {
guard let dapp = viewModel?.dapp else { return }
delegate?.onRemove(dapp: dapp, inCell: self)
}
}

@ -0,0 +1,79 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
class MyDappCell: UITableViewCell {
static let identifier = "MyDappCell"
private let iconImageViewHolder = UIView()
private var viewModel: MyDappCellViewModel?
let iconImageView = UIImageView()
let titleLabel = UILabel()
let urlLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
let labelsVerticalStackView = [
titleLabel,
urlLabel].asStackView(axis: .vertical)
iconImageView.translatesAutoresizingMaskIntoConstraints = false
iconImageViewHolder.addSubview(iconImageView)
let mainStackView = [.spacerWidth(29), iconImageViewHolder, .spacerWidth(26), labelsVerticalStackView, .spacerWidth(29)].asStackView(axis: .horizontal, alignment: .center)
mainStackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(mainStackView)
NSLayoutConstraint.activate([
mainStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
mainStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
mainStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 7),
mainStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -7),
iconImageView.widthAnchor.constraint(equalToConstant: 44),
iconImageView.widthAnchor.constraint(equalTo: iconImageView.heightAnchor),
iconImageView.leadingAnchor.constraint(equalTo: iconImageViewHolder.leadingAnchor),
iconImageView.trailingAnchor.constraint(equalTo: iconImageViewHolder.trailingAnchor),
iconImageView.topAnchor.constraint(equalTo: iconImageViewHolder.topAnchor),
iconImageView.bottomAnchor.constraint(equalTo: iconImageViewHolder.bottomAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: MyDappCellViewModel) {
self.viewModel = viewModel
titleLabel.font = viewModel.nameFont
titleLabel.textColor = viewModel.nameColor
titleLabel.text = viewModel.name
urlLabel.font = viewModel.domainNameFont
urlLabel.textColor = viewModel.domainNameColor
urlLabel.text = viewModel.domainName
iconImageViewHolder.layer.shadowColor = viewModel.imageViewShadowColor.cgColor
iconImageViewHolder.layer.shadowOffset = viewModel.imageViewShadowOffset
iconImageViewHolder.layer.shadowOpacity = viewModel.imageViewShadowOpacity
iconImageViewHolder.layer.shadowRadius = viewModel.imageViewShadowRadius
iconImageView.backgroundColor = viewModel.backgroundColor
iconImageView.contentMode = .scaleAspectFill
iconImageView.clipsToBounds = true
iconImageView.kf.setImage(with: viewModel.imageUrl, placeholder: viewModel.fallbackImage)
//TODO ugly hack to get the image view's frame. Can't figure out a good point to retrieve the correct frame otherwise
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
self.iconImageView.layer.cornerRadius = self.iconImageView.frame.size.width / 2
self.iconImageViewHolder.layer.cornerRadius = self.iconImageViewHolder.frame.size.width / 2
self.iconImageViewHolder.layer.shadowPath = UIBezierPath(roundedRect: self.iconImageViewHolder.bounds, cornerRadius: self.iconImageViewHolder.layer.cornerRadius).cgPath
}
}
}

@ -0,0 +1,75 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
protocol MyDappsViewControllerHeaderViewDelegate: class {
func didEnterEditMode(inHeaderView headerView: MyDappsViewControllerHeaderView)
func didExitEditMode(inHeaderView headerView: MyDappsViewControllerHeaderView)
}
class MyDappsViewControllerHeaderView: UIView {
private let header = DappsHomeHeaderView()
private let toggleEditModeButton = UIButton(type: .system)
private var isEditing = false {
didSet {
if isEditing {
configure(viewModel: .init(title: viewModel?.title ?? ""))
delegate?.didEnterEditMode(inHeaderView: self)
} else {
configure(viewModel: .init(title: viewModel?.title ?? ""))
delegate?.didExitEditMode(inHeaderView: self)
}
}
}
private var viewModel: DappsHomeHeaderViewViewModel?
weak var delegate: MyDappsViewControllerHeaderViewDelegate?
init() {
super.init(frame: .zero)
header.translatesAutoresizingMaskIntoConstraints = false
addSubview(header)
toggleEditModeButton.addTarget(self, action: #selector(toggleEditMode), for: .touchUpInside)
toggleEditModeButton.translatesAutoresizingMaskIntoConstraints = false
addSubview(toggleEditModeButton)
NSLayoutConstraint.activate([
header.trailingAnchor.constraint(equalTo: trailingAnchor),
header.leadingAnchor.constraint(equalTo: leadingAnchor),
header.topAnchor.constraint(equalTo: topAnchor, constant: 50),
header.bottomAnchor.constraint(equalTo: toggleEditModeButton.topAnchor, constant: -30),
toggleEditModeButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
toggleEditModeButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: DappsHomeHeaderViewViewModel) {
self.viewModel = viewModel
backgroundColor = viewModel.backgroundColor
header.configure(viewModel: viewModel)
if isEditing {
toggleEditModeButton.setTitle(R.string.localizable.done().localizedUppercase, for: .normal)
} else {
toggleEditModeButton.setTitle(R.string.localizable.editButtonTitle().localizedUppercase, for: .normal)
}
toggleEditModeButton.titleLabel?.font = Fonts.bold(size: 12)
}
@objc private func toggleEditMode() {
isEditing = !isEditing
}
func exitEditMode() {
isEditing = false
}
}

@ -8,4 +8,8 @@ class ScreenChecker {
let iPhone6Width = CGFloat(375)
return UIScreen.main.bounds.width < iPhone6Width
}
func isBigScreen() -> Bool {
return UIScreen.main.bounds.width >= 768 && UIScreen.main.bounds.height >= 768
}
}

@ -71,7 +71,9 @@ extension UIViewController {
}
public var isTopViewController: Bool {
if navigationController != nil {
if navigationController != nil && navigationController?.tabBarController != nil {
return (tabBarController?.selectedViewController as? UINavigationController)?.visibleViewController == self
} else if navigationController != nil {
return navigationController?.visibleViewController === self
} else if tabBarController != nil {
return tabBarController?.selectedViewController == self && presentedViewController == nil

@ -207,8 +207,8 @@ class InCoordinator: Coordinator {
return coordinator
}
private func createBrowserCoordinator(session: WalletSession, keystore: Keystore, realm: Realm) -> BrowserCoordinator {
let coordinator = BrowserCoordinator(session: session, keystore: keystore, sharedRealm: realm)
private func createBrowserCoordinator(session: WalletSession, keystore: Keystore, realm: Realm) -> DappBrowserCoordinator {
let coordinator = DappBrowserCoordinator(session: session, keystore: keystore, sharedRealm: realm)
coordinator.delegate = self
coordinator.start()
coordinator.rootViewController.tabBarItem = UITabBarItem(title: R.string.localizable.browserTabbarItemTitle(), image: R.image.dapps_icon(), selectedImage: nil)
@ -532,13 +532,13 @@ extension InCoordinator: CanOpenURL {
balanceCoordinator: balance
)
let browserCoordinator = BrowserCoordinator(session: session, keystore: keystore, sharedRealm: realm)
let browserCoordinator = DappBrowserCoordinator(session: session, keystore: keystore, sharedRealm: realm)
browserCoordinator.delegate = self
browserCoordinator.start()
addCoordinator(browserCoordinator)
let controller = browserCoordinator.navigationController
browserCoordinator.openURL(url)
browserCoordinator.open(url: url, browserOnly: true, animated: false)
viewController.present(controller, animated: true, completion: nil)
}
@ -627,15 +627,10 @@ extension InCoordinator: PromptBackupCoordinatorDelegate {
}
}
extension InCoordinator: BrowserCoordinatorDelegate {
func didSentTransaction(transaction: SentTransaction, in coordinator: BrowserCoordinator) {
extension InCoordinator: DappBrowserCoordinatorDelegate{
func didSentTransaction(transaction: SentTransaction, inCoordinator coordinator: DappBrowserCoordinator) {
handlePendingTransaction(transaction: transaction)
}
func didPressCloseButton(in coordinator: BrowserCoordinator) {
coordinator.navigationController.dismiss(animated: true)
removeCoordinator(coordinator)
}
}
struct NoTokenError: LocalizedError {

@ -132,8 +132,8 @@
"browser.reload.button.title" = "Reload";
"browser.addbookmark.button.title" = "Add Bookmark";
"browser.tabbar.item.title" = "Browser";
"browser.history.confirm.delete.title" = "Are you sure you would like to delete?";
"browser.noHistory.label.title" = "No history yet";
"browser.history.confirm.delete.title" = "Remove History?";
"browser.noHistory.label.title" = "Your browser history appears here.";
"browser.bookmarks.confirm.delete.title" = "Are you sure you would like to delete this bookmark?";
"browser.noBookmarks.label.title" = "No bookmarks yet!";
"browser.url.textfield.placeholder" = "Search or enter website URL";
@ -306,3 +306,19 @@
"camera.qrCode.denied.prompt.button" = "Open Settings";
"openSeaNonFungibleTokens.url.open" = "Open on %@";
"address.ens.labelMessage" = "Ethereum address or ENS name";
"dappBrowser.title" = "Welcome to the decentralised web";
"myDappsButton.imageLabel" = "My Dapps";
"discoverDappsButton.imageLabel" = "Discover Dapps";
"historyButton.imageLabel" = "My History";
"addButton.title" = "Add";
"removeButton.title" = "Remove";
"clearButton.title" = "Clear";
"editButton.title" = "Edit";
"dappBrowser.browserHistory" = "Browser History";
"dappBrowser.clearHistory" = "Clear History?";
"dappBrowser.clearHistory.prompt" = "Are you sure you want to clear your history?";
"dappBrowser.myDapps.empty" = "You have no dapps displayed yet. Start by surfing the browser.";
"dappBrowser.myDapps.edit" = "Edit Dapp";
"dappBrowser.myDapps.edit.title.label" = "Title";
"dappBrowser.myDapps.edit.url.label" = "Address";
"dappBrowser.clearMyDapps" = "Remove Dapp?";

@ -132,8 +132,8 @@
"browser.reload.button.title" = "Volver a cargar";
"browser.addbookmark.button.title" = "Añadir marcador";
"browser.tabbar.item.title" = "Navegador";
"browser.history.confirm.delete.title" = "Are you sure you would like to delete?";
"browser.noHistory.label.title" = "No history yet";
"browser.history.confirm.delete.title" = "Remove History?";
"browser.noHistory.label.title" = "Your browser history appears here.";
"browser.bookmarks.confirm.delete.title" = "Are you sure you would like to delete this bookmark?";
"browser.noBookmarks.label.title" = "No bookmarks yet!";
"browser.url.textfield.placeholder" = "Search or enter website URL";
@ -306,3 +306,19 @@
"camera.qrCode.denied.prompt.button" = "Abrir Ajustes";
"openSeaNonFungibleTokens.url.open" = "Abrir en %@";
"address.ens.labelMessage" = "Ethereum dirección o ENS nombre";
"dappBrowser.title" = "Welcome to the decentralised web";
"myDappsButton.imageLabel" = "My Dapps";
"discoverDappsButton.imageLabel" = "Discover Dapps";
"historyButton.imageLabel" = "My History";
"addButton.title" = "Add";
"removeButton.title" = "Remove";
"clearButton.title" = "Clear";
"editButton.title" = "Edit";
"dappBrowser.browserHistory" = "Browser History";
"dappBrowser.clearHistory" = "Clear History?";
"dappBrowser.clearHistory.prompt" = "Are you sure you want to clear your history?";
"dappBrowser.myDapps.empty" = "You have no dapps displayed yet. Start by surfing the browser.";
"dappBrowser.myDapps.edit" = "Edit Dapp";
"dappBrowser.myDapps.edit.title.label" = "Title";
"dappBrowser.myDapps.edit.url.label" = "Address";
"dappBrowser.clearMyDapps" = "Remove Dapp?";

@ -132,8 +132,8 @@
"browser.reload.button.title" = "再読み込み";
"browser.addbookmark.button.title" = "ブックマークを追加";
"browser.tabbar.item.title" = "ブラウザ";
"browser.history.confirm.delete.title" = "Are you sure you would like to delete?";
"browser.noHistory.label.title" = "No history yet";
"browser.history.confirm.delete.title" = "Remove History?";
"browser.noHistory.label.title" = "Your browser history appears here.";
"browser.bookmarks.confirm.delete.title" = "Are you sure you would like to delete this bookmark?";
"browser.noBookmarks.label.title" = "No bookmarks yet!";
"browser.url.textfield.placeholder" = "Search or enter website URL";
@ -306,3 +306,19 @@
"camera.qrCode.denied.prompt.button" = "設定を開く";
"openSeaNonFungibleTokens.url.open" = "%@ で開く";
"address.ens.labelMessage" = "Ethereum address or ENS name";
"dappBrowser.title" = "Welcome to the decentralised web";
"myDappsButton.imageLabel" = "My Dapps";
"discoverDappsButton.imageLabel" = "Discover Dapps";
"historyButton.imageLabel" = "My History";
"addButton.title" = "Add";
"removeButton.title" = "Remove";
"clearButton.title" = "Clear";
"editButton.title" = "Edit";
"dappBrowser.browserHistory" = "Browser History";
"dappBrowser.clearHistory" = "Clear History?";
"dappBrowser.clearHistory.prompt" = "Are you sure you want to clear your history?";
"dappBrowser.myDapps.empty" = "You have no dapps displayed yet. Start by surfing the browser.";
"dappBrowser.myDapps.edit" = "Edit Dapp";
"dappBrowser.myDapps.edit.title.label" = "Title";
"dappBrowser.myDapps.edit.url.label" = "Address";
"dappBrowser.clearMyDapps" = "Remove Dapp?";

@ -132,8 +132,8 @@
"browser.reload.button.title" = "다시 불러오기";
"browser.addbookmark.button.title" = "북마크 추가";
"browser.tabbar.item.title" = "브라우저";
"browser.history.confirm.delete.title" = "Are you sure you would like to delete?";
"browser.noHistory.label.title" = "No history yet";
"browser.history.confirm.delete.title" = "Remove History?";
"browser.noHistory.label.title" = "Your browser history appears here.";
"browser.bookmarks.confirm.delete.title" = "Are you sure you would like to delete this bookmark?";
"browser.noBookmarks.label.title" = "No bookmarks yet!";
"browser.url.textfield.placeholder" = "Search or enter website URL";
@ -306,3 +306,19 @@
"camera.qrCode.denied.prompt.button" = "설정 열기";
"openSeaNonFungibleTokens.url.open" = "%@에서 열기";
"address.ens.labelMessage" = "Ethereum address or ENS name";
"dappBrowser.title" = "Welcome to the decentralised web";
"myDappsButton.imageLabel" = "My Dapps";
"discoverDappsButton.imageLabel" = "Discover Dapps";
"historyButton.imageLabel" = "My History";
"addButton.title" = "Add";
"removeButton.title" = "Remove";
"clearButton.title" = "Clear";
"editButton.title" = "Edit";
"dappBrowser.browserHistory" = "Browser History";
"dappBrowser.clearHistory" = "Clear History?";
"dappBrowser.clearHistory.prompt" = "Are you sure you want to clear your history?";
"dappBrowser.myDapps.empty" = "You have no dapps displayed yet. Start by surfing the browser.";
"dappBrowser.myDapps.edit" = "Edit Dapp";
"dappBrowser.myDapps.edit.title.label" = "Title";
"dappBrowser.myDapps.edit.url.label" = "Address";
"dappBrowser.clearMyDapps" = "Remove Dapp?";

@ -132,7 +132,7 @@
"browser.reload.button.title" = "刷新";
"browser.addbookmark.button.title" = "添加书签";
"browser.tabbar.item.title" = "浏览器";
"browser.history.confirm.delete.title" = "确定要删除吗?";
"browser.history.confirm.delete.title" = "确定要删除历史记录吗?";
"browser.noHistory.label.title" = "目前没有历史记录";
"browser.bookmarks.confirm.delete.title" = "确定要删除此书签吗?";
"browser.noBookmarks.label.title" = "目前没有书签";
@ -306,3 +306,19 @@
"camera.qrCode.denied.prompt.button" = "打开设置";
"openSeaNonFungibleTokens.url.open" = "在 %@ 应用内打开";
"address.ens.labelMessage" = "Ethereum address or ENS name";
"dappBrowser.title" = "Welcome to the decentralised web";
"myDappsButton.imageLabel" = "My Dapps";
"discoverDappsButton.imageLabel" = "Discover Dapps";
"historyButton.imageLabel" = "My History";
"addButton.title" = "Add";
"removeButton.title" = "Remove";
"clearButton.title" = "Clear";
"editButton.title" = "Edit";
"dappBrowser.browserHistory" = "Browser History";
"dappBrowser.clearHistory" = "Clear History?";
"dappBrowser.clearHistory.prompt" = "Are you sure you want to clear your history?";
"dappBrowser.myDapps.empty" = "You have no dapps displayed yet. Start by surfing the browser.";
"dappBrowser.myDapps.edit" = "Edit Dapp";
"dappBrowser.myDapps.edit.title.label" = "Title";
"dappBrowser.myDapps.edit.url.label" = "Address";
"dappBrowser.clearMyDapps" = "Remove Dapp?";

@ -36,7 +36,6 @@ func applyStyle() {
UIRefreshControl.appearance().tintColor = Colors.appWhite
UIImageView.appearance().tintColor = Colors.lightBlue
UIImageView.appearance(whenContainedInInstancesOf: [BrowserNavigationBar.self]).tintColor = .white
BalanceTitleView.appearance().titleTextColor = UIColor.white
BalanceTitleView.appearance().subTitleTextColor = UIColor(white: 0.9, alpha: 1)
@ -100,4 +99,12 @@ extension UISegmentedControl {
enum Metrics {
static let greenButtonHeight = CGFloat(48)
enum DappsHome {
enum Icon {
static let shadowOffset = CGSize(width: 0, height: 0)
static let shadowOpacity = Float(0.15)
static let shadowRadius = CGFloat(6)
}
}
}

@ -0,0 +1,47 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
///Wrap a child view and add insets around it. Useful for UITableView header/footers
class BoxView<T: UIView>: UIView {
let view: T
private var leftConstraint: NSLayoutConstraint?
private var rightConstraint: NSLayoutConstraint?
private var topConstraint: NSLayoutConstraint?
private var bottomConstraint: NSLayoutConstraint?
var insets: UIEdgeInsets {
didSet {
updateConstraintsConstants()
}
}
init(view: T, insets: UIEdgeInsets = .zero) {
self.view = view
self.insets = insets
super.init(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
addSubview(view)
leftConstraint = view.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0)
rightConstraint = trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0)
topConstraint = view.topAnchor.constraint(equalTo: topAnchor, constant: 0)
bottomConstraint = bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
if let leftConstraint = leftConstraint, let rightConstraint = rightConstraint, let topConstraint = topConstraint, let bottomConstraint = bottomConstraint {
NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint])
}
updateConstraintsConstants()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func updateConstraintsConstants() {
leftConstraint?.constant = insets.left
rightConstraint?.constant = insets.right
topConstraint?.constant = insets.top
bottomConstraint?.constant = insets.bottom
}
}

@ -27,7 +27,7 @@ class InCoordinatorTests: XCTestCase {
XCTAssert(tabbarController?.viewControllers!.count == 4)
XCTAssert((tabbarController?.viewControllers?[0] as? UINavigationController)?.viewControllers[0] is TokensViewController)
XCTAssert((tabbarController?.viewControllers?[1] as? UINavigationController)?.viewControllers[0] is TransactionsViewController)
XCTAssert((tabbarController?.viewControllers?[2] as? UINavigationController)?.viewControllers[0] is MasterBrowserViewController)
XCTAssert((tabbarController?.viewControllers?[2] as? UINavigationController)?.viewControllers[0] is DappsHomeViewController)
XCTAssert((tabbarController?.viewControllers?[3] as? UINavigationController)?.viewControllers[0] is SettingsViewController)
}

Loading…
Cancel
Save