diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 03fdc0625d..3bffe92fa3 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -1060,6 +1060,7 @@ 2D077E472BFDCB8700FE422E /* ManualBackupChainTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D077E462BFDCB8700FE422E /* ManualBackupChainTableViewCell.swift */; }; 2D0780432D13273F003E35B1 /* DAppBrowserTabsButtonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D0780422D13273F003E35B1 /* DAppBrowserTabsButtonViewModel.swift */; }; 2D0B77F72D14392500654497 /* UIApplication+TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D0B77F62D14392500654497 /* UIApplication+TabBarController.swift */; }; + 2D0C3B5D2D1E95650094AF20 /* DAppSearchingByQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D0C3B5C2D1E95650094AF20 /* DAppSearchingByQuery.swift */; }; 2D141FDC2C5AE4230013D952 /* EndedReferendumProgressViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D141FDB2C5AE4230013D952 /* EndedReferendumProgressViewModelFactory.swift */; }; 2D1A5F552C358E280073773D /* CustomNetworkSetupFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D1A5F542C358E280073773D /* CustomNetworkSetupFactory.swift */; }; 2D1A5F572C35A8720073773D /* PartialCustomChainModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D1A5F562C35A8720073773D /* PartialCustomChainModel.swift */; }; @@ -1084,6 +1085,7 @@ 2D1D66042CD89573009C6C2F /* AssetListStyleSwitcherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D1D66032CD89573009C6C2F /* AssetListStyleSwitcherView.swift */; }; 2D1D66062CD92209009C6C2F /* QRDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D1D66052CD92209009C6C2F /* QRDisplayView.swift */; }; 2D2F6F522C50E52D005020EF /* VotingCurveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D2F6F512C50E52D005020EF /* VotingCurveTests.swift */; }; + 2D31D3062D149F74004BF46B /* ModalCardPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D31D3052D149F74004BF46B /* ModalCardPresentationController.swift */; }; 2D32BE122C6A49900047F520 /* ExtrinsicAssetConversionFeeEstimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32BE052C6A49900047F520 /* ExtrinsicAssetConversionFeeEstimator.swift */; }; 2D32BE132C6A49900047F520 /* ExtrinsicAssetConversionFeeInstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32BE062C6A49900047F520 /* ExtrinsicAssetConversionFeeInstaller.swift */; }; 2D32BE152C6A49900047F520 /* ExtrinsicFeeEstimationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32BE082C6A49900047F520 /* ExtrinsicFeeEstimationProtocol.swift */; }; @@ -1115,6 +1117,7 @@ 2D3EA7DE2CFE36BF0033AFD2 /* DAppCategoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3EA7DD2CFE36BF0033AFD2 /* DAppCategoryViewModel.swift */; }; 2D3EA7E02CFEDC0F0033AFD2 /* DAppCategoryViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3EA7DF2CFEDC0F0033AFD2 /* DAppCategoryViewModelFactory.swift */; }; 2D3EA7E22CFF0D540033AFD2 /* DAppSearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3EA7E12CFF0D540033AFD2 /* DAppSearchViewModel.swift */; }; + 2D42A0EE2D27E00100EB46CE /* UIScreen+Corners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42A0ED2D27E00100EB46CE /* UIScreen+Corners.swift */; }; 2D442CEF2CD0F7DF00A0380F /* AppearanceDepending.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D442CEE2CD0F7DF00A0380F /* AppearanceDepending.swift */; }; 2D442CF12CD0F85400A0380F /* IconAppearanceDepending.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D442CF02CD0F85400A0380F /* IconAppearanceDepending.swift */; }; 2D442CF32CD103ED00A0380F /* BarcodeCreationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D442CF22CD103ED00A0380F /* BarcodeCreationError.swift */; }; @@ -1155,6 +1158,9 @@ 2D5A61CD2C0F58DD006D58E3 /* NetworksEmptyPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A61CC2C0F58DD006D58E3 /* NetworksEmptyPlaceholderView.swift */; }; 2D5A61D02C0FAC5B006D58E3 /* IntegrateNetworksBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A61CF2C0FAC5B006D58E3 /* IntegrateNetworksBanner.swift */; }; 2D5FC1A42C5220900013352B /* AccountVoteFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5FC1A32C5220900013352B /* AccountVoteFactory.swift */; }; + 2D60C9BD2D1AA97C00027EC6 /* ModalCardPresentationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D60C9BC2D1AA97C00027EC6 /* ModalCardPresentationConfiguration.swift */; }; + 2D60C9BF2D1AA9E500027EC6 /* ModalCardPresentationStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D60C9BE2D1AA9E500027EC6 /* ModalCardPresentationStyle.swift */; }; + 2D60C9C12D1AABA100027EC6 /* ModalCardPresentationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D60C9C02D1AABA100027EC6 /* ModalCardPresentationFactory.swift */; }; 2D6287352C917A2B00060814 /* VotingPowerLocal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6287342C917A2B00060814 /* VotingPowerLocal.swift */; }; 2D6287372C917E3900060814 /* VotingPowerMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6287362C917E3900060814 /* VotingPowerMapper.swift */; }; 2D62873A2C9180C800060814 /* VotingPowerLocalstorageSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6287392C9180C800060814 /* VotingPowerLocalstorageSubscriber.swift */; }; @@ -1226,12 +1232,7 @@ 2D8684582BFF8CE600A663D2 /* BaseExportPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8684572BFF8CE600A663D2 /* BaseExportPresenter.swift */; }; 2D86845E2BFF9DA000A663D2 /* BackupAttentionInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D86845D2BFF9DA000A663D2 /* BackupAttentionInteractor.swift */; }; 2D8684602C0086A500A663D2 /* ManualBackupKeyListViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D86845F2C0086A500A663D2 /* ManualBackupKeyListViewModelFactory.swift */; }; - 2D8851DB2D0888690099B078 /* CardPresentingTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8851D62D0888690099B078 /* CardPresentingTransition.swift */; }; - 2D8851DC2D0888690099B078 /* CardLayoutPresentationContoroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8851D82D0888690099B078 /* CardLayoutPresentationContoroller.swift */; }; - 2D8851DD2D0888690099B078 /* CardDismissingTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8851D52D0888690099B078 /* CardDismissingTransition.swift */; }; 2D8851DE2D0888690099B078 /* UIViewController+PresentCardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8851D92D0888690099B078 /* UIViewController+PresentCardLayout.swift */; }; - 2D8851DF2D0888690099B078 /* CardLayoutTransitionDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8851D32D0888690099B078 /* CardLayoutTransitionDelegateProtocol.swift */; }; - 2D8851E02D0888690099B078 /* CardLayoutTransitionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8851D22D0888690099B078 /* CardLayoutTransitionDelegate.swift */; }; 2D8BA36F2C99A6ED002ED440 /* SwipeGovDetailsInteractorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8BA36E2C99A6ED002ED440 /* SwipeGovDetailsInteractorError.swift */; }; 2D8BA3712C99A728002ED440 /* SwipeGovVotingListInteractorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8BA3702C99A728002ED440 /* SwipeGovVotingListInteractorError.swift */; }; 2D8EFB242CF0939B00ED7174 /* WebViewFilesRenderOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8EFB232CF0939B00ED7174 /* WebViewFilesRenderOperationFactory.swift */; }; @@ -1252,6 +1253,7 @@ 2D95DF5D2C6F9129009BB063 /* HydraExtrinsicFeeInstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D95DF5C2C6F9129009BB063 /* HydraExtrinsicFeeInstaller.swift */; }; 2D95DF5F2C6FAFF6009BB063 /* ExtrinsicFeeEstimatingWrapperFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D95DF5E2C6FAFF6009BB063 /* ExtrinsicFeeEstimatingWrapperFactory.swift */; }; 2D95DF612C74B79A009BB063 /* AssetConversionFeeSharedStateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D95DF602C74B79A009BB063 /* AssetConversionFeeSharedStateStore.swift */; }; + 2D966B432D22A53000BC4336 /* ModalCardPresentationTransformFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D966B422D22A53000BC4336 /* ModalCardPresentationTransformFactory.swift */; }; 2DA85A722C7F6E0900591900 /* SwipeGovBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA85A712C7F6E0900591900 /* SwipeGovBannerView.swift */; }; 2DA85A742C7FC5D100591900 /* RoundedGradientBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA85A732C7FC5D100591900 /* RoundedGradientBackgroundView.swift */; }; 2DA85A772C7FCCCE00591900 /* SwipeGovViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA85A762C7FCCCE00591900 /* SwipeGovViewModelFactory.swift */; }; @@ -1281,6 +1283,7 @@ 2DBB28932CD432FB00DFA0AD /* CopyAddressPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBB28922CD432FB00DFA0AD /* CopyAddressPresentable.swift */; }; 2DC075E32BF24AB400868563 /* CheckBoxIconDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC075E22BF24AB400868563 /* CheckBoxIconDetailsView.swift */; }; 2DC075E92BF4B97900868563 /* BackupAttentionTableTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC075E82BF4B97900868563 /* BackupAttentionTableTitleView.swift */; }; + 2DC304EC2D2BF27A0020B0E6 /* ModalCardPresentationControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC304EB2D2BF27A0020B0E6 /* ModalCardPresentationControllerDelegate.swift */; }; 2DCC875B2C5820BA0028C3CA /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849013D424A927E2008F705E /* Logger.swift */; }; 2DCC875D2C58D0D00028C3CA /* GovernanceTotalVotesFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DCC875C2C58D0D00028C3CA /* GovernanceTotalVotesFactory.swift */; }; 2DCC87602C596C560028C3CA /* BaseCloudBackupCreatePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DCC875F2C596C560028C3CA /* BaseCloudBackupCreatePresenter.swift */; }; @@ -6478,6 +6481,7 @@ 2D077E462BFDCB8700FE422E /* ManualBackupChainTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualBackupChainTableViewCell.swift; sourceTree = ""; }; 2D0780422D13273F003E35B1 /* DAppBrowserTabsButtonViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppBrowserTabsButtonViewModel.swift; sourceTree = ""; }; 2D0B77F62D14392500654497 /* UIApplication+TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+TabBarController.swift"; sourceTree = ""; }; + 2D0C3B5C2D1E95650094AF20 /* DAppSearchingByQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppSearchingByQuery.swift; sourceTree = ""; }; 2D141FDB2C5AE4230013D952 /* EndedReferendumProgressViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndedReferendumProgressViewModelFactory.swift; sourceTree = ""; }; 2D1A5F542C358E280073773D /* CustomNetworkSetupFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNetworkSetupFactory.swift; sourceTree = ""; }; 2D1A5F562C35A8720073773D /* PartialCustomChainModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialCustomChainModel.swift; sourceTree = ""; }; @@ -6502,6 +6506,7 @@ 2D1D66032CD89573009C6C2F /* AssetListStyleSwitcherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetListStyleSwitcherView.swift; sourceTree = ""; }; 2D1D66052CD92209009C6C2F /* QRDisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRDisplayView.swift; sourceTree = ""; }; 2D2F6F512C50E52D005020EF /* VotingCurveTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VotingCurveTests.swift; sourceTree = ""; }; + 2D31D3052D149F74004BF46B /* ModalCardPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalCardPresentationController.swift; sourceTree = ""; }; 2D32BE052C6A49900047F520 /* ExtrinsicAssetConversionFeeEstimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtrinsicAssetConversionFeeEstimator.swift; sourceTree = ""; }; 2D32BE062C6A49900047F520 /* ExtrinsicAssetConversionFeeInstaller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtrinsicAssetConversionFeeInstaller.swift; sourceTree = ""; }; 2D32BE082C6A49900047F520 /* ExtrinsicFeeEstimationProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtrinsicFeeEstimationProtocol.swift; sourceTree = ""; }; @@ -6533,6 +6538,7 @@ 2D3EA7DD2CFE36BF0033AFD2 /* DAppCategoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppCategoryViewModel.swift; sourceTree = ""; }; 2D3EA7DF2CFEDC0F0033AFD2 /* DAppCategoryViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppCategoryViewModelFactory.swift; sourceTree = ""; }; 2D3EA7E12CFF0D540033AFD2 /* DAppSearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppSearchViewModel.swift; sourceTree = ""; }; + 2D42A0ED2D27E00100EB46CE /* UIScreen+Corners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScreen+Corners.swift"; sourceTree = ""; }; 2D442CEE2CD0F7DF00A0380F /* AppearanceDepending.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceDepending.swift; sourceTree = ""; }; 2D442CF02CD0F85400A0380F /* IconAppearanceDepending.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconAppearanceDepending.swift; sourceTree = ""; }; 2D442CF22CD103ED00A0380F /* BarcodeCreationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarcodeCreationError.swift; sourceTree = ""; }; @@ -6572,6 +6578,9 @@ 2D5A61CC2C0F58DD006D58E3 /* NetworksEmptyPlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworksEmptyPlaceholderView.swift; sourceTree = ""; }; 2D5A61CF2C0FAC5B006D58E3 /* IntegrateNetworksBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrateNetworksBanner.swift; sourceTree = ""; }; 2D5FC1A32C5220900013352B /* AccountVoteFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountVoteFactory.swift; sourceTree = ""; }; + 2D60C9BC2D1AA97C00027EC6 /* ModalCardPresentationConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalCardPresentationConfiguration.swift; sourceTree = ""; }; + 2D60C9BE2D1AA9E500027EC6 /* ModalCardPresentationStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalCardPresentationStyle.swift; sourceTree = ""; }; + 2D60C9C02D1AABA100027EC6 /* ModalCardPresentationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalCardPresentationFactory.swift; sourceTree = ""; }; 2D6287342C917A2B00060814 /* VotingPowerLocal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VotingPowerLocal.swift; sourceTree = ""; }; 2D6287362C917E3900060814 /* VotingPowerMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VotingPowerMapper.swift; sourceTree = ""; }; 2D6287392C9180C800060814 /* VotingPowerLocalstorageSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VotingPowerLocalstorageSubscriber.swift; sourceTree = ""; }; @@ -6643,11 +6652,6 @@ 2D8684572BFF8CE600A663D2 /* BaseExportPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseExportPresenter.swift; sourceTree = ""; }; 2D86845D2BFF9DA000A663D2 /* BackupAttentionInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupAttentionInteractor.swift; sourceTree = ""; }; 2D86845F2C0086A500A663D2 /* ManualBackupKeyListViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualBackupKeyListViewModelFactory.swift; sourceTree = ""; }; - 2D8851D22D0888690099B078 /* CardLayoutTransitionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardLayoutTransitionDelegate.swift; sourceTree = ""; }; - 2D8851D32D0888690099B078 /* CardLayoutTransitionDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardLayoutTransitionDelegateProtocol.swift; sourceTree = ""; }; - 2D8851D52D0888690099B078 /* CardDismissingTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardDismissingTransition.swift; sourceTree = ""; }; - 2D8851D62D0888690099B078 /* CardPresentingTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentingTransition.swift; sourceTree = ""; }; - 2D8851D82D0888690099B078 /* CardLayoutPresentationContoroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardLayoutPresentationContoroller.swift; sourceTree = ""; }; 2D8851D92D0888690099B078 /* UIViewController+PresentCardLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+PresentCardLayout.swift"; sourceTree = ""; }; 2D8BA36E2C99A6ED002ED440 /* SwipeGovDetailsInteractorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeGovDetailsInteractorError.swift; sourceTree = ""; }; 2D8BA3702C99A728002ED440 /* SwipeGovVotingListInteractorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeGovVotingListInteractorError.swift; sourceTree = ""; }; @@ -6669,6 +6673,7 @@ 2D95DF5C2C6F9129009BB063 /* HydraExtrinsicFeeInstaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HydraExtrinsicFeeInstaller.swift; sourceTree = ""; }; 2D95DF5E2C6FAFF6009BB063 /* ExtrinsicFeeEstimatingWrapperFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtrinsicFeeEstimatingWrapperFactory.swift; sourceTree = ""; }; 2D95DF602C74B79A009BB063 /* AssetConversionFeeSharedStateStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetConversionFeeSharedStateStore.swift; sourceTree = ""; }; + 2D966B422D22A53000BC4336 /* ModalCardPresentationTransformFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalCardPresentationTransformFactory.swift; sourceTree = ""; }; 2DA85A712C7F6E0900591900 /* SwipeGovBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeGovBannerView.swift; sourceTree = ""; }; 2DA85A732C7FC5D100591900 /* RoundedGradientBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedGradientBackgroundView.swift; sourceTree = ""; }; 2DA85A762C7FCCCE00591900 /* SwipeGovViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeGovViewModelFactory.swift; sourceTree = ""; }; @@ -6698,6 +6703,7 @@ 2DBB28922CD432FB00DFA0AD /* CopyAddressPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyAddressPresentable.swift; sourceTree = ""; }; 2DC075E22BF24AB400868563 /* CheckBoxIconDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBoxIconDetailsView.swift; sourceTree = ""; }; 2DC075E82BF4B97900868563 /* BackupAttentionTableTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupAttentionTableTitleView.swift; sourceTree = ""; }; + 2DC304EB2D2BF27A0020B0E6 /* ModalCardPresentationControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalCardPresentationControllerDelegate.swift; sourceTree = ""; }; 2DC70DB97D2E9350022A899B /* TokensManagePresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TokensManagePresenter.swift; sourceTree = ""; }; 2DCC875C2C58D0D00028C3CA /* GovernanceTotalVotesFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GovernanceTotalVotesFactory.swift; sourceTree = ""; }; 2DCC875F2C596C560028C3CA /* BaseCloudBackupCreatePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseCloudBackupCreatePresenter.swift; sourceTree = ""; }; @@ -6884,7 +6890,6 @@ 4C57B5C83F5A27949AAE9A87 /* DAppBrowserWidgetViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppBrowserWidgetViewLayout.swift; sourceTree = ""; }; 4C602661DE4D6CAC482AF721 /* ParaStkCollatorsSearchViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkCollatorsSearchViewFactory.swift; sourceTree = ""; }; 4C71DEF78B69F017DF460AB7 /* CrowdloanContributionSetupViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CrowdloanContributionSetupViewController.swift; sourceTree = ""; }; - 4CB3C834CCCD632B6CB30BEB /* Pods_novawalletAll_novawallet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_novawalletAll_novawallet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4CE605788A0F121ECFC71C30 /* SwapExecutionPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SwapExecutionPresenter.swift; sourceTree = ""; }; 4D8246CDA02F544AF9DA2B11 /* ParaStkStakeSetupProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkStakeSetupProtocols.swift; sourceTree = ""; }; 4DAF102188EF43E4CFB62A5C /* SwipeGovReferendumDetailsViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SwipeGovReferendumDetailsViewFactory.swift; sourceTree = ""; }; @@ -13730,33 +13735,18 @@ path = Presenter; sourceTree = ""; }; - 2D8851D42D0888690099B078 /* CardLayoutTransitionDelegate */ = { + 2D8851DA2D0888690099B078 /* ModalCard */ = { isa = PBXGroup; children = ( - 2D8851D22D0888690099B078 /* CardLayoutTransitionDelegate.swift */, - 2D8851D32D0888690099B078 /* CardLayoutTransitionDelegateProtocol.swift */, - ); - path = CardLayoutTransitionDelegate; - sourceTree = ""; - }; - 2D8851D72D0888690099B078 /* Transitions */ = { - isa = PBXGroup; - children = ( - 2D8851D52D0888690099B078 /* CardDismissingTransition.swift */, - 2D8851D62D0888690099B078 /* CardPresentingTransition.swift */, - ); - path = Transitions; - sourceTree = ""; - }; - 2D8851DA2D0888690099B078 /* CardLayoutPresentationController */ = { - isa = PBXGroup; - children = ( - 2D8851D42D0888690099B078 /* CardLayoutTransitionDelegate */, - 2D8851D72D0888690099B078 /* Transitions */, - 2D8851D82D0888690099B078 /* CardLayoutPresentationContoroller.swift */, + 2D31D3052D149F74004BF46B /* ModalCardPresentationController.swift */, + 2DC304EB2D2BF27A0020B0E6 /* ModalCardPresentationControllerDelegate.swift */, + 2D966B422D22A53000BC4336 /* ModalCardPresentationTransformFactory.swift */, + 2D60C9BC2D1AA97C00027EC6 /* ModalCardPresentationConfiguration.swift */, + 2D60C9BE2D1AA9E500027EC6 /* ModalCardPresentationStyle.swift */, + 2D60C9C02D1AABA100027EC6 /* ModalCardPresentationFactory.swift */, 2D8851D92D0888690099B078 /* UIViewController+PresentCardLayout.swift */, ); - path = CardLayoutPresentationController; + path = ModalCard; sourceTree = ""; }; 2D8EFB222CF0937F00ED7174 /* WebViewRenderFIlesOperationFactory */ = { @@ -18979,9 +18969,9 @@ 8490144124A93CDC008F705E /* ViewController */ = { isa = PBXGroup; children = ( - 2D8851DA2D0888690099B078 /* CardLayoutPresentationController */, 84FD91AE29B08F5F007851D3 /* SearchController */, F5AF5EB89229D84F3701AAAA /* ChainAddressDetails */, + 2D8851DA2D0888690099B078 /* ModalCard */, 84754C862510BAE100854599 /* ModalAlert */, 84D8F15324D80C8300AF43E9 /* ModalPicker */, 8428769624AE049A00D91AD8 /* SelectionListViewController */, @@ -19009,6 +18999,7 @@ 88DC3E1F292CA9B900DBCE4D /* Style */, F409672526B29B04008CD244 /* UIScrollView+ScrollToPage.swift */, 8490144E24A93E2E008F705E /* UIImage+Drawing.swift */, + 2D42A0ED2D27E00100EB46CE /* UIScreen+Corners.swift */, 849014C424AA890D008F705E /* UIFont+Style.swift */, 8428766A24ADF51D00D91AD8 /* UIViewController+Content.swift */, 84EBC55E24F71D6A00459D15 /* UITableViewCell+ReorderColor.swift */, @@ -23844,6 +23835,7 @@ BE0F7D4389B932A1F2E17361 /* DAppSearchViewLayout.swift */, DBF9C192200F9B998724FC6C /* DAppSearchViewFactory.swift */, 84EE780527C4DF130027357F /* DAppSearchInteractor.swift */, + 2D0C3B5C2D1E95650094AF20 /* DAppSearchingByQuery.swift */, ); path = DAppSearch; sourceTree = ""; @@ -25936,6 +25928,7 @@ 844493A7294853AF0066DE91 /* GovCommonOperationFactory.swift in Sources */, 84873B0926029CBD000A83EE /* StakingStateViewModelFactory.swift in Sources */, 84C3F78C26020DE800D47501 /* StakingStateMachineProtocols.swift in Sources */, + 2D0C3B5D2D1E95650094AF20 /* DAppSearchingByQuery.swift in Sources */, 846CA7722707A3060011124C /* SingleToMultiasset.xcmappingmodel in Sources */, 8425EA9525EA82CE00C307C9 /* AccountIdentity.swift in Sources */, 845BB8DB25E462B100E5FCDC /* SubstrateConstansts.swift in Sources */, @@ -26660,6 +26653,7 @@ 844DD65525EAF32D00B1DA97 /* SelectValidatorsStartViewModel.swift in Sources */, 845FB6732A22285C0003BCA6 /* MultistakingSyncService.swift in Sources */, 84AE7ABD27D55E3400495267 /* NftDetailsProgress.swift in Sources */, + 2DC304EC2D2BF27A0020B0E6 /* ModalCardPresentationControllerDelegate.swift in Sources */, 8470D6D0253E321C009E9A5D /* StorageSubscriptionProtocols.swift in Sources */, 8499FE6C27BD319300712589 /* RMRKV2OperationFactory.swift in Sources */, 84D184F42A04F5210060C1BD /* StackInfoTableCell+WallectConnect.swift in Sources */, @@ -27541,6 +27535,7 @@ 88D02FEA2942ED4F00E26390 /* RoundedButton+Style.swift in Sources */, 842BDB2A278C4F3C00AB4B5A /* DAppBrowserAuthorizingState.swift in Sources */, 84FEADF228783F55001DFC26 /* BaseParaStakingRewardCalculatoService.swift in Sources */, + 2D42A0EE2D27E00100EB46CE /* UIScreen+Corners.swift in Sources */, 0CE360232C343300006A6CE4 /* PriorityQueue.swift in Sources */, 848CC93D28D9F6D8009EB4B0 /* OnChainDispatchTime.swift in Sources */, 841E5534282CD9A900C8438F /* StakingType.swift in Sources */, @@ -27600,6 +27595,7 @@ 8434B5FE298D2CC900FACF4C /* GovernanceBaseEditDelegationProtocols.swift in Sources */, 844CB56A26F9C57D00396E13 /* WalletLocalStorageSubscriber.swift in Sources */, 842A738A27DE14B3006EE1EA /* TransactionLocalStorageHandler.swift in Sources */, + 2D60C9BD2D1AA97C00027EC6 /* ModalCardPresentationConfiguration.swift in Sources */, 8454C2652632B0EF00657DAD /* EventCodingPath.swift in Sources */, 77FDFC482B0E1CBD005A3569 /* ImportWalletUrlParsingService.swift in Sources */, 843910D4253F8DAD00E3C217 /* NetworkAvailabilityLayerPresenter.swift in Sources */, @@ -27724,12 +27720,7 @@ 84EBAB0B265E28590015E446 /* ChainDateCalculator.swift in Sources */, 2DEDF68D2C36FDCB007D5F4A /* LightChainModel.swift in Sources */, 887404B829828D5500EE270A /* LoadMoreFooterView.swift in Sources */, - 2D8851DB2D0888690099B078 /* CardPresentingTransition.swift in Sources */, - 2D8851DC2D0888690099B078 /* CardLayoutPresentationContoroller.swift in Sources */, - 2D8851DD2D0888690099B078 /* CardDismissingTransition.swift in Sources */, 2D8851DE2D0888690099B078 /* UIViewController+PresentCardLayout.swift in Sources */, - 2D8851DF2D0888690099B078 /* CardLayoutTransitionDelegateProtocol.swift in Sources */, - 2D8851E02D0888690099B078 /* CardLayoutTransitionDelegate.swift in Sources */, 84754C9A2513871300854599 /* SNAddressType+Codable.swift in Sources */, 84ED6BE82869A18F00B3C558 /* TransferConfirmCrossChainViewFactory.swift in Sources */, 88BB0B3D29C1E16300D041C1 /* KiltWeb3n+Ownership.swift in Sources */, @@ -28011,6 +28002,7 @@ 845B89262959627A00EE25B0 /* SecurityLayerWireframe.swift in Sources */, 777AE2AB2ABCB4A5004989C0 /* HtmlParsingOperationFactory.swift in Sources */, 8489A6D227FD5FB80040C066 /* StackActionCell.swift in Sources */, + 2D60C9C12D1AABA100027EC6 /* ModalCardPresentationFactory.swift in Sources */, 0CA50CB52AFE6F31005668CD /* GetTokenOptionsInteractor.swift in Sources */, F429324F26280F6B00752C2C /* StakingRewardDetailsViewModel.swift in Sources */, F4A11B5A272FEB0B0030E85B /* CrowdloanYourContributionsCell.swift in Sources */, @@ -28982,6 +28974,7 @@ 84E5D14227E32E8B00D27B1E /* AccountInputView.swift in Sources */, 2D6287472C941DFE00060814 /* SwipeGovVotingListItemView.swift in Sources */, 2B0FC94B4AE9AFE9532F493F /* ReferralCrowdloanViewFactory.swift in Sources */, + 2D60C9BF2D1AA9E500027EC6 /* ModalCardPresentationStyle.swift in Sources */, 84B91AED287078B0001A0420 /* StorageProviderSourceFallback.swift in Sources */, 84A70D9929CC7F0200C648AD /* PriceDataChange+Mapping.swift in Sources */, 59745D3C9602745E1417D2F6 /* ChainAssetSelectionInteractor.swift in Sources */, @@ -29439,6 +29432,7 @@ EFF8F905CE4E8A212FE79EE4 /* ParaStkYourCollatorsViewController.swift in Sources */, 0C1338142AB834750036BCD6 /* QRImageViewModelFactory.swift in Sources */, 77DF403D2B7DC91200ABDB53 /* SettingsSubscriber.swift in Sources */, + 2D966B432D22A53000BC4336 /* ModalCardPresentationTransformFactory.swift in Sources */, 84C355BF298A52F1005072CF /* ReferendumsInteractor+Protocol.swift in Sources */, FEEBBD315C4EFD4F8F237887 /* ParaStkYourCollatorsViewFactory.swift in Sources */, C1115B135F9C544C91C06B58 /* ParaStkUnstakeProtocols.swift in Sources */, @@ -30201,6 +30195,7 @@ 2D1A5F552C358E280073773D /* CustomNetworkSetupFactory.swift in Sources */, 25E4B008933E2EF7F2FAAA46 /* StakingMoreOptionsViewController.swift in Sources */, 20B1463FB1F2431A0C913DCE /* StakingMoreOptionsViewLayout.swift in Sources */, + 2D31D3062D149F74004BF46B /* ModalCardPresentationController.swift in Sources */, FF507CA7B1A33AD160DB59DE /* StakingMoreOptionsViewFactory.swift in Sources */, 07AB0BC861AA5F134DB9AC26 /* StartStakingInfoProtocols.swift in Sources */, 77F085A407EDCCF906FD6E22 /* StartStakingInfoWireframe.swift in Sources */, diff --git a/novawallet/Common/Extension/UIKit/UIApplication+TabBarController.swift b/novawallet/Common/Extension/UIKit/UIApplication+TabBarController.swift index 31b1fdbb59..1455c7b7b9 100644 --- a/novawallet/Common/Extension/UIKit/UIApplication+TabBarController.swift +++ b/novawallet/Common/Extension/UIKit/UIApplication+TabBarController.swift @@ -2,10 +2,14 @@ import UIKit extension UIApplication { var tabBarController: MainTabBarViewController? { - delegate? - .window?? - .rootViewController? + rootContainer? .children .first(where: { $0 is UITabBarController }) as? MainTabBarViewController } + + var rootContainer: NovaMainAppContainerViewController? { + delegate? + .window?? + .rootViewController as? NovaMainAppContainerViewController + } } diff --git a/novawallet/Common/Extension/UIKit/UIScreen+Corners.swift b/novawallet/Common/Extension/UIKit/UIScreen+Corners.swift new file mode 100644 index 0000000000..831f0079dd --- /dev/null +++ b/novawallet/Common/Extension/UIKit/UIScreen+Corners.swift @@ -0,0 +1,62 @@ +import UIKit + +extension UIScreen { + var cornerRadius: CGFloat { + let identifier = deviceModelIdentifier() + + switch identifier { + case "iPhone10,3", "iPhone10,6", + "iPhone11,2", + "iPhone11,4", "iPhone11,6", + "iPhone12,3", + "iPhone12,5": + return 39.0 + + case "iPhone11,8", + "iPhone12,1": + return 41.5 + + case "iPhone13,1", + "iPhone14,4": + return 44.0 + + case "iPhone13,2", + "iPhone13,3", + "iPhone14,2", + "iPhone14,7": + return 47.33 + + case "iPhone13,4", + "iPhone14,3", + "iPhone14,8": + return 53.33 + + case "iPhone15,2", + "iPhone15,3", + "iPhone15,4", + "iPhone15,5", + "iPhone16,1", + "iPhone16,2", + "iPhone17,3", + "iPhone17,4": + return 55.0 + + case "iPhone17,1", + "iPhone17,2": + return 62.0 + default: + return 0 + } + } + + private func deviceModelIdentifier() -> String { + var systemInfo = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + let identifier = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + return identifier + } +} diff --git a/novawallet/Common/Protocols/TransactionSigningPresenting.swift b/novawallet/Common/Protocols/TransactionSigningPresenting.swift index bd70b35625..17484ede22 100644 --- a/novawallet/Common/Protocols/TransactionSigningPresenting.swift +++ b/novawallet/Common/Protocols/TransactionSigningPresenting.swift @@ -69,7 +69,7 @@ final class TransactionSigningPresenter: TransactionSigningPresenting { completion(.failure(HardwareSigningError.signingCancelled)) } - controller.present(navigationController, animated: true) + controller.presentWithCardLayout(navigationController, animated: true) } func presentParitySignerFlow( diff --git a/novawallet/Common/Protocols/YourWalletsPresentable.swift b/novawallet/Common/Protocols/YourWalletsPresentable.swift index f13d7910be..a114482830 100644 --- a/novawallet/Common/Protocols/YourWalletsPresentable.swift +++ b/novawallet/Common/Protocols/YourWalletsPresentable.swift @@ -7,7 +7,7 @@ protocol YourWalletsPresentable { address: AccountAddress?, delegate: YourWalletsDelegate ) - func hideYourWallets(from view: ControllerBackedProtocol?) + func hideYourWallets(from view: YourWalletsPresentationProtocol) } extension YourWalletsPresentable { @@ -32,7 +32,7 @@ extension YourWalletsPresentable { view?.controller.present(viewController.controller, animated: true) } - func hideYourWallets(from view: ControllerBackedProtocol?) { - view?.controller.dismiss(animated: true) + func hideYourWallets(from view: YourWalletsPresentationProtocol) { + view.controller.dismiss(animated: true) } } diff --git a/novawallet/Common/View/CollectionView/TitleCollectionHeaderView.swift b/novawallet/Common/View/CollectionView/TitleCollectionHeaderView.swift index d4fc792569..e56fb29f36 100644 --- a/novawallet/Common/View/CollectionView/TitleCollectionHeaderView.swift +++ b/novawallet/Common/View/CollectionView/TitleCollectionHeaderView.swift @@ -47,6 +47,14 @@ final class TitleCollectionHeaderView: UICollectionReusableView { } } + var buttonWidth: CGFloat = 40.0 { + didSet { + button.snp.updateConstraints { make in + make.width.greaterThanOrEqualTo(buttonWidth) + } + } + } + override init(frame: CGRect) { super.init(frame: frame) @@ -69,13 +77,15 @@ final class TitleCollectionHeaderView: UICollectionReusableView { displayContentView.addSubview(button) button.snp.makeConstraints { make in - make.centerY.trailing.equalToSuperview() + make.top.bottom.equalToSuperview() + make.trailing.equalToSuperview() + make.width.greaterThanOrEqualTo(buttonWidth) } displayContentView.addSubview(titleView) titleView.snp.makeConstraints { make in make.leading.centerY.equalToSuperview() - make.trailing.equalTo(button.snp.leading) + make.trailing.lessThanOrEqualTo(button.snp.leading).offset(-8) } } } diff --git a/novawallet/Common/ViewController/CardLayoutPresentationController/CardLayoutPresentationContoroller.swift b/novawallet/Common/ViewController/CardLayoutPresentationController/CardLayoutPresentationContoroller.swift deleted file mode 100644 index fa996dfc7f..0000000000 --- a/novawallet/Common/ViewController/CardLayoutPresentationController/CardLayoutPresentationContoroller.swift +++ /dev/null @@ -1,52 +0,0 @@ -import UIKit - -final class CardLayoutPresentationController: UIViewController { - // we keep strong reference to delegate pan gesture processing - // because translation change is tied with transition. - // Delegate doesn't retains controller so we're safe here - - // swiftlint:disable weak_delegate - private let transitionDelegate: CardLayoutTransitionDelegateProtocol - - init(transitionDelegate: CardLayoutTransitionDelegateProtocol) { - self.transitionDelegate = transitionDelegate - - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - setupActions() - } - - static func topOffset() -> CGFloat { - (UIApplication.shared.keyWindow?.safeAreaInsets.top ?? 0) + 12 - } -} - -// MARK: Private - -private extension CardLayoutPresentationController { - func setupActions() { - let pan = UIPanGestureRecognizer( - target: self, - action: #selector(panGesture(_:)) - ) - - view.addGestureRecognizer(pan) - } - - @objc func panGesture(_ sender: UIPanGestureRecognizer) { - transitionDelegate.didReceivePanState( - sender.state, - translation: sender.translation(in: view), - for: view - ) - } -} diff --git a/novawallet/Common/ViewController/CardLayoutPresentationController/CardLayoutTransitionDelegate/CardLayoutTransitionDelegate.swift b/novawallet/Common/ViewController/CardLayoutPresentationController/CardLayoutTransitionDelegate/CardLayoutTransitionDelegate.swift deleted file mode 100644 index bb28284c71..0000000000 --- a/novawallet/Common/ViewController/CardLayoutPresentationController/CardLayoutTransitionDelegate/CardLayoutTransitionDelegate.swift +++ /dev/null @@ -1,106 +0,0 @@ -import UIKit -import SoraUI - -final class CardLayoutTransitionDelegate: NSObject { - private weak var presentedController: UIViewController? - - private var transition = UIPercentDrivenInteractiveTransition() - private var dimmingViewUUID = UUID() - - private var started = false - private var shouldFinish = false -} - -// MARK: UIViewControllerTransitioningDelegate - -extension CardLayoutTransitionDelegate: UIViewControllerTransitioningDelegate { - func animationController(forDismissed _: UIViewController) -> UIViewControllerAnimatedTransitioning? { - let animator = BlockViewAnimator(duration: Constants.dismissTransitionDuration) - - return CardDismissingTransition( - transitionDuration: Constants.dismissTransitionDuration, - animator: animator, - dimmingViewTag: dimmingViewUUID.hashValue - ) - } - - func animationController( - forPresented presented: UIViewController, - presenting _: UIViewController, - source _: UIViewController - ) -> UIViewControllerAnimatedTransitioning? { - presentedController = presented - - return CardPresentingTransition( - transitionDuration: Constants.presentTransitionDuration, - dimmingViewTag: dimmingViewUUID.hashValue - ) - } - - func interactionControllerForDismissal( - using _: UIViewControllerAnimatedTransitioning - ) -> UIViewControllerInteractiveTransitioning? { - started - ? transition - : nil - } - - func interactionControllerForPresentation( - using _: UIViewControllerAnimatedTransitioning - ) -> UIViewControllerInteractiveTransitioning? { - started - ? transition - : nil - } -} - -// MARK: CardLayoutTransitionDelegateProtocol - -extension CardLayoutTransitionDelegate: CardLayoutTransitionDelegateProtocol { - func didReceivePanState( - _ state: UIGestureRecognizer.State, - translation: CGPoint, - for view: UIView - ) { - let percentThreshold: CGFloat = 0.1 - - let verticalMovement = translation.y / view.bounds.height - let downwardMovement = fmaxf(Float(verticalMovement), 0.0) - let downwardMovementPercent = fminf(downwardMovement, 1.0) - let progress = CGFloat(downwardMovementPercent) - - switch state { - case .began: - started = true - presentedController?.dismiss( - animated: true, - completion: nil - ) - case .changed: - shouldFinish = progress > percentThreshold - transition.update(progress) - case .cancelled: - started = false - transition.cancel() - case .ended: - started = false - - if shouldFinish { - transition.finish() - } else { - transition.cancel() - } - default: - break - } - } -} - -// MARK: Constants - -private extension CardLayoutTransitionDelegate { - enum Constants { - static let presentTransitionDuration: TimeInterval = 0.5 - static let dismissTransitionDuration: TimeInterval = 0.25 - } -} diff --git a/novawallet/Common/ViewController/CardLayoutPresentationController/CardLayoutTransitionDelegate/CardLayoutTransitionDelegateProtocol.swift b/novawallet/Common/ViewController/CardLayoutPresentationController/CardLayoutTransitionDelegate/CardLayoutTransitionDelegateProtocol.swift deleted file mode 100644 index 5315afb4b0..0000000000 --- a/novawallet/Common/ViewController/CardLayoutPresentationController/CardLayoutTransitionDelegate/CardLayoutTransitionDelegateProtocol.swift +++ /dev/null @@ -1,9 +0,0 @@ -import UIKit - -protocol CardLayoutTransitionDelegateProtocol { - func didReceivePanState( - _ state: UIGestureRecognizer.State, - translation: CGPoint, - for view: UIView - ) -} diff --git a/novawallet/Common/ViewController/CardLayoutPresentationController/Transitions/CardDismissingTransition.swift b/novawallet/Common/ViewController/CardLayoutPresentationController/Transitions/CardDismissingTransition.swift deleted file mode 100644 index d09bafa641..0000000000 --- a/novawallet/Common/ViewController/CardLayoutPresentationController/Transitions/CardDismissingTransition.swift +++ /dev/null @@ -1,71 +0,0 @@ -import UIKit -import SoraUI - -final class CardDismissingTransition: NSObject { - private let transitionDuration: TimeInterval - private let animator: BlockViewAnimatorProtocol - private let dimmingViewTag: Int - - init( - transitionDuration: TimeInterval = 0.25, - animator: BlockViewAnimatorProtocol, - dimmingViewTag: Int - ) { - self.transitionDuration = transitionDuration - self.animator = animator - self.dimmingViewTag = dimmingViewTag - - super.init() - } -} - -// MARK: UIViewControllerAnimatedTransitioning - -extension CardDismissingTransition: UIViewControllerAnimatedTransitioning { - func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval { - transitionDuration - } - - func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - guard - let sourceController = transitionContext.viewController( - forKey: UITransitionContextViewControllerKey.from - ), - let destinationController = transitionContext.viewController( - forKey: UITransitionContextViewControllerKey.to - ) - else { return } - - let coveredContextView: UIView - - if let tabBarController = destinationController as? UITabBarController { - let navController = tabBarController.selectedViewController as? UINavigationController - coveredContextView = navController?.topViewController?.view ?? tabBarController.view - } else { - coveredContextView = destinationController.view - } - - let sourceFrame = sourceController.view.bounds.offsetBy( - dx: 0, - dy: sourceController.view.bounds.height - ) - - coveredContextView.layer.masksToBounds = true - - let dimmingView = coveredContextView.subviews.first { $0.tag == dimmingViewTag } - - animator.animate { - destinationController.view.transform = .identity - coveredContextView.layer.cornerRadius = 0 - dimmingView?.alpha = 0 - - sourceController.view.frame = sourceFrame - } completionBlock: { _ in - if !transitionContext.transitionWasCancelled { - dimmingView?.removeFromSuperview() - } - - transitionContext.completeTransition(!transitionContext.transitionWasCancelled) - } - } -} diff --git a/novawallet/Common/ViewController/CardLayoutPresentationController/Transitions/CardPresentingTransition.swift b/novawallet/Common/ViewController/CardLayoutPresentationController/Transitions/CardPresentingTransition.swift deleted file mode 100644 index 9eaca156a0..0000000000 --- a/novawallet/Common/ViewController/CardLayoutPresentationController/Transitions/CardPresentingTransition.swift +++ /dev/null @@ -1,152 +0,0 @@ -import UIKit -import SoraUI - -final class CardPresentingTransition: NSObject { - private let transitionDuration: TimeInterval - private let dimmingViewTag: Int - - init( - transitionDuration: TimeInterval = 0.5, - dimmingViewTag: Int - ) { - self.transitionDuration = transitionDuration - self.dimmingViewTag = dimmingViewTag - - super.init() - } -} - -// MARK: Private - -private extension CardPresentingTransition { - func createSourceViewTransform(_ sourceView: UIView) -> CGAffineTransform { - let widthDelta: CGFloat = UIConstants.horizontalInset * 2 - let scale = (sourceView.bounds.width - widthDelta) / sourceView.bounds.width - let heightDiffAfterScale = (sourceView.bounds.height - (sourceView.bounds.height * scale)) / 2 - let yOffset = sourceView.safeAreaInsets.top - heightDiffAfterScale - - let sourceTransform = CGAffineTransform.identity - let sourceScaleTransform = CGAffineTransform( - scaleX: scale, - y: scale - ) - let sourceTranslateTransform = CGAffineTransform( - translationX: .zero, - y: yOffset - ) - - return sourceTransform - .concatenating(sourceScaleTransform) - .concatenating(sourceTranslateTransform) - } - - func createDestinationViewTransform(_ finalFrame: CGRect) -> CGAffineTransform { - CGAffineTransform( - translationX: 0, - y: finalFrame.minY - ) - } - - func createDimmingView(for view: UIView) -> UIView { - let dimmingView = UIView(frame: view.bounds) - let dimmingColor = UIColor.white.withAlphaComponent(0.1) - - dimmingView.backgroundColor = dimmingColor - dimmingView.clipsToBounds = true - dimmingView.tag = dimmingViewTag - - return dimmingView - } - - func calculateFinalDestinationFrame(using sourceView: UIView) -> CGRect { - let finalOrigin = sourceView.bounds.offsetBy( - dx: 0, - dy: CardLayoutPresentationController.topOffset() - ).origin - - let finalFrameSize = CGSize( - width: sourceView.bounds.width, - height: sourceView.bounds.height - CardLayoutPresentationController.topOffset() - ) - - let finalDestinationViewFrame = CGRect( - origin: finalOrigin, - size: finalFrameSize - ) - - return finalDestinationViewFrame - } -} - -// MARK: UIViewControllerAnimatedTransitioning - -extension CardPresentingTransition: UIViewControllerAnimatedTransitioning { - func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval { - transitionDuration - } - - func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - guard - let sourceController = transitionContext.viewController(forKey: .from), - let destinationController = transitionContext.viewController(forKey: .to) - else { return } - - let coveredContextView: UIView - - if let tabBarController = sourceController as? UITabBarController { - let navController = tabBarController.selectedViewController as? UINavigationController - coveredContextView = navController?.topViewController?.view ?? tabBarController.view - } else { - coveredContextView = sourceController.view - } - - let initialDestinationViewFrame = coveredContextView.bounds.offsetBy( - dx: 0, - dy: coveredContextView.bounds.maxY - ) - let finalDestinationViewFrame = calculateFinalDestinationFrame(using: coveredContextView) - - let destinationTransform = createDestinationViewTransform(finalDestinationViewFrame) - let sourceTransform = createSourceViewTransform(sourceController.view) - - coveredContextView.layer.masksToBounds = true - destinationController.view.layer.masksToBounds = true - destinationController.view.frame = initialDestinationViewFrame - - transitionContext.containerView.addSubview(destinationController.view) - - let dimmingView = createDimmingView(for: coveredContextView) - dimmingView.alpha = 0 - - coveredContextView.addSubview(dimmingView) - - // TODO: Refactor to using BlockViewAnimator after UIKit update to support spring params - UIView.animate( - withDuration: transitionDuration(using: transitionContext), - delay: 0, - usingSpringWithDamping: 1, - initialSpringVelocity: 0.4, - options: .curveEaseIn, - animations: { - destinationController.view.transform = destinationTransform - destinationController.view.frame = finalDestinationViewFrame - destinationController.view.layer.cornerRadius = Constants.destinationViewCornerRadius - - coveredContextView.layer.cornerRadius = Constants.sourceViewCornerRadius - sourceController.view.transform = sourceTransform - dimmingView.alpha = 1.0 - } - ) { _ in - transitionContext.completeTransition(!transitionContext.transitionWasCancelled) - } - } -} - -// MARK: Constants - -private extension CardPresentingTransition { - enum Constants { - static let sourceViewCornerRadius: CGFloat = 12 - static let destinationViewCornerRadius: CGFloat = 16 - } -} diff --git a/novawallet/Common/ViewController/CardLayoutPresentationController/UIViewController+PresentCardLayout.swift b/novawallet/Common/ViewController/CardLayoutPresentationController/UIViewController+PresentCardLayout.swift deleted file mode 100644 index 0cdc8a1696..0000000000 --- a/novawallet/Common/ViewController/CardLayoutPresentationController/UIViewController+PresentCardLayout.swift +++ /dev/null @@ -1,33 +0,0 @@ -import UIKit - -extension UIViewController { - func presentWithCardLayout( - _ viewController: UIViewController, - animated: Bool, - completion: (() -> Void)? = nil - ) { - let transitioningDelegate = CardLayoutTransitionDelegate() - let container = CardLayoutPresentationController(transitionDelegate: transitioningDelegate) - - container.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - - container.addChild(viewController) - container.view.addSubview(viewController.view) - - viewController.view.snp.makeConstraints { make in - make.top.trailing.leading.bottom.equalToSuperview() - } - container.view.layoutIfNeeded() - - container.didMove(toParent: viewController) - - container.transitioningDelegate = transitioningDelegate - container.modalPresentationStyle = .overCurrentContext - - tabBarController?.present( - container, - animated: animated, - completion: completion - ) - } -} diff --git a/novawallet/Common/ViewController/ModalCard/ModalCardPresentationConfiguration.swift b/novawallet/Common/ViewController/ModalCard/ModalCardPresentationConfiguration.swift new file mode 100644 index 0000000000..c6d82e3947 --- /dev/null +++ b/novawallet/Common/ViewController/ModalCard/ModalCardPresentationConfiguration.swift @@ -0,0 +1,45 @@ +import Foundation +import SoraUI + +public struct ModalCardPresentationConfiguration { + public let contentAppearanceAnimator: BlockViewAnimatorProtocol + public let contentDissmisalAnimator: BlockViewAnimatorProtocol + public let style: ModalCardPresentationStyle + + public let extendUnderSafeArea: Bool + public let dismissPercentThreshold: CGFloat + public let dismissVelocityThreshold: CGFloat + public let dismissMinimumOffset: CGFloat + public let dismissFinishSpeedFactor: CGFloat + public let dismissCancelSpeedFactor: CGFloat + + public init( + contentAppearanceAnimator: BlockViewAnimatorProtocol = BlockViewAnimator(duration: 0.45, delay: 0.0, options: [.curveLinear]), + contentDissmisalAnimator: BlockViewAnimatorProtocol = BlockViewAnimator(duration: 0.35, delay: 0.0, options: [.curveLinear]), + style: ModalCardPresentationStyle = .defaultStyle, + extendUnderSafeArea: Bool = false, + dismissPercentThreshold: CGFloat = 0.35, + dismissVelocityThreshold: CGFloat = 1280, + dismissMinimumOffset: CGFloat = 87, + dismissFinishSpeedFactor: CGFloat = 0.3, + dismissCancelSpeedFactor: CGFloat = 0.3 + ) { + self.contentAppearanceAnimator = contentAppearanceAnimator + self.contentDissmisalAnimator = contentDissmisalAnimator + self.style = style + self.extendUnderSafeArea = extendUnderSafeArea + self.dismissPercentThreshold = dismissPercentThreshold + self.dismissVelocityThreshold = dismissVelocityThreshold + self.dismissMinimumOffset = dismissMinimumOffset + self.dismissFinishSpeedFactor = dismissFinishSpeedFactor + self.dismissCancelSpeedFactor = dismissCancelSpeedFactor + } + + public init(backdropOpacity: CGFloat) { + let style = ModalCardPresentationStyle( + backdropColor: UIColor.white.withAlphaComponent(backdropOpacity) + ) + + self.init(style: style) + } +} diff --git a/novawallet/Common/ViewController/ModalCard/ModalCardPresentationController.swift b/novawallet/Common/ViewController/ModalCard/ModalCardPresentationController.swift new file mode 100644 index 0000000000..fbf3263fb0 --- /dev/null +++ b/novawallet/Common/ViewController/ModalCard/ModalCardPresentationController.swift @@ -0,0 +1,423 @@ +import UIKit +import SoraUI + +public final class ModalCardPresentationController: UIPresentationController { + private weak var observedScrollView: UIScrollView? + private var backgroundView: UIView? + + private let contextRootViewController: UIViewController + private let configuration: ModalCardPresentationConfiguration + private let transformFactory: ModalCardPresentationTransformFactoryProtocol + + weak var presentationDelegate: ModalCardPresentationControllerDelegate? { + presentedViewController as? ModalCardPresentationControllerDelegate + } + + var interactiveDismissal: UIPercentDrivenInteractiveTransition? + var initialTranslation: CGPoint = .zero + + init( + presentedViewController: UIViewController, + contextRootViewController: UIViewController, + transformFactory: ModalCardPresentationTransformFactoryProtocol, + presenting presentingViewController: UIViewController?, + configuration: ModalCardPresentationConfiguration + ) { + self.contextRootViewController = contextRootViewController + self.configuration = configuration + self.transformFactory = transformFactory + + super.init(presentedViewController: presentedViewController, presenting: presentingViewController) + } + + // MARK: Presentation overridings + + override public var shouldPresentInFullscreen: Bool { + false + } + + override public func presentationTransitionWillBegin() { + super.presentationTransitionWillBegin() + + configurePresentedView() + attachPanGesture() + animateBackground(appearing: true) + } + + override public func dismissalTransitionWillBegin() { + super.dismissalTransitionWillBegin() + + animateBackground(appearing: false) + } + + override public var frameOfPresentedViewInContainerView: CGRect { + guard let containerView else { return .zero } + + return calculatePresentedViewFrame(in: containerView) + } +} + +// MARK: Private + +private extension ModalCardPresentationController { + func configureBackgroundView(on view: UIView) { + if let currentBackgroundView = backgroundView { + view.addSubview(currentBackgroundView) + } else { + let newBackgroundView = UIView(frame: view.bounds) + + newBackgroundView.backgroundColor = configuration.style.backdropColor + + view.addSubview(newBackgroundView) + backgroundView = newBackgroundView + } + + backgroundView?.frame = view.bounds + } + + func configurePresentedView() { + presentedViewController.view.layer.masksToBounds = true + presentedViewController.view.layer.cornerRadius = Constants.destinationViewCornerRadius + } + + func attachPanGesture() { + let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPan(sender:))) + panGestureRecognizer.cancelsTouchesInView = false + containerView?.addGestureRecognizer(panGestureRecognizer) + panGestureRecognizer.delegate = self + } + + func calculatePresentedViewFrame(in containerView: UIView) -> CGRect { + let finalFrame: CGRect + + let topOffset: CGFloat = containerView.safeAreaInsets.top + Constants.topOffset + + let finalOrigin = containerView.bounds.offsetBy( + dx: 0, + dy: topOffset + ).origin + + let presentedByModalCardController = presentingViewController.presentationController is ModalCardPresentationController + + let finalHeight: CGFloat = if presentedByModalCardController { + presentingViewController.view.bounds.height + } else { + presentingViewController.view.bounds.height - topOffset + } + + let finalFrameSize = CGSize( + width: containerView.bounds.width, + height: finalHeight + ) + + finalFrame = CGRect( + origin: finalOrigin, + size: finalFrameSize + ) + + return finalFrame + } + + func dismiss(animated: Bool) { + finishPresentation( + with: { self.presentedViewController.dismiss(animated: animated, completion: nil) }, + cancelClosure: nil + ) + } + + func stopPullToDismiss(finished: Bool) { + guard let interactiveDismissal else { return } + + self.interactiveDismissal = nil + + if finished { + interactiveDismissal.completionSpeed = configuration.dismissFinishSpeedFactor + finishPresentation( + with: interactiveDismissal.finish, + cancelClosure: interactiveDismissal.cancel + ) + } else { + interactiveDismissal.completionSpeed = configuration.dismissCancelSpeedFactor + interactiveDismissal.cancel() + } + } + + func finishPresentation( + with finishClosure: () -> Void, + cancelClosure: (() -> Void)? + ) { + if let presentationDelegate { + if presentationDelegate.presentationControllerShouldDismiss(self) { + finishClosure() + } else { + cancelClosure?() + presentationDelegate.presentationControllerDidAttemptToDismiss(self) + } + } else { + finishClosure() + } + } + + func canDrag( + basedOn scrollView: UIScrollView?, + with panGesture: UIPanGestureRecognizer + ) -> Bool { + guard let scrollView else { return true } + guard let presentedView, !containsActiveLongPress(scrollView) else { return false } + + if let tableView = scrollView as? UITableView, tableView.isEditing { + let touchPoint = panGesture.location(in: presentedView) + + return !tableView.frame.contains(touchPoint) + } + + let contentOffsetY = scrollView.contentOffset.y + scrollView.adjustedContentInset.top + + return contentOffsetY <= 0 + } + + func containsActiveLongPress(_ scrollView: UIScrollView?) -> Bool { + guard let recognizers = scrollView?.gestureRecognizers else { return false } + + return recognizers.contains { recognizer in + if let longPress = recognizer as? UILongPressGestureRecognizer { + let state = longPress.state + + return state == .began || + state == .changed + } + return false + } + } + + // MARK: Interactive dismissal + + func handlePan(from panGestureRecognizer: UIPanGestureRecognizer, on view: UIView) { + let translation = panGestureRecognizer.translation(in: view) + let velocity = panGestureRecognizer.velocity(in: view) + + switch panGestureRecognizer.state { + case .began, .changed: + let scrollProgress = max(0.0, (translation.y - initialTranslation.y) / max(1.0, view.bounds.size.height)) + let progress = min(1.0, scrollProgress) + let scrolledFromTop = translation.y <= 0 && scrollProgress == 0 + + guard canDrag(basedOn: observedScrollView, with: panGestureRecognizer) else { + observedScrollView?.isScrollEnabled = true + + return + } + + observedScrollView?.isScrollEnabled = scrolledFromTop + + if let interactiveDismissal { + interactiveDismissal.update(progress) + } else { + interactiveDismissal = UIPercentDrivenInteractiveTransition() + initialTranslation = translation + presentedViewController.dismiss(animated: true) + interactiveDismissal?.update(progress) + } + case .cancelled, .ended: + observedScrollView?.isScrollEnabled = true + + if let interactiveDismissal = interactiveDismissal { + let thresholdReached = interactiveDismissal.percentComplete >= configuration.dismissPercentThreshold + let shouldDismiss = (thresholdReached && velocity.y >= 0) || + (velocity.y >= configuration.dismissVelocityThreshold && + translation.y >= configuration.dismissMinimumOffset) + stopPullToDismiss(finished: panGestureRecognizer.state != .cancelled && shouldDismiss) + } + default: + break + } + } + + // MARK: Action + + @objc func didPan(sender: Any?) { + guard let panGestureRecognizer = sender as? UIPanGestureRecognizer else { return } + guard let view = panGestureRecognizer.view else { return } + + handlePan(from: panGestureRecognizer, on: view) + } +} + +// MARK: Animation + +private extension ModalCardPresentationController { + func animateBackground(appearing: Bool) { + let animatableProperties = createBackgroundAnimationProperties(for: appearing) + let coveredContextView = prepareCoveredContextView() + + configureBackgroundView(on: coveredContextView) + + backgroundView?.alpha = animatableProperties.backDropViewInitialAlpha + + let animationBlock: (UIViewControllerTransitionCoordinatorContext) -> Void = { [weak self] _ in + guard let self else { return } + + backgroundView?.alpha = animatableProperties.backDropViewFinalAlpha + coveredContextView.layer.cornerRadius = animatableProperties.cornerRadius + + if appearing { + applySourceAppearanceTransform() + } else { + applySourceDismissTransform() + } + } + + let completionBlock: (UIViewControllerTransitionCoordinatorContext) -> Void = { [weak self] context in + guard let self, !appearing, !context.isCancelled else { return } + completeTransitionAnimation(for: coveredContextView) + } + + presentingViewController + .transitionCoordinator? + .animate( + alongsideTransition: animationBlock, + completion: completionBlock + ) + } + + func createBackgroundAnimationProperties(for appearing: Bool) -> BackgroundProperties { + let presenterPresentedByOther: Bool = presentingViewController.presentingViewController != nil + + let alphaFromValue: CGFloat + let alphaToValue: CGFloat + let cornerRadius: CGFloat + + if appearing { + alphaFromValue = 0.0 + alphaToValue = 1.0 + cornerRadius = Constants.sourceViewCornerRadius + } else { + alphaFromValue = 1.0 + alphaToValue = 0.0 + cornerRadius = presenterPresentedByOther + ? Constants.destinationViewCornerRadius + : UIScreen.main.cornerRadius + } + + return BackgroundProperties( + cornerRadius: cornerRadius, + backDropViewInitialAlpha: alphaFromValue, + backDropViewFinalAlpha: alphaToValue + ) + } + + func prepareCoveredContextView() -> UIView { + let coveredContextView: UIView + + if + let tabBarController = presentingViewController as? UITabBarController, + let navController = tabBarController.selectedViewController as? UINavigationController { + coveredContextView = navController.view + } else { + coveredContextView = presentingViewController.navigationController?.view + ?? presentingViewController.view + } + + coveredContextView.layer.masksToBounds = true + coveredContextView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + coveredContextView.layer.cornerCurve = .continuous + + return coveredContextView + } + + func completeTransitionAnimation(for coveredContextView: UIView) { + let presenterPresentedByOther: Bool = presentingViewController.presentingViewController != nil + + if presenterPresentedByOther { + coveredContextView.layer.maskedCorners = presentedView?.layer.maskedCorners ?? [] + coveredContextView.layer.cornerRadius = presentedView?.layer.cornerRadius ?? 0 + } else { + coveredContextView.layer.cornerRadius = 0 + } + } +} + +// MARK: Public + +public extension ModalCardPresentationController { + func applySourceDismissTransform() { + guard let sourceView = presentingViewController.view else { return } + + let transform = transformFactory.createDismissingTransform(for: presentingViewController) + + sourceView.transform = transform + + let parentPresentationController = presentingViewController.presentationController + as? ModalCardPresentationController + + parentPresentationController?.applySourceDismissTransform() + } + + func applySourceAppearanceTransform() { + guard let sourceView = presentingViewController.view else { return } + + let transform = transformFactory.createAppearanceTransform(for: presentingViewController) + + sourceView.transform = transform + + let parentPresentationController = presentingViewController.presentationController + as? ModalCardPresentationController + + parentPresentationController?.applySourceAppearanceTransform() + } + + func updateLayout() { + guard + let presentedView, + let containerView + else { return } + + let presentedViewHeight = presentedView.bounds.height + let topOffset = containerView.safeAreaInsets.top + Constants.topOffset + let presenetedViewHeightDelta = contextRootViewController.view.bounds.height - presentedViewHeight - topOffset + + let height = presentedView.frame.height + presenetedViewHeightDelta + + presentedViewController.view.frame = CGRect( + x: presentedView.frame.origin.x, + y: presentedView.frame.origin.y, + width: presentedView.frame.width, + height: height + ) + } +} + +// MARK: UIGestureRecognizerDelegate + +extension ModalCardPresentationController: UIGestureRecognizerDelegate { + public func gestureRecognizer( + _: UIGestureRecognizer, + shouldRecognizeSimultaneouslyWith second: UIGestureRecognizer + ) -> Bool { + guard let scrollView = second.view as? UIScrollView else { + return false + } + + if observedScrollView !== scrollView { + observedScrollView = scrollView + } + + return true + } +} + +// MARK: Constants + +private extension ModalCardPresentationController { + enum Constants { + static let topOffset: CGFloat = 12 + static let sourceViewCornerRadius: CGFloat = 10 + static let destinationViewCornerRadius: CGFloat = 12 + } +} + +private struct BackgroundProperties { + let cornerRadius: CGFloat + let backDropViewInitialAlpha: CGFloat + let backDropViewFinalAlpha: CGFloat +} diff --git a/novawallet/Common/ViewController/ModalCard/ModalCardPresentationControllerDelegate.swift b/novawallet/Common/ViewController/ModalCard/ModalCardPresentationControllerDelegate.swift new file mode 100644 index 0000000000..dba2bc2488 --- /dev/null +++ b/novawallet/Common/ViewController/ModalCard/ModalCardPresentationControllerDelegate.swift @@ -0,0 +1,10 @@ +import UIKit + +public protocol ModalCardPresentationControllerDelegate: AnyObject { + func presentationControllerShouldDismiss(_: UIPresentationController) -> Bool + func presentationControllerDidAttemptToDismiss(_: UIPresentationController) +} + +extension ModalCardPresentationControllerDelegate { + func presentationControllerDidAttemptToDismiss(_: UIPresentationController) {} +} diff --git a/novawallet/Common/ViewController/ModalCard/ModalCardPresentationFactory.swift b/novawallet/Common/ViewController/ModalCard/ModalCardPresentationFactory.swift new file mode 100644 index 0000000000..03a4de2447 --- /dev/null +++ b/novawallet/Common/ViewController/ModalCard/ModalCardPresentationFactory.swift @@ -0,0 +1,164 @@ +import UIKit +import SoraUI + +public class ModalCardPresentationFactory: NSObject { + let configuration: ModalCardPresentationConfiguration + let presentingViewController: UIViewController + let contextRootViewController: UIViewController + + weak var presentation: ModalCardPresentationController? + + public init( + configuration: ModalCardPresentationConfiguration, + presentingViewController: UIViewController, + contextRootViewController: UIViewController + ) { + self.configuration = configuration + self.presentingViewController = presentingViewController + self.contextRootViewController = contextRootViewController + + super.init() + } +} + +// MARK: UIViewControllerTransitioningDelegate + +extension ModalCardPresentationFactory: UIViewControllerTransitioningDelegate { + public func animationController( + forPresented _: UIViewController, + presenting _: UIViewController, + source _: UIViewController + ) -> UIViewControllerAnimatedTransitioning? { + ModalCardPresentationAppearanceAnimator(animator: configuration.contentAppearanceAnimator) + } + + public func animationController(forDismissed _: UIViewController) + -> UIViewControllerAnimatedTransitioning? { + ModalCardPresentationDismissAnimator( + animator: configuration.contentDissmisalAnimator, + finalPositionOffset: 0.0 + ) + } + + public func presentationController( + forPresented presented: UIViewController, + presenting _: UIViewController?, + source _: UIViewController + ) -> UIPresentationController? { + let presentation = ModalCardPresentationController( + presentedViewController: presented, + contextRootViewController: contextRootViewController, + transformFactory: ModalCardPresentationTransformFactory(), + presenting: presentingViewController, + configuration: configuration + ) + + self.presentation = presentation + + return presentation + } + + public func interactionControllerForDismissal(using _: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + presentation?.interactiveDismissal + } +} + +public final class ModalCardPresentationAppearanceAnimator: NSObject { + let animator: BlockViewAnimatorProtocol + + public init(animator: BlockViewAnimatorProtocol) { + self.animator = animator + + super.init() + } +} + +// MARK: UIViewControllerAnimatedTransitioning + +extension ModalCardPresentationAppearanceAnimator: UIViewControllerAnimatedTransitioning { + public func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval { + animator.duration + } + + public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + guard let presentedController = transitionContext.viewController(forKey: .to) else { + return + } + + let finalFrame = transitionContext.finalFrame(for: presentedController) + var initialFrame = finalFrame + initialFrame.origin.y += finalFrame.size.height + + presentedController.view.frame = initialFrame + transitionContext.containerView.addSubview(presentedController.view) + + let animationBlock: () -> Void = { + presentedController.view.frame = finalFrame + } + + let completionBlock: (Bool) -> Void = { finished in + transitionContext.completeTransition(finished) + } + + // TODO: Use spring-block animator after UIKit update + UIView.animate( + withDuration: 0.45, + delay: 0, + usingSpringWithDamping: 1, + initialSpringVelocity: 0.4, + options: .curveEaseIn, + animations: animationBlock, + completion: completionBlock + ) + } +} + +public final class ModalCardPresentationDismissAnimator: NSObject { + let animator: BlockViewAnimatorProtocol + let finalPositionOffset: CGFloat + + public init( + animator: BlockViewAnimatorProtocol, + finalPositionOffset: CGFloat + ) { + self.animator = animator + self.finalPositionOffset = finalPositionOffset + + super.init() + } +} + +// MARK: UIViewControllerAnimatedTransitioning + +extension ModalCardPresentationDismissAnimator: UIViewControllerAnimatedTransitioning { + public func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval { + animator.duration + } + + public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + guard let presentedController = transitionContext.viewController(forKey: .from) else { + return + } + + let initialFrame = presentedController.view.frame + var finalFrame = initialFrame + finalFrame.origin.y = transitionContext.containerView.frame.maxY + finalPositionOffset + + let animationBlock: () -> Void = { + presentedController.view.frame = finalFrame + } + + let completionBlock: (Bool) -> Void = { _ in + if !transitionContext.transitionWasCancelled { + presentedController.view.removeFromSuperview() + } + + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + } + + animator.animate( + block: animationBlock, + completionBlock: completionBlock + ) + } +} diff --git a/novawallet/Common/ViewController/ModalCard/ModalCardPresentationStyle.swift b/novawallet/Common/ViewController/ModalCard/ModalCardPresentationStyle.swift new file mode 100644 index 0000000000..60bb8106e0 --- /dev/null +++ b/novawallet/Common/ViewController/ModalCard/ModalCardPresentationStyle.swift @@ -0,0 +1,16 @@ +import Foundation +import SoraUI + +public struct ModalCardPresentationStyle { + public let backdropColor: UIColor + + public init(backdropColor: UIColor) { + self.backdropColor = backdropColor + } +} + +public extension ModalCardPresentationStyle { + static var defaultStyle: ModalCardPresentationStyle { + ModalCardPresentationStyle(backdropColor: UIColor.darkGray.withAlphaComponent(0.3)) + } +} diff --git a/novawallet/Common/ViewController/ModalCard/ModalCardPresentationTransformFactory.swift b/novawallet/Common/ViewController/ModalCard/ModalCardPresentationTransformFactory.swift new file mode 100644 index 0000000000..725ec5f73d --- /dev/null +++ b/novawallet/Common/ViewController/ModalCard/ModalCardPresentationTransformFactory.swift @@ -0,0 +1,85 @@ +import Foundation +import UIKit + +protocol ModalCardPresentationTransformFactoryProtocol { + func createAppearanceTransform(for presentingViewController: UIViewController) -> CGAffineTransform + func createDismissingTransform(for presentingViewController: UIViewController) -> CGAffineTransform +} + +class ModalCardPresentationTransformFactory {} + +// MARK: Private + +private extension ModalCardPresentationTransformFactory { + func presentsLastController(_ presentingViewController: UIViewController) -> Bool { + presentingViewController + .presentedViewController? + .presentedViewController == nil + } + + func createLastPresenterTransform(for presentingViewController: UIViewController) -> CGAffineTransform { + guard let sourceView = presentingViewController.view else { return CGAffineTransform.identity } + + let presenterIsModalCard = (presentingViewController.presentationController as? ModalCardPresentationController) != nil + + let widthDelta: CGFloat = UIConstants.horizontalInset * 2 + let scale = (sourceView.bounds.width - widthDelta) / sourceView.bounds.width + + let heightDeltaAfterScale = (sourceView.bounds.height - (sourceView.bounds.height * scale)) / 2 + + var yOffset = heightDeltaAfterScale + Constants.cardPresenterTopInset + + if !presenterIsModalCard { + yOffset -= presentingViewController.presentedViewController?.view?.frame.origin.y ?? 0 + } + + let sourceTransform = CGAffineTransform.identity + let sourceScaleTransform = CGAffineTransform( + scaleX: scale, + y: scale + ) + let sourceTranslateTransform = CGAffineTransform( + translationX: .zero, + y: -yOffset + ) + + let finalTransform = sourceTransform + .concatenating(sourceScaleTransform) + .concatenating(sourceTranslateTransform) + + return finalTransform + } +} + +// MARK: ModalCardPresentationTransformFactoryProtocol + +extension ModalCardPresentationTransformFactory: ModalCardPresentationTransformFactoryProtocol { + func createAppearanceTransform(for presentingViewController: UIViewController) -> CGAffineTransform { + if presentsLastController(presentingViewController) { + createLastPresenterTransform(for: presentingViewController) + } else { + presentingViewController.view.transform.concatenating( + CGAffineTransform( + translationX: .zero, + y: Constants.cardPresenterTopInset + ) + ) + } + } + + func createDismissingTransform(for presentingViewController: UIViewController) -> CGAffineTransform { + if presentsLastController(presentingViewController) { + .identity + } else { + createLastPresenterTransform(for: presentingViewController) + } + } +} + +// MARK: Constants + +private extension ModalCardPresentationTransformFactory { + enum Constants { + static let cardPresenterTopInset: CGFloat = 10 + } +} diff --git a/novawallet/Common/ViewController/ModalCard/UIViewController+PresentCardLayout.swift b/novawallet/Common/ViewController/ModalCard/UIViewController+PresentCardLayout.swift new file mode 100644 index 0000000000..85fe26ddf2 --- /dev/null +++ b/novawallet/Common/ViewController/ModalCard/UIViewController+PresentCardLayout.swift @@ -0,0 +1,52 @@ +import UIKit +import SoraUI + +extension UIViewController { + func presentWithCardLayout( + _ viewController: UIViewController, + animated: Bool, + completion: (() -> Void)? = nil + ) { + let contextRootViewController = viewController.tabBarController ?? UIApplication.shared.rootContainer + + guard let contextRootViewController else { return } + + viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + let appearanceAnimator = BlockViewAnimator( + duration: 0.25, + delay: 0.0, + options: [.curveEaseOut] + ) + let dismissalAnimator = BlockViewAnimator( + duration: 0.25, + delay: 0.0, + options: [.curveLinear] + ) + + let configuration = ModalCardPresentationConfiguration( + contentAppearanceAnimator: appearanceAnimator, + contentDissmisalAnimator: dismissalAnimator, + style: ModalCardPresentationStyle.defaultStyle, + extendUnderSafeArea: true, + dismissFinishSpeedFactor: 0.6, + dismissCancelSpeedFactor: 0.6 + ) + + let factory = ModalCardPresentationFactory( + configuration: configuration, + presentingViewController: self, + contextRootViewController: contextRootViewController + ) + + viewController.modalTransitioningFactory = factory + viewController.modalPresentationStyle = .custom + viewController.definesPresentationContext = true + + present( + viewController, + animated: animated, + completion: completion + ) + } +} diff --git a/novawallet/Common/ViewController/NavigationController/ImportantFlowNavigationController.swift b/novawallet/Common/ViewController/NavigationController/ImportantFlowNavigationController.swift index 61a98da9c6..b277299777 100644 --- a/novawallet/Common/ViewController/NavigationController/ImportantFlowNavigationController.swift +++ b/novawallet/Common/ViewController/NavigationController/ImportantFlowNavigationController.swift @@ -24,15 +24,11 @@ class ImportantFlowNavigationController: NovaNavigationController, ControllerBac required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } - - override func viewDidLoad() { - super.viewDidLoad() - - presentationController?.delegate = self - } } -extension ImportantFlowNavigationController: UIAdaptivePresentationControllerDelegate, AlertPresentable { +// MARK: ModalCardPresentationControllerDelegate + +extension ImportantFlowNavigationController: ModalCardPresentationControllerDelegate { func presentationControllerShouldDismiss(_: UIPresentationController) -> Bool { let containsImportantViews = viewControllers.contains { ($0 as? ImportantViewProtocol) != nil } return !containsImportantViews @@ -59,3 +55,7 @@ extension ImportantFlowNavigationController: UIAdaptivePresentationControllerDel present(viewModel: viewModel, style: .actionSheet, from: self) } } + +// MARK: AlertPresentable + +extension ImportantFlowNavigationController: AlertPresentable {} diff --git a/novawallet/Common/ViewController/NavigationController/NovaNavigationController.swift b/novawallet/Common/ViewController/NavigationController/NovaNavigationController.swift index 1d2247cc4f..223151c900 100644 --- a/novawallet/Common/ViewController/NavigationController/NovaNavigationController.swift +++ b/novawallet/Common/ViewController/NavigationController/NovaNavigationController.swift @@ -29,6 +29,29 @@ class NovaNavigationController: UINavigationController, UINavigationControllerDe topViewController } + override init(rootViewController: UIViewController) { + super.init(rootViewController: rootViewController) + + definesPresentationContext = false + } + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + + definesPresentationContext = false + } + + init() { + super.init(nibName: nil, bundle: nil) + + definesPresentationContext = false + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + private func setup() { delegate = self diff --git a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserViewController.swift b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserViewController.swift index a7d84988e1..e7a79f7ee5 100644 --- a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserViewController.swift +++ b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserViewController.swift @@ -83,7 +83,7 @@ final class DAppBrowserViewController: UIViewController, ViewHolder { } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - let isOldLandscape = view.frame.size.width > view.frame.size.height + let isOldLandscape = traitCollection.verticalSizeClass == .compact super.viewWillTransition(to: size, with: coordinator) diff --git a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/DAppBrowserWidgetPresenter.swift b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/DAppBrowserWidgetPresenter.swift index c0a3ee4eb0..953dded604 100644 --- a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/DAppBrowserWidgetPresenter.swift +++ b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/DAppBrowserWidgetPresenter.swift @@ -142,26 +142,16 @@ extension DAppBrowserWidgetPresenter: DAppBrowserWidgetInteractorOutputProtocol func didReceive(_ browserTabs: [UUID: DAppBrowserTab]) { self.browserTabs = browserTabs - let initialStateWithTabs = state == .disabled && !browserTabs.isEmpty - - guard !initialStateWithTabs else { - state = .closed - view?.didReceiveRequestForMinimizing() - return - } - switch state { - case .disabled: + case .disabled where !browserTabs.isEmpty: state = .closed - case .closed where !browserTabs.isEmpty: - state = .fullBrowser + view?.didReceiveRequestForMinimizing() case .miniature where browserTabs.isEmpty: state = .closed + provideModel() default: break } - - provideModel() } func didReceiveWalletChanged() { diff --git a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/DAppBrowserWidgetWireframe.swift b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/DAppBrowserWidgetWireframe.swift index de3d0170c5..3f21667849 100644 --- a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/DAppBrowserWidgetWireframe.swift +++ b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/DAppBrowserWidgetWireframe.swift @@ -33,10 +33,12 @@ class DAppBrowserWidgetWireframe: NSObject, DAppBrowserWidgetWireframeProtocol { } func showMiniature(from view: (any DAppBrowserParentWidgetViewProtocol)?) { - view?.controller.children.forEach { - $0.willMove(toParent: nil) - $0.view.removeFromSuperview() - $0.removeFromParent() + view?.controller.children.forEach { child in + child.dismiss(animated: true) { + child.willMove(toParent: nil) + child.view.removeFromSuperview() + child.removeFromParent() + } } } } diff --git a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Common/DAppBrowserCloseTransition.swift b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Common/DAppBrowserCloseTransition.swift index 7f1b9ed501..33e62f632d 100644 --- a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Common/DAppBrowserCloseTransition.swift +++ b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Common/DAppBrowserCloseTransition.swift @@ -2,9 +2,9 @@ import Foundation import UIKit struct DAppBrowserCloseTransition { - private let dependencies: DAppBrowserLayoutTransitionDependencies + private let dependencies: FadeContentDAppBrowserTransitionDependencies - init(dependencies: DAppBrowserLayoutTransitionDependencies) { + init(dependencies: FadeContentDAppBrowserTransitionDependencies) { self.dependencies = dependencies } } @@ -13,18 +13,16 @@ struct DAppBrowserCloseTransition { extension DAppBrowserCloseTransition: DAppBrowserWidgetTransitionProtocol { func start() { - let containerView = dependencies.layoutClosure() - let layoutAnimatables = dependencies.animatableClosure - let transformClosure = dependencies.transformClosure + let containerView = dependencies.layoutDependencies.layoutClosure() + let layoutAnimatables = dependencies.layoutDependencies.animatableClosure + let childNavigationClosure = dependencies.childNavigation - UIView.animate(withDuration: 0.2) { + dependencies.blockAnimator.animate { containerView?.layoutIfNeeded() - UIView.performWithoutAnimation { - transformClosure?() - } - layoutAnimatables?() + } completionBlock: { _ in + childNavigationClosure?() {} } } } diff --git a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/DAppBrowserWidgetTransitionBuilder.swift b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/DAppBrowserWidgetTransitionBuilder.swift index e1227fc4c4..b5f064c81f 100644 --- a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/DAppBrowserWidgetTransitionBuilder.swift +++ b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/DAppBrowserWidgetTransitionBuilder.swift @@ -36,8 +36,15 @@ private extension DAppBrowserWidgetTransitionBuilder { throw DAppBrowserWidgetTransitionBuilderError.missingRequiredDependencies } + let dependencies = FadeContentDAppBrowserTransitionDependencies( + browserViewClosure: nil, + widgetViewClosure: nil, + childNavigation: childNavigation, + layoutDependencies: layoutDependencies + ) + return DAppBrowserCloseTransition( - dependencies: layoutDependencies + dependencies: dependencies ) } diff --git a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Fade/FadeContentDAppBrowserMaximizeTransition.swift b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Fade/FadeContentDAppBrowserMaximizeTransition.swift index 74d1b598c4..1670ff56fa 100644 --- a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Fade/FadeContentDAppBrowserMaximizeTransition.swift +++ b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Fade/FadeContentDAppBrowserMaximizeTransition.swift @@ -14,7 +14,7 @@ struct FadeContentDAppBrowserMaximizeTransition { extension FadeContentDAppBrowserMaximizeTransition: DAppBrowserWidgetTransitionProtocol { func start() { - guard let widgetView = dependencies.widgetViewClosure() else { + guard let widgetView = dependencies.widgetViewClosure?() else { return } @@ -24,15 +24,14 @@ extension FadeContentDAppBrowserMaximizeTransition: DAppBrowserWidgetTransitionP let childNavigation = dependencies.childNavigation let layoutClosure = dependencies.layoutDependencies.layoutClosure let layoutAnimatables = dependencies.layoutDependencies.animatableClosure - let transformClosure = dependencies.layoutDependencies.transformClosure disappearanceAnimator.animate( view: widgetView.contentContainerView, completionBlock: nil ) - childNavigation { - guard let browserView = dependencies.browserViewClosure() else { + childNavigation?() { + guard let browserView = dependencies.browserViewClosure?() else { return } @@ -47,10 +46,6 @@ extension FadeContentDAppBrowserMaximizeTransition: DAppBrowserWidgetTransitionP ) { layoutAnimatables?() containerView?.layoutIfNeeded() - - UIView.performWithoutAnimation { - transformClosure?() - } } appearanceAnimator.animate( diff --git a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Fade/FadeContentDAppBrowserMinimizeTransition.swift b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Fade/FadeContentDAppBrowserMinimizeTransition.swift index 6ccbec5bfa..3027475df5 100644 --- a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Fade/FadeContentDAppBrowserMinimizeTransition.swift +++ b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Fade/FadeContentDAppBrowserMinimizeTransition.swift @@ -19,19 +19,18 @@ extension FadeContentDAppBrowserMinimizeTransition: DAppBrowserWidgetTransitionP let childNavigation = dependencies.childNavigation let layoutClosure = dependencies.layoutDependencies.layoutClosure let layoutAnimatables = dependencies.layoutDependencies.animatableClosure - let transformClosure = dependencies.layoutDependencies.transformClosure let containerView = layoutClosure() - if let browserView = dependencies.browserViewClosure() { + if let browserView = dependencies.browserViewClosure?() { disappearanceAnimator.animate(view: browserView) { _ in - childNavigation {} + childNavigation?() {} } } else { - childNavigation {} + childNavigation?() {} } - let widgetView = dependencies.widgetViewClosure() + let widgetView = dependencies.widgetViewClosure?() widgetView?.contentContainerView.alpha = 0 UIView.animate( @@ -43,12 +42,8 @@ extension FadeContentDAppBrowserMinimizeTransition: DAppBrowserWidgetTransitionP ) { layoutAnimatables?() containerView?.layoutIfNeeded() - - UIView.performWithoutAnimation { - transformClosure?() - } } completion: { _ in - guard let widgetView = dependencies.widgetViewClosure() else { + guard let widgetView = dependencies.widgetViewClosure?() else { return } diff --git a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Fade/FadeContentDAppBrowserTransitionDependencies.swift b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Fade/FadeContentDAppBrowserTransitionDependencies.swift index 0411457a2f..d98273c410 100644 --- a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Fade/FadeContentDAppBrowserTransitionDependencies.swift +++ b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/Transition/Fade/FadeContentDAppBrowserTransitionDependencies.swift @@ -3,24 +3,21 @@ import SoraUI struct DAppBrowserLayoutTransitionDependencies { let layoutClosure: () -> (UIView?) let animatableClosure: (() -> Void)? - let transformClosure: (() -> Void)? init( layoutClosure: @escaping () -> UIView?, - animatableClosure: (() -> Void)? = nil, - transformClosure: (() -> Void)? = nil + animatableClosure: (() -> Void)? = nil ) { self.layoutClosure = layoutClosure self.animatableClosure = animatableClosure - self.transformClosure = transformClosure } } struct FadeContentDAppBrowserTransitionDependencies { - let browserViewClosure: () -> UIView? - let widgetViewClosure: () -> DAppBrowserWidgetView? + let browserViewClosure: (() -> UIView?)? + let widgetViewClosure: (() -> DAppBrowserWidgetView?)? - let childNavigation: DAppBrowserChildNavigationClosure + let childNavigation: DAppBrowserChildNavigationClosure? let layoutDependencies: DAppBrowserLayoutTransitionDependencies let appearanceAnimator: ViewAnimatorProtocol = FadeAnimator( @@ -39,9 +36,9 @@ struct FadeContentDAppBrowserTransitionDependencies { ) init( - browserViewClosure: @escaping () -> UIView?, - widgetViewClosure: @escaping () -> DAppBrowserWidgetView?, - childNavigation: @escaping DAppBrowserChildNavigationClosure, + browserViewClosure: (() -> UIView?)?, + widgetViewClosure: (() -> DAppBrowserWidgetView?)?, + childNavigation: DAppBrowserChildNavigationClosure?, layoutDependencies: DAppBrowserLayoutTransitionDependencies ) { self.browserViewClosure = browserViewClosure diff --git a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/View/DAppBrowserWidgetView.swift b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/View/DAppBrowserWidgetView.swift index d1ac8de87c..9dbac41ce6 100644 --- a/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/View/DAppBrowserWidgetView.swift +++ b/novawallet/Modules/DApp/DAppBrowser/DAppBrowserWidget/View/DAppBrowserWidgetView.swift @@ -44,7 +44,8 @@ private extension DAppBrowserWidgetView { func setupLayout() { addSubview(backgroundView) backgroundView.snp.makeConstraints { make in - make.edges.equalToSuperview() + make.top.leading.trailing.equalToSuperview() + make.bottom.equalToSuperview().inset(-Constants.borderWidth) } addSubview(contentContainerView) diff --git a/novawallet/Modules/DApp/DAppFavorites/DAppFavoritesPresenter.swift b/novawallet/Modules/DApp/DAppFavorites/DAppFavoritesPresenter.swift index 32fd34049f..05aa3acebe 100644 --- a/novawallet/Modules/DApp/DAppFavorites/DAppFavoritesPresenter.swift +++ b/novawallet/Modules/DApp/DAppFavorites/DAppFavoritesPresenter.swift @@ -105,6 +105,12 @@ extension DAppFavoritesPresenter: DAppFavoritesInteractorOutputProtocol { let currentFavorites = favorites let updatedFavorites = changes.mergeToDict(currentFavorites) + guard !updatedFavorites.isEmpty else { + wireframe.close(from: view) + + return + } + favorites = updatedFavorites guard currentFavorites.count != updatedFavorites.count else { return } diff --git a/novawallet/Modules/DApp/DAppList/View/DAppCategoriesView.swift b/novawallet/Modules/DApp/DAppList/View/DAppCategoriesView.swift index cd4d0027a4..0587647873 100644 --- a/novawallet/Modules/DApp/DAppList/View/DAppCategoriesView.swift +++ b/novawallet/Modules/DApp/DAppList/View/DAppCategoriesView.swift @@ -40,7 +40,7 @@ final class DAppCategoriesView: UIView { var chagesStateOnSelect: Bool = true private var categoryItems: [CategoryChip] = [] - private var viewModels: [DAppCategoryViewModel] = [] + var viewModels: [DAppCategoryViewModel] = [] private(set) var selectedIndex: Int? @@ -107,6 +107,7 @@ final class DAppCategoriesView: UIView { } for (index, category) in categories.enumerated() { + category.imageViewModel?.cancel(on: categoryItems[index].contentView.imageView) category.imageViewModel?.loadImage( on: categoryItems[index].contentView.imageView, targetSize: CGSize(width: 20, height: 20), diff --git a/novawallet/Modules/DApp/DAppList/ViewModel/DAppListViewModelFactory/DAppListViewModelFactory.swift b/novawallet/Modules/DApp/DAppList/ViewModel/DAppListViewModelFactory/DAppListViewModelFactory.swift index f3cdab9e00..b9b5736aa1 100644 --- a/novawallet/Modules/DApp/DAppList/ViewModel/DAppListViewModelFactory/DAppListViewModelFactory.swift +++ b/novawallet/Modules/DApp/DAppList/ViewModel/DAppListViewModelFactory/DAppListViewModelFactory.swift @@ -1,9 +1,9 @@ import Foundation import SubstrateSdk -private typealias IndexedDApp = (index: Int, dapp: DApp) +typealias IndexedDApp = (index: Int, dapp: DApp) -final class DAppListViewModelFactory { +final class DAppListViewModelFactory: DAppSearchingByQuery { private let dappCategoriesViewModelFactory: DAppCategoryViewModelFactoryProtocol private let walletSwitchViewModelFactory = WalletSwitchViewModelFactory() @@ -272,17 +272,7 @@ extension DAppListViewModelFactory: DAppListViewModelFactoryProtocol { dAppList: DAppList, favorites: [String: DAppFavorite] ) -> DAppListViewModel { - let dAppsByQuery: [IndexedDApp] = dAppList.dApps.enumerated().compactMap { valueIndex in - guard let query = query, !query.isEmpty else { - return IndexedDApp(index: valueIndex.offset, dapp: valueIndex.element) - } - - if valueIndex.element.name.localizedCaseInsensitiveContains(query) { - return IndexedDApp(index: valueIndex.offset, dapp: valueIndex.element) - } else { - return nil - } - } + let dAppsByQuery: [IndexedDApp] = search(by: query, in: dAppList) let actualDApps: [IndexedDApp] = dAppsByQuery.filter { indexedDApp in guard let category else { return true } diff --git a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift index d1f4d3187a..6803c34bcb 100644 --- a/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift +++ b/novawallet/Modules/DApp/DAppOperationConfirm/DAppOperationConfirmViewController.swift @@ -33,8 +33,6 @@ final class DAppOperationConfirmViewController: UIViewController, ViewHolder { } private func setupHandlers() { - presentationController?.delegate = self - rootView.accountCell.addTarget(self, action: #selector(actionShowAccountOptions), for: .touchUpInside) rootView.confirmButton.addTarget(self, action: #selector(actionConfirm), for: .touchUpInside) rootView.rejectButton.addTarget(self, action: #selector(actionReject), for: .touchUpInside) @@ -140,7 +138,7 @@ extension DAppOperationConfirmViewController: Localizable { } } -extension DAppOperationConfirmViewController: UIAdaptivePresentationControllerDelegate { +extension DAppOperationConfirmViewController: ModalCardPresentationControllerDelegate { func presentationControllerShouldDismiss(_: UIPresentationController) -> Bool { false } diff --git a/novawallet/Modules/DApp/DAppSearch/DAppSearchPresenter.swift b/novawallet/Modules/DApp/DAppSearch/DAppSearchPresenter.swift index 96df2fce6b..c6404759d2 100644 --- a/novawallet/Modules/DApp/DAppSearch/DAppSearchPresenter.swift +++ b/novawallet/Modules/DApp/DAppSearch/DAppSearchPresenter.swift @@ -2,7 +2,7 @@ import Foundation import SoraFoundation import Operation_iOS -final class DAppSearchPresenter { +final class DAppSearchPresenter: DAppSearchingByQuery { weak var view: DAppSearchViewProtocol? let wireframe: DAppSearchWireframeProtocol let interactor: DAppSearchInteractorInputProtocol @@ -96,16 +96,24 @@ extension DAppSearchPresenter: DAppSearchPresenterProtocol { } func selectSearchQuery() { + let proceedClosure: () -> Void = { [weak self] in + self?.delegate?.didCompleteDAppSearchResult( + .query(string: self?.query ?? "") + ) + self?.wireframe.close(from: self?.view) + } + + guard search(by: query, in: dAppList).isEmpty else { + proceedClosure() + + return + } + wireframe.showUnknownDappWarning( from: view, email: applicationConfig.supportEmail, locale: localizationManager.selectedLocale, - handler: { [weak self] in - self?.delegate?.didCompleteDAppSearchResult( - .query(string: self?.query ?? "") - ) - self?.wireframe.close(from: self?.view) - } + handler: proceedClosure ) } diff --git a/novawallet/Modules/DApp/DAppSearch/DAppSearchViewController.swift b/novawallet/Modules/DApp/DAppSearch/DAppSearchViewController.swift index e328a826b0..6519eb13bc 100644 --- a/novawallet/Modules/DApp/DAppSearch/DAppSearchViewController.swift +++ b/novawallet/Modules/DApp/DAppSearch/DAppSearchViewController.swift @@ -171,7 +171,10 @@ extension DAppSearchViewController: UITableViewDataSource { cell.bind(title: searchTitle ?? "") return cell case .dapps: - guard let dAppViewModel = viewModel?.dApps[indexPath.row] else { + guard + indexPath.row < viewModel?.dApps.count ?? 0, + let dAppViewModel = viewModel?.dApps[indexPath.row] + else { return UITableViewCell() } @@ -271,7 +274,7 @@ extension DAppSearchViewController: DAppSearchViewProtocol { func didReceive(viewModel: DAppListViewModel?) { self.viewModel = viewModel - rootView.categoriesView.bind(categories: viewModel?.categories ?? []) + rootView.updateCategoriesView(with: viewModel?.categories ?? []) rootView.categoriesView.setSelectedIndex( viewModel?.selectedCategoryIndex, diff --git a/novawallet/Modules/DApp/DAppSearch/DAppSearchViewLayout.swift b/novawallet/Modules/DApp/DAppSearch/DAppSearchViewLayout.swift index f303c7254e..989e4d2f80 100644 --- a/novawallet/Modules/DApp/DAppSearch/DAppSearchViewLayout.swift +++ b/novawallet/Modules/DApp/DAppSearch/DAppSearchViewLayout.swift @@ -3,7 +3,10 @@ import SoraUI final class DAppSearchViewLayout: UIView { let searchBar = CustomSearchBar() - let categoriesView = DAppCategoriesView() + + let categoriesView: DAppCategoriesView = .create { view in + view.alpha = 0.0 + } let topContainerView = UIView() @@ -26,6 +29,22 @@ final class DAppSearchViewLayout: UIView { return item }() + private let appearanceAnimator: ViewAnimatorProtocol = FadeAnimator( + from: 0.0, + to: 1.0, + duration: 0.15 + ) + private let disappearanceAnimator: ViewAnimatorProtocol = FadeAnimator( + from: 1.0, + to: 0.0, + duration: 0.15 + ) + private let blockAnimator: BlockViewAnimatorProtocol = BlockViewAnimator( + duration: 0.2, + delay: 0.0, + options: [.curveLinear] + ) + override init(frame: CGRect) { super.init(frame: frame) @@ -46,7 +65,7 @@ final class DAppSearchViewLayout: UIView { categoriesView.snp.makeConstraints { make in make.top.equalTo(safeAreaLayoutGuide) - make.height.equalTo(DAppCategoriesView.preferredHeight) + make.height.equalTo(0.0) make.leading.trailing.equalToSuperview() } @@ -60,4 +79,55 @@ final class DAppSearchViewLayout: UIView { make.leading.trailing.bottom.equalToSuperview() } } + + func updateCategoriesView(with viewModels: [DAppCategoryViewModel]) { + guard viewModels.isEmpty != categoriesView.viewModels.isEmpty else { + categoriesView.bind(categories: viewModels) + + return + } + + if viewModels.isEmpty { + hideCategoriesView { [weak self] in + self?.categoriesView.bind(categories: viewModels) + } + } else { + categoriesView.bind(categories: viewModels) + showCategoriesView() + } + } + + func hideCategoriesView(updateClosure: @escaping () -> Void) { + categoriesView.snp.updateConstraints { make in + make.height.equalTo(0.0) + } + + disappearanceAnimator.animate( + view: categoriesView, + completionBlock: nil + ) + + blockAnimator.animate( + block: { [weak self] in self?.layoutIfNeeded() }, + completionBlock: { _ in updateClosure() } + ) + } + + func showCategoriesView() { + categoriesView.snp.updateConstraints { make in + make.height.equalTo(DAppCategoriesView.preferredHeight) + } + + blockAnimator.animate( + block: { [weak self] in self?.layoutIfNeeded() }, + completionBlock: { [weak self] _ in + guard let self else { return } + + appearanceAnimator.animate( + view: categoriesView, + completionBlock: nil + ) + } + ) + } } diff --git a/novawallet/Modules/DApp/DAppSearch/DAppSearchingByQuery.swift b/novawallet/Modules/DApp/DAppSearch/DAppSearchingByQuery.swift new file mode 100644 index 0000000000..4ef816e256 --- /dev/null +++ b/novawallet/Modules/DApp/DAppSearch/DAppSearchingByQuery.swift @@ -0,0 +1,31 @@ +import Foundation + +protocol DAppSearchingByQuery { + func search( + by query: String?, + in dAppList: DAppList? + ) -> [IndexedDApp] +} + +extension DAppSearchingByQuery { + func search( + by query: String?, + in dAppList: DAppList? + ) -> [IndexedDApp] { + guard let dAppList else { return [] } + + return dAppList.dApps.enumerated().compactMap { valueIndex in + guard let query = query, !query.isEmpty else { + return IndexedDApp(index: valueIndex.offset, dapp: valueIndex.element) + } + + if valueIndex.element.name.localizedCaseInsensitiveContains(query) { + return IndexedDApp(index: valueIndex.offset, dapp: valueIndex.element) + } else if let queryURL = URL(string: query), valueIndex.element.url.host == queryURL.host { + return IndexedDApp(index: valueIndex.offset, dapp: valueIndex.element) + } else { + return nil + } + } + } +} diff --git a/novawallet/Modules/NovaMainAppContainer/NovaMainAppContainerViewController.swift b/novawallet/Modules/NovaMainAppContainer/NovaMainAppContainerViewController.swift index 9c7c5ea47b..b0fe306d7a 100644 --- a/novawallet/Modules/NovaMainAppContainer/NovaMainAppContainerViewController.swift +++ b/novawallet/Modules/NovaMainAppContainer/NovaMainAppContainerViewController.swift @@ -55,21 +55,14 @@ private extension NovaMainAppContainerViewController { } func browserCloseLayoutDependencies() -> DAppBrowserLayoutTransitionDependencies { - var transform: CGAffineTransform? - var presentingController: UIViewController? - - if let presentedViewController = tabBar?.presentedController() { - presentingController = presentedViewController.presentingViewController - transform = presentingController?.view.transform - } - - presentingController?.view.transform = .identity + let topContainerBottomOffset = Constants.topContainerBottomOffset(for: view) + let minimizedWidgetHeight = Constants.minimizedWidgetHeight(for: view) return DAppBrowserLayoutTransitionDependencies( layoutClosure: { [weak self] in self?.browserWidget?.view.snp.updateConstraints { make in - make.bottom.equalToSuperview().inset(-Constants.topContainerBottomOffset) - make.height.equalTo(Constants.minimizedWidgetHeight) + make.bottom.equalToSuperview().inset(-topContainerBottomOffset) + make.height.equalTo(minimizedWidgetHeight) } self?.topContainerBottomConstraint?.constant = 0 @@ -79,24 +72,22 @@ private extension NovaMainAppContainerViewController { animatableClosure: { [weak self] in self?.tabBar?.view.layer.maskedCorners = [] self?.updateModalsLayoutIfNeeded() - }, - transformClosure: { - guard let transform else { return } - - presentingController?.view.transform = transform } ) } func browserMinimizeLayoutDependencies() -> DAppBrowserLayoutTransitionDependencies { - DAppBrowserLayoutTransitionDependencies( + let topContainerBottomOffset = Constants.topContainerBottomOffset(for: view) + let minimizedWidgetHeight = Constants.minimizedWidgetHeight(for: view) + + return DAppBrowserLayoutTransitionDependencies( layoutClosure: { [weak self] in self?.browserWidget?.view.snp.updateConstraints { make in make.bottom.equalToSuperview() - make.height.equalTo(Constants.minimizedWidgetHeight) + make.height.equalTo(minimizedWidgetHeight) } - self?.topContainerBottomConstraint?.constant = -Constants.topContainerBottomOffset + self?.topContainerBottomConstraint?.constant = -topContainerBottomOffset return self?.rootView }, @@ -111,6 +102,7 @@ private extension NovaMainAppContainerViewController { func browserMaximizeLayoutDependencies() -> DAppBrowserLayoutTransitionDependencies { let fullHeight = view.frame.size.height + let topContainerBottomOffset = Constants.topContainerBottomOffset(for: view) return DAppBrowserLayoutTransitionDependencies( layoutClosure: { [weak self] in @@ -119,7 +111,7 @@ private extension NovaMainAppContainerViewController { make.bottom.equalToSuperview() } - self?.topContainerBottomConstraint?.constant = -Constants.topContainerBottomOffset + self?.topContainerBottomConstraint?.constant = -topContainerBottomOffset return self?.rootView } @@ -127,15 +119,12 @@ private extension NovaMainAppContainerViewController { } func updateModalsLayoutIfNeeded() { - if - let cardModalController = tabBar?.presentedController() as? CardLayoutPresentationController, - let presentingController = cardModalController.presentingViewController { - cardModalController.view.frame = CGRect( - x: cardModalController.view.frame.minX, - y: cardModalController.view.frame.minY, - width: presentingController.view.bounds.width, - height: presentingController.view.bounds.height - ) + var presentedViewController = tabBar?.presentedController() + + while presentedViewController != nil { + let presentationController = presentedViewController?.presentationController as? ModalCardPresentationController + presentationController?.updateLayout() + presentedViewController = presentedViewController?.presentedViewController } } } @@ -147,24 +136,38 @@ extension NovaMainAppContainerViewController { bottomView: UIView, topView: UIView ) { - rootView.addSubview(topView) + let topViewContainer: UIView = .create { view in + view.clipsToBounds = true + } - topView.snp.makeConstraints { make in + rootView.addSubview(topViewContainer) + + topViewContainer.snp.makeConstraints { make in make.leading.trailing.top.equalToSuperview() } - topContainerBottomConstraint = topView.bottomAnchor.constraint(equalTo: rootView.bottomAnchor) + topViewContainer.addSubview(topView) + + topView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + topContainerBottomConstraint = topViewContainer.bottomAnchor.constraint(equalTo: rootView.bottomAnchor) topContainerBottomConstraint?.isActive = true topView.layer.cornerRadius = 16 + topView.layer.maskedCorners = [] topView.layer.masksToBounds = true rootView.addSubview(bottomView) + let topContainerBottomOffset = Constants.topContainerBottomOffset(for: view) + let minimizedWidgetHeight = Constants.minimizedWidgetHeight(for: view) + bottomView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() - make.bottom.equalToSuperview().inset(-Constants.topContainerBottomOffset) - make.height.equalTo(Constants.minimizedWidgetHeight) + make.bottom.equalToSuperview().inset(-topContainerBottomOffset) + make.height.equalTo(minimizedWidgetHeight) } } } @@ -219,8 +222,16 @@ extension NovaMainAppContainerViewController: NovaMainAppContainerViewProtocol { private extension NovaMainAppContainerViewController { enum Constants { - static let minimizedWidgetHeight: CGFloat = 78 static let childSpacing: CGFloat = 6 - static let topContainerBottomOffset: CGFloat = minimizedWidgetHeight + childSpacing + + static func minimizedWidgetHeight(for view: UIView) -> CGFloat { + view.safeAreaInsets.bottom + 44 + } + + static func topContainerBottomOffset(for view: UIView) -> CGFloat { + let minimizedWidgetHeight = minimizedWidgetHeight(for: view) + + return minimizedWidgetHeight + childSpacing + } } } diff --git a/novawallet/Modules/Settings/View/SettingsViewLayout.swift b/novawallet/Modules/Settings/View/SettingsViewLayout.swift index aaff9b1117..03e0203b6d 100644 --- a/novawallet/Modules/Settings/View/SettingsViewLayout.swift +++ b/novawallet/Modules/Settings/View/SettingsViewLayout.swift @@ -26,9 +26,20 @@ final class SettingsViewLayout: UIView { override func layoutSubviews() { super.layoutSubviews() - headerView.frame = CGRect(origin: .zero, size: CGSize(width: bounds.width, height: 118)) - footerView.frame = CGRect(origin: .zero, size: CGSize(width: bounds.width, height: 92)) - tableView.contentInset = .init(top: 0, left: 0, bottom: footerView.frame.height + 6, right: 0) + headerView.bounds = CGRect( + origin: .zero, + size: CGSize(width: bounds.width, height: 118) + ) + footerView.bounds = CGRect( + origin: .zero, + size: CGSize(width: bounds.width, height: 92) + ) + tableView.contentInset = .init( + top: 0, + left: 0, + bottom: footerView.bounds.height + 6, + right: 0 + ) } private func setupLayout() { diff --git a/novawallet/Modules/Staking/StakingProxy/Add/StakingSetupProxyPresenter.swift b/novawallet/Modules/Staking/StakingProxy/Add/StakingSetupProxyPresenter.swift index 75eed337bb..7de2b9bb8c 100644 --- a/novawallet/Modules/Staking/StakingProxy/Add/StakingSetupProxyPresenter.swift +++ b/novawallet/Modules/Staking/StakingProxy/Add/StakingSetupProxyPresenter.swift @@ -291,14 +291,18 @@ extension StakingSetupProxyPresenter: ModalPickerViewControllerDelegate { } extension StakingSetupProxyPresenter: YourWalletsDelegate { - func didSelectYourWallet(address: AccountAddress) { - wireframe.hideYourWallets(from: view) + func yourWallets( + selectionView: YourWalletsPresentationProtocol, + didSelect address: AccountAddress + ) { + wireframe.hideYourWallets(from: selectionView) + view?.didReceiveYourWallets(state: .inactive) updateRecepientAddress(address) provideInputViewModel() } - func didCloseYourWalletSelection() { + func yourWalletsDidClose(selectionView _: YourWalletsPresentationProtocol) { view?.didReceiveYourWallets(state: .inactive) } } diff --git a/novawallet/Modules/Swaps/Execution/SwapExecutionViewController.swift b/novawallet/Modules/Swaps/Execution/SwapExecutionViewController.swift index 23d431dd2e..c1c81ed636 100644 --- a/novawallet/Modules/Swaps/Execution/SwapExecutionViewController.swift +++ b/novawallet/Modules/Swaps/Execution/SwapExecutionViewController.swift @@ -41,8 +41,6 @@ final class SwapExecutionViewController: UIViewController, ViewHolder { } private func setupHandlers() { - presentationController?.delegate = self - rootView.detailsView.delegate = self rootView.rateCell.addTarget(self, action: #selector(rateAction), for: .touchUpInside) @@ -147,7 +145,7 @@ extension SwapExecutionViewController: CollapsableContainerViewDelegate { func didChangeExpansion(isExpanded _: Bool, sender _: AnyObject) {} } -extension SwapExecutionViewController: UIAdaptivePresentationControllerDelegate { +extension SwapExecutionViewController: ModalCardPresentationControllerDelegate { func presentationControllerShouldDismiss(_: UIPresentationController) -> Bool { false } diff --git a/novawallet/Modules/Swaps/Execution/SwapExecutionWireframe.swift b/novawallet/Modules/Swaps/Execution/SwapExecutionWireframe.swift index a46e68cfe3..565e0927ec 100644 --- a/novawallet/Modules/Swaps/Execution/SwapExecutionWireframe.swift +++ b/novawallet/Modules/Swaps/Execution/SwapExecutionWireframe.swift @@ -41,7 +41,10 @@ final class SwapExecutionWireframe: SwapExecutionWireframeProtocol { let navigationController = NovaNavigationController(rootViewController: swapView.controller) presenter?.dismiss(animated: true) { - presenter?.present(navigationController, animated: true) + presenter?.presentWithCardLayout( + navigationController, + animated: true + ) } } @@ -61,7 +64,7 @@ final class SwapExecutionWireframe: SwapExecutionWireframeProtocol { let navigationController = NovaNavigationController(rootViewController: routeDetailsView.controller) - view?.controller.present(navigationController, animated: true) + view?.controller.presentWithCardLayout(navigationController, animated: true) } func showFeeDetails( @@ -80,6 +83,6 @@ final class SwapExecutionWireframe: SwapExecutionWireframeProtocol { let navigationController = NovaNavigationController(rootViewController: routeDetailsView.controller) - view?.controller.present(navigationController, animated: true) + view?.controller.presentWithCardLayout(navigationController, animated: true) } } diff --git a/novawallet/Modules/Transfer/TransferSetup/TransferSetupPresenter.swift b/novawallet/Modules/Transfer/TransferSetup/TransferSetupPresenter.swift index 6ba61552a2..5b1784b358 100644 --- a/novawallet/Modules/Transfer/TransferSetup/TransferSetupPresenter.swift +++ b/novawallet/Modules/Transfer/TransferSetup/TransferSetupPresenter.swift @@ -512,15 +512,20 @@ extension TransferSetupPresenter: AddressScanDelegate { } extension TransferSetupPresenter: YourWalletsDelegate { - func didSelectYourWallet(address: AccountAddress) { - wireframe.hideYourWallets(from: view) + func yourWallets( + selectionView: YourWalletsPresentationProtocol, + didSelect address: AccountAddress + ) { + wireframe.hideYourWallets(from: selectionView) childPresenter?.changeRecepient(address: address) view?.changeYourWalletsViewState(.inactive) recipientAddress = .address(address) } - func didCloseYourWalletSelection() { + func yourWalletsDidClose( + selectionView _: YourWalletsPresentationProtocol + ) { view?.changeYourWalletsViewState(.inactive) } } diff --git a/novawallet/Modules/YourWallets/YourWalletsPresenter.swift b/novawallet/Modules/YourWallets/YourWalletsPresenter.swift index 0f38c80095..fc988a1309 100644 --- a/novawallet/Modules/YourWallets/YourWalletsPresenter.swift +++ b/novawallet/Modules/YourWallets/YourWalletsPresenter.swift @@ -161,11 +161,19 @@ extension YourWalletsPresenter: YourWalletsPresenterProtocol { func didSelect(viewModel: YourWalletsCellViewModel.CommonModel) { selectedAddress = viewModel.displayAddress.address updateSelectedCell() - delegate?.didSelectYourWallet(address: viewModel.displayAddress.address) + + if let view { + delegate?.yourWallets( + selectionView: view, + didSelect: viewModel.displayAddress.address + ) + } } func viewWillDisappear() { - delegate?.didCloseYourWalletSelection() + if let view { + delegate?.yourWalletsDidClose(selectionView: view) + } } } diff --git a/novawallet/Modules/YourWallets/YourWalletsProtocols.swift b/novawallet/Modules/YourWallets/YourWalletsProtocols.swift index 974617bc69..5f303197f3 100644 --- a/novawallet/Modules/YourWallets/YourWalletsProtocols.swift +++ b/novawallet/Modules/YourWallets/YourWalletsProtocols.swift @@ -1,6 +1,8 @@ import UIKit -protocol YourWalletsViewProtocol: ControllerBackedProtocol { +protocol YourWalletsPresentationProtocol: ControllerBackedProtocol {} + +protocol YourWalletsViewProtocol: YourWalletsPresentationProtocol { func update(viewModel: [YourWalletsViewSectionModel]) func update(header: String) func calculateEstimatedHeight(sections: Int, items: Int) -> CGFloat @@ -13,8 +15,8 @@ protocol YourWalletsPresenterProtocol: AnyObject { } protocol YourWalletsDelegate: AnyObject { - func didSelectYourWallet(address: AccountAddress) - func didCloseYourWalletSelection() + func yourWallets(selectionView: YourWalletsPresentationProtocol, didSelect address: AccountAddress) + func yourWalletsDidClose(selectionView: YourWalletsPresentationProtocol) } extension YourWalletsDelegate {