From 8f51f4f0d969cb3c37585ad6409816ab73da2238 Mon Sep 17 00:00:00 2001 From: Hwee-Boon Yar Date: Mon, 10 Dec 2018 15:22:34 +0800 Subject: [PATCH] Implement redesigned Dapp browser and new Dapp home screens --- AlphaWallet.xcodeproj/project.pbxproj | 184 ++++-- .../discoverDapps.imageset/Contents.json | 23 + .../discoverDapps.imageset/discoverDapps.png | Bin 0 -> 1604 bytes .../discoverDapps@2x.png | Bin 0 -> 3505 bytes .../discoverDapps@3x.png | Bin 0 -> 5578 bytes .../history.imageset/Contents.json | 23 + .../history.imageset/history.png | Bin 0 -> 1728 bytes .../history.imageset/history@2x.png | Bin 0 -> 3589 bytes .../history.imageset/history@3x.png | Bin 0 -> 5688 bytes .../myDapps.imageset/Contents.json | 23 + .../myDapps.imageset/myDapps.png | Bin 0 -> 1038 bytes .../myDapps.imageset/myDapps@2x.png | Bin 0 -> 2078 bytes .../myDapps.imageset/myDapps@3x.png | Bin 0 -> 3366 bytes .../Coordinators/BrowserCoordinator.swift | 429 ------------- .../Coordinators/DappBrowserCoordinator.swift | 574 ++++++++++++++++++ .../Browser/Storage/BookmarksStore.swift | 39 +- AlphaWallet/Browser/Types/BrowserAction.swift | 13 - AlphaWallet/Browser/Types/Favicon.swift | 4 +- .../BookmarkViewController.swift | 114 ---- .../BrowserHistoryViewController.swift | 171 ++++++ .../BrowserViewController.swift | 121 +--- .../DappsAutoCompletionViewController.swift | 85 +++ .../DappsHomeViewController.swift | 192 ++++++ .../DiscoverDappsViewController.swift | 174 ++++++ .../EditMyDappViewController.swift | 207 +++++++ .../HistoryViewController.swift | 113 ---- .../MasterBrowserViewController.swift | 136 ----- .../MyDappsViewController.swift | 165 +++++ .../Browser/ViewModel/BookmarkViewModel.swift | 26 - .../ViewModel/BookmarksViewModel.swift | 31 - .../BrowserHistoryCellViewModel.swift | 60 ++ AlphaWallet/Browser/ViewModel/Dapp.swift | 10 + .../ViewModel/DappButtonViewModel.swift | 17 + .../ViewModel/DappViewCellViewModel.swift | 61 ++ AlphaWallet/Browser/ViewModel/Dapps.swift | 35 ++ .../DappsAutoCompletionCellViewModel.swift | 44 ++ ...utoCompletionViewControllerViewModel.swift | 18 + .../DappsHomeEmptyViewViewModel.swift | 8 + .../DappsHomeHeaderViewViewModel.swift | 20 + ...omeViewControllerHeaderViewViewModel.swift | 38 ++ .../DappsHomeViewControllerViewModel.swift | 20 + .../ViewModel/DiscoverDappCellViewModel.swift | 99 +++ ...DiscoverDappsViewControllerViewModel.swift | 15 + .../EditMyDappViewControllerViewModel.swift | 152 +++++ .../ViewModel/HistoriesViewModel.swift | 5 +- .../Browser/ViewModel/HistoryViewModel.swift | 27 - .../ViewModel/MarketplaceViewModel.swift | 9 - .../ViewModel/MyDappCellViewModel.swift | 80 +++ .../MyDappsViewControllerViewModel.swift | 19 + .../Browser/Views/BrowserHistoryCell.swift | 82 +++ ...owserHistoryViewControllerHeaderView.swift | 52 ++ .../Browser/Views/BrowserNavigationBar.swift | 120 ---- .../Views/DappBrowserNavigationBar.swift | 298 +++++++++ AlphaWallet/Browser/Views/DappButton.swift | 43 ++ AlphaWallet/Browser/Views/DappViewCell.swift | 158 +++++ .../Views/DappsAutoCompletionCell.swift | 44 ++ .../Browser/Views/DappsHomeEmptyView.swift | 43 ++ .../Browser/Views/DappsHomeHeaderView.swift | 47 ++ .../DappsHomeViewControllerHeaderView.swift | 59 ++ .../Browser/Views/DiscoverDappCell.swift | 121 ++++ AlphaWallet/Browser/Views/MyDappCell.swift | 79 +++ .../MyDappsViewControllerHeaderView.swift | 75 +++ AlphaWallet/Core/Helpers/ScreenChecker.swift | 4 + AlphaWallet/Extensions/UIViewController.swift | 4 +- AlphaWallet/InCoordinator.swift | 17 +- .../Localization/en.lproj/Localizable.strings | 20 +- .../Localization/es.lproj/Localizable.strings | 20 +- .../Localization/ja.lproj/Localizable.strings | 20 +- .../Localization/ko.lproj/Localizable.strings | 20 +- .../zh-Hans.lproj/Localizable.strings | 18 +- AlphaWallet/Style/AppStyle.swift | 9 +- AlphaWallet/UI/BoxView.swift | 47 ++ .../Coordinators/InCoordinatorTests.swift | 2 +- 73 files changed, 3795 insertions(+), 1191 deletions(-) create mode 100644 AlphaWallet/Assets.xcassets/discoverDapps.imageset/Contents.json create mode 100644 AlphaWallet/Assets.xcassets/discoverDapps.imageset/discoverDapps.png create mode 100644 AlphaWallet/Assets.xcassets/discoverDapps.imageset/discoverDapps@2x.png create mode 100644 AlphaWallet/Assets.xcassets/discoverDapps.imageset/discoverDapps@3x.png create mode 100644 AlphaWallet/Assets.xcassets/history.imageset/Contents.json create mode 100644 AlphaWallet/Assets.xcassets/history.imageset/history.png create mode 100644 AlphaWallet/Assets.xcassets/history.imageset/history@2x.png create mode 100644 AlphaWallet/Assets.xcassets/history.imageset/history@3x.png create mode 100644 AlphaWallet/Assets.xcassets/myDapps.imageset/Contents.json create mode 100644 AlphaWallet/Assets.xcassets/myDapps.imageset/myDapps.png create mode 100644 AlphaWallet/Assets.xcassets/myDapps.imageset/myDapps@2x.png create mode 100644 AlphaWallet/Assets.xcassets/myDapps.imageset/myDapps@3x.png delete mode 100644 AlphaWallet/Browser/Coordinators/BrowserCoordinator.swift create mode 100644 AlphaWallet/Browser/Coordinators/DappBrowserCoordinator.swift delete mode 100644 AlphaWallet/Browser/Types/BrowserAction.swift delete mode 100644 AlphaWallet/Browser/ViewControllers/BookmarkViewController.swift create mode 100644 AlphaWallet/Browser/ViewControllers/BrowserHistoryViewController.swift create mode 100644 AlphaWallet/Browser/ViewControllers/DappsAutoCompletionViewController.swift create mode 100644 AlphaWallet/Browser/ViewControllers/DappsHomeViewController.swift create mode 100644 AlphaWallet/Browser/ViewControllers/DiscoverDappsViewController.swift create mode 100644 AlphaWallet/Browser/ViewControllers/EditMyDappViewController.swift delete mode 100644 AlphaWallet/Browser/ViewControllers/HistoryViewController.swift delete mode 100644 AlphaWallet/Browser/ViewControllers/MasterBrowserViewController.swift create mode 100644 AlphaWallet/Browser/ViewControllers/MyDappsViewController.swift delete mode 100644 AlphaWallet/Browser/ViewModel/BookmarkViewModel.swift delete mode 100644 AlphaWallet/Browser/ViewModel/BookmarksViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/BrowserHistoryCellViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/Dapp.swift create mode 100644 AlphaWallet/Browser/ViewModel/DappButtonViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/DappViewCellViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/Dapps.swift create mode 100644 AlphaWallet/Browser/ViewModel/DappsAutoCompletionCellViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/DappsAutoCompletionViewControllerViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/DappsHomeEmptyViewViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/DappsHomeHeaderViewViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/DappsHomeViewControllerHeaderViewViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/DappsHomeViewControllerViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/DiscoverDappCellViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/DiscoverDappsViewControllerViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/EditMyDappViewControllerViewModel.swift delete mode 100644 AlphaWallet/Browser/ViewModel/HistoryViewModel.swift delete mode 100644 AlphaWallet/Browser/ViewModel/MarketplaceViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/MyDappCellViewModel.swift create mode 100644 AlphaWallet/Browser/ViewModel/MyDappsViewControllerViewModel.swift create mode 100644 AlphaWallet/Browser/Views/BrowserHistoryCell.swift create mode 100644 AlphaWallet/Browser/Views/BrowserHistoryViewControllerHeaderView.swift delete mode 100644 AlphaWallet/Browser/Views/BrowserNavigationBar.swift create mode 100644 AlphaWallet/Browser/Views/DappBrowserNavigationBar.swift create mode 100644 AlphaWallet/Browser/Views/DappButton.swift create mode 100644 AlphaWallet/Browser/Views/DappViewCell.swift create mode 100644 AlphaWallet/Browser/Views/DappsAutoCompletionCell.swift create mode 100644 AlphaWallet/Browser/Views/DappsHomeEmptyView.swift create mode 100644 AlphaWallet/Browser/Views/DappsHomeHeaderView.swift create mode 100644 AlphaWallet/Browser/Views/DappsHomeViewControllerHeaderView.swift create mode 100644 AlphaWallet/Browser/Views/DiscoverDappCell.swift create mode 100644 AlphaWallet/Browser/Views/MyDappCell.swift create mode 100644 AlphaWallet/Browser/Views/MyDappsViewControllerHeaderView.swift create mode 100644 AlphaWallet/UI/BoxView.swift diff --git a/AlphaWallet.xcodeproj/project.pbxproj b/AlphaWallet.xcodeproj/project.pbxproj index d377e4461..64fdfd8f0 100644 --- a/AlphaWallet.xcodeproj/project.pbxproj +++ b/AlphaWallet.xcodeproj/project.pbxproj @@ -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 = ""; }; 29E2E3391F7A008C000CF94A /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; 29E2E33D1F7A2423000CF94A /* TransactionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHeaderView.swift; sourceTree = ""; }; - 29E6E06B1FE897D90079265A /* BrowserCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserCoordinator.swift; sourceTree = ""; }; 29E6E06D1FE897EE0079265A /* BrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = ""; }; 29E6E06F1FEA12910079265A /* TransactionConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionConfigurator.swift; sourceTree = ""; }; 29E6E0711FEA200D0079265A /* ConfirmPaymentDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmPaymentDetailsViewModel.swift; sourceTree = ""; }; @@ -833,14 +858,15 @@ 5E7C70FB40612BB02594EC00 /* ChooseTokenCardTransferModeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChooseTokenCardTransferModeViewController.swift; sourceTree = ""; }; 5E7C7103135DCCCAB96EE5FC /* OnboardingPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPage.swift; sourceTree = ""; }; 5E7C7117B1DF438E213B406A /* History.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = History.swift; sourceTree = ""; }; + 5E7C712F42374C0B8DF8C64F /* BrowserHistoryCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserHistoryCellViewModel.swift; sourceTree = ""; }; 5E7C7142F1598ECC93F3A673 /* GeneralisedTime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralisedTime.swift; sourceTree = ""; }; 5E7C715BBA7416942FDA8516 /* HistoriesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoriesViewModel.swift; sourceTree = ""; }; 5E7C715F395B973FB61056CF /* HelpViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelpViewController.swift; sourceTree = ""; }; 5E7C71684B93F60206992E10 /* AlphaWalletSettingsTextRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlphaWalletSettingsTextRow.swift; path = Views/AlphaWalletSettingsTextRow.swift; sourceTree = ""; }; + 5E7C7171B802C0C2718EEED0 /* MyDappsViewControllerHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDappsViewControllerHeaderView.swift; sourceTree = ""; }; 5E7C7185AA9F93D4F0B67AF7 /* ABIEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ABIEncoder.swift; sourceTree = ""; }; 5E7C719FC57B89C2F24B9BA3 /* ContractERC721Transfer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContractERC721Transfer.swift; sourceTree = ""; }; 5E7C71C2C110B621EFDE336F /* TokensCardViewControllerTitleHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensCardViewControllerTitleHeader.swift; sourceTree = ""; }; - 5E7C71CAD32891B5DC651054 /* BookmarksViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksViewModel.swift; sourceTree = ""; }; 5E7C71CE10548877F1124BF2 /* WelcomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewModel.swift; sourceTree = ""; }; 5E7C71E355BD14E975AF7491 /* TokensDataStoreTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensDataStoreTest.swift; sourceTree = ""; }; 5E7C71EBD4C95AD4E11F3352 /* AlphaWalletSettingsButtonRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlphaWalletSettingsButtonRow.swift; path = Views/AlphaWalletSettingsButtonRow.swift; sourceTree = ""; }; @@ -850,8 +876,6 @@ 5E7C727433F7B8E322B3C68A /* SetTransferTokensCardExpiryDateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetTransferTokensCardExpiryDateViewController.swift; sourceTree = ""; }; 5E7C727CF8549291E71C1640 /* Ether.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Ether.swift; sourceTree = ""; }; 5E7C7287B9288EAA0D66BAC4 /* PreferenceOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceOption.swift; sourceTree = ""; }; - 5E7C729B595A03A5CF3EC9F4 /* BookmarkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkViewModel.swift; sourceTree = ""; }; - 5E7C72BCB25F6212B3029E34 /* HistoryViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewModel.swift; sourceTree = ""; }; 5E7C72BEB789700C49FF64A6 /* DeletedContract.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeletedContract.swift; sourceTree = ""; }; 5E7C72CD0C22247A6AF7C95E /* GetERC721BalanceEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetERC721BalanceEncode.swift; sourceTree = ""; }; 5E7C72CEFD7E32ACE303AB1F /* BookmarkViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BookmarkViewCell.xib; sourceTree = ""; }; @@ -861,12 +885,15 @@ 5E7C7318B6059C18BE87ECAE /* ABIValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ABIValue.swift; sourceTree = ""; }; 5E7C731B6F01534683227123 /* NonFungibleTokenViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NonFungibleTokenViewCellViewModel.swift; sourceTree = ""; }; 5E7C7322ADC54452545C345A /* CallForAssetAttributeCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallForAssetAttributeCoordinator.swift; sourceTree = ""; }; + 5E7C7324C9AC776E3A7B43D1 /* MyDappCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDappCellViewModel.swift; sourceTree = ""; }; 5E7C734D61C0347C1638A1F7 /* BaseTokenListFormatTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTokenListFormatTableViewCell.swift; sourceTree = ""; }; 5E7C736169A5251141791726 /* FetchAssetDefinitionsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchAssetDefinitionsCoordinator.swift; sourceTree = ""; }; + 5E7C7375430F36C549EA8748 /* DappsHomeViewControllerHeaderViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeViewControllerHeaderViewViewModel.swift; sourceTree = ""; }; 5E7C7382EAC8B9CE5EE0668D /* OpenSeaNonFungible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungible.swift; sourceTree = ""; }; 5E7C73CAB804322C4A631C67 /* AssetDefinitionsOverridesViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionsOverridesViewCell.swift; sourceTree = ""; }; 5E7C73D0DCE61EA2DE2DA21D /* AmountTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AmountTextField.swift; path = Views/AmountTextField.swift; sourceTree = ""; }; 5E7C73D26F24C4AAE981E2F2 /* ImportWalletTab.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletTab.swift; sourceTree = ""; }; + 5E7C73D55C366BCC53208686 /* BrowserHistoryCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserHistoryCell.swift; sourceTree = ""; }; 5E7C73DF5FBFE756097D32B1 /* EthCurrencyHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthCurrencyHelper.swift; sourceTree = ""; }; 5E7C73E57ADDF29E0A5FB87E /* OpenSeaNonFungibleTokenCardRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungibleTokenCardRowView.swift; sourceTree = ""; }; 5E7C73E8500C2573331D800D /* Function.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Function.swift; sourceTree = ""; }; @@ -875,18 +902,23 @@ 5E7C741196D9D9C9C3EE5E30 /* LockCreatePasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockCreatePasscodeViewController.swift; sourceTree = ""; }; 5E7C74159ED115D14384A1CB /* CanScanQRCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CanScanQRCode.swift; sourceTree = ""; }; 5E7C7419F47CC8B2996AA8F9 /* TransferTokensCardQuantitySelectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTokensCardQuantitySelectionViewController.swift; sourceTree = ""; }; + 5E7C74294562EB79EFCD3559 /* DiscoverDappCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverDappCellViewModel.swift; sourceTree = ""; }; 5E7C743172FCBDCD362C03A6 /* ImportWalletTabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletTabBar.swift; sourceTree = ""; }; 5E7C7477E69BEDF0C4950D5A /* TokenListFormatTableViewCellWithCheckbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenListFormatTableViewCellWithCheckbox.swift; sourceTree = ""; }; 5E7C7487BDF72352446E1266 /* ImportTokenViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportTokenViewControllerTests.swift; sourceTree = ""; }; 5E7C74A1A13A1A6CB9E61BAC /* TokenListFormatRowViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenListFormatRowViewModel.swift; sourceTree = ""; }; + 5E7C74B424FB5DE3A4D6A2F4 /* DappsHomeEmptyViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeEmptyViewViewModel.swift; sourceTree = ""; }; 5E7C74B82783A94091A43470 /* EthTokenViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthTokenViewCellViewModel.swift; sourceTree = ""; }; 5E7C74B9EB81C51E956566E7 /* TokensDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensDataStore.swift; sourceTree = ""; }; + 5E7C74BE9900543A755CB76A /* DappsAutoCompletionCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsAutoCompletionCellViewModel.swift; sourceTree = ""; }; 5E7C74BEC095303B66FB4B1E /* ProtectionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProtectionCoordinator.swift; sourceTree = ""; }; 5E7C74C0C1803DD17FE9EBA7 /* ServersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServersViewController.swift; sourceTree = ""; }; + 5E7C74D2C599646C65B95E2F /* DappsAutoCompletionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsAutoCompletionCell.swift; sourceTree = ""; }; 5E7C74D5CEC9A6C4483B1C77 /* PreferencesController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesController.swift; sourceTree = ""; }; 5E7C74DCC21272EC231A20E2 /* RequestViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestViewController.swift; sourceTree = ""; }; 5E7C7534FB6BF4D199643246 /* AlphaWalletSettingsSwitchRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlphaWalletSettingsSwitchRow.swift; path = Views/AlphaWalletSettingsSwitchRow.swift; sourceTree = ""; }; 5E7C7535095323B035CA47C0 /* ImportMagicTokenViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportMagicTokenViewController.swift; sourceTree = ""; }; + 5E7C754BF8B4CC2DA82B1025 /* DappButtonViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappButtonViewModel.swift; sourceTree = ""; }; 5E7C754C0E2E57F32A61F9A3 /* SetTransferTokensCardExpiryDateViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetTransferTokensCardExpiryDateViewControllerViewModel.swift; sourceTree = ""; }; 5E7C755132D9B6F95080A1BE /* TransferNFTCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferNFTCoordinator.swift; sourceTree = ""; }; 5E7C7558286761EF1ADD2988 /* ABIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ABIError.swift; sourceTree = ""; }; @@ -901,6 +933,7 @@ 5E7C75CC640BAFFE0E789F44 /* WalletFilterViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletFilterViewModel.swift; sourceTree = ""; }; 5E7C75CE3F1D6B7993E7A840 /* OnboardingCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingCollectionViewController.swift; sourceTree = ""; }; 5E7C75D384C0D727BB43305E /* SettingsHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsHeaderView.swift; sourceTree = ""; }; + 5E7C75E7D995ABE0E6B7AD55 /* DappButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappButton.swift; sourceTree = ""; }; 5E7C75F65E8C1E20EBA6A5F4 /* Scrollable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scrollable.swift; sourceTree = ""; }; 5E7C7607B0EF9B8F1BC41073 /* TermsOfServiceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TermsOfServiceViewController.swift; sourceTree = ""; }; 5E7C761E148B46943FC38979 /* BaseOpenSeaNonFungibleTokenCardTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseOpenSeaNonFungibleTokenCardTableViewCell.swift; sourceTree = ""; }; @@ -916,14 +949,16 @@ 5E7C76C895E7BFA47233068C /* LocalesCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalesCoordinator.swift; sourceTree = ""; }; 5E7C76D132F4BEA5CE4FFD0A /* StringExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensionTests.swift; sourceTree = ""; }; 5E7C76D3CFA12C2236E73E10 /* TransferTokensCardViaWalletAddressViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTokensCardViaWalletAddressViewControllerViewModel.swift; sourceTree = ""; }; + 5E7C76EE22984D66A3C18E70 /* DappsAutoCompletionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsAutoCompletionViewController.swift; sourceTree = ""; }; 5E7C7721E0E4D4EFDD35E196 /* ScanQRCodeCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanQRCodeCoordinator.swift; sourceTree = ""; }; 5E7C772DC28C5110021894E3 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; 5E7C77316522DF2B256F1F92 /* TokensCardViewControllerHeaderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensCardViewControllerHeaderViewModel.swift; sourceTree = ""; }; 5E7C774BCA281E4B077DBBFA /* WhatIsEthereumInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhatIsEthereumInfoViewController.swift; sourceTree = ""; }; - 5E7C7752CDF1417A704035C3 /* BookmarkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkViewController.swift; sourceTree = ""; }; 5E7C775FD95FE80B0F1CEA33 /* TokenAdaptorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenAdaptorTest.swift; sourceTree = ""; }; + 5E7C776B129861728FFB8CC8 /* EditMyDappViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditMyDappViewControllerViewModel.swift; sourceTree = ""; }; 5E7C778A54D7D3E196BC5542 /* DAppRequster.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DAppRequster.swift; sourceTree = ""; }; 5E7C778F20D32B70D7FF2135 /* TokenCardRedemptionInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenCardRedemptionInfoViewController.swift; sourceTree = ""; }; + 5E7C77A400E5145C04083FEB /* Dapps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dapps.swift; sourceTree = ""; }; 5E7C77B790551456E111ED4F /* PeekOpenSeaNonFungibleTokenViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeekOpenSeaNonFungibleTokenViewController.swift; sourceTree = ""; }; 5E7C77C2844B3579A59C3F2F /* CallSmartContractFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallSmartContractFunction.swift; sourceTree = ""; }; 5E7C77E1E6194F5A1DC8D645 /* ScreenChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenChecker.swift; sourceTree = ""; }; @@ -935,9 +970,12 @@ 5E7C787CA216AFED8023A35F /* CallForAssetAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallForAssetAttribute.swift; sourceTree = ""; }; 5E7C7892A9FC3F53B13498D9 /* GenerateSellMagicLinkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateSellMagicLinkViewController.swift; sourceTree = ""; }; 5E7C78B001F9F95F404D5FEF /* HowDoIGetMyMoneyInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HowDoIGetMyMoneyInfoViewController.swift; sourceTree = ""; }; + 5E7C78B61907C2C1E2BCD478 /* DappsHomeViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeViewControllerViewModel.swift; sourceTree = ""; }; 5E7C78B63FDE2FAF25389260 /* TransferTokensCardViaWalletAddressViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTokensCardViaWalletAddressViewController.swift; sourceTree = ""; }; 5E7C78E5C8FAEA752B32626D /* UIActivityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIActivityViewController.swift; sourceTree = ""; }; + 5E7C790B6371A5BCD733A4BE /* DappsHomeEmptyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeEmptyView.swift; sourceTree = ""; }; 5E7C791BD7AFEA4A419BAE24 /* OpenSeaNonFungibleTokenCardTableViewCellWithoutCheckbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungibleTokenCardTableViewCellWithoutCheckbox.swift; sourceTree = ""; }; + 5E7C793CDFA907BFDFECB6CB /* DappViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappViewCell.swift; sourceTree = ""; }; 5E7C793E23E2364B73C4D813 /* WelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; 5E7C794F8EBAEE5E8F2821C2 /* MarketplaceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketplaceViewController.swift; sourceTree = ""; }; 5E7C796039C0F47CDCA236C0 /* TokenCardsViewControllerHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenCardsViewControllerHeader.swift; sourceTree = ""; }; @@ -945,15 +983,16 @@ 5E7C79778E4BFE1322711EA6 /* LocaleViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocaleViewModel.swift; sourceTree = ""; }; 5E7C7981AB6584B25C72D46B /* LockEnterPasscodeCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockEnterPasscodeCoordinator.swift; sourceTree = ""; }; 5E7C7982FA14CBFDFD93B3D0 /* AssetDefinitionStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionStore.swift; sourceTree = ""; }; + 5E7C799836611BEE66000EE1 /* DappsHomeHeaderViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeHeaderViewViewModel.swift; sourceTree = ""; }; + 5E7C799D2B7D91072FC0050B /* DappsHomeViewControllerHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeViewControllerHeaderView.swift; sourceTree = ""; }; 5E7C79D674D45A07E694CE31 /* LockView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockView.swift; sourceTree = ""; }; - 5E7C79DCBECE3385649FAFDF /* MasterBrowserViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterBrowserViewController.swift; sourceTree = ""; }; 5E7C79E3BC4CACB123840A42 /* LocaleViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocaleViewCell.swift; sourceTree = ""; }; 5E7C79ED9F842D3FC102AC54 /* TokenViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenViewCellViewModel.swift; sourceTree = ""; }; 5E7C79EF9D2C12F396364B92 /* AssetDefinitionDiskBackingStoreWithOverridesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionDiskBackingStoreWithOverridesTests.swift; sourceTree = ""; }; + 5E7C79FA3E6A05845ECFCDCF /* BrowserHistoryViewControllerHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserHistoryViewControllerHeaderView.swift; sourceTree = ""; }; 5E7C79FE0C70AC4198F2AEB7 /* ResultResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultResult.swift; sourceTree = ""; }; 5E7C7A16ABC8BD5D508AA641 /* ImportWalletHelpBubbleViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletHelpBubbleViewViewModel.swift; sourceTree = ""; }; 5E7C7A40B418A8F13AA16C29 /* AssetDefinitionStoreCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionStoreCoordinator.swift; sourceTree = ""; }; - 5E7C7A794D1437F435CBB780 /* HistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewController.swift; sourceTree = ""; }; 5E7C7A9876B43B1D9D17A9A9 /* OpenSeaNonFungibleTokenDisplayHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungibleTokenDisplayHelper.swift; sourceTree = ""; }; 5E7C7AB3440C01136DF4F3E9 /* LockCreatePasscodeCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockCreatePasscodeCoordinator.swift; sourceTree = ""; }; 5E7C7AB4464F82391AAD68C1 /* BookmarksStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksStore.swift; sourceTree = ""; }; @@ -970,6 +1009,7 @@ 5E7C7B1FB2702A2A8A4EBD76 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = ""; }; 5E7C7B29A9E728402D144C05 /* AppLocale.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppLocale.swift; sourceTree = ""; }; 5E7C7B3302309706CA0F972A /* TokensViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensViewController.swift; sourceTree = ""; }; + 5E7C7B54826BFDD53DF3E5BF /* DiscoverDappCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverDappCell.swift; sourceTree = ""; }; 5E7C7B6380E6EB88AF8810CD /* ConfirmSignMessageViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfirmSignMessageViewControllerViewModel.swift; sourceTree = ""; }; 5E7C7B6FAFE62FBAADB85228 /* Web3Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Web3Error.swift; sourceTree = ""; }; 5E7C7B7A45EDFA8ED1E25863 /* SendHeaderViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendHeaderViewViewModel.swift; sourceTree = ""; }; @@ -989,6 +1029,7 @@ 5E7C7C12E88EB0B73AA1E562 /* TokenCardRowViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenCardRowViewModelProtocol.swift; sourceTree = ""; }; 5E7C7C2872E213BBB05D55BA /* ImportWalletTabBarViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletTabBarViewModel.swift; sourceTree = ""; }; 5E7C7C34A7BDCFE17CEF8F79 /* OpenSeaNonFungibleTokenAttributeCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungibleTokenAttributeCellViewModel.swift; sourceTree = ""; }; + 5E7C7C5454600A70DCFD7C0E /* BoxView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoxView.swift; sourceTree = ""; }; 5E7C7C58586099F082973073 /* WalletFilterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletFilterView.swift; sourceTree = ""; }; 5E7C7C781CCE43B6451671B9 /* TokenListFormatTableViewCellWithoutCheckbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenListFormatTableViewCellWithoutCheckbox.swift; sourceTree = ""; }; 5E7C7C8CA3706DC14167786C /* BrowserURLParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserURLParserTests.swift; sourceTree = ""; }; @@ -1003,6 +1044,7 @@ 5E7C7CDB0BAD5D27D2F24F57 /* ServerViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerViewCell.swift; sourceTree = ""; }; 5E7C7CFDE7DEA8C06C4100AF /* TextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; 5E7C7D07B7D0738A1832AB58 /* AssetDefinitionInMemoryBackingStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionInMemoryBackingStore.swift; sourceTree = ""; }; + 5E7C7D19E3CF96929FB8CEA3 /* DappsAutoCompletionViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsAutoCompletionViewControllerViewModel.swift; sourceTree = ""; }; 5E7C7D27B0DA47F340CEA70C /* TokenListFormatRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenListFormatRowView.swift; sourceTree = ""; }; 5E7C7D2C43C15D0762C7F374 /* UIStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = ""; }; 5E7C7D46C7CABC31A7477F37 /* GenerateTransferMagicLinkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateTransferMagicLinkViewController.swift; sourceTree = ""; }; @@ -1010,6 +1052,8 @@ 5E7C7D5F3CAE69CF932AB236 /* LockPasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockPasscodeViewController.swift; sourceTree = ""; }; 5E7C7D674F6B2415FB5552B0 /* CanOpenContractWebPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CanOpenContractWebPage.swift; sourceTree = ""; }; 5E7C7D69938F484C2A186FAE /* URLViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = URLViewModel.swift; path = Protocols/URLViewModel.swift; sourceTree = ""; }; + 5E7C7D8C3613A9BD9F147B3C /* DappViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappViewCellViewModel.swift; sourceTree = ""; }; + 5E7C7D8D618A8A8D55479CDF /* Dapp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dapp.swift; sourceTree = ""; }; 5E7C7D913DAA3322F1C7DD46 /* OpenSeaNonFungibleTokenCardRowViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungibleTokenCardRowViewModel.swift; sourceTree = ""; }; 5E7C7D931F68BFB5E1DCE001 /* TokenCardRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TokenCardRowView.swift; path = Views/TokenCardRowView.swift; sourceTree = ""; }; 5E7C7DBF46316B08D3905C8E /* Web3Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Web3Request.swift; sourceTree = ""; }; @@ -1032,11 +1076,13 @@ 5E7C7F19B130116A0B495B96 /* Web3RequestType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Web3RequestType.swift; sourceTree = ""; }; 5E7C7F1B66DB15E6167416F8 /* SearchEngine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchEngine.swift; sourceTree = ""; }; 5E7C7F3DD81D44A996789FC4 /* UniversalLinkInPasteboardCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalLinkInPasteboardCoordinator.swift; sourceTree = ""; }; + 5E7C7F55495A6095B3E86248 /* EditMyDappViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditMyDappViewController.swift; sourceTree = ""; }; 5E7C7F5C10E3895E805EA7E0 /* BaseTokenCardTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTokenCardTableViewCell.swift; sourceTree = ""; }; 5E7C7F610139D24D947B1625 /* EnterSellTokensCardPriceQuantityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterSellTokensCardPriceQuantityViewController.swift; sourceTree = ""; }; 5E7C7F66EE7899E4573C64AE /* OpenSeaNonFungibleTokenCardTableViewCellWithCheckbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSeaNonFungibleTokenCardTableViewCellWithCheckbox.swift; sourceTree = ""; }; 5E7C7F718714A0EA529664E7 /* BrowserErrorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserErrorView.swift; sourceTree = ""; }; 5E7C7F7C7E37B5CD147EF784 /* web3.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = web3.min.js; sourceTree = ""; }; + 5E7C7F840AFFD4459FD3DBD6 /* DiscoverDappsViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverDappsViewControllerViewModel.swift; sourceTree = ""; }; 5E7C7F89E3480D3680750EA9 /* TokenRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenRowView.swift; sourceTree = ""; }; 5E7C7F932B48011A24C26733 /* TokensCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensCoordinator.swift; sourceTree = ""; }; 5E7C7FB99843529061368DA1 /* LocalesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalesViewModel.swift; sourceTree = ""; }; @@ -1044,6 +1090,7 @@ 5E7C7FC75FF544B1DF0B0D8B /* PushNotificationsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotificationsCoordinator.swift; sourceTree = ""; }; 5E7C7FCE2427A30ACD860DF8 /* ServerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerViewModel.swift; sourceTree = ""; }; 5E7C7FE30D58E4022AF04E48 /* AssetDefinitionStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionStoreTests.swift; sourceTree = ""; }; + 5E7C7FE5EEC96A7CDF62213F /* DappsHomeHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeHeaderView.swift; sourceTree = ""; }; 5E7C7FF84A4377FC395772C3 /* SellTokensCardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SellTokensCardViewController.swift; sourceTree = ""; }; 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 = ""; }; 613D048A1FDE162B008DE72E /* AlphaWalletProviderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlphaWalletProviderFactory.swift; sourceTree = ""; }; @@ -1071,18 +1118,26 @@ 73ED85A420349BE400593BF3 /* StringFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringFormatter.swift; sourceTree = ""; }; 73ED85A62034BFEF00593BF3 /* UITextFieldAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextFieldAdditions.swift; sourceTree = ""; }; 73ED85A82034C42D00593BF3 /* StringFormatterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringFormatterTest.swift; sourceTree = ""; }; + 76F1D3AB33F1310B03B18E37 /* MyDappsViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDappsViewControllerViewModel.swift; sourceTree = ""; }; 76F1D419EE36261E50ABAFAE /* ClaimOrderCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaimOrderCoordinator.swift; sourceTree = ""; }; 76F1D473FF303828D93C95EB /* GetERC721BalanceCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetERC721BalanceCoordinator.swift; sourceTree = ""; }; + 76F1D4E689C3ECFD38CBBC47 /* DappBrowserCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappBrowserCoordinator.swift; sourceTree = ""; }; 76F1D4F77311FBF3A442E4B5 /* GetIsERC721ContractCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetIsERC721ContractCoordinator.swift; sourceTree = ""; }; 76F1D8877226D5DD086B135D /* CreateRedeemTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateRedeemTests.swift; sourceTree = ""; }; + 76F1D8A3F2F0F86B632BFBCE /* DiscoverDappsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverDappsViewController.swift; sourceTree = ""; }; + 76F1D948016AAE8470099748 /* MyDappCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDappCell.swift; sourceTree = ""; }; 76F1D96298E216CBFC3DD78B /* UniversalLinkHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalLinkHandlerTests.swift; sourceTree = ""; }; 76F1DACA9404AD6740BEADBB /* ClaimOrderCoordinatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaimOrderCoordinatorTests.swift; sourceTree = ""; }; 76F1DADFD07E2941897FD2E1 /* OrderHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderHandler.swift; sourceTree = ""; }; + 76F1DAFCBB43B6639472A229 /* BrowserHistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserHistoryViewController.swift; sourceTree = ""; }; + 76F1DB8034ACC2FC91F818F9 /* MyDappsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDappsViewController.swift; sourceTree = ""; }; 76F1DC4B9964504DA12D8D3C /* GetENSNameEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetENSNameEncode.swift; sourceTree = ""; }; 76F1DCD54618349AC91C6DF8 /* UniversalLinkHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalLinkHandler.swift; sourceTree = ""; }; + 76F1DD09B44FD653C1500DA8 /* DappsHomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappsHomeViewController.swift; sourceTree = ""; }; 76F1DE7BEE799DDFB68D0F54 /* GetENSOwnerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetENSOwnerCoordinator.swift; sourceTree = ""; }; 76F1DE8ADA3176D0277EDF20 /* OrderSigningTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderSigningTests.swift; sourceTree = ""; }; 76F1DE9EF8E265016BF02659 /* GasLimitConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GasLimitConfiguration.swift; sourceTree = ""; }; + 76F1DF357BEAC88C6AEB6D58 /* DappBrowserNavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DappBrowserNavigationBar.swift; sourceTree = ""; }; 76F1DF5CF4A922E6FFCB7B2A /* GetContractInteractions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetContractInteractions.swift; sourceTree = ""; }; 76F1DF80932454E9F58B7830 /* CreateRedeem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateRedeem.swift; sourceTree = ""; }; 771A8484203242B400528D28 /* InCoordinatorViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InCoordinatorViewModelTests.swift; sourceTree = ""; }; @@ -1094,7 +1149,6 @@ 7721A6BD202A5677004DB16C /* DecryptError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecryptError.swift; sourceTree = ""; }; 7721A6BF202B1D3E004DB16C /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 7721A6C7202EF81B004DB16C /* CustomRPC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomRPC.swift; sourceTree = ""; }; - 775C00B420195BFB001B5EBC /* BrowserAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserAction.swift; sourceTree = ""; }; 77872D222023F43B0032D687 /* TransactionsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionsTracker.swift; sourceTree = ""; }; 77872D24202505B70032D687 /* EnterPasswordViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterPasswordViewController.swift; sourceTree = ""; }; 77872D26202505C00032D687 /* EnterPasswordViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterPasswordViewModel.swift; sourceTree = ""; }; @@ -1103,9 +1157,7 @@ 77872D2F2026DC570032D687 /* SplashViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = ""; }; 77872D312027AA4A0032D687 /* SliderTextFieldRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderTextFieldRow.swift; sourceTree = ""; }; 778EAF7C1FF10AF400C8E2AB /* SettingsCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinatorTests.swift; sourceTree = ""; }; - 77B3BF342017D0D000EEC15A /* MarketplaceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketplaceViewModel.swift; sourceTree = ""; }; 77B3BF3B201908ED00EEC15A /* ConfirmCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmCoordinator.swift; sourceTree = ""; }; - 77B3BF492019247200EEC15A /* BrowserNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserNavigationBar.swift; sourceTree = ""; }; 77E0E772201FAD05009B4B31 /* BrowserURLParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserURLParser.swift; sourceTree = ""; }; 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 = ""; }; @@ -1392,6 +1444,7 @@ 5E7C73D0DCE61EA2DE2DA21D /* AmountTextField.swift */, 5E7C7CD1FB7D353704EF3389 /* DateEntryField.swift */, 5E7C73EFA9494B31C683A287 /* TimeEntryField.swift */, + 5E7C7C5454600A70DCFD7C0E /* BoxView.swift */, ); path = UI; sourceTree = ""; @@ -2093,8 +2146,8 @@ 29E6E0691FE897C70079265A /* Coordinators */ = { isa = PBXGroup; children = ( - 29E6E06B1FE897D90079265A /* BrowserCoordinator.swift */, 5E7C7721E0E4D4EFDD35E196 /* ScanQRCodeCoordinator.swift */, + 76F1D4E689C3ECFD38CBBC47 /* DappBrowserCoordinator.swift */, ); path = Coordinators; sourceTree = ""; @@ -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 = ""; @@ -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 = ""; @@ -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 = ""; @@ -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; }; diff --git a/AlphaWallet/Assets.xcassets/discoverDapps.imageset/Contents.json b/AlphaWallet/Assets.xcassets/discoverDapps.imageset/Contents.json new file mode 100644 index 000000000..785ff4d6a --- /dev/null +++ b/AlphaWallet/Assets.xcassets/discoverDapps.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/AlphaWallet/Assets.xcassets/discoverDapps.imageset/discoverDapps.png b/AlphaWallet/Assets.xcassets/discoverDapps.imageset/discoverDapps.png new file mode 100644 index 0000000000000000000000000000000000000000..f9537bbc85f58e8c3cad6d590f1866a499510bb1 GIT binary patch literal 1604 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 literal 0 HcmV?d00001 diff --git a/AlphaWallet/Assets.xcassets/discoverDapps.imageset/discoverDapps@3x.png b/AlphaWallet/Assets.xcassets/discoverDapps.imageset/discoverDapps@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a41f678da5324e8bb729055bfa402378a15dd064 GIT binary patch literal 5578 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 WXeJdGB}|Px?zez+vRCodHU2BY8RTW{h^7k zf@tuEF@ijzfgcuwZ5o0vrFL z_CDv_d(S=RwsTi9v-e(m?bo;N*?XV0_Bp3$W@hF*dC>Xup4sG=kE(;ncyuAQtfHbK z-?sf^eDl2&;00W!d8QG0)n$?oi{}T9-j0`i#(6dc*g&!I(OIv&Ao==$<4Svhi9UJd z+w!b@lLBl&hQ+r5YpdTTpDEu4hOIuEd>H&9T>&-_OnejXY+{=Bo*AdN1}EvG@hR$` zoS`!l(=aFx@1y>iHCwZKScp1 zK!fsj_MW5dT_bd8Xp+2_byxo{8AdU$$x9l_Y3=-4TGdwN>bDLq1pIhO1q20Jmpd{v zL0gUw)7!(7t`1$!j8E2=)B3CHX>mhE$}s%^7h#ce6rh6&AwS*>%RKk)AZ_a!l{#l- zc-Q;3_QZl~6Q2?Vg&@Yel)4qWTwT)RDU(r-S4=-+}+Omsk z2&@EM0YSjuxUZK^j!n&4z)b`1tS+UeR0ycj^a5%c@!!2SRG4;j1eG+VWMHJ@rii5EfuB@>S_m0x8gcA$X8VE*T>J)VNxUqX1i`2=3||pLP3u8G9xnt^i=>MS{8O z3XlvGmN@Qk3zQxLf|+25(rR34D>gAD6XD3zjALF_CF09!EMw0bqdC0{i+4ce@u*N&ml!orIGoW&nJSHww3Ml?Ny~TkN=v=OB{bgH~SvC&rLb;eCe-_9kA7H zM2-S<(A5FJnG!eM%#M&d=hxAdwPlWTqzs063r+Im+rx@Des+>x={idXVl)_fVJFIP zT>&yKQA%8%teVmy`eH{dt-Yd-=2w?Vw|v8LR!(P4ne(@1ZVi1f!guf|&(hz!MlD?lkU2!8|e%4YN)a#xkP2%-Y9Pg|8{DG zoJG%oGf+fkvKx*6(Hc-D$N!D6YOPrWLcXt&4k@XO=&URyP%%^ zx>fY^1HJU&Xd>(;JnI9d0=z0%>IJq}o?1y5aZEKEu4^K%`CZx>PtT>N-|nY>@U)a`vH_2Fk!QRDa@{Gl{tBM6zrVPJZsYmd zMzFE(hSUFlXX)_Z1WkC{8CLOWhUHC_w6?R3ZfdSH{^~Hb{b)%mEpDu!XOH&N)I}4% ztqVEhClwIouU$ri&CD-W%%P@dgByrv@MyZI%<)4`Va1}W_OZ_M+X zjt$VG2YYE+TSH2CJNT0&ZF~yOed^8C$&eY5D**HUPkF0pnv*y2g1Czp+g#F)o-sbp zG~jBYiFZ;zUh4i)xCBFHM6Q6(wpG(rb>*&Mym}goZ8VOb7x~V3M~{08x{Nmi%X2st zxdzP)XBXr(N0|y`!@HZehdP~LbHOvbFHeVFx~(%Qew3Xb5Am9j4*lxL8Cq0ZP7CkA#|8xm<8nmkuc15tZMnx$K z$n}+QH~+eHI?CI_qcI)q#lhlZ!>QX-EXZFr^i%J5rfD7H6a`47A|#>kvk76{yqyhf zI&I^-+c@bZUJrD-lW*L(yP=R^D4|}dQ31M880i(wRW9r1FyHB#7Q-;w3tGnxwg zcPUY=0d7<>L<9!5CvR$}R>M?dtMbol78tWIH9h!dH}wSc0l<%3&HKi&I6W)Ik@z!1 zTLWNwfAGy7>dW$1oi6woB2G_wcHDLD9}X8r@%e+ORHo)sbmlXbM0FvQw;fBndFD60CXTzC8PsB+S$lg+vA7&s9!rc z23ymn&&;C@*ESLS!$Ih>QUC^=t`gD(UqHpFhWmGR(TnWqQXh^hVW-2r>}K4&s)N?F zSBtnc>@*zwx@4~a8D&rL^iJ875GMF)o3EZ8rS-eI90$BiOMl`i%61aYGfnCZ~&goAVnO8D;H?jWK$m-zvMuls}1D2_iy zNs(OvQ4Y$lt*`RIfYt5pozDTjxr@Rful6sm6aWL*m-wNKT+(X3FL;1&(saTp>&FiE z>Fk`RBb|c0Ml+|fgq~nC0^ZMd4WAqO&9VMaACHBP7r@`c&j{#*h4;}zz5IT=RM(^~ zmy2{>C;&$SakB*QK{#68SySqq;m5j>Ew!<7AK@D^o1b?pABBwTS+dv_(7>i-{PH_A zbHI)4#D8vO2ffUT;_puk(QpRNW546+P%L&mfBevVXg6EM2Pq%B0&qYI57Obu6`e}i zrh_Tt4qg}boH@tmM#pG3pZ)i;`v-YIL)yWP2M=h(Nkxf7f}KJ3hxG1xEt+lPeTy3C zzN;Esc6l9~DL&cU#m;=p*LVyR@2^Ye5hc_&K0OoJBTCyoH`f`0>Ti%Z!jvlj3{i$Y z@LZ=~;AsYDM8C)TbFb&Ta8)eiGo1@F?9fSmI4_Qe^rX82zNAwWAk4VJ*ZFArTd#M~ zH?C};dpheWlQW;TzQ^^Ls;xi5cN7rkFJC$$;V9d2HuJ6oM+U#iQxZ(Y**q^C=Y!fZ zCQ~R^g0ludK&Bo&jA{)qbrL+hiWA2gSMnoGx3*SOv(L%pe0o&@`me76N#J?92j$fi zN(~bu5Nn33}a;Y@&>)Q*I83KZxU}>twg|04)J$8XILqNw)#}>6F6=2GQKwCip zfFUEYVW(o9RDi98<^1J{0%e8(zPjN@#i}-RVdUM?TtRrYw?OG30L*S??ELUGUIA*M zT7Jm|KHE{C^bi1Mz)ncTRCow4N^rs^^py^``%bN-5O*VB!%W9GMFDCxB8c$TmI}f* zQwkIe0bm8pR19q;9R-M>O$hvTi*oC$^=kW4%L{^)VD7pCBtsoEc+l$F4*!F$YPs1M z2S&ikhNF&2QUO5<+|QqF!Ut;#WJUnk03((Ba7`+}R!Ay92;qmWZ=@@#3U|jUUWfw| zU;~U)%tTIjl9teUZ>S^?iaA=1>pa-(pLl1m@I9H2fx|B!;Nx`+D$o*_5aMdwN#iqC zI=~;-Yg7jEAdd&!H}hvrwx1dk@mU#S&d0Q){_+8Y3ba?;`H-8c02Ef!esO%RW9x~L zS@($(EVS6|AOv}V>pney_fQ93-F2=zppw3`nh%nLWvml1BV zR<>Sz)us)60a^NPk~1m*1#Cd8e3KkL+p(8F`mld6LA?_fK4**9xXX*Ir8D^+;`ll~ zWsAQye$;7sW0_+YgL%OOq>RUwr}7~y>7)X{^aB?%LaMA9N4}o6@uV&ZP30sn%uojJ zRi{Zt=c#Emj(XL!8h7RSeM?dWMUA5(HLb>x2Qu-z_)ei1|LH(US4@1Br^Zo6O{;M| z&q@0!075E|YFy7VrENU$b<*qB^TSH(pxEj)$>}_=w4R4{bY997BpopUq4LzYt&B;= zCM*27LmeEoKYE^?hOEj{<5G{2hi}T8zzV|CW%RT(Y~xG5ghAy*KGVb;fyosL)FGT_9hz$S}KPXQfps9?SA9vgYqS_}@eo!9j(PjS+wV#z1^@s6MGM1400001b5ch_0Itp) z=>Px~@<~KNRCodHT?=epN0mNK9KVv-aS}U^K9V*G?WRrAZCXmZl$T(EEm{ODyGto6 z)FtXe0>r9FOM!-ks(>i!uGlRqREVV%SU{{m;<*ygmMtx%ZC<3IN!p|(v12EW?Zofc z<$S*xGoF9u%-rk$G560s((ilkoHKK0&iOnucOGZzrlzJ|q7?MfOX1Oy$m&^18&6N8 z7Vhin>QWUWNxUSodS>d90w{@5lgJ`dN#*r|oK5#hjh#e3i7a%U2tc6*4wXX&lE^~d zMx{u@mTtMjk{ZjFpCoC7%7)4zKfwSBF;a*OUK%O~Sw(5$sJL0YCNZKaUuc{Vxeyt= z*9)Lf!-dF&$S7qbkyUYO`5J0a=paNkL?$FH5kMhEtfY(&AR&42rBMlfsPUw{grrdk z87F{3jNy|B;U79ZLAwXW>G0459T_=8J)=`JJUK-pXQt>(_=ldP>&)}&`6H~kejc^V zpGTcd^XQ!BdRnt^0c~n)ptXw@cxxOVkbHb7&?RF9kk9Z)P?C{&h`YOgjDCJ>m>xeq zN&^#92+Ap?2HF1?L0X~UpY8H2e`S)sr(vP z&&}G6>)L2bySqk`z#{RGdz~K)APFEYUiZiZ-MfE)p6nYdzt-|M6lq}V(nh*w!G<7ZQ(q}e zxmf_RMK&YoGMJvoDcc&5Y$(}Ym3jt1Heo63;hvFvZcAw~!&l5t!j>S(-g*&rxfuZI z0KB4*Wdstpb3w$yDLQn~_wY>~_`{do^B0LzzTgs3+gGO#6Vsz|pr!pvtqEKv=l zUVz15In|a1(%RHIm0=>ze8x(`oN7x0rcF(oo6jgc!V=d&Si_tzL#-|Fpfs>4Y@3vd z0AwP~eBQ|L@LuP0GW}^XC#EJ zH#ya&25h!9F=~U5jgncp#(8ywrJxIWFBX2|?7-wS?^7G%e!>u6Yjq=% zvMp>aEhN(o>jaPjj4q7(;ToEDXN=H>#f`L~wSlkv=?~5VSVt{WzF;&-aB=5M+>!Z4U>fMpSi5DzVN$h z`SkuP=x*=882z$)n5PAXsDC`0ItsO|4yYLbNlMhgmDTOW%Npm?->+$<*R5JetqbN$ z#ZAlus4iL3RQTO|Q9JGE8>R0Y9;B!FnkCY0tAj);gB3tJV5G=d;4N*KPyevCmHy`3 z#nd1)O{FRe7ObO&!*5UjC_TU{7IvJ>7LaVZZ~>|SpjrZqDm)sdcVF1XwN4u~pxvqh zUCOo6XSX)dPOgbQxA!>p*3but0+hD{po2@luYx^-UkQf;pT4}4Zn$ts)xe`?cFWQx zy7%R)=!#`cdT^BYx{dXFS7yBcaz%_ad}CW9-Fww4x@>84R9S50ru0*n0n}|&o%*mm z>L}_1g`loHS($sPZ#!=>y>COi_!;hc3(AdU4fr%aGCVOo?t`%(?pdPmw5Tv9#9v@d z^|YQp`7dAx22Jd*Ckf5t{?U85Rs_gDv*$PsW^CIxbYzYX)k}bpcu5pjZrNt;4;>q#-|-yZNP;mgKxYku=o%ghfIF2niyB<7DGEmi`@guB zymMDC{hk3EGuc3+@>}3dOiaw$!LEbGcK^4JbgTGLTvxJBoIAN`IFCJch0^}PG5Xe_ zL3)hAqkp=F#<$7AW7ylzZ>2ZzFv0x5ue}K#;r_zD6ZF%Fwu1lQ+LeXk={1e(9)d+p zm`ypchM{TMzPTe9JO_F7_`eVKvuCvFHNd4T7hLq+^UMkQ#`6Po{ROS`*Q*y1nk#Y^cz?vI4f_g)yJ_y4A=Ea1`KkIj1bJkv)X zeY%H7r2W$y7&rRJr5%Lvsw|L+ky!xBLIG4B-^~BvZ{VMghPfk*zQ%peo~)?5nWuQ{ z=hnx&xNqejL|n?FI_ z(~DJJ0EeS&zP$e={oBr-!cq`PkMHDB^J_Zd8o!eA3tr{`^x;dEIeq@A!amA14#ru= z(j7#kk}2u+(cX~)h~R`vqT4Q6PVlyx70MKV-gf?CdIgWWNOY730Wt3sBf(qkR}b`y zS;GRhkMWmfR=Pu(0T2cYuV33HDu0YU+<%HY%)_M`+!5cFj`?lw5B!asVY)vuK#FU6hSTFpW5{)0-%{AoO zMXTr>Uj68z>n~{I1!_Z4E}sQf)ZpR_fb@d6z~lR?6ILzI=$U^D zF9wrV5q5L!a4*+BSt4xW`--PO^e(Q2s!1WX_X4Pz1yJvPJNyo$c z(H~Cn=|t{rQs;9mgvI_bQ}h7BB;{IRru!JzK0oU^9kY&|c?0Ob$>5g=%~;G7J%F%# zLL$s@C0K;x7qDRg zTC5Z`fLeKN^SRuFxo9^J%$>~W9xS~~n0Aq9qvs$wDQ>xWQ3J@=fvnafX{n8}k5?s1 zooSyQN}Zo1>BS8o-fAq-KHd#lE3{W|n}aRau~O6k!mK zBgL7kKWR!*8vOLs68Xvle?>7;oVogwrX&Et;ZrRU03^*lNxG<}9xB?fHu0=FmP&-| zo2ut_U)mOfPg`sg4xwG_9V7N_GgzmH6_n7CjhYkgg0-blj49}}C{#OUj*grsGY0c_w}&qX7=ST07YcoX(3UX6Db zuUDR|z&uUJht@Dhu1bVtxcsYO~gpv2l_R~$f4P9;E8O7JLy z!q3oq)-9ngF_5k-v8xte9|!yRW2CA82#P2th~^3&Y=6f3Y8S2MrKorQ<%+_#>dq#4 znXM}gGaSvMM82;yDQ-D=RRa(TRSpo%6+Fln_P1~Q^-=mYJ719Kn$8xw|LWEBE_R_O z3|UD@%z6ggo^v;Aqx=;IXX8FX zEhR16?Pbuy3x2E=9e|*y5D--hJj#%TX6-rtTI2rF5~0Lo>~4JV#vvDTM$;+j&wHhn7S-C8V+% zbjAASJJ<)1CFHU|w{2W5x^_VsFrDI_jfLa4as--M>3tIP^u5iHTQp0K%DEAKlbJn4J92t5#OszYbQZ zZsQY#03P3>vF{un;KN@^e+)x80asH1+QMt3w{Py`C8yK>v3i>;bdp^8>H-BOJJeHL!pL0pKW&m^v*EpZNtfT0JLaf)pF#;cC&-U#Nl`HP$ zIHiz}bi!f0K7xOsyLKNB8hrGnRcU@^KI0|DT*S*EaO9I~Tm_5OG3tw1Z%lCF=z(%M zAz#7EGD1)5L7Cuq{L_1SGkflQO#tW?_Np3k{(Cd;eE26`6o1{hi`cc?V8OA|K#4{s z=WcCeMfz4F+kH&{2nIp7`JZ`=<((dH_K542HqL+x_f!8J_At#HRqJIV&FPnkDb? z3IJ)ZsPP`;DT)Vq!Zj-cg}$=Z41iQY;o1EUT$6m5Prbo*T`t17&JM0|Fy8buUu%(cFqKb}k!fy_$9r(} zBz_op#KCo#;FOM02rU9?YYYecvU`{w?iu3KnzOKyC$x03+*t!4T@AnIJ`#R#q5!7` zSM&JM%h(${j^4%&3Cv-+nmd4{k2}N%c+C!WjN*qr7sAd)Ix7LB8ye$2|NG)8BK$OJ z;ISWo3G-(dtcHJ{A-=(SowKKY(G=piO$;EoD~|X?Nk=i02V;Rez}20t)fLGLs>Mj- zirV=ZuXmL6d(0>0RGS*mZL8Z{zc)*s7!56<9s|rb<A1>l>fQ`2~;=|8uWyr(AHDI%?4Ff7g@QP|6DFPy{Sy)eP<#3d&a+P1J zq5;?xww1_+jUoU^2`Jvnm&JRAr^;h~O{RggHuVBXN1eKVZD+EjqsfYrOIJe!%BIT3 z$b)|G%2NSAA%c1Wm$o&~suDTa*3-@JtD^zf5;jdj3NzLkNHJ{*D&7ri7u(o*SOztq zY^iK(D}sw>1(2D5E%LP;O|+h!B;}NC4Zw!5rAaoMn=*Kw0Z?cJJ(2fbkk>-GKC*b9 z#)djpW&mUsUKeulHZN(Q%iEK+on~73bM-U;+rfsQ5PREl>I9G}Y`#D}-?F}yVm;5( zEJObMXAQt+upLO*QgO{^6>huE0+1Qee6}Tkz2N_P@e=lXW8>ywF=_y|g3Vw%ADI|1 zpJxFmZDO0a8oX)UG@5;BEeAVe_1U<)&4e9t&v>x%~l>8X?BNG_)eYXIXs_*6H) zwtN8c@lgyVlL$bF=mU+(N1R<;8-4ZpQ*?k`ujEvI4Pbf#Bg|@$P=BK`ybAL@L(Y`Z z!)5@4K%4PH!b}XUzis_ zNW=t$`J7Z{51(f9i=GjBqHml^<)aXJI2#Eo39nq*Kv>VCmSp&h4n3unG2_kx5SSt0 zD5c!tp)<6b0rkT089Kr{L2=682=5&p;Vr~x!wy*J(nr`aicNpbyxkgGZ?Jpf{FZv! z#A|r;WvC&bz`IH*R|Fuupwb}1J9V)q1uWQp$@YZ{t-Y4C6p%rd|vZ43oNE7e287wqUE;(PCF1{^%E@7_nS!*B&yOeAT4-HG2F62$J zy~~uz^0Upzw(_{-l7!o2ZDDx7Gu&1KsSLY}Wec~3A)Tb*QXG{p+tu|Rn`{hWE`vyU zlj68|Ny3xJs(jW>O|kP?%T$IEaPe&6wlJiVG+ez~imS`a_1={~U%D=Z`{E_xhrGij zlO#Netja&)_!opIY2%8>rs7p5l7=H`Gmd(lN>e?%qG)fgGG%u2zi?bkQ9Gf!u@wHaowqwo}(KeY3d(?}K^!Tyg+l=JPvoFqE e`FOu8jXweerOQ*|$^-QP0000Px&$Vo&&RA>d=nY(TiK@^5(9V>Apgq?5+qCp4=A+P{ZCp2^vP*d>)hzCGaJOS_m zAPQC zR4BckPM~UW<&)~s?n;9ouvRhc&O98Dut_?eCd+J3ca@tkW*=s`%qQxdPA3|A2|#^q ztErx?Z>w6pslGKks`0nF`TQN_OEZDmKR2VQkXREU3ft#h4?eL)rmCGp&WAeyg^jezv=#K7Uu*?I{-2&FZ46 z8nZzskQNBd^wY23Uch4nFn}0HFhR&D(}FG;{6oQ=H(L`QdC3GPz(iSqOUT!wji;aeIwT(40GA0|GK^QICyfWIx zvW1Z1%F%MnhRlQaTM92}z}w2p>O)ogUlsigYZhV?LtJgmc3JX4tJMk}>94Izt6#FTnOL32D~ELC zk%zh5GXVPV=sqO;Oh6H$jTDSLLM$Y$ZNEmo|GC2`0G?bpGW3*-Pe1A~ig$z=MzQ4X zh3^Sh+o4Vq3|KLog(!?B)b-8`cqvmIh2Y5l`Y=ag?rP@KQie=h9c;0*^0YErf6``S%>5p~2v6cTEgg}1ZU= z2T;iG0_eEpyZg32u?r6@8;m^RHSA#Rb0#7OOn5j#8*WBczcv(ppa0(Kj`f-qq4Lz; zS+#Poq_0^eD0r++UPx+*hxe|RCodHo!xHSL=?wo(l;_H(b;53JAm-AjBge z#2fG-UV)ZtZa^T>i+}_L2@n#f6_B>1At}iwyTyMe$2mTG#va>in`X~Q&CLAI$JoEK z_HL%px-%FI?l`60xueR7XPzC7AwEgM|Iwh+>Es?wo=={g7U*aN$O2c^Mho&jD;M`0 z^*9s`t()av)|TX!1@J-f=}44i3$fF|wB2XH#Ia@BeKwy?=bE|#d>}`q)1tEX<(Ga{ z=Xo$>exLuSbgY*Yzz0FoHSM3BzPo&5%YC!fbAN5^x|_X$yRqGOTm9_!5_!^X&u5Nw z+*0?5Tb!P7_svYYrx&K(xuqF*&(x8jL(>YOX$bV{xCLoIL$xng|828 zK6Ko7oiGu-CqH$3+MPc&@19?t9XfsuZm`zx55Gi1;9t45?LPWt-TkoM8#ox33lRTijXQm z9{i1+fqUo5O}Fy*Hd}Ulhi6Z8-P@-Z-Tc%+8HyGmDL@GPH&<5O@3(iym$-7~4;`I! z@10#PTm&lALFq(tUXXtDTQOew246smreFd#U__0xBTe??6BmFctRz3~&u!2#Pu+sJ1C8-f(^?n-Vnyr_ z#m8EfQ~{*JSJ$`3?sL9mP-KoP0L-$QEJ#uSZlE!K!?jk705Kzmh~{INEh&IBZ0ru= z?FFgZJevTvWWX+~@q)w!-~x@mjyBc~>)uWloT@f313NTgDaWyK|3~Ty-~lT)?YCVH zs>qnL<4k!D(uY2V!U@Lfy*~H1aRCBgM}T>lrX^Q^G-5yBS~&ueon*_iCI#Rk#`w<^ zYvl-JvE#+Scv3Dw4x|2`tS5DF7{z3ffK|ggyeK zp}nAj>jb;=?S;J|`W-CAtruv+$0QIpA%4`J)!c?DAyd6#Z6}*4ti$6!YYFpb0Ro1Nn;FA~b zPi;FR7vF}ac_Uj9Sgfrr6_ats6e_H&Orq zwC!lUoOTP+xiqF)0UATCFJuMKxiqF)0UATCFJuMKxiqF)0UATCFO({P+^f(-ObsCr z$}%nhIfo=RpK=6zj8j)YxzrX_g@FBa^gg{&@0$AlI+|Yz7&o#Nfpe^Kf^EfCd1Z&j z+AS!QR84-~0?bC=B5ti5fe?0dD;g>Q8BRoB-)^lO0b)liv$7i(UT*rl_P+sZiF_q0I1Zt#jXqdj-}AG#=;NGo{89L zuXX~!ZK(oq!~En#{MZf(vObIe9(4kC)HqYpcprQEoL)XT$9-)aK)}b$$M~RB`^m`> z{j*D5hi9f(3nKtlV8$8dMGh4}2E6*nyp(M2ArQifih~pwCk5aJ$`1`yJ%92jN!EJ^ zfDu^vIC{dO4>}QK*`)S*^w=gmu*RBB0BpdBH^~q-O-rr-=|Ilt;q=zy3+};L`|TK< zcmor#0VB?gd`P1?^`v)pcXw0YmI4REhzczlfW;&b0v`o(iR%rg%z9I0_%K89V+eH!JWUW+5!D0>dZmsj(V z8lUqqa`HJJOB>C-&ywU&_!xnF&d0E!XJ4L033)9fTo9P`~4cYREP)z1^@s6MGM1400001b5ch_0Itp) z=>Px>;7LS5RCodHUE7OYR~cVtF3HR!(@COD+7y#$QV=l_6cxnc5c}kE32@xbFmr#s@(5Han$|okqLE0eCi6c-97$KV&{oD8U&HC1!xpsfw zytC6c8@ofZJs6su{?PQ}Z}ePsZiRdyy9rxfm@_N$bLQmIoOx<_!JIv^XfCcDGUtvi z29FXUB8BuLazxw-jtLY39szweJ{^45Hy@ba-rP2S`QNVD+8;usEoTMR7U#_8KfGkV zdTPa7Ja*U{#YYsMj)I14Mg$6hk6=C1x{y2rsvMBnlEK! zay1_bd^(Efv*`^61Mf-0M-HFvAGaTvSKrz)SF_WVw(2WzAv>+TaDL5v>bU!eze=85ISDIEkZBIMOM3FIOO z7INvXWJjGJ{PUK%tJgRv)6Cjxc8&D?Pn>E=xrOJmySSI1K4!jt`iOP!WsxX}KsNFWvIne}vs2cuGXiO=Uj?4c2=r2RN}IP_ z$fCvV!%R<3Bbqa^Yvo)@Dm~Qa3>$sdPC(4%^pmZ0nJS zid5qsBm!18d~Gtq*R>_?GbRuhaz~pSvh~fq(KQdZ)p30*fa_CW=EBpKxy@GEjR_y*s`F|!V+_oI9hwhc zz8jkz*UyCv$e8S|WQRfhk&vp=ydKyALo~uPv?C7&CTNN87!}CdCiALD+EyT*;GesaC2xA@$Pt~=xqXHaDj&bVlCI}=0 z@%bCs3t;+6MrwKMYY!}eDK(BYWODA3O%RBJ*?PPTrmbBSC9`|xsn|5>5aB_R=|d>N_dYznV&Mjrh34Tr^aTD!?)3$G&;sX&MuQ%9hXLw8Eo z-JSY4Aazp&;$@S@T&Lr9RUj|cUIMkNM7oemLINrY_E3o_V)Hh?>z7eBP>dD^xyo` z&9d^)I5n)mFTZ$dOb$sBKDUjnr!XQ<4fm=kRa}8o?5zZ}&UtdJ(gzpY zxGSqIfdIrWzI-Nk4ZijJ>&8VQd@hvts|@@$b@|`7t$9_Mvvt852=x?J0}z^=f)c37 z=+z*1B~T4OXmSclpeCbNgWQ!sH2|T>DJX%Oj9v|LR|3@lgeIq;1ZpyRHOO5FR09y2 zoPrXl$>`M}cO_5_KxlFbN}wjASA*P@Ks5lN$tfs-nv7lza<_h!!C$P#cvfF!RP}C+ zz7I6XJpNS%G`6oVq@3opeIE!3y58r3u1LcRVZQIiio~ue=OQ^(>8q!(Dy!Rp*h-)d zfL!Gkl|WTiw*#@2Kpg68&Q<_>k8z- zmac!0`vifwf{&LMCd_8NXI;O<3UF*W=6vj1E{Yi|^ZEWTiEm-m)uRF{^HCIVCOa3y zw3AEo&H$=tb_Gr@g$d;2NZ328&r{3e-y54YNC zPTkfNfoy>;9Xp&FSM3{80UNe94$yX_ZbTp{BxCB&9$7GJ_|{lkt119YfvvBFxDm~Y z&wWUbLcT&bQC9}0RswO>(fBJTmz!84&Dm1{!jv$EDIa@W$0{p<##%P`DVFj>EmhV&ZZW+ zjd=q*V2DPT+V<(wao-67$v`p)&0oIom|4}|g=D#EKL=*O4vny+q~)CCp?>ONYD}P0 zOAF?EA3tXFA$_R4_0a<>U#iaxxe=a1Viae^NZPG@TK#Q*vtd!xSkaNR=^C6 zFr-Z7oaC;a+m8su1#k+@De%{xTrtm{K0;INYgz$d1gvZ=gtgSm7{McubikYkeeq+f zN+Mg4Y9a%9UOfN7oioR-j1+tQe*Yl_;@^TnmNI|s#ymlurtQ5Wq*O_02u=!+&j>yZC!b+GbV(+->24nXY+|$Q1C|ugJ3Hvf%_m zKp#P3^f!0==9Q~=%-?VC$^3MF02RRRJh)YtcUvKHg!D*V+Kgl&0>LPiRPecy9gTkc z-@E4Oy?sozRa^l)p1?26^dli(P68jA!&llkWs*QJl!UR#q>Y@D&+G5+nx9>}XKw5c zAktQH1@Kh;J7BY)Mwl)GmAqgSpsWxwH?U~$ zZg`XMvnLK2yw5|gWW<1;*9M8&d`I%ZV32WR`qrsR*|qz9vz`&^`gY&Ev(q;l+0khy zyYAlU56uVngt_b63P*e~ijV%5=XwU8-r(zrGb;<`V)hOXe+|_J>_Nyz0T8ZMg@d5SINDSg5(hmdF1!&w%d0;b9lR{GFV z6qDvIJJRRrbY$$A7r5FcnRU8u|fKj(xz%J%wz2`n)>i;PUMahr0hE{;y#=8< zF>A&6`fT#*C^xS^2n53zAbd7HU2OamJJ`0l`9S0Q4%zwiJ{_$QvKbwL$T&m-*oAD0 w>M0lAhhlQuCXPcO+4dCjDXPbsp%}3FKM56%jY#LtzW@LL07*qoM6N<$g6K1Di2wiq literal 0 HcmV?d00001 diff --git a/AlphaWallet/Browser/Coordinators/BrowserCoordinator.swift b/AlphaWallet/Browser/Coordinators/BrowserCoordinator.swift deleted file mode 100644 index 1b1974d8a..000000000 --- a/AlphaWallet/Browser/Coordinators/BrowserCoordinator.swift +++ /dev/null @@ -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() - } - } -} diff --git a/AlphaWallet/Browser/Coordinators/DappBrowserCoordinator.swift b/AlphaWallet/Browser/Coordinators/DappBrowserCoordinator.swift new file mode 100644 index 000000000..edb0ddb93 --- /dev/null +++ b/AlphaWallet/Browser/Coordinators/DappBrowserCoordinator.swift @@ -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) + } +} diff --git a/AlphaWallet/Browser/Storage/BookmarksStore.swift b/AlphaWallet/Browser/Storage/BookmarksStore.swift index 648d5345c..14506131a 100644 --- a/AlphaWallet/Browser/Storage/BookmarksStore.swift +++ b/AlphaWallet/Browser/Storage/BookmarksStore.swift @@ -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() } } diff --git a/AlphaWallet/Browser/Types/BrowserAction.swift b/AlphaWallet/Browser/Types/BrowserAction.swift deleted file mode 100644 index d37f7471e..000000000 --- a/AlphaWallet/Browser/Types/BrowserAction.swift +++ /dev/null @@ -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 -} diff --git a/AlphaWallet/Browser/Types/Favicon.swift b/AlphaWallet/Browser/Types/Favicon.swift index a09f1b006..db5e8321a 100644 --- a/AlphaWallet/Browser/Types/Favicon.swift +++ b/AlphaWallet/Browser/Types/Favicon.swift @@ -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") } } diff --git a/AlphaWallet/Browser/ViewControllers/BookmarkViewController.swift b/AlphaWallet/Browser/ViewControllers/BookmarkViewController.swift deleted file mode 100644 index a747803f4..000000000 --- a/AlphaWallet/Browser/ViewControllers/BookmarkViewController.swift +++ /dev/null @@ -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) - } -} diff --git a/AlphaWallet/Browser/ViewControllers/BrowserHistoryViewController.swift b/AlphaWallet/Browser/ViewControllers/BrowserHistoryViewController.swift new file mode 100644 index 000000000..2b1a53d2c --- /dev/null +++ b/AlphaWallet/Browser/ViewControllers/BrowserHistoryViewController.swift @@ -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) + } + }) + } +} diff --git a/AlphaWallet/Browser/ViewControllers/BrowserViewController.swift b/AlphaWallet/Browser/ViewControllers/BrowserViewController.swift index c0ab8a81a..1bc128dfc 100644 --- a/AlphaWallet/Browser/ViewControllers/BrowserViewController.swift +++ b/AlphaWallet/Browser/ViewControllers/BrowserViewController.swift @@ -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) } } diff --git a/AlphaWallet/Browser/ViewControllers/DappsAutoCompletionViewController.swift b/AlphaWallet/Browser/ViewControllers/DappsAutoCompletionViewController.swift new file mode 100644 index 000000000..7011592a2 --- /dev/null +++ b/AlphaWallet/Browser/ViewControllers/DappsAutoCompletionViewController.swift @@ -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) + } +} diff --git a/AlphaWallet/Browser/ViewControllers/DappsHomeViewController.swift b/AlphaWallet/Browser/ViewControllers/DappsHomeViewController.swift new file mode 100644 index 000000000..19c743ab1 --- /dev/null +++ b/AlphaWallet/Browser/ViewControllers/DappsHomeViewController.swift @@ -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 + } +} diff --git a/AlphaWallet/Browser/ViewControllers/DiscoverDappsViewController.swift b/AlphaWallet/Browser/ViewControllers/DiscoverDappsViewController.swift new file mode 100644 index 000000000..8f1312377 --- /dev/null +++ b/AlphaWallet/Browser/ViewControllers/DiscoverDappsViewController.swift @@ -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 + } +} diff --git a/AlphaWallet/Browser/ViewControllers/EditMyDappViewController.swift b/AlphaWallet/Browser/ViewControllers/EditMyDappViewController.swift new file mode 100644 index 000000000..fbcec0e99 --- /dev/null +++ b/AlphaWallet/Browser/ViewControllers/EditMyDappViewController.swift @@ -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 + } +} diff --git a/AlphaWallet/Browser/ViewControllers/HistoryViewController.swift b/AlphaWallet/Browser/ViewControllers/HistoryViewController.swift deleted file mode 100644 index dbde65c5b..000000000 --- a/AlphaWallet/Browser/ViewControllers/HistoryViewController.swift +++ /dev/null @@ -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 - } - } - } - } -} diff --git a/AlphaWallet/Browser/ViewControllers/MasterBrowserViewController.swift b/AlphaWallet/Browser/ViewControllers/MasterBrowserViewController.swift deleted file mode 100644 index e0b02cbf3..000000000 --- a/AlphaWallet/Browser/ViewControllers/MasterBrowserViewController.swift +++ /dev/null @@ -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() - } -} diff --git a/AlphaWallet/Browser/ViewControllers/MyDappsViewController.swift b/AlphaWallet/Browser/ViewControllers/MyDappsViewController.swift new file mode 100644 index 000000000..9c58d42c5 --- /dev/null +++ b/AlphaWallet/Browser/ViewControllers/MyDappsViewController.swift @@ -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) + } +} diff --git a/AlphaWallet/Browser/ViewModel/BookmarkViewModel.swift b/AlphaWallet/Browser/ViewModel/BookmarkViewModel.swift deleted file mode 100644 index d803f6b1f..000000000 --- a/AlphaWallet/Browser/ViewModel/BookmarkViewModel.swift +++ /dev/null @@ -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) - } -} diff --git a/AlphaWallet/Browser/ViewModel/BookmarksViewModel.swift b/AlphaWallet/Browser/ViewModel/BookmarksViewModel.swift deleted file mode 100644 index 2a4a36060..000000000 --- a/AlphaWallet/Browser/ViewModel/BookmarksViewModel.swift +++ /dev/null @@ -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]) - } -} diff --git a/AlphaWallet/Browser/ViewModel/BrowserHistoryCellViewModel.swift b/AlphaWallet/Browser/ViewModel/BrowserHistoryCellViewModel.swift new file mode 100644 index 000000000..448972f62 --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/BrowserHistoryCellViewModel.swift @@ -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 + } +} diff --git a/AlphaWallet/Browser/ViewModel/Dapp.swift b/AlphaWallet/Browser/ViewModel/Dapp.swift new file mode 100644 index 000000000..416f984f1 --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/Dapp.swift @@ -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 +} diff --git a/AlphaWallet/Browser/ViewModel/DappButtonViewModel.swift b/AlphaWallet/Browser/ViewModel/DappButtonViewModel.swift new file mode 100644 index 000000000..40edd50db --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/DappButtonViewModel.swift @@ -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 +} diff --git a/AlphaWallet/Browser/ViewModel/DappViewCellViewModel.swift b/AlphaWallet/Browser/ViewModel/DappViewCellViewModel.swift new file mode 100644 index 000000000..5c0e6def1 --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/DappViewCellViewModel.swift @@ -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)! + } +} diff --git a/AlphaWallet/Browser/ViewModel/Dapps.swift b/AlphaWallet/Browser/ViewModel/Dapps.swift new file mode 100644 index 000000000..f4b8e5a97 --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/Dapps.swift @@ -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) + }() +} diff --git a/AlphaWallet/Browser/ViewModel/DappsAutoCompletionCellViewModel.swift b/AlphaWallet/Browser/ViewModel/DappsAutoCompletionCellViewModel.swift new file mode 100644 index 000000000..f09ddadbc --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/DappsAutoCompletionCellViewModel.swift @@ -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) + } +} diff --git a/AlphaWallet/Browser/ViewModel/DappsAutoCompletionViewControllerViewModel.swift b/AlphaWallet/Browser/ViewModel/DappsAutoCompletionViewControllerViewModel.swift new file mode 100644 index 000000000..0decafca3 --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/DappsAutoCompletionViewControllerViewModel.swift @@ -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) } + } + } +} diff --git a/AlphaWallet/Browser/ViewModel/DappsHomeEmptyViewViewModel.swift b/AlphaWallet/Browser/ViewModel/DappsHomeEmptyViewViewModel.swift new file mode 100644 index 000000000..222ca40b1 --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/DappsHomeEmptyViewViewModel.swift @@ -0,0 +1,8 @@ +// Copyright © 2018 Stormbird PTE. LTD. + +import Foundation + +struct DappsHomeEmptyViewViewModel { + let headerViewViewModel: DappsHomeHeaderViewViewModel + let title: String +} diff --git a/AlphaWallet/Browser/ViewModel/DappsHomeHeaderViewViewModel.swift b/AlphaWallet/Browser/ViewModel/DappsHomeHeaderViewViewModel.swift new file mode 100644 index 000000000..27f76d52e --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/DappsHomeHeaderViewViewModel.swift @@ -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) + } +} diff --git a/AlphaWallet/Browser/ViewModel/DappsHomeViewControllerHeaderViewViewModel.swift b/AlphaWallet/Browser/ViewModel/DappsHomeViewControllerHeaderViewViewModel.swift new file mode 100644 index 000000000..c0e77a2ce --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/DappsHomeViewControllerHeaderViewViewModel.swift @@ -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() + } +} diff --git a/AlphaWallet/Browser/ViewModel/DappsHomeViewControllerViewModel.swift b/AlphaWallet/Browser/ViewModel/DappsHomeViewControllerViewModel.swift new file mode 100644 index 000000000..00f29d11f --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/DappsHomeViewControllerViewModel.swift @@ -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 + } +} \ No newline at end of file diff --git a/AlphaWallet/Browser/ViewModel/DiscoverDappCellViewModel.swift b/AlphaWallet/Browser/ViewModel/DiscoverDappCellViewModel.swift new file mode 100644 index 000000000..a53f0898f --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/DiscoverDappCellViewModel.swift @@ -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 + } +} diff --git a/AlphaWallet/Browser/ViewModel/DiscoverDappsViewControllerViewModel.swift b/AlphaWallet/Browser/ViewModel/DiscoverDappsViewControllerViewModel.swift new file mode 100644 index 000000000..c065fca02 --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/DiscoverDappsViewControllerViewModel.swift @@ -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 + } +} diff --git a/AlphaWallet/Browser/ViewModel/EditMyDappViewControllerViewModel.swift b/AlphaWallet/Browser/ViewModel/EditMyDappViewControllerViewModel.swift new file mode 100644 index 000000000..6d57527eb --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/EditMyDappViewControllerViewModel.swift @@ -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() + } +} diff --git a/AlphaWallet/Browser/ViewModel/HistoriesViewModel.swift b/AlphaWallet/Browser/ViewModel/HistoriesViewModel.swift index 1605ea671..94572e219 100644 --- a/AlphaWallet/Browser/ViewModel/HistoriesViewModel.swift +++ b/AlphaWallet/Browser/ViewModel/HistoriesViewModel.swift @@ -3,12 +3,9 @@ import Foundation struct HistoriesViewModel { - private let store: HistoryStore - init( - store: HistoryStore - ) { + init(store: HistoryStore) { self.store = store } diff --git a/AlphaWallet/Browser/ViewModel/HistoryViewModel.swift b/AlphaWallet/Browser/ViewModel/HistoryViewModel.swift deleted file mode 100644 index 8e6d39c2a..000000000 --- a/AlphaWallet/Browser/ViewModel/HistoryViewModel.swift +++ /dev/null @@ -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) - } -} diff --git a/AlphaWallet/Browser/ViewModel/MarketplaceViewModel.swift b/AlphaWallet/Browser/ViewModel/MarketplaceViewModel.swift deleted file mode 100644 index 188c842ac..000000000 --- a/AlphaWallet/Browser/ViewModel/MarketplaceViewModel.swift +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright SIX DAY LLC. All rights reserved. - -import Foundation - -struct MarketplaceViewModel { - var title: String { - return R.string.localizable.aMarketplaceTabbarItemTitle() - } -} diff --git a/AlphaWallet/Browser/ViewModel/MyDappCellViewModel.swift b/AlphaWallet/Browser/ViewModel/MyDappCellViewModel.swift new file mode 100644 index 000000000..02b610e5b --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/MyDappCellViewModel.swift @@ -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 + } +} diff --git a/AlphaWallet/Browser/ViewModel/MyDappsViewControllerViewModel.swift b/AlphaWallet/Browser/ViewModel/MyDappsViewControllerViewModel.swift new file mode 100644 index 000000000..48ef9436f --- /dev/null +++ b/AlphaWallet/Browser/ViewModel/MyDappsViewControllerViewModel.swift @@ -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] + } +} diff --git a/AlphaWallet/Browser/Views/BrowserHistoryCell.swift b/AlphaWallet/Browser/Views/BrowserHistoryCell.swift new file mode 100644 index 000000000..2025abbfa --- /dev/null +++ b/AlphaWallet/Browser/Views/BrowserHistoryCell.swift @@ -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 + } + } +} diff --git a/AlphaWallet/Browser/Views/BrowserHistoryViewControllerHeaderView.swift b/AlphaWallet/Browser/Views/BrowserHistoryViewControllerHeaderView.swift new file mode 100644 index 000000000..370d206e5 --- /dev/null +++ b/AlphaWallet/Browser/Views/BrowserHistoryViewControllerHeaderView.swift @@ -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) + } +} diff --git a/AlphaWallet/Browser/Views/BrowserNavigationBar.swift b/AlphaWallet/Browser/Views/BrowserNavigationBar.swift deleted file mode 100644 index 80699d027..000000000 --- a/AlphaWallet/Browser/Views/BrowserNavigationBar.swift +++ /dev/null @@ -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) - } -} diff --git a/AlphaWallet/Browser/Views/DappBrowserNavigationBar.swift b/AlphaWallet/Browser/Views/DappBrowserNavigationBar.swift new file mode 100644 index 000000000..dea52a859 --- /dev/null +++ b/AlphaWallet/Browser/Views/DappBrowserNavigationBar.swift @@ -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 + } + } +} diff --git a/AlphaWallet/Browser/Views/DappButton.swift b/AlphaWallet/Browser/Views/DappButton.swift new file mode 100644 index 000000000..029ee3557 --- /dev/null +++ b/AlphaWallet/Browser/Views/DappButton.swift @@ -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 + } +} diff --git a/AlphaWallet/Browser/Views/DappViewCell.swift b/AlphaWallet/Browser/Views/DappViewCell.swift new file mode 100644 index 000000000..df5ab316f --- /dev/null +++ b/AlphaWallet/Browser/Views/DappViewCell.swift @@ -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) + } +} diff --git a/AlphaWallet/Browser/Views/DappsAutoCompletionCell.swift b/AlphaWallet/Browser/Views/DappsAutoCompletionCell.swift new file mode 100644 index 000000000..f1d12d563 --- /dev/null +++ b/AlphaWallet/Browser/Views/DappsAutoCompletionCell.swift @@ -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 + } +} diff --git a/AlphaWallet/Browser/Views/DappsHomeEmptyView.swift b/AlphaWallet/Browser/Views/DappsHomeEmptyView.swift new file mode 100644 index 000000000..f7ff865bf --- /dev/null +++ b/AlphaWallet/Browser/Views/DappsHomeEmptyView.swift @@ -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 + } +} diff --git a/AlphaWallet/Browser/Views/DappsHomeHeaderView.swift b/AlphaWallet/Browser/Views/DappsHomeHeaderView.swift new file mode 100644 index 000000000..4ac982fb9 --- /dev/null +++ b/AlphaWallet/Browser/Views/DappsHomeHeaderView.swift @@ -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 + } +} diff --git a/AlphaWallet/Browser/Views/DappsHomeViewControllerHeaderView.swift b/AlphaWallet/Browser/Views/DappsHomeViewControllerHeaderView.swift new file mode 100644 index 000000000..5213b6082 --- /dev/null +++ b/AlphaWallet/Browser/Views/DappsHomeViewControllerHeaderView.swift @@ -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)) + } +} diff --git a/AlphaWallet/Browser/Views/DiscoverDappCell.swift b/AlphaWallet/Browser/Views/DiscoverDappCell.swift new file mode 100644 index 000000000..c52a75b2e --- /dev/null +++ b/AlphaWallet/Browser/Views/DiscoverDappCell.swift @@ -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) + } +} diff --git a/AlphaWallet/Browser/Views/MyDappCell.swift b/AlphaWallet/Browser/Views/MyDappCell.swift new file mode 100644 index 000000000..f1fd992e3 --- /dev/null +++ b/AlphaWallet/Browser/Views/MyDappCell.swift @@ -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 + } + } +} diff --git a/AlphaWallet/Browser/Views/MyDappsViewControllerHeaderView.swift b/AlphaWallet/Browser/Views/MyDappsViewControllerHeaderView.swift new file mode 100644 index 000000000..d84fff5b4 --- /dev/null +++ b/AlphaWallet/Browser/Views/MyDappsViewControllerHeaderView.swift @@ -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 + } +} diff --git a/AlphaWallet/Core/Helpers/ScreenChecker.swift b/AlphaWallet/Core/Helpers/ScreenChecker.swift index 803f2d8a4..74b025f00 100644 --- a/AlphaWallet/Core/Helpers/ScreenChecker.swift +++ b/AlphaWallet/Core/Helpers/ScreenChecker.swift @@ -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 + } } diff --git a/AlphaWallet/Extensions/UIViewController.swift b/AlphaWallet/Extensions/UIViewController.swift index 39016d3b3..6ae333b10 100644 --- a/AlphaWallet/Extensions/UIViewController.swift +++ b/AlphaWallet/Extensions/UIViewController.swift @@ -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 diff --git a/AlphaWallet/InCoordinator.swift b/AlphaWallet/InCoordinator.swift index 7bb345665..5bbf29510 100644 --- a/AlphaWallet/InCoordinator.swift +++ b/AlphaWallet/InCoordinator.swift @@ -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 { diff --git a/AlphaWallet/Localization/en.lproj/Localizable.strings b/AlphaWallet/Localization/en.lproj/Localizable.strings index 0cb77708c..de01edc5d 100644 --- a/AlphaWallet/Localization/en.lproj/Localizable.strings +++ b/AlphaWallet/Localization/en.lproj/Localizable.strings @@ -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?"; diff --git a/AlphaWallet/Localization/es.lproj/Localizable.strings b/AlphaWallet/Localization/es.lproj/Localizable.strings index c2432225a..f35b26181 100644 --- a/AlphaWallet/Localization/es.lproj/Localizable.strings +++ b/AlphaWallet/Localization/es.lproj/Localizable.strings @@ -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?"; diff --git a/AlphaWallet/Localization/ja.lproj/Localizable.strings b/AlphaWallet/Localization/ja.lproj/Localizable.strings index cdc80e969..e944bd05e 100644 --- a/AlphaWallet/Localization/ja.lproj/Localizable.strings +++ b/AlphaWallet/Localization/ja.lproj/Localizable.strings @@ -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?"; diff --git a/AlphaWallet/Localization/ko.lproj/Localizable.strings b/AlphaWallet/Localization/ko.lproj/Localizable.strings index fd1abcde5..569846f98 100644 --- a/AlphaWallet/Localization/ko.lproj/Localizable.strings +++ b/AlphaWallet/Localization/ko.lproj/Localizable.strings @@ -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?"; diff --git a/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings b/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings index 18a64d747..46c623cd3 100644 --- a/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings +++ b/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings @@ -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?"; diff --git a/AlphaWallet/Style/AppStyle.swift b/AlphaWallet/Style/AppStyle.swift index da54acf61..5fddd28c1 100644 --- a/AlphaWallet/Style/AppStyle.swift +++ b/AlphaWallet/Style/AppStyle.swift @@ -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) + } + } } diff --git a/AlphaWallet/UI/BoxView.swift b/AlphaWallet/UI/BoxView.swift new file mode 100644 index 000000000..de75e688a --- /dev/null +++ b/AlphaWallet/UI/BoxView.swift @@ -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: 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 + } +} diff --git a/AlphaWalletTests/Coordinators/InCoordinatorTests.swift b/AlphaWalletTests/Coordinators/InCoordinatorTests.swift index 44b523e1f..5e20ef1f0 100644 --- a/AlphaWalletTests/Coordinators/InCoordinatorTests.swift +++ b/AlphaWalletTests/Coordinators/InCoordinatorTests.swift @@ -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) }