diff --git a/RadixWallet.xcodeproj/project.pbxproj b/RadixWallet.xcodeproj/project.pbxproj index 89e3c782f9..a327456d6c 100644 --- a/RadixWallet.xcodeproj/project.pbxproj +++ b/RadixWallet.xcodeproj/project.pbxproj @@ -715,6 +715,8 @@ 48FFFB082ADC6FD300B2B213 /* SwiftUINavigationCore in Frameworks */ = {isa = PBXBuildFile; productRef = 48FFFB072ADC6FD300B2B213 /* SwiftUINavigationCore */; }; 48FFFB0A2ADC721800B2B213 /* Atomics in Frameworks */ = {isa = PBXBuildFile; productRef = 48FFFB092ADC721800B2B213 /* Atomics */; }; 48FFFB0D2ADC744700B2B213 /* TextBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 48FFFB0C2ADC744700B2B213 /* TextBuilder */; }; + 5B135B392C7636DA004AAD2E /* HiddenEntities+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B135B382C7636DA004AAD2E /* HiddenEntities+View.swift */; }; + 5B135B3B2C7636FD004AAD2E /* HiddenEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B135B3A2C7636FD004AAD2E /* HiddenEntities.swift */; }; 5B1C4FD52BBB0B0C00B9436F /* AppsFlyerLib-Strict in Frameworks */ = {isa = PBXBuildFile; productRef = 5B1C4FD42BBB0B0C00B9436F /* AppsFlyerLib-Strict */; }; 5B1C4FD82BBB0C1E00B9436F /* AppsFlyerClient+Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1C4FD72BBB0C1E00B9436F /* AppsFlyerClient+Interface.swift */; }; 5B1C4FDA2BBB0DCF00B9436F /* AppsFlyerClient+Live.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1C4FD92BBB0DCF00B9436F /* AppsFlyerClient+Live.swift */; }; @@ -723,6 +725,7 @@ 5B272DDB2C36E9D300B74F1F /* AppEventsClient+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B272DDA2C36E9D300B74F1F /* AppEventsClient+Test.swift */; }; 5B2A45022BD6680400AEC8AD /* ContactSupportClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2A45012BD6680400AEC8AD /* ContactSupportClient.swift */; }; 5B2A45042BD6689100AEC8AD /* ContactSupportClient+Live.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2A45032BD6689100AEC8AD /* ContactSupportClient+Live.swift */; }; + 5B3C48A92C7E190C00DB160D /* AssetRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3C48A82C7E190C00DB160D /* AssetRow.swift */; }; 5B43B08B2BDAAD4B00AA1E92 /* AddressDetails+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B43B0892BDAAD4B00AA1E92 /* AddressDetails+View.swift */; }; 5B43B08C2BDAAD4B00AA1E92 /* AddressDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B43B08A2BDAAD4B00AA1E92 /* AddressDetails.swift */; }; 5B45E2FA2BC45770007C4C84 /* FactorSourceAccess+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B45E2F82BC45770007C4C84 /* FactorSourceAccess+View.swift */; }; @@ -755,20 +758,21 @@ 5BBC7D9E2C3D390E00B04BD6 /* BootstrapClient+Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBC7D9B2C3D390D00B04BD6 /* BootstrapClient+Interface.swift */; }; 5BBC7D9F2C3D390E00B04BD6 /* BootstrapClient+Live.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBC7D9C2C3D390D00B04BD6 /* BootstrapClient+Live.swift */; }; 5BBC7DA42C3D442800B04BD6 /* URLRequest+Extra.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBC7DA32C3D442800B04BD6 /* URLRequest+Extra.swift */; }; - 5BBC7DA62C3EFA9700B04BD6 /* HideAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBC7DA52C3EFA9700B04BD6 /* HideAccountView.swift */; }; 5BBC7DA82C40278E00B04BD6 /* NonFungibleResourceAsset+Reducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBC7DA72C40278E00B04BD6 /* NonFungibleResourceAsset+Reducer.swift */; }; 5BBC7DAA2C403F3400B04BD6 /* AccountCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBC7DA92C403F3400B04BD6 /* AccountCard.swift */; }; 5BC82B6C2BED18A1009AC162 /* FactoryReset+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC82B6A2BED18A1009AC162 /* FactoryReset+View.swift */; }; 5BC82B6D2BED18A1009AC162 /* FactoryReset+Reducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC82B6B2BED18A1009AC162 /* FactoryReset+Reducer.swift */; }; + 5BEB9CD22C6F6B24001FD9D4 /* HideResource+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEB9CD02C6F6B24001FD9D4 /* HideResource+View.swift */; }; + 5BEB9CD32C6F6B24001FD9D4 /* HIdeResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEB9CD12C6F6B24001FD9D4 /* HIdeResource.swift */; }; + 5BEB9CD52C6F6CC9001FD9D4 /* ConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEB9CD42C6F6CC9001FD9D4 /* ConfirmationView.swift */; }; + 5BFA4FBA2C736B740030B517 /* HiddenAssets+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFA4FB82C736B740030B517 /* HiddenAssets+View.swift */; }; + 5BFA4FBB2C736B740030B517 /* HiddenAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFA4FB92C736B740030B517 /* HiddenAssets.swift */; }; 830818482B9F1621002D8351 /* HTTPClient+Live.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830818472B9F1621002D8351 /* HTTPClient+Live.swift */; }; 8308184A2B9F162B002D8351 /* HTTPClient+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830818492B9F162B002D8351 /* HTTPClient+Mock.swift */; }; 8308184C2B9F169B002D8351 /* TokenPriceClient+Live.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8308184B2B9F169B002D8351 /* TokenPriceClient+Live.swift */; }; 8308184E2B9F16AD002D8351 /* TokenPriceClient+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8308184D2B9F16AD002D8351 /* TokenPriceClient+Mock.swift */; }; 830E43AF2C2D8B5100B6E95F /* SessionStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830E43AE2C2D8B5100B6E95F /* SessionStorageTests.swift */; }; - 830EA9D62AE94033004C8051 /* AccountAndPersonaHiding+Reducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830EA9D52AE94033004C8051 /* AccountAndPersonaHiding+Reducer.swift */; }; 830EA9DB2AEA8770004C8051 /* EntitiesVisibilityClient+Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830EA9DA2AEA8770004C8051 /* EntitiesVisibilityClient+Interface.swift */; }; - 830EA9DD2AEB8197004C8051 /* AccountAndPersonaHiding+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830EA9DC2AEB8197004C8051 /* AccountAndPersonaHiding+View.swift */; }; - 830EA9E02AEB8219004C8051 /* AccountAndPersonaHidingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830EA9DF2AEB8219004C8051 /* AccountAndPersonaHidingTests.swift */; }; 830EA9E52AEB9088004C8051 /* DefaultValueDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830EA9E42AEB9088004C8051 /* DefaultValueDecodable.swift */; }; 830EA9E72AEBA793004C8051 /* EntitiesVisibilityClient+Live.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830EA9E62AEBA793004C8051 /* EntitiesVisibilityClient+Live.swift */; }; 830EA9E92AEBA7C5004C8051 /* EntitiesVisibilityClient+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830EA9E82AEBA7C5004C8051 /* EntitiesVisibilityClient+Test.swift */; }; @@ -1909,6 +1913,8 @@ 48DD0FF62BCBCBB900C54C43 /* Stage1MigrateToSargon+AssetException.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stage1MigrateToSargon+AssetException.swift"; sourceTree = ""; }; 48FF43142AE43C7C00C568B9 /* TimeLimit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeLimit.swift; sourceTree = ""; }; 48FFFAF12ADC23AC00B2B213 /* Exports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exports.swift; sourceTree = ""; }; + 5B135B382C7636DA004AAD2E /* HiddenEntities+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HiddenEntities+View.swift"; sourceTree = ""; }; + 5B135B3A2C7636FD004AAD2E /* HiddenEntities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenEntities.swift; sourceTree = ""; }; 5B1C4FD72BBB0C1E00B9436F /* AppsFlyerClient+Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppsFlyerClient+Interface.swift"; sourceTree = ""; }; 5B1C4FD92BBB0DCF00B9436F /* AppsFlyerClient+Live.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppsFlyerClient+Live.swift"; sourceTree = ""; }; 5B272DD62C36E89600B74F1F /* AppEventsClient+Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppEventsClient+Interface.swift"; sourceTree = ""; }; @@ -1916,6 +1922,7 @@ 5B272DDA2C36E9D300B74F1F /* AppEventsClient+Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppEventsClient+Test.swift"; sourceTree = ""; }; 5B2A45012BD6680400AEC8AD /* ContactSupportClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSupportClient.swift; sourceTree = ""; }; 5B2A45032BD6689100AEC8AD /* ContactSupportClient+Live.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactSupportClient+Live.swift"; sourceTree = ""; }; + 5B3C48A82C7E190C00DB160D /* AssetRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetRow.swift; sourceTree = ""; }; 5B43B0892BDAAD4B00AA1E92 /* AddressDetails+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddressDetails+View.swift"; sourceTree = ""; }; 5B43B08A2BDAAD4B00AA1E92 /* AddressDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressDetails.swift; sourceTree = ""; }; 5B45E2F82BC45770007C4C84 /* FactorSourceAccess+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FactorSourceAccess+View.swift"; sourceTree = ""; }; @@ -1948,20 +1955,21 @@ 5BBC7D9B2C3D390D00B04BD6 /* BootstrapClient+Interface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BootstrapClient+Interface.swift"; sourceTree = ""; }; 5BBC7D9C2C3D390D00B04BD6 /* BootstrapClient+Live.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BootstrapClient+Live.swift"; sourceTree = ""; }; 5BBC7DA32C3D442800B04BD6 /* URLRequest+Extra.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+Extra.swift"; sourceTree = ""; }; - 5BBC7DA52C3EFA9700B04BD6 /* HideAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HideAccountView.swift; sourceTree = ""; }; 5BBC7DA72C40278E00B04BD6 /* NonFungibleResourceAsset+Reducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NonFungibleResourceAsset+Reducer.swift"; sourceTree = ""; }; 5BBC7DA92C403F3400B04BD6 /* AccountCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCard.swift; sourceTree = ""; }; 5BC82B6A2BED18A1009AC162 /* FactoryReset+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FactoryReset+View.swift"; sourceTree = ""; }; 5BC82B6B2BED18A1009AC162 /* FactoryReset+Reducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FactoryReset+Reducer.swift"; sourceTree = ""; }; + 5BEB9CD02C6F6B24001FD9D4 /* HideResource+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HideResource+View.swift"; sourceTree = ""; }; + 5BEB9CD12C6F6B24001FD9D4 /* HIdeResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HIdeResource.swift; sourceTree = ""; }; + 5BEB9CD42C6F6CC9001FD9D4 /* ConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationView.swift; sourceTree = ""; }; + 5BFA4FB82C736B740030B517 /* HiddenAssets+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HiddenAssets+View.swift"; sourceTree = ""; }; + 5BFA4FB92C736B740030B517 /* HiddenAssets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenAssets.swift; sourceTree = ""; }; 830818472B9F1621002D8351 /* HTTPClient+Live.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HTTPClient+Live.swift"; sourceTree = ""; }; 830818492B9F162B002D8351 /* HTTPClient+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HTTPClient+Mock.swift"; sourceTree = ""; }; 8308184B2B9F169B002D8351 /* TokenPriceClient+Live.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TokenPriceClient+Live.swift"; sourceTree = ""; }; 8308184D2B9F16AD002D8351 /* TokenPriceClient+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TokenPriceClient+Mock.swift"; sourceTree = ""; }; 830E43AE2C2D8B5100B6E95F /* SessionStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionStorageTests.swift; sourceTree = ""; }; - 830EA9D52AE94033004C8051 /* AccountAndPersonaHiding+Reducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountAndPersonaHiding+Reducer.swift"; sourceTree = ""; }; 830EA9DA2AEA8770004C8051 /* EntitiesVisibilityClient+Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EntitiesVisibilityClient+Interface.swift"; sourceTree = ""; }; - 830EA9DC2AEB8197004C8051 /* AccountAndPersonaHiding+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountAndPersonaHiding+View.swift"; sourceTree = ""; }; - 830EA9DF2AEB8219004C8051 /* AccountAndPersonaHidingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountAndPersonaHidingTests.swift; sourceTree = ""; }; 830EA9E22AEB8933004C8051 /* EntitiesHidingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntitiesHidingTests.swift; sourceTree = ""; }; 830EA9E42AEB9088004C8051 /* DefaultValueDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultValueDecodable.swift; sourceTree = ""; }; 830EA9E62AEBA793004C8051 /* EntitiesVisibilityClient+Live.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EntitiesVisibilityClient+Live.swift"; sourceTree = ""; }; @@ -2639,7 +2647,6 @@ 5B45E2F32BC45706007C4C84 /* FactorSourceAccess */, A41557512B7645D40040AD4E /* TransactionHistory */, 48AE39D82B0CBE3900813CF3 /* AccountRecoveryScan */, - 830EA9D42AE94014004C8051 /* AccountAndPersonaHidingFeature */, 48CFBC6C2ADC10D800E77A5C /* AccountPreferencesFeature */, 48CFBC7D2ADC10D800E77A5C /* GatewaySettingsFeature */, 48CFBC8B2ADC10D800E77A5C /* SplashFeature */, @@ -2701,7 +2708,6 @@ 48CFBC742ADC10D800E77A5C /* ThirdPartyDeposits */, 48CFBC722ADC10D800E77A5C /* UpdateAccountLabel+Reducer.swift */, 48CFBC732ADC10D800E77A5C /* UpdateAccountLabel+View.swift */, - 5BBC7DA52C3EFA9700B04BD6 /* HideAccountView.swift */, ); path = Children; sourceTree = ""; @@ -3929,6 +3935,7 @@ 48CFBE362ADC10D800E77A5C /* NonFungibleAssetList */, 48CFBE402ADC10D800E77A5C /* FungibleAssetList */, 48CFBE4C2ADC10D800E77A5C /* PoolUnitsList */, + 5BEB9CCF2C6F6AFD001FD9D4 /* HideResource */, 48CFBE5F2ADC10D800E77A5C /* HelperViews */, 48CFBE662ADC10D800E77A5C /* DetailsContainerWithHeaderView.swift */, ); @@ -4953,6 +4960,8 @@ 5BBC43A82BBAC6B0005747B1 /* AppTextEditor.swift */, 5B6499B92BCFDB1E000F2176 /* ShareView.swift */, A4DCCC4A2C2DA08000438A7B /* GeometryExtensions.swift */, + 5BEB9CD42C6F6CC9001FD9D4 /* ConfirmationView.swift */, + 5B3C48A82C7E190C00DB160D /* AssetRow.swift */, ); path = Components; sourceTree = ""; @@ -5418,6 +5427,15 @@ path = KeychainClientTests; sourceTree = ""; }; + 5B135B372C7636B6004AAD2E /* HiddenEntities */ = { + isa = PBXGroup; + children = ( + 5B135B382C7636DA004AAD2E /* HiddenEntities+View.swift */, + 5B135B3A2C7636FD004AAD2E /* HiddenEntities.swift */, + ); + path = HiddenEntities; + sourceTree = ""; + }; 5B1C4FD62BBB0C0100B9436F /* AppsFlyerClient */ = { isa = PBXGroup; children = ( @@ -5489,6 +5507,8 @@ 5B758D552BCE7AEC00348722 /* Preferences+View.swift */, 5B758D562BCE7AEC00348722 /* Preferences.swift */, 48CFBD8F2ADC10D800E77A5C /* DefaultDepositGuarantees */, + 5B135B372C7636B6004AAD2E /* HiddenEntities */, + 5BFA4FB72C736B500030B517 /* HiddenAssets */, ); path = Preferences; sourceTree = ""; @@ -5533,39 +5553,40 @@ path = BootstrapClient; sourceTree = ""; }; - 830E43AD2C2D8B3800B6E95F /* Mobile */ = { + 5BEB9CCF2C6F6AFD001FD9D4 /* HideResource */ = { isa = PBXGroup; children = ( - 830E43AE2C2D8B5100B6E95F /* SessionStorageTests.swift */, + 5BEB9CD02C6F6B24001FD9D4 /* HideResource+View.swift */, + 5BEB9CD12C6F6B24001FD9D4 /* HIdeResource.swift */, ); - path = Mobile; + path = HideResource; sourceTree = ""; }; - 830EA9D42AE94014004C8051 /* AccountAndPersonaHidingFeature */ = { + 5BFA4FB72C736B500030B517 /* HiddenAssets */ = { isa = PBXGroup; children = ( - 830EA9D52AE94033004C8051 /* AccountAndPersonaHiding+Reducer.swift */, - 830EA9DC2AEB8197004C8051 /* AccountAndPersonaHiding+View.swift */, + 5BFA4FB82C736B740030B517 /* HiddenAssets+View.swift */, + 5BFA4FB92C736B740030B517 /* HiddenAssets.swift */, ); - path = AccountAndPersonaHidingFeature; + path = HiddenAssets; sourceTree = ""; }; - 830EA9D92AEA8750004C8051 /* EntitiesVisibilityClient */ = { + 830E43AD2C2D8B3800B6E95F /* Mobile */ = { isa = PBXGroup; children = ( - 830EA9DA2AEA8770004C8051 /* EntitiesVisibilityClient+Interface.swift */, - 830EA9E62AEBA793004C8051 /* EntitiesVisibilityClient+Live.swift */, - 830EA9E82AEBA7C5004C8051 /* EntitiesVisibilityClient+Test.swift */, + 830E43AE2C2D8B5100B6E95F /* SessionStorageTests.swift */, ); - path = EntitiesVisibilityClient; + path = Mobile; sourceTree = ""; }; - 830EA9DE2AEB8201004C8051 /* AccountAndPersonaHidingTests */ = { + 830EA9D92AEA8750004C8051 /* EntitiesVisibilityClient */ = { isa = PBXGroup; children = ( - 830EA9DF2AEB8219004C8051 /* AccountAndPersonaHidingTests.swift */, + 830EA9DA2AEA8770004C8051 /* EntitiesVisibilityClient+Interface.swift */, + 830EA9E62AEBA793004C8051 /* EntitiesVisibilityClient+Live.swift */, + 830EA9E82AEBA7C5004C8051 /* EntitiesVisibilityClient+Test.swift */, ); - path = AccountAndPersonaHidingTests; + path = EntitiesVisibilityClient; sourceTree = ""; }; 831F0CF02C2576AE00D6F5BF /* DappOriginVerification */ = { @@ -6312,7 +6333,6 @@ isa = PBXGroup; children = ( 83EE474E2AF027CE00155F03 /* AssetTransferTests */, - 830EA9DE2AEB8201004C8051 /* AccountAndPersonaHidingTests */, 8328806A2AE6724B0014FBF3 /* AccountPreferencesTests */, E6DBA2782ADEBFB200A38425 /* SettingsFeatureTests */, E6DBA27B2ADEBFB200A38425 /* AppFeatureTests */, @@ -6989,7 +7009,6 @@ E6DBA31C2ADEBFB300A38425 /* RadixConnectE2E.swift in Sources */, E6DBA3282ADEBFB300A38425 /* Misc+Extensions.swift in Sources */, E6DBA3432ADEBFB300A38425 /* UserDefaultsClientTests.swift in Sources */, - 830EA9E02AEB8219004C8051 /* AccountAndPersonaHidingTests.swift in Sources */, E6DBA31A2ADEBFB300A38425 /* DataChannelClientTests.swift in Sources */, E6DBA32B2ADEBFB300A38425 /* JSON+Sendable.swift in Sources */, E6DBA3202ADEBFB300A38425 /* PeerConnectionMocks.swift in Sources */, @@ -7094,8 +7113,12 @@ 48CFC2872ADC10D900E77A5C /* MinimumPercentageStepper+View.swift in Sources */, A40816022C7E0D08005E65B9 /* StateAccountResourcePreferencesPageRequest.swift in Sources */, E76645A42C23138300065D9A /* Throwable.swift in Sources */, + 5BEB9CD22C6F6B24001FD9D4 /* HideResource+View.swift in Sources */, + 5BEB9CD32C6F6B24001FD9D4 /* HIdeResource.swift in Sources */, 5B272DD72C36E89600B74F1F /* AppEventsClient+Interface.swift in Sources */, 48CFC2A12ADC10D900E77A5C /* PersonaFeature.swift in Sources */, + 5BFA4FBA2C736B740030B517 /* HiddenAssets+View.swift in Sources */, + 5BFA4FBB2C736B740030B517 /* HiddenAssets.swift in Sources */, A4DCCC502C2DA32C00438A7B /* HomeCardsClient+Interface.swift in Sources */, 48CFC4162ADC10DA00E77A5C /* PasteboardClient+Test.swift in Sources */, A40815BF2C7E0D08005E65B9 /* NetworkConfigurationResponse.swift in Sources */, @@ -7116,7 +7139,6 @@ 48CFC5FD2ADC10DA00E77A5C /* HTTPURLSessionResponse.swift in Sources */, 83856D622B0279080026452A /* VerifyMnemonic+View.swift in Sources */, A408163A2C7E0D08005E65B9 /* StreamTransactionsResponse.swift in Sources */, - 5BBC7DA62C3EFA9700B04BD6 /* HideAccountView.swift in Sources */, A40816282C7E0D08005E65B9 /* StateKeyValueStoreKeysResponseItem.swift in Sources */, 48CFC4332ADC10DA00E77A5C /* Either+Extra.swift in Sources */, 48CFC2C72ADC10D900E77A5C /* MessageMode+View.swift in Sources */, @@ -7152,6 +7174,8 @@ 5BB7C1762BC81F50001216EB /* ImportOlympiaNameLedger.swift in Sources */, 48CFC4022ADC10D900E77A5C /* KeychainClient+Interface.swift in Sources */, 48CFC44E2ADC10DA00E77A5C /* AppPreferencesClient+Interface.swift in Sources */, + 48CFC5432ADC10DA00E77A5C /* EntityMetadataCollection.swift in Sources */, + 5BEB9CD52C6F6CC9001FD9D4 /* ConfirmationView.swift in Sources */, 48CFC3442ADC10D900E77A5C /* AccountPermission.swift in Sources */, 48CFC5E82ADC10DA00E77A5C /* FeatureReducer.swift in Sources */, A40815FD2C7E0D08005E65B9 /* StateAccountLockerPageVaultsRequest.swift in Sources */, @@ -7268,7 +7292,6 @@ 48CFC2BF2ADC10D900E77A5C /* ImportMnemonicsFlowCoordinator.swift in Sources */, 48CFC3632ADC10D900E77A5C /* AssetsView+Reducer.swift in Sources */, A408162F2C7E0D08005E65B9 /* StateNonFungibleLocationResponse.swift in Sources */, - 830EA9DD2AEB8197004C8051 /* AccountAndPersonaHiding+View.swift in Sources */, 48CFC2792ADC10D900E77A5C /* P2PLinkRow+Reducer.swift in Sources */, 48CFC30F2ADC10D900E77A5C /* ImportOlympiaWalletCoordinator.swift in Sources */, E68878592BDBFD42003F3393 /* Stage2MigrateToSargon+LedgerHardwareWalletFactorSource+New.swift in Sources */, @@ -7301,6 +7324,9 @@ 48CFC2552ADC10D900E77A5C /* Splash.swift in Sources */, 48CFC6232ADC10DA00E77A5C /* AccountPortfolio+Extensions.swift in Sources */, 48CFC3292ADC10D900E77A5C /* CameraPermission+Reducer.swift in Sources */, + 48CFC53A2ADC10DA00E77A5C /* NotSyncedUpError.swift in Sources */, + 5B3C48A92C7E190C00DB160D /* AssetRow.swift in Sources */, + 48CFC54E2ADC10DA00E77A5C /* StateEntityNonFungibleResourceVaultsPageResponse.swift in Sources */, A40816172C7E0D08005E65B9 /* StateEntityNonFungibleIdsPageRequest.swift in Sources */, A40815812C7E0D08005E65B9 /* InternalServerError.swift in Sources */, A40816402C7E0D08005E65B9 /* TransactionDetailsOptIns.swift in Sources */, @@ -7359,6 +7385,8 @@ A40816212C7E0D08005E65B9 /* StateKeyValueStoreDataRequest.swift in Sources */, 48CFC65C2ADC10DB00E77A5C /* Profile+Persona+Add.swift in Sources */, 48CFC5B52ADC10DA00E77A5C /* AppTextField.swift in Sources */, + 48CFC4B42ADC10DA00E77A5C /* TransactionPreviewRequest.swift in Sources */, + 5B135B3B2C7636FD004AAD2E /* HiddenEntities.swift in Sources */, A408165D2C7E0D08005E65B9 /* ValidatorCollectionItemEffectiveFeeFactor.swift in Sources */, A40815762C7E0D08005E65B9 /* EntitySchemaCollectionItem.swift in Sources */, 48CFC5E32ADC10DA00E77A5C /* AttributedString+Extra.swift in Sources */, @@ -7466,7 +7494,7 @@ 48CFC6022ADC10DA00E77A5C /* P2P+LedgerHardwareWallet.swift in Sources */, 48CFC46A2ADC10DA00E77A5C /* ImportLegacyWalletClient+Live.swift in Sources */, 5B1C4FD82BBB0C1E00B9436F /* AppsFlyerClient+Interface.swift in Sources */, - 830EA9D62AE94033004C8051 /* AccountAndPersonaHiding+Reducer.swift in Sources */, + 48CFC5052ADC10DA00E77A5C /* TransactionPreviewResponseLogsInner.swift in Sources */, 5B1C4FDA2BBB0DCF00B9436F /* AppsFlyerClient+Live.swift in Sources */, A4CFB55F2BAA821E00778BDD /* HScrollBar.swift in Sources */, 48CFC44D2ADC10DA00E77A5C /* MnemonicClient+Live.swift in Sources */, @@ -7487,6 +7515,7 @@ 483A3DE62BD2678900055932 /* Stage1MigrateToSargon+FactorSourceCryptoParameters.swift in Sources */, 48CFC36B2ADC10D900E77A5C /* FungibleTokenDetails+Reducer.swift in Sources */, 5BBC7D9F2C3D390E00B04BD6 /* BootstrapClient+Live.swift in Sources */, + 5B135B392C7636DA004AAD2E /* HiddenEntities+View.swift in Sources */, 4855B1C72BCAC82200DD0A47 /* Stage1MigrateToSargon+SignatureOfEntity.swift in Sources */, A40816302C7E0D08005E65B9 /* StateNonFungibleLocationResponseItem.swift in Sources */, 48CFC3322ADC10D900E77A5C /* ImportMnemonic.swift in Sources */, diff --git a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift index dcb419ae70..2f4cb39b3e 100644 --- a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift +++ b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift @@ -25,12 +25,21 @@ extension AccountPortfoliosClient: DependencyKey { } } + /// Update when hidden resources change + Task { + for try await _ in await appPreferencesClient.appPreferenceUpdates().removeDuplicates(by: { $0.resources.hiddenResources == $1.resources.hiddenResources }) { + guard !Task.isCancelled else { return } + let accountAddresses = state.portfoliosSubject.value.wrappedValue.map { $0.map(\.key) } ?? [] + _ = try await fetchAccountPortfolios(accountAddresses, forceRefreshEntities: false, forceRefreshPrices: true) + } + } + /// Fetches the pool and stake units details for a given account; Will update the portfolio accordingly @Sendable - func fetchPoolAndStakeUnitsDetails(_ account: OnLedgerEntity.OnLedgerAccount, cachingStrategy: OnLedgerEntitiesClient.CachingStrategy) async { + func fetchPoolAndStakeUnitsDetails(_ account: OnLedgerEntity.OnLedgerAccount, hiddenResources: [ResourceIdentifier], cachingStrategy: OnLedgerEntitiesClient.CachingStrategy) async { async let poolDetailsFetch = Task { do { - let poolUnitDetails = try await onLedgerEntitiesClient.getOwnedPoolUnitsDetails(account, cachingStrategy: cachingStrategy) + let poolUnitDetails = try await onLedgerEntitiesClient.getOwnedPoolUnitsDetails(account, hiddenResources: hiddenResources, cachingStrategy: cachingStrategy) await state.set(poolDetails: .success(poolUnitDetails), forAccount: account.address) } catch { await state.set(poolDetails: .failure(error), forAccount: account.address) @@ -68,11 +77,12 @@ extension AccountPortfoliosClient: DependencyKey { @Sendable func fetchAccountPortfolios( _ accountAddresses: [AccountAddress], - _ forceRefresh: Bool + forceRefreshEntities: Bool, + forceRefreshPrices: Bool ) async throws -> [AccountPortfolio] { let gateway = await gatewaysClient.getCurrentGateway() await state.setRadixGateway(gateway) - if forceRefresh { + if forceRefreshEntities { for accountAddress in accountAddresses { cacheClient.removeFolder(.init(address: accountAddress)) } @@ -80,13 +90,15 @@ extension AccountPortfoliosClient: DependencyKey { /// Explicetely load and set the currency target and visibility to make sure /// it is available for usage before resources are loaded - let preferences = await appPreferencesClient.getPreferences().display - await state.setSelectedCurrency(preferences.fiatCurrencyPriceTarget) - await state.setIsCurrencyAmountVisble(preferences.isCurrencyAmountVisible) + let preferences = await appPreferencesClient.getPreferences() + let display = preferences.display + await state.setSelectedCurrency(display.fiatCurrencyPriceTarget) + await state.setIsCurrencyAmountVisble(display.isCurrencyAmountVisible) let accounts = try await onLedgerEntitiesClient.getAccounts(accountAddresses) + let hiddenResources = preferences.resources.hiddenResources - let portfolios = accounts.map { AccountPortfolio(account: $0) } + let portfolios = accounts.map { AccountPortfolio(account: $0, hiddenResources: hiddenResources) } await state.handlePortfoliosUpdate(portfolios) /// Put together all resources from already fetched and new accounts @@ -120,11 +132,11 @@ extension AccountPortfoliosClient: DependencyKey { } }() - await applyTokenPrices(Array(allResources), forceRefresh: forceRefresh) + await applyTokenPrices(Array(allResources), forceRefresh: forceRefreshPrices) // Load additional details _ = await accounts.map(\.nonEmptyVaults).parallelMap { - await fetchPoolAndStakeUnitsDetails($0, cachingStrategy: forceRefresh ? .forceUpdate : .useCache) + await fetchPoolAndStakeUnitsDetails($0, hiddenResources: hiddenResources, cachingStrategy: forceRefreshEntities ? .forceUpdate : .useCache) } return Array(state.portfoliosSubject.value.wrappedValue!.values) @@ -140,7 +152,8 @@ extension AccountPortfoliosClient: DependencyKey { } let account = try await onLedgerEntitiesClient.getAccount(accountAddress) - let portfolio = AccountPortfolio(account: account) + let hiddenResources = await appPreferencesClient.getHiddenResources() + let portfolio = AccountPortfolio(account: account, hiddenResources: hiddenResources) if case let .success(tokenPrices) = await state.tokenPrices { await applyTokenPrices( @@ -150,7 +163,7 @@ extension AccountPortfoliosClient: DependencyKey { } await state.handlePortfolioUpdate(portfolio) - await fetchPoolAndStakeUnitsDetails(account.nonEmptyVaults, cachingStrategy: forceRefresh ? .forceUpdate : .useCache) + await fetchPoolAndStakeUnitsDetails(account.nonEmptyVaults, hiddenResources: hiddenResources, cachingStrategy: forceRefresh ? .forceUpdate : .useCache) return portfolio } @@ -158,7 +171,7 @@ extension AccountPortfoliosClient: DependencyKey { return AccountPortfoliosClient( fetchAccountPortfolios: { accountAddresses, forceRefresh in try await Task.detached { - try await fetchAccountPortfolios(accountAddresses, forceRefresh) + try await fetchAccountPortfolios(accountAddresses, forceRefreshEntities: forceRefresh, forceRefreshPrices: forceRefresh) }.value }, fetchAccountPortfolio: { accountAddress, forceRefresh in diff --git a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+State.swift b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+State.swift index 606085b865..0172a283c5 100644 --- a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+State.swift +++ b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+State.swift @@ -4,6 +4,7 @@ import Foundation extension AccountPortfoliosClient { public struct AccountPortfolio: Sendable, Hashable, CustomDebugStringConvertible { public var account: OnLedgerEntity.OnLedgerAccount + public let hiddenResources: [ResourceIdentifier] public var poolUnitDetails: Loadable<[OnLedgerEntitiesClient.OwnedResourcePoolDetails]> = .idle public var stakeUnitDetails: Loadable> = .idle @@ -12,6 +13,28 @@ extension AccountPortfoliosClient { public var debugDescription: String { account.debugDescription } + + init(account: OnLedgerEntity.OnLedgerAccount, hiddenResources: [ResourceIdentifier]) { + var modified = account + + // Remove every hidden fungible resource + modified.fungibleResources.nonXrdResources.removeAll(where: { resource in + hiddenResources.contains(.fungible(resource.resourceAddress)) + }) + + // Remove every hidden non fungible resource + modified.nonFungibleResources.removeAll(where: { resource in + hiddenResources.contains(.nonFungible(resource.resourceAddress)) + }) + + // Remove every hidden pool unit + modified.poolUnitResources.poolUnits.removeAll(where: { poolUnit in + hiddenResources.contains(.poolUnit(poolUnit.resourcePoolAddress)) + }) + + self.account = modified + self.hiddenResources = hiddenResources + } } /// Internal state that holds all loaded portfolios. diff --git a/RadixWallet/Clients/AppPreferencesClient/AppPreferencesClient+Interface.swift b/RadixWallet/Clients/AppPreferencesClient/AppPreferencesClient+Interface.swift index 5edfb39cb7..45a1f349f8 100644 --- a/RadixWallet/Clients/AppPreferencesClient/AppPreferencesClient+Interface.swift +++ b/RadixWallet/Clients/AppPreferencesClient/AppPreferencesClient+Interface.swift @@ -70,6 +70,14 @@ extension AppPreferencesClient { display.isCurrencyAmountVisible.toggle() } } + + public func getHiddenResources() async -> [ResourceIdentifier] { + await getPreferences().resources.hiddenResources + } + + public func isResourceHidden(_ resource: ResourceIdentifier) async -> Bool { + await getHiddenResources().contains(resource) + } } // MARK: AppPreferencesClient.Error diff --git a/RadixWallet/Clients/EntitiesVisibilityClient/EntitiesVisibilityClient+Interface.swift b/RadixWallet/Clients/EntitiesVisibilityClient/EntitiesVisibilityClient+Interface.swift index 1b1d1705bf..0c05ba22d5 100644 --- a/RadixWallet/Clients/EntitiesVisibilityClient/EntitiesVisibilityClient+Interface.swift +++ b/RadixWallet/Clients/EntitiesVisibilityClient/EntitiesVisibilityClient+Interface.swift @@ -1,46 +1,22 @@ // MARK: - EntitiesVisibilityClient /// Controls the visibility of the entities in the Wallet public struct EntitiesVisibilityClient: Sendable { - public var hideAccounts: HideAccounts - public var hidePersonas: HidePersonas - public var unhideAllEntities: UnhideAllEntities - public var getHiddenEntityCounts: GetHiddenEntityCounts + public var hideAccount: HideAccount + public var hidePersona: HidePersona + public var unhideAccount: UnhideAccount + public var unhidePersona: UnhidePersona + public var getHiddenEntities: GetHiddenEntities } extension EntitiesVisibilityClient { - public struct HiddenEntityCounts: Hashable, Sendable { - public let hiddenAccountsCount: Int - public let hiddenPersonasCount: Int + public struct HiddenEntities: Hashable, Sendable { + public let accounts: Accounts + public let personas: Personas } - public typealias HideAccounts = @Sendable (Set) async throws -> Void - public typealias HidePersonas = @Sendable (Set) async throws -> Void - public typealias UnhideAllEntities = @Sendable () async throws -> Void - public typealias GetHiddenEntityCounts = @Sendable () async throws -> HiddenEntityCounts -} - -extension EntitiesVisibilityClient { - public func hideAccounts(ids: some Collection) async throws { - try await hideAccounts(Set(ids)) - } - - public func hidePersonas(ids: some Collection) async throws { - try await hidePersonas(Set(ids)) - } - - public func hide(accounts: some Collection) async throws { - try await hideAccounts(ids: accounts.map(\.id)) - } - - public func hide(personas: some Collection) async throws { - try await hidePersonas(ids: personas.map(\.id)) - } - - public func hide(account: Account) async throws { - try await hide(accounts: [account]) - } - - public func hide(persona: Persona) async throws { - try await hide(personas: [persona]) - } + public typealias HideAccount = @Sendable (Account.ID) async throws -> Void + public typealias HidePersona = @Sendable (Persona.ID) async throws -> Void + public typealias UnhideAccount = @Sendable (Account.ID) async throws -> Void + public typealias UnhidePersona = @Sendable (Persona.ID) async throws -> Void + public typealias GetHiddenEntities = @Sendable () async throws -> HiddenEntities } diff --git a/RadixWallet/Clients/EntitiesVisibilityClient/EntitiesVisibilityClient+Live.swift b/RadixWallet/Clients/EntitiesVisibilityClient/EntitiesVisibilityClient+Live.swift index dd7d22fe48..b2fba7af4c 100644 --- a/RadixWallet/Clients/EntitiesVisibilityClient/EntitiesVisibilityClient+Live.swift +++ b/RadixWallet/Clients/EntitiesVisibilityClient/EntitiesVisibilityClient+Live.swift @@ -7,28 +7,29 @@ extension EntitiesVisibilityClient: DependencyKey { profileStore: ProfileStore = .shared ) -> Self { .init( - hideAccounts: { idsOfAccounts in + hideAccount: { id in try await profileStore.updatingOnCurrentNetwork { network in - network.hideAccounts(ids: idsOfAccounts) + network.hideAccount(id: id) } }, - hidePersonas: { idsOfPersonas in + hidePersona: { id in try await profileStore.updatingOnCurrentNetwork { network in - network.hidePersonas(ids: idsOfPersonas) + network.hidePersona(id: id) } }, - unhideAllEntities: { + unhideAccount: { id in try await profileStore.updatingOnCurrentNetwork { network in - network.unhideAllEntities() + network.unhideAccount(id: id) } }, - getHiddenEntityCounts: { + unhidePersona: { id in + try await profileStore.updatingOnCurrentNetwork { network in + network.unhidePersona(id: id) + } + }, + getHiddenEntities: { let network = try await profileStore.network() - - return .init( - hiddenAccountsCount: network.getHiddenAccounts().count, - hiddenPersonasCount: network.getHiddenPersonas().count - ) + return .init(accounts: network.getHiddenAccounts(), personas: network.getHiddenPersonas()) } ) } diff --git a/RadixWallet/Clients/EntitiesVisibilityClient/EntitiesVisibilityClient+Test.swift b/RadixWallet/Clients/EntitiesVisibilityClient/EntitiesVisibilityClient+Test.swift index ec214c6e49..3c6269cacf 100644 --- a/RadixWallet/Clients/EntitiesVisibilityClient/EntitiesVisibilityClient+Test.swift +++ b/RadixWallet/Clients/EntitiesVisibilityClient/EntitiesVisibilityClient+Test.swift @@ -10,16 +10,18 @@ extension EntitiesVisibilityClient: TestDependencyKey { public static let previewValue = Self.noop public static let noop = Self( - hideAccounts: { _ in throw NoopError() }, - hidePersonas: { _ in throw NoopError() }, - unhideAllEntities: { throw NoopError() }, - getHiddenEntityCounts: { throw NoopError() } + hideAccount: { _ in throw NoopError() }, + hidePersona: { _ in throw NoopError() }, + unhideAccount: { _ in throw NoopError() }, + unhidePersona: { _ in throw NoopError() }, + getHiddenEntities: { throw NoopError() } ) public static let testValue = Self( - hideAccounts: unimplemented("\(Self.self).hideAccounts"), - hidePersonas: unimplemented("\(Self.self).hidePersonas"), - unhideAllEntities: unimplemented("\(Self.self).unhideAllEntities"), - getHiddenEntityCounts: unimplemented("\(Self.self).getHiddenEntityCounts") + hideAccount: unimplemented("\(Self.self).hideAccount"), + hidePersona: unimplemented("\(Self.self).hidePersona"), + unhideAccount: unimplemented("\(Self.self).unhideAccount"), + unhidePersona: unimplemented("\(Self.self).unhidePersona"), + getHiddenEntities: unimplemented("\(Self.self).getHiddenEntities") ) } diff --git a/RadixWallet/Clients/GatewayAPI/GatewayAPIClient/GatewayAPIClient+Interface.swift b/RadixWallet/Clients/GatewayAPI/GatewayAPIClient/GatewayAPIClient+Interface.swift index 4f4c2cccfc..0c4673f11e 100644 --- a/RadixWallet/Clients/GatewayAPI/GatewayAPIClient/GatewayAPIClient+Interface.swift +++ b/RadixWallet/Clients/GatewayAPI/GatewayAPIClient/GatewayAPIClient+Interface.swift @@ -1,8 +1,6 @@ import Algorithms import Sargon -public typealias ResourceIdentifier = String - // MARK: - GatewayAPIClient public struct GatewayAPIClient: Sendable, DependencyKey { // MARK: Request diff --git a/RadixWallet/Clients/OnLedgerEntitiesClient/Helpers/OnLedgerEntitiesClient+ComplexTypes.swift b/RadixWallet/Clients/OnLedgerEntitiesClient/Helpers/OnLedgerEntitiesClient+ComplexTypes.swift index 3448de9793..63d3fb1e0a 100644 --- a/RadixWallet/Clients/OnLedgerEntitiesClient/Helpers/OnLedgerEntitiesClient+ComplexTypes.swift +++ b/RadixWallet/Clients/OnLedgerEntitiesClient/Helpers/OnLedgerEntitiesClient+ComplexTypes.swift @@ -29,6 +29,9 @@ extension OnLedgerEntitiesClient { let amount = resourceQuantifier.amount let resourceAddress = resource.resourceAddress + @Dependency(\.appPreferencesClient) var appPreferencesClient + let hiddenResources = await appPreferencesClient.getHiddenResources() + let guarantee: TransactionGuarantee? = { () -> TransactionGuarantee? in guard case let .predicted(predictedAmount) = resourceQuantifier else { return nil } let guaranteedAmount = defaultDepositGuarantee * predictedAmount.value @@ -50,7 +53,8 @@ extension OnLedgerEntitiesClient { entities: entities, resourceAssociatedDapps: resourceAssociatedDapps, networkID: networkID, - guarantee: guarantee + guarantee: guarantee, + hiddenResources: hiddenResources ) } @@ -64,11 +68,13 @@ extension OnLedgerEntitiesClient { guarantee: guarantee ) } + // Normal fungible resource let isXRD = resourceAddress.isXRD(on: networkID) + let isHidden = hiddenResources.contains(.fungible(resourceAddress)) let details: ResourceBalance.Fungible = .init(isXRD: isXRD, amount: .init(nominalAmount: amount), guarantee: guarantee) - return .init(resource: resource, details: .fungible(details)) + return .init(resource: resource, details: .fungible(details), isHidden: isHidden) } private func poolUnit( @@ -78,7 +84,8 @@ extension OnLedgerEntitiesClient { entities: TransactionReview.ResourcesInfo = [:], resourceAssociatedDapps: TransactionReview.ResourceAssociatedDapps? = nil, networkID: NetworkID, - guarantee: TransactionGuarantee? + guarantee: TransactionGuarantee?, + hiddenResources: [ResourceIdentifier] ) async throws -> ResourceBalance { let resourceAddress = resource.resourceAddress @@ -104,6 +111,8 @@ extension OnLedgerEntitiesClient { } } + let isHidden = hiddenResources.contains(.poolUnit(poolContribution.poolAddress)) + return .init( resource: resource, details: .poolUnit(.init( @@ -115,19 +124,23 @@ extension OnLedgerEntitiesClient { nonXrdResources: nonXrdResources ), guarantee: guarantee - )) + )), + isHidden: isHidden ) } else { guard let details = try await getPoolUnitDetails(resource, forAmount: amount) else { throw FailedToGetPoolUnitDetails() } + let isHidden = hiddenResources.contains(.poolUnit(details.address)) + return .init( resource: resource, details: .poolUnit(.init( details: details, guarantee: guarantee - )) + )), + isHidden: isHidden ) } } @@ -147,8 +160,8 @@ extension OnLedgerEntitiesClient { worth = amount * validator.xrdVaultBalance / totalSupply } else { - if let stake = try validatorStakes.first(where: { $0.validatorAddress == validator.address }) { - guard try stake.liquidStakeUnitAddress == validator.stakeUnitResourceAddress else { + if let stake = validatorStakes.first(where: { $0.validatorAddress == validator.address }) { + guard stake.liquidStakeUnitAddress == validator.stakeUnitResourceAddress else { throw StakeUnitAddressMismatch() } // Distribute the worth in proportion to the amounts, if needed @@ -187,18 +200,21 @@ extension OnLedgerEntitiesClient { switch resourceInfo { case let .left(resource): + @Dependency(\.appPreferencesClient) var appPreferencesClient + let hiddenResources = await appPreferencesClient.getHiddenResources() + let existingTokenIds = ids.filter { id in !newlyCreatedNonFungibles.contains { newId in newId.resourceAddress == resourceAddress && newId.nonFungibleLocalId == id } } - let newTokens = try ids.filter { id in + let newTokens = ids.filter { id in newlyCreatedNonFungibles.contains { newId in newId.resourceAddress == resourceAddress && newId.nonFungibleLocalId == id } }.map { - try OnLedgerEntity.NonFungibleToken(resourceAddress: resourceAddress, nftID: $0, nftData: nil) + OnLedgerEntity.NonFungibleToken(resourceAddress: resourceAddress, nftID: $0, nftData: nil) } let tokens = try await getNonFungibleTokenData(.init( @@ -220,7 +236,8 @@ extension OnLedgerEntitiesClient { )] } else { result = tokens.map { token in - ResourceBalance(resource: resource, details: .nonFungible(token)) + let isHidden = hiddenResources.contains(.nonFungible(token.id.resourceAddress)) + return ResourceBalance(resource: resource, details: .nonFungible(token), isHidden: isHidden) } guard result.count == ids.count else { @@ -241,7 +258,7 @@ extension OnLedgerEntitiesClient { ) } .map { id in - ResourceBalance(resource: resource, details: .nonFungible(.init(id: id, data: nil))) + ResourceBalance(resource: resource, details: .nonFungible(.init(id: id, data: nil)), isHidden: false) } guard result.count == ids.count else { diff --git a/RadixWallet/Clients/OnLedgerEntitiesClient/OnLedgerEntitiesClient+Interface.swift b/RadixWallet/Clients/OnLedgerEntitiesClient/OnLedgerEntitiesClient+Interface.swift index c07bd4bb75..8a7352ff50 100644 --- a/RadixWallet/Clients/OnLedgerEntitiesClient/OnLedgerEntitiesClient+Interface.swift +++ b/RadixWallet/Clients/OnLedgerEntitiesClient/OnLedgerEntitiesClient+Interface.swift @@ -534,9 +534,12 @@ extension OnLedgerEntitiesClient { @Sendable public func getOwnedPoolUnitsDetails( _ account: OnLedgerEntity.OnLedgerAccount, + hiddenResources: [ResourceIdentifier], cachingStrategy: CachingStrategy = .useCache ) async throws -> [OwnedResourcePoolDetails] { - let ownedPoolUnits = account.poolUnitResources.poolUnits + let ownedPoolUnits = account.poolUnitResources.poolUnits.filter { poolUnit in + !hiddenResources.contains(.poolUnit(poolUnit.resourcePoolAddress)) + } let pools = try await getEntities( ownedPoolUnits.map(\.resourcePoolAddress).map(\.asGeneral), [.dappDefinition], diff --git a/RadixWallet/Core/DesignSystem/Components/AssetRow.swift b/RadixWallet/Core/DesignSystem/Components/AssetRow.swift new file mode 100644 index 0000000000..b17b26d995 --- /dev/null +++ b/RadixWallet/Core/DesignSystem/Components/AssetRow.swift @@ -0,0 +1,48 @@ +import SwiftUI + +struct AssetRow: View { + let name: String? + let address: LedgerIdentifiable.Address + let type: Thumbnail.ContentType + let url: URL? + let accessory: Accessory? + + init( + name: String?, + address: LedgerIdentifiable.Address, + type: Thumbnail.ContentType, + url: URL?, + @ViewBuilder accessory: () -> Accessory + ) { + self.name = name + self.address = address + self.type = type + self.url = url + self.accessory = accessory() + } + + var body: some SwiftUI.View { + HStack(spacing: .zero) { + Thumbnail(type, url: url) + + VStack(alignment: .leading, spacing: .zero) { + Text(name ?? "-") + .textStyle(.body1HighImportance) + .foregroundColor(.app.gray1) + .lineLimit(1) + + AddressView(.address(address)) + .textStyle(.body2Regular) + .foregroundColor(.app.gray2) + } + .padding(.horizontal, .small2) + + Spacer() + + if let accessory { + accessory + } + } + .padding(.medium3) + } +} diff --git a/RadixWallet/Core/DesignSystem/Components/ConfirmationView.swift b/RadixWallet/Core/DesignSystem/Components/ConfirmationView.swift new file mode 100644 index 0000000000..ebafec8bc0 --- /dev/null +++ b/RadixWallet/Core/DesignSystem/Components/ConfirmationView.swift @@ -0,0 +1,97 @@ +import SwiftUI + +public typealias ConfirmationAction = ConfirmationView.Action + +// MARK: - ConfirmationView +public struct ConfirmationView: View { + let kind: Kind + let onAction: (Action) -> Void + + public var body: some View { + content + .withNavigationBar { + onAction(.cancel) + } + .presentationDetents([.fraction(0.6)]) + .presentationDragIndicator(.visible) + .presentationBackground(.blur) + } + + public var content: some View { + VStack(spacing: .zero) { + VStack(spacing: .medium2) { + Image(systemName: "eye.fill") + .renderingMode(.template) + .resizable() + .scaledToFit() + .frame(.small) + .foregroundColor(.app.gray3) + + Text(title) + .textStyle(.sheetTitle) + .foregroundColor(.app.gray1) + + Text(markdown: message, emphasizedColor: .app.gray1, emphasizedFont: .app.body1Header) + .textStyle(.body1Regular) + .foregroundColor(.app.gray1) + } + .padding(.horizontal, .small2) + + Spacer() + + HStack(spacing: .small2) { + Button(L10n.Common.cancel) { + onAction(.cancel) + } + .buttonStyle(.secondaryRectangular) + + Button(primaryAction) { + onAction(.confirm) + } + .buttonStyle(.primaryRectangular) + } + } + .multilineTextAlignment(.center) + .padding(.horizontal, .medium1) + .padding(.bottom, .medium2) + } +} + +extension ConfirmationView { + public enum Kind: Hashable, Sendable { + case hideAccount + case hideAsset + case hideCollection(name: String) + } + + public enum Action: Sendable { + case cancel + case confirm + } +} + +extension ConfirmationView { + var title: String { + switch kind { + case .hideAccount: L10n.Confirmation.HideAccount.title + case .hideAsset: L10n.Confirmation.HideAsset.title + case .hideCollection: L10n.Confirmation.HideCollection.title + } + } + + var message: String { + switch kind { + case .hideAccount: L10n.Confirmation.HideAccount.message + case .hideAsset: L10n.Confirmation.HideAsset.message + case let .hideCollection(name): L10n.Confirmation.HideCollection.message(name) + } + } + + var primaryAction: String { + switch kind { + case .hideAccount: L10n.Confirmation.HideAccount.button + case .hideAsset: L10n.Confirmation.HideAsset.button + case .hideCollection: L10n.Confirmation.HideCollection.button + } + } +} diff --git a/RadixWallet/Core/DesignSystem/Components/PlainListRow.swift b/RadixWallet/Core/DesignSystem/Components/PlainListRow.swift index 7468ae9a3f..d7b461a766 100644 --- a/RadixWallet/Core/DesignSystem/Components/PlainListRow.swift +++ b/RadixWallet/Core/DesignSystem/Components/PlainListRow.swift @@ -196,12 +196,14 @@ private extension PlainListRowCore.ViewState { .secondaryHeader case .settings, .dappAndPersona: .body1Header + case .hiddenPersona: + .body1HighImportance } } var subtitleTextStyle: TextStyle { switch context { - case .toggle: + case .toggle, .hiddenPersona: .body2Regular case .settings, .dappAndPersona: detail == nil ? .body1Regular : .body2Regular @@ -210,7 +212,7 @@ private extension PlainListRowCore.ViewState { var subtitleForegroundColor: Color { switch context { - case .toggle: + case .toggle, .hiddenPersona: .app.gray2 case .settings, .dappAndPersona: .app.gray1 @@ -219,7 +221,7 @@ private extension PlainListRowCore.ViewState { var titleLineLimit: Int? { switch context { - case .settings, .dappAndPersona: + case .settings, .dappAndPersona, .hiddenPersona: 1 case .toggle: nil @@ -228,7 +230,7 @@ private extension PlainListRowCore.ViewState { var subtitleLineLimit: Int { switch context { - case .toggle: + case .toggle, .hiddenPersona: 2 case .settings, .dappAndPersona: 3 @@ -243,12 +245,14 @@ private extension PlainListRowCore.ViewState { .medium1 case .dappAndPersona: .medium3 + case .hiddenPersona: + .medium3 } } var horizontalPadding: CGFloat { switch context { - case .toggle, .settings: + case .toggle, .settings, .hiddenPersona: .medium3 case .dappAndPersona: .medium1 @@ -262,6 +266,7 @@ extension PlainListRowCore.ViewState { case settings case toggle case dappAndPersona + case hiddenPersona } } diff --git a/RadixWallet/Core/FeaturePrelude/AccountCard/AccountCard.swift b/RadixWallet/Core/FeaturePrelude/AccountCard/AccountCard.swift index 17420aaff7..362649cf12 100644 --- a/RadixWallet/Core/FeaturePrelude/AccountCard/AccountCard.swift +++ b/RadixWallet/Core/FeaturePrelude/AccountCard/AccountCard.swift @@ -130,6 +130,11 @@ extension AccountCard.Kind { static var innerCompact: Self { .compact(addCornerRadius: false) } + + /// Behaves same way as `.selection` but it is never selected. + static var hiddenEntity: Self { + .selection(isSelected: false) + } } private extension AccountCard.Kind { diff --git a/RadixWallet/Core/SharedModels/Assets/OnLedgerEntity.swift b/RadixWallet/Core/SharedModels/Assets/OnLedgerEntity.swift index 567e2497b9..4d01ad1697 100644 --- a/RadixWallet/Core/SharedModels/Assets/OnLedgerEntity.swift +++ b/RadixWallet/Core/SharedModels/Assets/OnLedgerEntity.swift @@ -384,7 +384,7 @@ extension OnLedgerEntity { public let resourceAddress: ResourceAddress public let atLedgerState: AtLedgerState public let metadata: Metadata - public let nonFungibleIdsCount: Int + public var nonFungibleIdsCount: Int /// The vault where the owned ids are stored public let vaultAddress: VaultAddress @@ -519,7 +519,7 @@ extension OnLedgerEntity { extension OnLedgerEntity.OnLedgerAccount { public struct PoolUnitResources: Sendable, Hashable, Codable { public var radixNetworkStakes: IdentifiedArrayOf - public let poolUnits: [PoolUnit] + public var poolUnits: [PoolUnit] } public struct RadixNetworkStake: Sendable, Hashable, Codable, Identifiable, CustomDebugStringConvertible { diff --git a/RadixWallet/Features/AccountAndPersonaHidingFeature/AccountAndPersonaHiding+Reducer.swift b/RadixWallet/Features/AccountAndPersonaHidingFeature/AccountAndPersonaHiding+Reducer.swift deleted file mode 100644 index c1f65d6177..0000000000 --- a/RadixWallet/Features/AccountAndPersonaHidingFeature/AccountAndPersonaHiding+Reducer.swift +++ /dev/null @@ -1,111 +0,0 @@ -// MARK: - AccountAndPersonaHiding - -public struct AccountAndPersonaHiding: Sendable, FeatureReducer { - // MARK: - State - - public struct State: Hashable, Sendable { - public var hiddenEntityCounts: EntitiesVisibilityClient.HiddenEntityCounts? - - @PresentationState - public var destination: Destination.State? = nil - } - - public enum ViewAction: Hashable, Sendable { - case task - case unhideAllTapped - } - - public enum InternalAction: Hashable, Sendable { - case hiddenEntityCountsLoaded(EntitiesVisibilityClient.HiddenEntityCounts) - case didUnhideAllEntities - } - - public struct Destination: DestinationReducer { - @CasePathable - public enum State: Sendable, Hashable { - case confirmUnhideAllAlert(AlertState) - } - - @CasePathable - public enum Action: Sendable, Equatable { - case confirmUnhideAllAlert(ConfirmUnhideAllAlert) - - public enum ConfirmUnhideAllAlert: Hashable, Sendable { - case confirmTapped - case cancelTapped - } - } - - public var body: some ReducerOf { - EmptyReducer() - } - } - - // MARK: - Reducer - - @Dependency(\.entitiesVisibilityClient) var entitiesVisibilityClient - @Dependency(\.overlayWindowClient) var overlayWindowClient - @Dependency(\.errorQueue) var errorQueue - - public var body: some ReducerOf { - Reduce(core) - .ifLet(destinationPath, action: /Action.destination) { - Destination() - } - } - - private let destinationPath: WritableKeyPath> = \.$destination - - public func reduce(into state: inout State, viewAction: ViewAction) -> Effect { - switch viewAction { - case .task: - return .run { send in - let counts = try await entitiesVisibilityClient.getHiddenEntityCounts() - await send(.internal(.hiddenEntityCountsLoaded(counts))) - } - case .unhideAllTapped: - state.destination = .confirmUnhideAllAlert(.init( - title: .init(L10n.AppSettings.EntityHiding.unhideAllSection), - message: .init(L10n.AppSettings.EntityHiding.unhideAllConfirmation), - buttons: [ - .default(.init(L10n.Common.continue), action: .send(.confirmTapped)), - .cancel(.init(L10n.Common.cancel), action: .send(.cancelTapped)), - ] - )) - return .none - } - } - - public func reduce(into state: inout State, internalAction: InternalAction) -> Effect { - switch internalAction { - case let .hiddenEntityCountsLoaded(counts): - state.hiddenEntityCounts = counts - return .none - case .didUnhideAllEntities: - state.hiddenEntityCounts = .init(hiddenAccountsCount: 0, hiddenPersonasCount: 0) - return .none - } - } - - public func reduce(into state: inout State, presentedAction: Destination.Action) -> Effect { - switch presentedAction { - case let .confirmUnhideAllAlert(confirmUnhideAllAlert): - defer { - state.destination = nil - } - - switch confirmUnhideAllAlert { - case .confirmTapped: - return .run { send in - try await entitiesVisibilityClient.unhideAllEntities() - overlayWindowClient.scheduleHUD(.updatedAccount) - await send(.internal(.didUnhideAllEntities)) - } catch: { error, _ in - errorQueue.schedule(error) - } - case .cancelTapped: - return .none - } - } - } -} diff --git a/RadixWallet/Features/AccountAndPersonaHidingFeature/AccountAndPersonaHiding+View.swift b/RadixWallet/Features/AccountAndPersonaHidingFeature/AccountAndPersonaHiding+View.swift deleted file mode 100644 index 7ea9e357a5..0000000000 --- a/RadixWallet/Features/AccountAndPersonaHidingFeature/AccountAndPersonaHiding+View.swift +++ /dev/null @@ -1,86 +0,0 @@ -extension AccountAndPersonaHiding.State { - var viewState: AccountAndPersonaHiding.ViewState { - .init( - hiddenAccountsCount: hiddenEntityCounts?.hiddenAccountsCount ?? 0, - hiddenPersonasCount: hiddenEntityCounts?.hiddenPersonasCount ?? 0 - ) - } -} - -extension AccountAndPersonaHiding { - public struct ViewState: Equatable { - public let hiddenAccountsCount: Int - public let hiddenPersonasCount: Int - - public var hiddenAccountsText: String { - if hiddenAccountsCount == 1 { - L10n.AppSettings.EntityHiding.hiddenAccount(1) - } else { - L10n.AppSettings.EntityHiding.hiddenAccounts(hiddenAccountsCount) - } - } - - public var hiddenPersonasText: String { - if hiddenPersonasCount == 1 { - L10n.AppSettings.EntityHiding.hiddenPersona(1) - } else { - L10n.AppSettings.EntityHiding.hiddenPersonas(hiddenPersonasCount) - } - } - - public var unhideAllButtonControlState: ControlState { - if hiddenAccountsCount > 0 || hiddenPersonasCount > 0 { - .enabled - } else { - .disabled - } - } - } - - public struct View: SwiftUI.View { - public let store: StoreOf - - public var body: some SwiftUI.View { - WithViewStore(store, observe: \.viewState) { viewStore in - VStack(spacing: .medium3) { - Text(L10n.AppSettings.EntityHiding.text) - .textStyle(.body1Regular) - .foregroundColor(.app.gray2) - .flushedLeft - .padding(.top, .medium3) - - VStack(alignment: .leading, spacing: .small3) { - Text(viewStore.hiddenAccountsText) - - Text(viewStore.hiddenPersonasText) - } - .textStyle(.body1Header) - .foregroundColor(.app.gray2) - .centered - - Text(L10n.AppSettings.EntityHiding.unhideAllSection) - .textStyle(.body1HighImportance) - .foregroundColor(.app.gray2) - .flushedLeft - - Button(L10n.AppSettings.EntityHiding.unhideAllButton) { - viewStore.send(.view(.unhideAllTapped)) - } - .buttonStyle(.secondaryRectangular(shouldExpand: true)) - .controlState(viewStore.unhideAllButtonControlState) - - Spacer() - } - .padding(.horizontal, .medium3) - .task { @MainActor in - await viewStore.send(.view(.task)).finish() - } - .alert(store: store.scope( - state: \.$destination.confirmUnhideAllAlert, - action: \.destination.confirmUnhideAllAlert - )) - } - .radixToolbar(title: L10n.AppSettings.EntityHiding.title) - } - } -} diff --git a/RadixWallet/Features/AccountPreferencesFeature/AccountPreferences+Reducer.swift b/RadixWallet/Features/AccountPreferencesFeature/AccountPreferences+Reducer.swift index 5fba615ef3..180beb0d73 100644 --- a/RadixWallet/Features/AccountPreferencesFeature/AccountPreferences+Reducer.swift +++ b/RadixWallet/Features/AccountPreferencesFeature/AccountPreferences+Reducer.swift @@ -57,7 +57,7 @@ public struct AccountPreferences: Sendable, FeatureReducer { case updateAccountLabel(UpdateAccountLabel.Action) case thirdPartyDeposits(ManageThirdPartyDeposits.Action) case devPreferences(DevAccountPreferences.Action) - case hideAccount(HideAccountAction) + case hideAccount(ConfirmationAction) } public var body: some ReducerOf { @@ -174,7 +174,7 @@ public struct AccountPreferences: Sendable, FeatureReducer { private func hideAccountEffect(state: State) -> Effect { .run { [account = state.account] send in - try await entitiesVisibilityClient.hide(account: account) + try await entitiesVisibilityClient.hideAccount(account.id) overlayWindowClient.scheduleHUD(.accountHidden) await send(.delegate(.accountHidden)) } catch: { error, _ in diff --git a/RadixWallet/Features/AccountPreferencesFeature/AccountPreferences+View.swift b/RadixWallet/Features/AccountPreferencesFeature/AccountPreferences+View.swift index 35c079954e..95cb9cdbab 100644 --- a/RadixWallet/Features/AccountPreferencesFeature/AccountPreferences+View.swift +++ b/RadixWallet/Features/AccountPreferencesFeature/AccountPreferences+View.swift @@ -153,7 +153,7 @@ private extension View { private func hideAccount(with destinationStore: PresentationStoreOf, store: StoreOf) -> some View { sheet(store: destinationStore.scope(state: \.hideAccount, action: \.hideAccount)) { _ in - HideAccountView { action in + ConfirmationView(kind: .hideAccount) { action in store.send(.destination(.presented(.hideAccount(action)))) } } diff --git a/RadixWallet/Features/AccountPreferencesFeature/Children/HideAccountView.swift b/RadixWallet/Features/AccountPreferencesFeature/Children/HideAccountView.swift deleted file mode 100644 index eff5940ccd..0000000000 --- a/RadixWallet/Features/AccountPreferencesFeature/Children/HideAccountView.swift +++ /dev/null @@ -1,65 +0,0 @@ -import SwiftUI - -public typealias HideAccountAction = HideAccountView.Action - -// MARK: - HideAccountView -public struct HideAccountView: View { - let onAction: (Action) -> Void - - public var body: some View { - content - .withNavigationBar { - onAction(.cancel) - } - .presentationDetents([.fraction(0.6)]) - .presentationDragIndicator(.visible) - .presentationBackground(.blur) - } - - public var content: some View { - VStack(spacing: .zero) { - VStack(spacing: .medium2) { - Image(systemName: "eye.fill") - .renderingMode(.template) - .resizable() - .scaledToFit() - .frame(.small) - .foregroundColor(.app.gray3) - - Text(L10n.AccountSettings.HideAccount.title) - .textStyle(.sheetTitle) - .foregroundColor(.app.gray1) - - Text(L10n.AccountSettings.HideAccount.message) - .textStyle(.body1Regular) - .foregroundColor(.app.gray1) - } - .padding(.horizontal, .small2) - - Spacer() - - HStack(spacing: .small2) { - Button(L10n.Common.cancel) { - onAction(.cancel) - } - .buttonStyle(.secondaryRectangular) - - Button(L10n.AccountSettings.HideAccount.button) { - onAction(.confirm) - } - .buttonStyle(.primaryRectangular) - } - } - .multilineTextAlignment(.center) - .padding(.horizontal, .medium1) - .padding(.bottom, .medium2) - } -} - -// MARK: HideAccountView.Action -extension HideAccountView { - public enum Action: Sendable { - case cancel - case confirm - } -} diff --git a/RadixWallet/Features/AccountPreferencesFeature/Children/ThirdPartyDeposits/ResourcesList+View.swift b/RadixWallet/Features/AccountPreferencesFeature/Children/ThirdPartyDeposits/ResourcesList+View.swift index 398e749115..2e28d42105 100644 --- a/RadixWallet/Features/AccountPreferencesFeature/Children/ThirdPartyDeposits/ResourcesList+View.swift +++ b/RadixWallet/Features/AccountPreferencesFeature/Children/ThirdPartyDeposits/ResourcesList+View.swift @@ -109,15 +109,18 @@ extension ResourcesList.View { ScrollView { VStack(spacing: .zero) { ForEach(resources) { resource in - PlainListRow(viewState: .init( - rowCoreViewState: resource.rowCoreViewState, - accessory: { accesoryView(resource: resource) }, - icon: { iconView(resource: resource) } - )) - .background(Color.app.white) - .withSeparator + Card { + AssetRow( + name: resource.name, + address: resource.address.ledgerIdentifiable, + type: resource.thumbnailType, + url: resource.iconURL, + accessory: { accesoryView(resource: resource) } + ) + } } } + .padding(.horizontal, .medium3) } } @@ -217,17 +220,17 @@ extension ResourcesListMode { } } -extension ResourceViewState.Address { - var ledgerIdentifiable: LedgerIdentifiable { +private extension ResourceViewState.Address { + var ledgerIdentifiable: LedgerIdentifiable.Address { switch self { case let .assetException(exception): - .address(.resource(exception.address)) + .resource(exception.address) case let .allowedDepositor(.resource(resourceAddress)): - .address(.resource(resourceAddress)) + .resource(resourceAddress) case let .allowedDepositor(.nonFungible(nonFungibleGlobalID)): - .address(.nonFungibleGlobalID(nonFungibleGlobalID)) + .nonFungibleGlobalID(nonFungibleGlobalID) } } } diff --git a/RadixWallet/Features/AssetsFeature/Components/FungibleAssetList/Components/Details/FungibleTokenDetails+Reducer.swift b/RadixWallet/Features/AssetsFeature/Components/FungibleAssetList/Components/Details/FungibleTokenDetails+Reducer.swift index 5d03c04244..fe2f3ba112 100644 --- a/RadixWallet/Features/AssetsFeature/Components/FungibleAssetList/Components/Details/FungibleTokenDetails+Reducer.swift +++ b/RadixWallet/Features/AssetsFeature/Components/FungibleAssetList/Components/Details/FungibleTokenDetails+Reducer.swift @@ -9,6 +9,7 @@ public struct FungibleTokenDetails: Sendable, FeatureReducer { let isXRD: Bool let ownedFungibleResource: OnLedgerEntity.OwnedFungibleResource? let ledgerState: AtLedgerState? + var hideResource: HideResource.State public init( resourceAddress: ResourceAddress, @@ -22,6 +23,7 @@ public struct FungibleTokenDetails: Sendable, FeatureReducer { self.ownedFungibleResource = ownedFungibleResource self.isXRD = isXRD self.ledgerState = ledgerState + self.hideResource = .init(kind: .fungible(resourceAddress)) } } @@ -34,12 +36,25 @@ public struct FungibleTokenDetails: Sendable, FeatureReducer { case resourceLoadResult(TaskResult) } + @CasePathable + public enum ChildAction: Sendable, Equatable { + case hideResource(HideResource.Action) + } + @Dependency(\.onLedgerEntitiesClient) var onLedgerEntitiesClient @Dependency(\.errorQueue) var errorQueue @Dependency(\.dismiss) var dismiss public init() {} + public var body: some ReducerOf { + Scope(state: \.hideResource, action: \.child.hideResource) { + HideResource() + } + + Reduce(core) + } + public func reduce(into state: inout State, viewAction: ViewAction) -> Effect { switch viewAction { case .task: @@ -67,4 +82,13 @@ public struct FungibleTokenDetails: Sendable, FeatureReducer { return .none } } + + public func reduce(into state: inout State, childAction: ChildAction) -> Effect { + switch childAction { + case .hideResource(.delegate(.didHideResource)): + .run { _ in await dismiss() } + default: + .none + } + } } diff --git a/RadixWallet/Features/AssetsFeature/Components/FungibleAssetList/Components/Details/FungibleTokenDetails+View.swift b/RadixWallet/Features/AssetsFeature/Components/FungibleAssetList/Components/Details/FungibleTokenDetails+View.swift index c5c783a940..ff148832a8 100644 --- a/RadixWallet/Features/AssetsFeature/Components/FungibleAssetList/Components/Details/FungibleTokenDetails+View.swift +++ b/RadixWallet/Features/AssetsFeature/Components/FungibleAssetList/Components/Details/FungibleTokenDetails+View.swift @@ -61,8 +61,12 @@ extension FungibleTokenDetails { } thumbnailView: { Thumbnail(token: viewStore.thumbnail.wrappedValue ?? .other(nil), size: .veryLarge) } detailsView: { - AssetResourceDetailsSection(viewState: viewStore.details) - .padding(.bottom, .medium1) + VStack(spacing: .medium1) { + AssetResourceDetailsSection(viewState: viewStore.details) + + HideResource.View(store: store.hideResource) + } + .padding(.bottom, .medium1) } .task { @MainActor in await viewStore.send(.task).finish() @@ -71,3 +75,9 @@ extension FungibleTokenDetails { } } } + +private extension StoreOf { + var hideResource: StoreOf { + scope(state: \.hideResource, action: \.child.hideResource) + } +} diff --git a/RadixWallet/Features/AssetsFeature/Components/HelperViews/ResourceBalance/ResourceBalance.swift b/RadixWallet/Features/AssetsFeature/Components/HelperViews/ResourceBalance/ResourceBalance.swift index 24f6f411d4..d37ab0c966 100644 --- a/RadixWallet/Features/AssetsFeature/Components/HelperViews/ResourceBalance/ResourceBalance.swift +++ b/RadixWallet/Features/AssetsFeature/Components/HelperViews/ResourceBalance/ResourceBalance.swift @@ -11,9 +11,14 @@ public struct ResourceBalance: Sendable, Hashable { public let resource: OnLedgerEntity.Resource public var details: Details - public init(resource: OnLedgerEntity.Resource, details: Details) { + /// Indicates whether the resource is hidden in user's profile. + /// Value is optional since we won't check for cases that it doesn't matter. + public let isHidden: Bool? + + public init(resource: OnLedgerEntity.Resource, details: Details, isHidden: Bool? = nil) { self.resource = resource self.details = details + self.isHidden = isHidden } public enum Details: Sendable, Hashable { diff --git a/RadixWallet/Features/AssetsFeature/Components/HelperViews/ResourceBalance/ResourceBalanceButton.swift b/RadixWallet/Features/AssetsFeature/Components/HelperViews/ResourceBalance/ResourceBalanceButton.swift index b1998da7ba..9254e4fece 100644 --- a/RadixWallet/Features/AssetsFeature/Components/HelperViews/ResourceBalance/ResourceBalanceButton.swift +++ b/RadixWallet/Features/AssetsFeature/Components/HelperViews/ResourceBalance/ResourceBalanceButton.swift @@ -5,6 +5,7 @@ public struct ResourceBalanceButton: View { public let viewState: ResourceBalance.ViewState public let appearance: Appearance public let isSelected: Bool? + public let warning: String? public let onTap: () -> Void public enum Appearance { @@ -12,23 +13,34 @@ public struct ResourceBalanceButton: View { case transactionReview } - init(_ viewState: ResourceBalance.ViewState, appearance: Appearance, isSelected: Bool? = nil, onTap: @escaping () -> Void) { + init( + _ viewState: ResourceBalance.ViewState, + appearance: Appearance, + isSelected: Bool? = nil, + warning: String? = nil, + onTap: @escaping () -> Void + ) { self.viewState = viewState self.appearance = appearance self.isSelected = isSelected + self.warning = warning self.onTap = onTap } public var body: some View { - HStack(alignment: .center, spacing: .small2) { - Button(action: onTap) { + Button(action: onTap) { + VStack(alignment: .leading, spacing: .small2) { ResourceBalanceView(viewState, appearance: .standard, isSelected: isSelected) - .padding(.top, topPadding) - .padding(.bottom, bottomPadding) - .padding(.horizontal, horizontalSpacing) - .contentShape(Rectangle()) - .background(background) + + if let warning { + WarningErrorView(text: warning, type: .warning, useNarrowSpacing: true) + } } + .padding(.top, topPadding) + .padding(.horizontal, horizontalSpacing) + .padding(.bottom, bottomPadding) + .contentShape(Rectangle()) + .background(background) } } diff --git a/RadixWallet/Features/AssetsFeature/Components/HideResource/HIdeResource.swift b/RadixWallet/Features/AssetsFeature/Components/HideResource/HIdeResource.swift new file mode 100644 index 0000000000..6a5faf7e11 --- /dev/null +++ b/RadixWallet/Features/AssetsFeature/Components/HideResource/HIdeResource.swift @@ -0,0 +1,134 @@ +// MARK: - HideResource +@Reducer +public struct HideResource: Sendable, FeatureReducer { + @ObservableState + public struct State: Sendable, Hashable { + let kind: Kind + var shouldShow = true + + @Presents + var destination: Destination.State? = nil + + public init(kind: Kind) { + self.kind = kind + } + + public enum Kind: Hashable, Sendable { + case fungible(ResourceAddress) + case nonFungible(ResourceAddress, name: String?) + case poolUnit(PoolAddress) + } + + var resource: ResourceIdentifier { + switch kind { + case let .fungible(address): .fungible(address) + case let .nonFungible(address, _): .nonFungible(address) + case let .poolUnit(address): .poolUnit(address) + } + } + } + + public typealias Action = FeatureAction + + public enum InternalAction: Sendable, Equatable { + case setShouldShow(Bool) + } + + public enum ViewAction: Sendable, Equatable { + case task + case buttonTapped + } + + public enum DelegateAction: Sendable, Equatable { + case didHideResource + } + + // MARK: - Destination + public struct Destination: DestinationReducer { + @CasePathable + public enum State: Hashable, Sendable { + case confirmation + } + + @CasePathable + public enum Action: Equatable, Sendable { + case confirmation(ConfirmationAction) + } + + public var body: some ReducerOf { + EmptyReducer() + } + } + + @Dependency(\.appPreferencesClient) var appPreferencesClient + @Dependency(\.gatewaysClient) var gatewaysClient + + public var body: some ReducerOf { + Reduce(core) + .ifLet(destinationPath, action: /Action.destination) { + Destination() + } + } + + private let destinationPath: WritableKeyPath> = \.$destination + + public func reduce(into state: inout State, viewAction: ViewAction) -> Effect { + switch viewAction { + case .task: + return shouldShowEffect(state: state) + + case .buttonTapped: + state.destination = .confirmation + return .none + } + } + + public func reduce(into state: inout State, internalAction: InternalAction) -> Effect { + switch internalAction { + case let .setShouldShow(value): + state.shouldShow = value + return .none + } + } + + public func reduce(into state: inout State, presentedAction: Destination.Action) -> Effect { + switch presentedAction { + case .confirmation(.confirm): + state.destination = nil + return hideResourceEffect(state: state) + case .confirmation(.cancel): + state.destination = nil + return .none + } + } +} + +private extension HideResource { + func shouldShowEffect(state: State) -> Effect { + .run { send in + let isXrd: Bool + switch state.resource { + case let .fungible(resource): + let networkId = await gatewaysClient.getCurrentNetworkID() + isXrd = resource.isXRD(on: networkId) + case .nonFungible, .poolUnit: + isXrd = false + } + if isXrd { + await send(.internal(.setShouldShow(false))) + } else { + let isAlreadyHidden = await appPreferencesClient.isResourceHidden(state.resource) + await send(.internal(.setShouldShow(!isAlreadyHidden))) + } + } + } + + func hideResourceEffect(state: State) -> Effect { + .run { send in + try await appPreferencesClient.updating { preferences in + preferences.resources.hideResource(resource: state.resource) + } + await send(.delegate(.didHideResource)) + } + } +} diff --git a/RadixWallet/Features/AssetsFeature/Components/HideResource/HideResource+View.swift b/RadixWallet/Features/AssetsFeature/Components/HideResource/HideResource+View.swift new file mode 100644 index 0000000000..253f0b28ab --- /dev/null +++ b/RadixWallet/Features/AssetsFeature/Components/HideResource/HideResource+View.swift @@ -0,0 +1,86 @@ +// MARK: - HideResource.View +public extension HideResource { + struct View: SwiftUI.View { + private let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some SwiftUI.View { + WithPerceptionTracking { + if store.shouldShow { + VStack(spacing: .medium2) { + if store.showSeparator { + AssetDetailsSeparator() + } + + Button(store.title) { + store.send(.view(.buttonTapped)) + } + .buttonStyle(.secondaryRectangular(shouldExpand: true)) + .padding(.horizontal, .medium3) + } + } + } + .task { + await store.send(.view(.task)).finish() + } + .destination(store: store) + } + } +} + +private extension StoreOf { + var destination: PresentationStoreOf { + func scopeState(state: State) -> PresentationState { + state.$destination + } + return scope(state: scopeState, action: Action.destination) + } +} + +@MainActor +private extension View { + func destination(store: StoreOf) -> some View { + let destinationStore = store.destination + return confirmation(with: destinationStore, store: store) + } + + private func confirmation(with destinationStore: PresentationStoreOf, store: StoreOf) -> some View { + sheet(store: destinationStore.scope(state: \.confirmation, action: \.confirmation)) { _ in + ConfirmationView(kind: store.confirmationKind) { action in + store.send(.destination(.presented(.confirmation(action)))) + } + } + } +} + +private extension HideResource.State { + var title: String { + switch kind { + case .fungible, .poolUnit: + L10n.AssetDetails.hideAsset + case .nonFungible: + L10n.AssetDetails.hideCollection + } + } + + var showSeparator: Bool { + switch kind { + case .fungible, .poolUnit: + false + case .nonFungible: + true + } + } + + var confirmationKind: ConfirmationView.Kind { + switch kind { + case .fungible, .poolUnit: + .hideAsset + case let .nonFungible(_, name): + .hideCollection(name: name ?? "") + } + } +} diff --git a/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Details/NonFungibleTokenDetails+Reducer.swift b/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Details/NonFungibleTokenDetails+Reducer.swift index dfbfd5e120..59df23260e 100644 --- a/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Details/NonFungibleTokenDetails+Reducer.swift +++ b/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Details/NonFungibleTokenDetails+Reducer.swift @@ -4,13 +4,14 @@ import SwiftUI // MARK: - NonFungibleTokenDetails public struct NonFungibleTokenDetails: Sendable, FeatureReducer { public struct State: Sendable, Hashable { - public let resourceAddress: ResourceAddress - public var resourceDetails: Loadable - public let ownedResource: OnLedgerEntity.OwnedNonFungibleResource? - public let token: OnLedgerEntity.NonFungibleToken? - public let ledgerState: AtLedgerState - public let stakeClaim: OnLedgerEntitiesClient.StakeClaim? - public let isClaimStakeEnabled: Bool + let resourceAddress: ResourceAddress + var resourceDetails: Loadable + let ownedResource: OnLedgerEntity.OwnedNonFungibleResource? + let token: OnLedgerEntity.NonFungibleToken? + let ledgerState: AtLedgerState + let stakeClaim: OnLedgerEntitiesClient.StakeClaim? + let isClaimStakeEnabled: Bool + var hideResource: HideResource.State? public init( resourceAddress: ResourceAddress, @@ -28,6 +29,9 @@ public struct NonFungibleTokenDetails: Sendable, FeatureReducer { self.ledgerState = ledgerState self.stakeClaim = stakeClaim self.isClaimStakeEnabled = isClaimStakeEnabled + if stakeClaim == nil, case let .success(resource) = resourceDetails { + hideResource = .init(kind: .nonFungible(resourceAddress, name: resource.metadata.name)) + } } } @@ -45,11 +49,23 @@ public struct NonFungibleTokenDetails: Sendable, FeatureReducer { case tappedClaimStake(OnLedgerEntitiesClient.StakeClaim) } + @CasePathable + public enum ChildAction: Sendable, Equatable { + case hideResource(HideResource.Action) + } + @Dependency(\.onLedgerEntitiesClient) var onLedgerEntitiesClient @Dependency(\.dismiss) var dismiss public init() {} + public var body: some ReducerOf { + Reduce(core) + .ifLet(\.hideResource, action: \.child.hideResource) { + HideResource() + } + } + public func reduce(into state: inout State, viewAction: ViewAction) -> Effect { switch viewAction { case .task: @@ -77,10 +93,22 @@ public struct NonFungibleTokenDetails: Sendable, FeatureReducer { switch internalAction { case let .resourceLoadResult(.success(resource)): state.resourceDetails = .success(resource) + if state.stakeClaim == nil { + state.hideResource = .init(kind: .nonFungible(resource.resourceAddress, name: resource.metadata.name)) + } return .none case let .resourceLoadResult(.failure(err)): state.resourceDetails = .failure(err) return .none } } + + public func reduce(into state: inout State, childAction: ChildAction) -> Effect { + switch childAction { + case .hideResource(.delegate(.didHideResource)): + .run { _ in await dismiss() } + default: + .none + } + } } diff --git a/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Details/NonFungibleTokenDetails+View.swift b/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Details/NonFungibleTokenDetails+View.swift index 39309e3518..db76fb1264 100644 --- a/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Details/NonFungibleTokenDetails+View.swift +++ b/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Details/NonFungibleTokenDetails+View.swift @@ -119,6 +119,11 @@ extension NonFungibleTokenDetails { } AssetResourceDetailsSection(viewState: viewStore.resourceDetails) + + IfLetStore(store.scope(state: \.hideResource, action: \.child.hideResource)) { store in + HideResource.View(store: store) + .padding(.vertical, .medium1) + } } .padding(.vertical, .medium1) .background(.app.gray5, ignoresSafeAreaEdges: .bottom) diff --git a/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Row/NonFungbileAssetRow+Reducer.swift b/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Row/NonFungbileAssetRow+Reducer.swift index 3fc900817b..b64cce7bac 100644 --- a/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Row/NonFungbileAssetRow+Reducer.swift +++ b/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Row/NonFungbileAssetRow+Reducer.swift @@ -1,6 +1,7 @@ import ComposableArchitecture import SwiftUI +// MARK: - NonFungibleAssetList.Row extension NonFungibleAssetList { public struct Row: Sendable, FeatureReducer { public struct State: Sendable, Hashable, Identifiable { @@ -63,6 +64,7 @@ extension NonFungibleAssetList { } @Dependency(\.onLedgerEntitiesClient) var onLedgerEntitiesClient + @Dependency(\.appPreferencesClient) var appPreferencesClient @Dependency(\.errorQueue) var errorQueue public init() {} diff --git a/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Row/NonFungibleAssetRow+View.swift b/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Row/NonFungibleAssetRow+View.swift index 264e145d82..c79309a3c4 100644 --- a/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Row/NonFungibleAssetRow+View.swift +++ b/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/Components/Row/NonFungibleAssetRow+View.swift @@ -60,7 +60,7 @@ extension NonFungibleAssetList.Row.View { .textStyle(.secondaryHeader) } - Text("\(viewStore.resource.nonFungibleIdsCount)") + Text(L10n.Account.Nfts.itemsCount(viewStore.resource.nonFungibleIdsCount)) .font(.app.body2HighImportance) .foregroundColor(.app.gray2) } diff --git a/RadixWallet/Features/AssetsFeature/Components/PoolUnitsList/Components/PoolUnitDetails+View.swift b/RadixWallet/Features/AssetsFeature/Components/PoolUnitsList/Components/PoolUnitDetails+View.swift index 2b7d1ca577..acf686efc3 100644 --- a/RadixWallet/Features/AssetsFeature/Components/PoolUnitsList/Components/PoolUnitDetails+View.swift +++ b/RadixWallet/Features/AssetsFeature/Components/PoolUnitsList/Components/PoolUnitDetails+View.swift @@ -61,6 +61,8 @@ extension PoolUnitDetails { .padding(.horizontal, .large2) AssetResourceDetailsSection(viewState: viewStore.resourceDetails) + + HideResource.View(store: store.hideResource) } .padding(.bottom, .medium1) } @@ -68,3 +70,9 @@ extension PoolUnitDetails { } } } + +private extension StoreOf { + var hideResource: StoreOf { + scope(state: \.hideResource, action: \.child.hideResource) + } +} diff --git a/RadixWallet/Features/AssetsFeature/Components/PoolUnitsList/Components/PoolUnitDetails.swift b/RadixWallet/Features/AssetsFeature/Components/PoolUnitsList/Components/PoolUnitDetails.swift index ac6018bda2..3fe69a2ed4 100644 --- a/RadixWallet/Features/AssetsFeature/Components/PoolUnitsList/Components/PoolUnitDetails.swift +++ b/RadixWallet/Features/AssetsFeature/Components/PoolUnitsList/Components/PoolUnitDetails.swift @@ -5,6 +5,12 @@ import SwiftUI public struct PoolUnitDetails: Sendable, FeatureReducer { public struct State: Sendable, Hashable { let resourcesDetails: OnLedgerEntitiesClient.OwnedResourcePoolDetails + var hideResource: HideResource.State + + public init(resourcesDetails: OnLedgerEntitiesClient.OwnedResourcePoolDetails) { + self.resourcesDetails = resourcesDetails + self.hideResource = .init(kind: .poolUnit(resourcesDetails.address)) + } } @Dependency(\.dismiss) var dismiss @@ -13,6 +19,19 @@ public struct PoolUnitDetails: Sendable, FeatureReducer { case closeButtonTapped } + @CasePathable + public enum ChildAction: Sendable, Equatable { + case hideResource(HideResource.Action) + } + + public var body: some ReducerOf { + Scope(state: \.hideResource, action: \.child.hideResource) { + HideResource() + } + + Reduce(core) + } + public func reduce(into state: inout State, viewAction: ViewAction) -> Effect { switch viewAction { case .closeButtonTapped: @@ -21,4 +40,13 @@ public struct PoolUnitDetails: Sendable, FeatureReducer { } } } + + public func reduce(into state: inout State, childAction: ChildAction) -> Effect { + switch childAction { + case .hideResource(.delegate(.didHideResource)): + .run { _ in await dismiss() } + default: + .none + } + } } diff --git a/RadixWallet/Features/DappsAndPersonas/PersonaDetails/PersonaDetails.swift b/RadixWallet/Features/DappsAndPersonas/PersonaDetails/PersonaDetails.swift index 13691470be..e689c2aff4 100644 --- a/RadixWallet/Features/DappsAndPersonas/PersonaDetails/PersonaDetails.swift +++ b/RadixWallet/Features/DappsAndPersonas/PersonaDetails/PersonaDetails.swift @@ -249,7 +249,7 @@ public struct PersonaDetails: Sendable, FeatureReducer { return .none } return .run { send in - try await entitiesVisibilityClient.hide(persona: persona) + try await entitiesVisibilityClient.hidePersona(persona.id) overlayWindowClient.scheduleHUD(.personaHidden) await send(.delegate(.personaHidden)) } catch: { error, _ in diff --git a/RadixWallet/Features/SettingsFeature/Preferences/HiddenAssets/HiddenAssets+View.swift b/RadixWallet/Features/SettingsFeature/Preferences/HiddenAssets/HiddenAssets+View.swift new file mode 100644 index 0000000000..73fad87711 --- /dev/null +++ b/RadixWallet/Features/SettingsFeature/Preferences/HiddenAssets/HiddenAssets+View.swift @@ -0,0 +1,125 @@ +import SwiftUI + +// MARK: - HiddenAssets.View +extension HiddenAssets { + public struct View: SwiftUI.View { + public let store: StoreOf + + public var body: some SwiftUI.View { + WithPerceptionTracking { + ScrollView { + LazyVStack(alignment: .leading, spacing: .large3) { + Text(L10n.HiddenAssets.text) + .textStyle(.body1HighImportance) + .foregroundColor(.app.gray2) + + header(L10n.HiddenAssets.fungibles) + fungibles + + header(L10n.HiddenAssets.nonFungibles) + nonFungibles + + header(L10n.HiddenAssets.poolUnits) + poolUnits + } + .padding(.medium3) + } + .background(Color.app.gray5) + .radixToolbar(title: L10n.HiddenAssets.title) + .task { + store.send(.view(.task)) + } + .alert(store: store.scope(state: \.$destination.unhideAlert, action: \.destination.unhideAlert)) + } + } + + private func header(_ value: String) -> some SwiftUI.View { + Text(value) + .textStyle(.secondaryHeader) + .foregroundColor(.app.gray2) + } + + @ViewBuilder + private var fungibles: some SwiftUI.View { + if store.fungible.isEmpty { + emptyState + } else { + VStack(spacing: .medium3) { + ForEachStatic(store.fungible) { resource in + Card { + AssetRow( + name: resource.fungibleResourceName, + address: .resource(resource.resourceAddress), + type: .token(.other), + url: resource.metadata.iconURL, + accessory: { unhideButton(resource: .fungible(resource.resourceAddress)) } + ) + } + } + } + } + } + + @ViewBuilder + private var nonFungibles: some SwiftUI.View { + if store.nonFungible.isEmpty { + emptyState + } else { + VStack(spacing: .medium3) { + ForEachStatic(store.nonFungible) { resource in + Card { + AssetRow( + name: resource.metadata.name, + address: .resource(resource.resourceAddress), + type: .nft, + url: resource.metadata.iconURL, + accessory: { unhideButton(resource: .nonFungible(resource.resourceAddress)) } + ) + } + } + } + } + } + + @ViewBuilder + private var poolUnits: some SwiftUI.View { + if store.poolUnit.isEmpty { + emptyState + } else { + VStack(spacing: .medium3) { + ForEachStatic(store.poolUnit) { poolUnit in + Card { + AssetRow( + name: poolUnit.resource.fungibleResourceName, + address: .resourcePool(poolUnit.details.address), + type: .poolUnit, + url: poolUnit.resource.metadata.iconURL, + accessory: { unhideButton(resource: .poolUnit(poolUnit.details.address)) } + ) + } + } + } + } + } + + private func unhideButton(resource: ResourceIdentifier) -> some SwiftUI.View { + Button(L10n.HiddenAssets.unhide) { + store.send(.view(.unhideTapped(resource))) + } + .buttonStyle(.secondaryRectangular(shouldExpand: false)) + } + + private var emptyState: some SwiftUI.View { + ZStack { + AssetRow(name: "dummy", address: .resource(.mainnetXRD), type: .token(.other), url: nil, accessory: { unhideButton(resource: .fungible(.mainnetXRD)) }) + .hidden() + + Text(L10n.Common.none) + .textStyle(.secondaryHeader) + .foregroundColor(.app.gray2) + } + .background(Color.app.gray4) + .clipShape(RoundedRectangle(cornerRadius: .medium3)) + } + } +} diff --git a/RadixWallet/Features/SettingsFeature/Preferences/HiddenAssets/HiddenAssets.swift b/RadixWallet/Features/SettingsFeature/Preferences/HiddenAssets/HiddenAssets.swift new file mode 100644 index 0000000000..d255fe326c --- /dev/null +++ b/RadixWallet/Features/SettingsFeature/Preferences/HiddenAssets/HiddenAssets.swift @@ -0,0 +1,215 @@ +import ComposableArchitecture + +// MARK: - HiddenAssets +@Reducer +public struct HiddenAssets: Sendable, FeatureReducer { + @ObservableState + public struct State: Sendable, Hashable { + var fungible: [OnLedgerEntity.Resource] = [] + var nonFungible: [OnLedgerEntity.Resource] = [] + var poolUnit: [State.PoolUnitDetails] = [] + + @Presents + var destination: Destination.State? = nil + + public struct PoolUnitDetails: Sendable, Hashable { + let resource: OnLedgerEntity.Resource + let details: OnLedgerEntitiesClient.OwnedResourcePoolDetails + } + } + + public typealias Action = FeatureAction + + @CasePathable + public enum ViewAction: Sendable, Equatable { + case task + case unhideTapped(ResourceIdentifier) + } + + public enum InternalAction: Sendable, Equatable { + case loadResources([ResourceIdentifier]) + case setFungible([OnLedgerEntity.Resource]) + case setNonFungible([OnLedgerEntity.Resource]) + case setPoolUnit([State.PoolUnitDetails]) + case didUnhideResource(ResourceIdentifier) + } + + public struct Destination: DestinationReducer { + @CasePathable + public enum State: Sendable, Hashable { + case unhideAlert(AlertState) + } + + @CasePathable + public enum Action: Sendable, Equatable { + case unhideAlert(UnhideAlert) + + public enum UnhideAlert: Hashable, Sendable { + case confirmTapped(ResourceIdentifier) + case cancelTapped + } + } + + public var body: some ReducerOf { + EmptyReducer() + } + } + + @Dependency(\.appPreferencesClient) var appPreferencesClient + @Dependency(\.onLedgerEntitiesClient) var onLedgerEntitiesClient + @Dependency(\.accountsClient) var accountsClient + + public init() {} + + public var body: some ReducerOf { + Reduce(core) + } + + public func reduce(into state: inout State, viewAction: ViewAction) -> Effect { + switch viewAction { + case .task: + return .run { send in + let hiddenResources = await appPreferencesClient.getHiddenResources() + await send(.internal(.loadResources(hiddenResources))) + } + case let .unhideTapped(resource): + state.destination = .unhideAlert(.init( + title: { TextState(resource.unhideAlertTitle) }, + actions: { + ButtonState(role: .cancel, action: .cancelTapped) { + TextState(L10n.Common.cancel) + } + ButtonState(action: .confirmTapped(resource)) { + TextState(L10n.Common.confirm) + } + } + )) + return .none + } + } + + public func reduce(into state: inout State, internalAction: InternalAction) -> Effect { + switch internalAction { + case let .loadResources(hiddenResources): + return fungibleEffect(hiddenResources: hiddenResources) + .merge(with: nonFungibleEffect(hiddenResources: hiddenResources)) + .merge(with: poolUnitEffect(hiddenResources: hiddenResources)) + + case let .setFungible(values): + state.fungible = values + return .none + + case let .setNonFungible(values): + state.nonFungible = values + return .none + + case let .setPoolUnit(values): + state.poolUnit = values + return .none + + case let .didUnhideResource(resource): + switch resource { + case let .fungible(resourceAddress): + state.fungible.removeAll(where: { $0.resourceAddress == resourceAddress }) + case let .nonFungible(resourceAddress): + state.nonFungible.removeAll(where: { $0.resourceAddress == resourceAddress }) + case let .poolUnit(poolAddress): + state.poolUnit.removeAll(where: { $0.details.address == poolAddress }) + } + state.destination = nil + return .none + } + } + + public func reduce(into state: inout State, presentedAction: Destination.Action) -> Effect { + switch presentedAction { + case let .unhideAlert(action): + switch action { + case let .confirmTapped(resource): + return .run { send in + try await appPreferencesClient.updating { preferences in + preferences.resources.unhideResource(resource: resource) + } + await send(.internal(.didUnhideResource(resource)), animation: .default) + } + case .cancelTapped: + state.destination = nil + return .none + } + } + } + + private func fungibleEffect(hiddenResources: [ResourceIdentifier]) -> Effect { + .run { send in + let resources = try await onLedgerEntitiesClient.getEntities(addresses: hiddenResources.fungibleAddresses, metadataKeys: .resourceMetadataKeys).compactMap(\.resource) + await send(.internal(.setFungible(resources))) + } + } + + private func nonFungibleEffect(hiddenResources: [ResourceIdentifier]) -> Effect { + .run { send in + let resources = try await onLedgerEntitiesClient.getEntities(addresses: hiddenResources.nonFungibleAddresses, metadataKeys: .resourceMetadataKeys).compactMap(\.resource) + await send(.internal(.setNonFungible(resources))) + } + } + + private func poolUnitEffect(hiddenResources: [ResourceIdentifier]) -> Effect { + .run { send in + let resourcePools = try await onLedgerEntitiesClient.getEntities(addresses: hiddenResources.poolUnitAddresses, metadataKeys: .resourceMetadataKeys).compactMap(\.resourcePool) + let resources = try await resourcePools.parallelMap { + try await onLedgerEntitiesClient.getResource($0.poolUnitResourceAddress) + } + let poolUnitDetails = try await resources.parallelMap { resource in + if let details = try await onLedgerEntitiesClient.getPoolUnitDetails(resource, forAmount: .one) { + State.PoolUnitDetails(resource: resource, details: details) + } else { + nil + } + } + .compactMap { $0 } + await send(.internal(.setPoolUnit(poolUnitDetails))) + } + } +} + +// MARK: - Helpers + +private extension [ResourceIdentifier] { + var fungibleAddresses: [Address] { + compactMap { item in + guard case let .fungible(resourceAddress) = item else { + return nil + } + return resourceAddress.asGeneral + } + } + + var nonFungibleAddresses: [Address] { + compactMap { item in + guard case let .nonFungible(resourceAddress) = item else { + return nil + } + return resourceAddress.asGeneral + } + } + + var poolUnitAddresses: [Address] { + compactMap { item in + guard case let .poolUnit(poolAddress) = item else { + return nil + } + return poolAddress.asGeneral + } + } +} + +private extension ResourceIdentifier { + var unhideAlertTitle: String { + switch self { + case .fungible, .poolUnit: + L10n.HiddenAssets.UnhideConfirmation.asset + case .nonFungible: + L10n.HiddenAssets.UnhideConfirmation.collection + } + } +} diff --git a/RadixWallet/Features/SettingsFeature/Preferences/HiddenEntities/HiddenEntities+View.swift b/RadixWallet/Features/SettingsFeature/Preferences/HiddenEntities/HiddenEntities+View.swift new file mode 100644 index 0000000000..f43a3fe62e --- /dev/null +++ b/RadixWallet/Features/SettingsFeature/Preferences/HiddenEntities/HiddenEntities+View.swift @@ -0,0 +1,99 @@ +import SwiftUI + +// MARK: - HiddenEntities.View +extension HiddenEntities { + public struct View: SwiftUI.View { + public let store: StoreOf + + public var body: some SwiftUI.View { + WithPerceptionTracking { + ScrollView { + LazyVStack(alignment: .leading, spacing: .large3) { + Text(L10n.HiddenEntities.text) + .textStyle(.body1HighImportance) + .foregroundColor(.app.gray2) + + header(L10n.HiddenEntities.personas) + personas + + header(L10n.HiddenEntities.accounts) + accounts + } + .padding(.medium3) + } + .background(Color.app.gray5) + .radixToolbar(title: L10n.HiddenEntities.title) + .task { + store.send(.view(.task)) + } + .alert(store: store.scope(state: \.$destination.unhideAlert, action: \.destination.unhideAlert)) + } + } + + private func header(_ value: String) -> some SwiftUI.View { + Text(value) + .textStyle(.secondaryHeader) + .foregroundColor(.app.gray2) + } + + @ViewBuilder + private var personas: some SwiftUI.View { + if store.personas.isEmpty { + emptyState + } else { + VStack(spacing: .medium3) { + ForEachStatic(store.personas) { persona in + Card { + PlainListRow(viewState: .init( + rowCoreViewState: .init(context: .hiddenPersona, title: persona.displayName.value), + accessory: { unhideButton(action: .unhidePersonaTapped(persona.id)) }, + icon: { Thumbnail(.persona, url: nil) } + )) + } + } + } + } + } + + @ViewBuilder + private var accounts: some SwiftUI.View { + if store.accounts.isEmpty { + emptyState + } else { + VStack(spacing: .medium3) { + ForEachStatic(store.accounts) { account in + Card { + AccountCard(kind: .hiddenEntity, account: account) { + unhideButton(action: .unhideAccountTapped(account.id)) + } + } + } + } + } + } + + private func unhideButton(action: ViewAction) -> some SwiftUI.View { + Button(L10n.HiddenEntities.unhide) { + store.send(.view(action)) + } + .buttonStyle(.secondaryRectangular(shouldExpand: false)) + } + + private var emptyState: some SwiftUI.View { + ZStack { + PlainListRow(viewState: .init( + rowCoreViewState: .init(context: .hiddenPersona, title: "dummy"), + accessory: { unhideButton(action: .task) }, + icon: { Thumbnail(.persona, url: nil) } + )) + .hidden() + + Text(L10n.Common.none) + .textStyle(.secondaryHeader) + .foregroundColor(.app.gray2) + } + .background(Color.app.gray4) + .clipShape(RoundedRectangle(cornerRadius: .medium3)) + } + } +} diff --git a/RadixWallet/Features/SettingsFeature/Preferences/HiddenEntities/HiddenEntities.swift b/RadixWallet/Features/SettingsFeature/Preferences/HiddenEntities/HiddenEntities.swift new file mode 100644 index 0000000000..72e381b2a4 --- /dev/null +++ b/RadixWallet/Features/SettingsFeature/Preferences/HiddenEntities/HiddenEntities.swift @@ -0,0 +1,135 @@ +import ComposableArchitecture + +// MARK: - HiddenEntities +@Reducer +public struct HiddenEntities: Sendable, FeatureReducer { + @ObservableState + public struct State: Sendable, Hashable { + var personas: Personas = [] + var accounts: Accounts = [] + + @Presents + var destination: Destination.State? = nil + } + + public typealias Action = FeatureAction + + @CasePathable + public enum ViewAction: Sendable, Equatable { + case task + case unhidePersonaTapped(Persona.ID) + case unhideAccountTapped(Account.ID) + } + + public enum InternalAction: Sendable, Equatable { + case setEntities(EntitiesVisibilityClient.HiddenEntities) + case didUnhidePersona(Persona.ID) + case didUnhideAccount(Account.ID) + } + + public struct Destination: DestinationReducer { + @CasePathable + public enum State: Sendable, Hashable { + case unhideAlert(AlertState) + } + + @CasePathable + public enum Action: Sendable, Equatable { + case unhideAlert(UnhideAlert) + + public enum UnhideAlert: Hashable, Sendable { + case confirmPersonaTapped(Persona.ID) + case confirmAccountTapped(Account.ID) + case cancelTapped + } + } + + public var body: some ReducerOf { + EmptyReducer() + } + } + + @Dependency(\.entitiesVisibilityClient) var entitiesVisibilityClient + + public init() {} + + public var body: some ReducerOf { + Reduce(core) + } + + public func reduce(into state: inout State, viewAction: ViewAction) -> Effect { + switch viewAction { + case .task: + return .run { send in + let hiddenEntities = try await entitiesVisibilityClient.getHiddenEntities() + await send(.internal(.setEntities(hiddenEntities))) + } + case let .unhidePersonaTapped(personaId): + state.destination = .unhideAlert(.init( + title: { TextState(L10n.HiddenEntities.unhidePersonasConfirmation) }, + actions: { + ButtonState(role: .cancel, action: .cancelTapped) { + TextState(L10n.Common.cancel) + } + ButtonState(action: .confirmPersonaTapped(personaId)) { + TextState(L10n.Common.confirm) + } + } + )) + return .none + case let .unhideAccountTapped(accountId): + state.destination = .unhideAlert(.init( + title: { TextState(L10n.HiddenEntities.unhideAccountsConfirmation) }, + actions: { + ButtonState(role: .cancel, action: .cancelTapped) { + TextState(L10n.Common.cancel) + } + ButtonState(action: .confirmAccountTapped(accountId)) { + TextState(L10n.Common.confirm) + } + } + )) + return .none + } + } + + public func reduce(into state: inout State, internalAction: InternalAction) -> Effect { + switch internalAction { + case let .setEntities(hiddenEntities): + state.personas = hiddenEntities.personas + state.accounts = hiddenEntities.accounts + return .none + + case let .didUnhidePersona(personaId): + state.personas.remove(id: personaId) + state.destination = nil + return .none + + case let .didUnhideAccount(accountId): + state.accounts.remove(id: accountId) + state.destination = nil + return .none + } + } + + public func reduce(into state: inout State, presentedAction: Destination.Action) -> Effect { + switch presentedAction { + case let .unhideAlert(action): + switch action { + case let .confirmPersonaTapped(personaId): + return .run { send in + try await entitiesVisibilityClient.unhidePersona(personaId) + await send(.internal(.didUnhidePersona(personaId)), animation: .default) + } + case let .confirmAccountTapped(accountId): + return .run { send in + try await entitiesVisibilityClient.unhideAccount(accountId) + await send(.internal(.didUnhideAccount(accountId)), animation: .default) + } + case .cancelTapped: + state.destination = nil + return .none + } + } + } +} diff --git a/RadixWallet/Features/SettingsFeature/Preferences/Preferences+View.swift b/RadixWallet/Features/SettingsFeature/Preferences/Preferences+View.swift index 4d209c27ae..c8035cee4f 100644 --- a/RadixWallet/Features/SettingsFeature/Preferences/Preferences+View.swift +++ b/RadixWallet/Features/SettingsFeature/Preferences/Preferences+View.swift @@ -85,12 +85,19 @@ extension Preferences.View { icon: .asset(AssetResource.depositGuarantees), action: .depositGuaranteesButtonTapped ), + .header(L10n.Preferences.displayPreferences), .model( title: L10n.Preferences.HiddenEntities.title, subtitle: L10n.Preferences.HiddenEntities.subtitle, icon: .systemImage("eye.fill"), action: .hiddenEntitiesButtonTapped ), + .model( + title: L10n.Preferences.HiddenAssets.title, + subtitle: L10n.Preferences.HiddenAssets.subtitle, + icon: .systemImage("eye.fill"), + action: .hiddenAssetsButtonTapped + ), .header(L10n.Preferences.advancedPreferences), .model( title: L10n.Preferences.gateways, @@ -151,33 +158,31 @@ private extension View { let destinationStore = store.destination return depositGuarantees(with: destinationStore) .hiddenEntities(with: destinationStore) + .hiddenAssets(with: destinationStore) .gateways(with: destinationStore) } private func depositGuarantees(with destinationStore: PresentationStoreOf) -> some View { - navigationDestination( - store: destinationStore, - state: /Preferences.Destination.State.depositGuarantees, - action: Preferences.Destination.Action.depositGuarantees, - destination: { DefaultDepositGuarantees.View(store: $0) } - ) + navigationDestination(store: destinationStore.scope(state: \.depositGuarantees, action: \.depositGuarantees)) { + DefaultDepositGuarantees.View(store: $0) + } } private func hiddenEntities(with destinationStore: PresentationStoreOf) -> some View { - navigationDestination( - store: destinationStore, - state: /Preferences.Destination.State.hiddenEntities, - action: Preferences.Destination.Action.hiddenEntities, - destination: { AccountAndPersonaHiding.View(store: $0) } - ) + navigationDestination(store: destinationStore.scope(state: \.hiddenEntities, action: \.hiddenEntities)) { + HiddenEntities.View(store: $0) + } + } + + private func hiddenAssets(with destinationStore: PresentationStoreOf) -> some View { + navigationDestination(store: destinationStore.scope(state: \.hiddenAssets, action: \.hiddenAssets)) { + HiddenAssets.View(store: $0) + } } private func gateways(with destinationStore: PresentationStoreOf) -> some View { - navigationDestination( - store: destinationStore, - state: /Preferences.Destination.State.gateways, - action: Preferences.Destination.Action.gateways, - destination: { GatewaySettings.View(store: $0) } - ) + navigationDestination(store: destinationStore.scope(state: \.gateways, action: \.gateways)) { + GatewaySettings.View(store: $0) + } } } diff --git a/RadixWallet/Features/SettingsFeature/Preferences/Preferences.swift b/RadixWallet/Features/SettingsFeature/Preferences/Preferences.swift index 965dfd4961..0c57b48622 100644 --- a/RadixWallet/Features/SettingsFeature/Preferences/Preferences.swift +++ b/RadixWallet/Features/SettingsFeature/Preferences/Preferences.swift @@ -15,6 +15,7 @@ public struct Preferences: Sendable, FeatureReducer { case appeared case depositGuaranteesButtonTapped case hiddenEntitiesButtonTapped + case hiddenAssetsButtonTapped case gatewaysButtonTapped case developerModeToogled(Bool) case exportLogsButtonTapped @@ -26,26 +27,33 @@ public struct Preferences: Sendable, FeatureReducer { } public struct Destination: DestinationReducer { + @CasePathable public enum State: Sendable, Hashable { case depositGuarantees(DefaultDepositGuarantees.State) - case hiddenEntities(AccountAndPersonaHiding.State) + case hiddenEntities(HiddenEntities.State) + case hiddenAssets(HiddenAssets.State) case gateways(GatewaySettings.State) } + @CasePathable public enum Action: Sendable, Equatable { case depositGuarantees(DefaultDepositGuarantees.Action) - case hiddenEntities(AccountAndPersonaHiding.Action) + case hiddenEntities(HiddenEntities.Action) + case hiddenAssets(HiddenAssets.Action) case gateways(GatewaySettings.Action) } public var body: some ReducerOf { - Scope(state: /State.depositGuarantees, action: /Action.depositGuarantees) { + Scope(state: \.depositGuarantees, action: \.depositGuarantees) { DefaultDepositGuarantees() } - Scope(state: /State.hiddenEntities, action: /Action.hiddenEntities) { - AccountAndPersonaHiding() + Scope(state: \.hiddenEntities, action: \.hiddenEntities) { + HiddenEntities() } - Scope(state: /State.gateways, action: /Action.gateways) { + Scope(state: \.hiddenAssets, action: \.hiddenAssets) { + HiddenAssets() + } + Scope(state: \.gateways, action: \.gateways) { GatewaySettings() } } @@ -81,6 +89,10 @@ public struct Preferences: Sendable, FeatureReducer { state.destination = .hiddenEntities(.init()) return .none + case .hiddenAssetsButtonTapped: + state.destination = .hiddenAssets(.init()) + return .none + case .gatewaysButtonTapped: state.destination = .gateways(.init()) return .none diff --git a/RadixWallet/Features/TransactionReviewFeature/TransactionReview+Sections.swift b/RadixWallet/Features/TransactionReviewFeature/TransactionReview+Sections.swift index a98d352ecf..a275967bef 100644 --- a/RadixWallet/Features/TransactionReviewFeature/TransactionReview+Sections.swift +++ b/RadixWallet/Features/TransactionReviewFeature/TransactionReview+Sections.swift @@ -418,7 +418,7 @@ extension TransactionReview { guard !withdrawals.isEmpty else { return nil } let withdrawalAccounts = withdrawals.map { - TransactionReviewAccount.State(account: $0.key, transfers: $0.value) + TransactionReviewAccount.State(account: $0.key, transfers: $0.value, isDeposit: false) } .asIdentified() @@ -463,7 +463,7 @@ extension TransactionReview { let depositAccounts = deposits .filter { !$0.value.isEmpty } - .map { TransactionReviewAccount.State(account: $0.key, transfers: $0.value) } + .map { TransactionReviewAccount.State(account: $0.key, transfers: $0.value, isDeposit: true) } .asIdentified() guard !depositAccounts.isEmpty else { return nil } @@ -645,7 +645,7 @@ extension TransactionReview { guarantee: nil ) - return [.init(resource: resource, details: .fungible(details))] + return [.init(resource: resource, details: .fungible(details), isHidden: false)] } case let .nonFungible(_, indicator): diff --git a/RadixWallet/Features/TransactionReviewFeature/TransactionReviewAccount/TransactionReviewAccount+View.swift b/RadixWallet/Features/TransactionReviewFeature/TransactionReviewAccount/TransactionReviewAccount+View.swift index a6851a606d..ba7e72ab9b 100644 --- a/RadixWallet/Features/TransactionReviewFeature/TransactionReviewAccount/TransactionReviewAccount+View.swift +++ b/RadixWallet/Features/TransactionReviewFeature/TransactionReviewAccount/TransactionReviewAccount+View.swift @@ -47,7 +47,7 @@ extension TransactionReviewAccounts { extension TransactionReviewAccount.State { var viewState: TransactionReviewAccount.ViewState { - .init(account: account, transfers: transfers.elements, showApprovedMark: account.isApproved) + .init(account: account, transfers: transfers.elements, showApprovedMark: account.isApproved, isDeposit: isDeposit) } } @@ -57,6 +57,7 @@ extension TransactionReviewAccount { let account: TransactionReview.ReviewAccount let transfers: [TransactionReview.Transfer] // FIXME: GK use viewstate? let showApprovedMark: Bool + let isDeposit: Bool } @MainActor @@ -74,7 +75,7 @@ extension TransactionReviewAccount { VStack(spacing: .zero) { ForEach(viewStore.transfers) { transfer in - TransactionReviewResourceView(transfer: transfer.value) { token in + TransactionReviewResourceView(transfer: transfer.value, isDeposit: viewStore.isDeposit) { token in viewStore.send(.transferTapped(transfer.value, token)) } @@ -94,12 +95,13 @@ extension TransactionReviewAccount { // MARK: - TransactionReviewResourceView struct TransactionReviewResourceView: View { let transfer: ResourceBalance // FIXME: GK use viewstate + let isDeposit: Bool let onTap: (OnLedgerEntity.NonFungibleToken?) -> Void var body: some View { switch transfer.details { case .fungible, .nonFungible, .liquidStakeUnit, .poolUnit: - ResourceBalanceButton(transfer.viewState, appearance: .transactionReview) { + ResourceBalanceButton(transfer.viewState, appearance: .transactionReview, warning: warning) { onTap(nil) } case let .stakeClaimNFT(details): @@ -108,6 +110,13 @@ struct TransactionReviewResourceView: View { } } } + + private var warning: String? { + guard let isHidden = transfer.isHidden, isHidden else { + return nil + } + return isDeposit ? L10n.TransactionReview.HiddenAsset.deposit : L10n.TransactionReview.HiddenAsset.withdraw + } } extension [ResourceBalance.ViewState.Fungible] { // FIXME: GK use full diff --git a/RadixWallet/Features/TransactionReviewFeature/TransactionReviewAccount/TransactionReviewAccount.swift b/RadixWallet/Features/TransactionReviewFeature/TransactionReviewAccount/TransactionReviewAccount.swift index 9d2cd44c69..7c2aafdf2e 100644 --- a/RadixWallet/Features/TransactionReviewFeature/TransactionReviewAccount/TransactionReviewAccount.swift +++ b/RadixWallet/Features/TransactionReviewFeature/TransactionReviewAccount/TransactionReviewAccount.swift @@ -59,10 +59,12 @@ public struct TransactionReviewAccount: Sendable, FeatureReducer { public var id: AccountAddress { account.address } public let account: TransactionReview.ReviewAccount public var transfers: IdentifiedArrayOf + public let isDeposit: Bool - public init(account: TransactionReview.ReviewAccount, transfers: IdentifiedArrayOf) { + public init(account: TransactionReview.ReviewAccount, transfers: IdentifiedArrayOf, isDeposit: Bool) { self.account = account self.transfers = transfers + self.isDeposit = isDeposit } } diff --git a/RadixWallet/MIGRATE_TO_SARGON/Stage2/Atoms/Stage2MigrateToSargon+ProfileNetwork.swift b/RadixWallet/MIGRATE_TO_SARGON/Stage2/Atoms/Stage2MigrateToSargon+ProfileNetwork.swift index a3862a42ee..0325974ae7 100644 --- a/RadixWallet/MIGRATE_TO_SARGON/Stage2/Atoms/Stage2MigrateToSargon+ProfileNetwork.swift +++ b/RadixWallet/MIGRATE_TO_SARGON/Stage2/Atoms/Stage2MigrateToSargon+ProfileNetwork.swift @@ -55,19 +55,23 @@ extension ProfileNetwork { accounts = identified.elements } - public mutating func hideAccounts(ids idsOfAccountsToHide: Set) { + public mutating func hideAccount(id: Account.ID) { var identified = accounts.asIdentified() - for id in idsOfAccountsToHide { - identified[id: id]?.hide() - authorizedDapps.mutateAll { dapp in - dapp.referencesToAuthorizedPersonas.mutateAll { persona in - persona.sharedAccounts?.ids.removeAll(where: { $0 == id }) - } + identified[id: id]?.hide() + authorizedDapps.mutateAll { dapp in + dapp.referencesToAuthorizedPersonas.mutateAll { persona in + persona.sharedAccounts?.ids.removeAll(where: { $0 == id }) } } accounts = identified.elements } + public mutating func unhideAccount(id: Account.ID) { + var identifiedAccounts = accounts.asIdentified() + identifiedAccounts[id: id]?.unhide() + accounts = identifiedAccounts.elements + } + public func getPersonas() -> Personas { personas.asIdentified().nonHidden } @@ -102,19 +106,18 @@ extension ProfileNetwork { self.personas = identifiedPersonas.elements } - public mutating func hidePersonas(ids idsOfPersonaToHide: Set) { + public mutating func hidePersona(id: Persona.ID) { var identifiedPersonas = personas.asIdentified() var identifiedAuthorizedDapps = authorizedDapps.asIdentified() - for id in idsOfPersonaToHide { - /// Hide the personas themselves - identifiedPersonas[id: id]?.hide() - - /// Remove the persona reference on any authorized dapp - identifiedAuthorizedDapps.mutateAll { dapp in - var referencesToAuthorizedPersonas = dapp.referencesToAuthorizedPersonas.asIdentified() - referencesToAuthorizedPersonas.remove(id: id) - dapp.referencesToAuthorizedPersonas = referencesToAuthorizedPersonas.elements - } + + /// Hide the persona themself + identifiedPersonas[id: id]?.hide() + + /// Remove the persona reference on any authorized dapp + identifiedAuthorizedDapps.mutateAll { dapp in + var referencesToAuthorizedPersonas = dapp.referencesToAuthorizedPersonas.asIdentified() + referencesToAuthorizedPersonas.remove(id: id) + dapp.referencesToAuthorizedPersonas = referencesToAuthorizedPersonas.elements } self.personas = identifiedPersonas.elements @@ -123,12 +126,9 @@ extension ProfileNetwork { self.authorizedDapps = identifiedAuthorizedDapps.elements } - public mutating func unhideAllEntities() { - var identifiedAccounts = accounts.asIdentified() + public mutating func unhidePersona(id: Persona.ID) { var identifiedPersonas = personas.asIdentified() - identifiedAccounts.mutateAll { $0.unhide() } - identifiedPersonas.mutateAll { $0.unhide() } - accounts = identifiedAccounts.elements + identifiedPersonas[id: id]?.unhide() personas = identifiedPersonas.elements } diff --git a/RadixWalletTests/Features/AccountAndPersonaHidingTests/AccountAndPersonaHidingTests.swift b/RadixWalletTests/Features/AccountAndPersonaHidingTests/AccountAndPersonaHidingTests.swift deleted file mode 100644 index 39c3a9eb2e..0000000000 --- a/RadixWalletTests/Features/AccountAndPersonaHidingTests/AccountAndPersonaHidingTests.swift +++ /dev/null @@ -1,111 +0,0 @@ -@testable import Radix_Wallet_Dev -import Sargon -import XCTest - -// MARK: - AccountAndPersonaHidingTests -@MainActor -final class AccountAndPersonaHidingTests: TestCase { - func test_unhideAll_happyflow() async throws { - let counts = EntitiesVisibilityClient.HiddenEntityCounts(hiddenAccountsCount: 5, hiddenPersonasCount: 4) - - let store = TestStore( - initialState: AccountAndPersonaHiding.State(), - reducer: AccountAndPersonaHiding.init - ) - - store.dependencies.entitiesVisibilityClient.getHiddenEntityCounts = { - counts - } - - let unhideAllEntitiesExpectation = expectation(description: "Waiting to unhide all entities") - store.dependencies.entitiesVisibilityClient.unhideAllEntities = { - unhideAllEntitiesExpectation.fulfill() - } - - let scheduleCompletionHUD = ActorIsolated(nil) - store.dependencies.overlayWindowClient.scheduleHUD = { hud in - Task { - await scheduleCompletionHUD.setValue(hud) - } - } - - await store.send(.view(.task)) - - await store.receive(.internal(.hiddenEntityCountsLoaded(counts))) { - $0.hiddenEntityCounts = counts - } - - await store.send(.view(.unhideAllTapped)) { - $0.destination = .confirmUnhideAllAlert(.init( - title: .init(L10n.AppSettings.EntityHiding.unhideAllSection), - message: .init(L10n.AppSettings.EntityHiding.unhideAllConfirmation), - buttons: [ - .default(.init(L10n.Common.continue), action: .send(.confirmTapped)), - .cancel(.init(L10n.Common.cancel), action: .send(.cancelTapped)), - ] - )) - } - - await store.send(.destination(.presented(.confirmUnhideAllAlert(.confirmTapped)))) { - $0.destination = nil - } - - wait(for: [unhideAllEntitiesExpectation], timeout: 1.0) - - let scheduledCompletionHUD = await scheduleCompletionHUD.value - XCTAssertEqual(scheduledCompletionHUD, .updatedAccount) - - await store.receive(.internal(.didUnhideAllEntities)) { - $0.hiddenEntityCounts = .init(hiddenAccountsCount: 0, hiddenPersonasCount: 0) - } - } - - func test_unhideAll_failedToUnhide_stateRemainsUnchanged() async throws { - let counts = EntitiesVisibilityClient.HiddenEntityCounts(hiddenAccountsCount: 5, hiddenPersonasCount: 4) - - let store = TestStore( - initialState: AccountAndPersonaHiding.State(), - reducer: AccountAndPersonaHiding.init - ) - - store.dependencies.entitiesVisibilityClient.getHiddenEntityCounts = { - counts - } - - store.dependencies.entitiesVisibilityClient.unhideAllEntities = { - throw NSError.anyError - } - - let scheduleErrorExpectation = expectation(description: "Wait for Error is to bescheduled") - store.dependencies.errorQueue.schedule = { _ in - scheduleErrorExpectation.fulfill() - } - - await store.send(.view(.task)) - - await store.receive(.internal(.hiddenEntityCountsLoaded(counts))) { - $0.hiddenEntityCounts = counts - } - - await store.send(.view(.unhideAllTapped)) { - $0.destination = .confirmUnhideAllAlert(.init( - title: .init(L10n.AppSettings.EntityHiding.unhideAllSection), - message: .init(L10n.AppSettings.EntityHiding.unhideAllConfirmation), - buttons: [ - .default(.init(L10n.Common.continue), action: .send(.confirmTapped)), - .cancel(.init(L10n.Common.cancel), action: .send(.cancelTapped)), - ] - )) - } - - await store.send(.destination(.presented(.confirmUnhideAllAlert(.confirmTapped)))) { - $0.destination = nil - } - - wait(for: [scheduleErrorExpectation], timeout: 1.0) - } -} - -extension NSError { - static let anyError = NSError(domain: "test", code: 1) -} diff --git a/RadixWalletTests/Features/AccountPreferencesTests/AccountPreferencesTests.swift b/RadixWalletTests/Features/AccountPreferencesTests/AccountPreferencesTests.swift index f8df8a25ef..ba6e16a337 100644 --- a/RadixWalletTests/Features/AccountPreferencesTests/AccountPreferencesTests.swift +++ b/RadixWalletTests/Features/AccountPreferencesTests/AccountPreferencesTests.swift @@ -16,9 +16,9 @@ final class AccountPreferencesTests: TestCase { state.destination = .hideAccount } - let idsOfUpdatedAccounts = ActorIsolated?>(nil) - store.dependencies.entitiesVisibilityClient.hideAccounts = { accounts in - await idsOfUpdatedAccounts.setValue(accounts) + let idOfUpdatedAccount = ActorIsolated(nil) + store.dependencies.entitiesVisibilityClient.hideAccount = { account in + await idOfUpdatedAccount.setValue(account) } let scheduleCompletionHUD = ActorIsolated(nil) @@ -32,8 +32,8 @@ final class AccountPreferencesTests: TestCase { state.destination = nil } - let idsOfUpdatedAccounts_ = await idsOfUpdatedAccounts.value - XCTAssertEqual([account.id], idsOfUpdatedAccounts_) + let idOfUpdatedAccount_ = await idOfUpdatedAccount.value + XCTAssertEqual(account.id, idOfUpdatedAccount_) let scheduledCompletionHUD = await scheduleCompletionHUD.value XCTAssertEqual(scheduledCompletionHUD, .accountHidden) diff --git a/RadixWalletTests/ProfileTests/EntitiesHidingTests.swift b/RadixWalletTests/ProfileTests/EntitiesHidingTests.swift index 5a7f6d8291..e6f10c3787 100644 --- a/RadixWalletTests/ProfileTests/EntitiesHidingTests.swift +++ b/RadixWalletTests/ProfileTests/EntitiesHidingTests.swift @@ -141,10 +141,10 @@ final class EntitiesHidingTests: TestCase { extension ProfileNetwork { mutating func hide(account: Account) { - hideAccounts(ids: [account.id]) + hideAccount(id: account.id) } mutating func hide(persona: Persona) { - hidePersonas(ids: [persona.id]) + hidePersona(id: persona.id) } } diff --git a/RadixWalletTests/TestExtensions/TestUtils.swift b/RadixWalletTests/TestExtensions/TestUtils.swift index 060f804178..e0fc315e70 100644 --- a/RadixWalletTests/TestExtensions/TestUtils.swift +++ b/RadixWalletTests/TestExtensions/TestUtils.swift @@ -147,8 +147,8 @@ private func configureTestClients( ) { d.device.$name = "Test" d.device.$model = "Test" - d.entitiesVisibilityClient.hideAccounts = { _ in } - d.entitiesVisibilityClient.hidePersonas = { _ in } + d.entitiesVisibilityClient.hideAccount = { _ in } + d.entitiesVisibilityClient.hidePersona = { _ in } d.uuid = .incrementing d.date = .constant(Date(timeIntervalSince1970: 0)) d.mnemonicClient.generate = { _, _ in Mnemonic.sample }