diff --git a/Kukai Mobile.xcodeproj/project.pbxproj b/Kukai Mobile.xcodeproj/project.pbxproj index bee0c42d..9ea8e7b3 100644 --- a/Kukai Mobile.xcodeproj/project.pbxproj +++ b/Kukai Mobile.xcodeproj/project.pbxproj @@ -34,7 +34,7 @@ C008D4132A979B5B000B4503 /* AccountButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C008D4122A979B5B000B4503 /* AccountButtonCell.swift */; }; C008D4152A979D22000B4503 /* AccountReceiveAssetsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C008D4142A979D22000B4503 /* AccountReceiveAssetsCell.swift */; }; C008D4172A979D3A000B4503 /* AccountDiscoverCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C008D4162A979D3A000B4503 /* AccountDiscoverCell.swift */; }; - C009308F28CB3DC500763885 /* StakeHeadingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C009308E28CB3DC500763885 /* StakeHeadingCell.swift */; }; + C009308F28CB3DC500763885 /* ChooseBakerHeadingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C009308E28CB3DC500763885 /* ChooseBakerHeadingCell.swift */; }; C009CD832A1CD7C600CFB88C /* Data+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C009CD822A1CD7C600CFB88C /* Data+extensions.swift */; }; C009F88B28195B9C007EA8A8 /* AddressTypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C009F88A28195B9C007EA8A8 /* AddressTypeViewController.swift */; }; C009F88D28197F07007EA8A8 /* TezosDomainValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C009F88C28197F07007EA8A8 /* TezosDomainValidator.swift */; }; @@ -63,9 +63,7 @@ C0172A082A98EC6400163179 /* OnrampViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0172A072A98EC6400163179 /* OnrampViewController.swift */; }; C0172A0A2A98EE4D00163179 /* TitleSubtitleImageContainerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0172A092A98EE4D00163179 /* TitleSubtitleImageContainerCell.swift */; }; C01A633F297842C100278689 /* TokenDetailsChartCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A633E297842C100278689 /* TokenDetailsChartCell.xib */; }; - C01A6343297845D000278689 /* TokenDetailsBalanceAndBakerCell_baker.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A6342297845D000278689 /* TokenDetailsBalanceAndBakerCell_baker.xib */; }; - C01A63452978470000278689 /* TokenDetailsBalanceAndBakerCell_nobaker.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A6344297846FF00278689 /* TokenDetailsBalanceAndBakerCell_nobaker.xib */; }; - C01A63472978471000278689 /* TokenDetailsBalanceAndBakerCell_nostaking.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A63462978471000278689 /* TokenDetailsBalanceAndBakerCell_nostaking.xib */; }; + C01A6343297845D000278689 /* TokenDetailsBalanceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A6342297845D000278689 /* TokenDetailsBalanceCell.xib */; }; C01A6349297847AA00278689 /* TokenDetailsSendCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A6348297847AA00278689 /* TokenDetailsSendCell.xib */; }; C01A634B297847FB00278689 /* TokenDetailsStakingRewardsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A634A297847FB00278689 /* TokenDetailsStakingRewardsCell.xib */; }; C01A634D2978480700278689 /* TokenDetailsActivityHeaderCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A634C2978480700278689 /* TokenDetailsActivityHeaderCell.xib */; }; @@ -91,6 +89,7 @@ C025891F292D1154000D59F6 /* HiddenTokenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C025891E292D1154000D59F6 /* HiddenTokenCell.swift */; }; C0258921292D1166000D59F6 /* FavouriteTokenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0258920292D1166000D59F6 /* FavouriteTokenCell.swift */; }; C026747B2AB1D17100B3538E /* Test_09_SideMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C026747A2AB1D17100B3538E /* Test_09_SideMenu.swift */; }; + C02810422CFA0D9E005EA5E8 /* StakeOnboardingContainerNavController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02810412CFA0D9E005EA5E8 /* StakeOnboardingContainerNavController.swift */; }; C02914362A6589D900A8AF08 /* CreatePasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02914352A6589D900A8AF08 /* CreatePasscodeViewController.swift */; }; C02914382A6589EE00A8AF08 /* ConfirmPasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02914372A6589EE00A8AF08 /* ConfirmPasscodeViewController.swift */; }; C02A4FDA27DA421100F42DE4 /* Date+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02A4FD927DA421100F42DE4 /* Date+extensions.swift */; }; @@ -110,7 +109,7 @@ C031D3ED27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C031D3EC27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift */; }; C031D3EF27D114E300EABBE6 /* CollectiblesDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C031D3EE27D114E300EABBE6 /* CollectiblesDetailsViewController.swift */; }; C0321C7D2811792D0013E72B /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0321C7C2811792D0013E72B /* ThemeManager.swift */; }; - C034F2C32A6A913B000F4C4C /* ConfirmStakeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C034F2C22A6A913B000F4C4C /* ConfirmStakeViewController.swift */; }; + C034F2C32A6A913B000F4C4C /* ChooseBakerConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C034F2C22A6A913B000F4C4C /* ChooseBakerConfirmViewController.swift */; }; C034F2C52A6AD2E5000F4C4C /* BakerDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C034F2C42A6AD2E5000F4C4C /* BakerDetailsViewController.swift */; }; C03646262A30D4CD00F3F5C8 /* CollectibleDetailCreatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03646242A30D4CD00F3F5C8 /* CollectibleDetailCreatorCell.swift */; }; C03646272A30D4CD00F3F5C8 /* CollectibleDetailCreatorCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C03646252A30D4CD00F3F5C8 /* CollectibleDetailCreatorCell.xib */; }; @@ -146,11 +145,21 @@ C049E73E28819E2800887B64 /* DiscoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C049E73D28819E2800887B64 /* DiscoverViewController.swift */; }; C049E74028819E3800887B64 /* DiscoverViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C049E73F28819E3800887B64 /* DiscoverViewModel.swift */; }; C04B20A52930E1AF00C008CD /* ActivityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04B20A42930E1AF00C008CD /* ActivityService.swift */; }; + C04BA1EE2CF4CFAF00951249 /* LearnMoreSectionHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04BA1ED2CF4CFAF00951249 /* LearnMoreSectionHeaderCell.swift */; }; + C04BA1F02CF4CFBE00951249 /* LearnMoreItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04BA1EF2CF4CFBE00951249 /* LearnMoreItemCell.swift */; }; + C04BA1F22CF4D02A00951249 /* TokenDetailsLearnMoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04BA1F12CF4D02A00951249 /* TokenDetailsLearnMoreViewController.swift */; }; + C04BA1F42CF4D04A00951249 /* TokenDetailsLearnMoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04BA1F32CF4D04A00951249 /* TokenDetailsLearnMoreViewModel.swift */; }; + C04BA1F62CF765B900951249 /* StakeOnboardingContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04BA1F52CF765B900951249 /* StakeOnboardingContainerViewController.swift */; }; + C04BA1F82CF766EA00951249 /* PageIndicatorContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04BA1F72CF766DA00951249 /* PageIndicatorContainerView.swift */; }; + C04D98562CE64938009491BD /* TokenDetailsBakerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04D98542CE64938009491BD /* TokenDetailsBakerCell.swift */; }; + C04D98572CE64938009491BD /* TokenDetailsBakerCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C04D98552CE64938009491BD /* TokenDetailsBakerCell.xib */; }; + C04D985A2CE65884009491BD /* TokenDetailsStakeBalanceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04D98582CE65884009491BD /* TokenDetailsStakeBalanceCell.swift */; }; + C04D985B2CE65884009491BD /* TokenDetailsStakeBalanceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C04D98592CE65884009491BD /* TokenDetailsStakeBalanceCell.xib */; }; C04E0EB22ADE7EE7001BF56F /* UpdateWarningCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E0EB12ADE7EE7001BF56F /* UpdateWarningCell.swift */; }; C04E0EB42ADE9216001BF56F /* RequiredUpdateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E0EB32ADE9216001BF56F /* RequiredUpdateViewController.swift */; }; C04E0EB62ADE92CA001BF56F /* AppUpdateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E0EB52ADE92CA001BF56F /* AppUpdateService.swift */; }; C04E2860293F940E00DC4171 /* TokenDetailsChartCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E285F293F940E00DC4171 /* TokenDetailsChartCell.swift */; }; - C04E2864293F94C500DC4171 /* TokenDetailsBalanceAndBakerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2863293F94C500DC4171 /* TokenDetailsBalanceAndBakerCell.swift */; }; + C04E2864293F94C500DC4171 /* TokenDetailsBalanceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2863293F94C500DC4171 /* TokenDetailsBalanceCell.swift */; }; C04E2866293F94D700DC4171 /* TokenDetailsSendCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2865293F94D700DC4171 /* TokenDetailsSendCell.swift */; }; C04E2868293F94EA00DC4171 /* TokenDetailsStakingRewardsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2867293F94EA00DC4171 /* TokenDetailsStakingRewardsCell.swift */; }; C04E286A293F950900DC4171 /* TokenDetailsActivityHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2869293F950900DC4171 /* TokenDetailsActivityHeaderCell.swift */; }; @@ -319,8 +328,9 @@ C0BBF17E2C4149D000138C39 /* LookingForDevicesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BBF17D2C4149D000138C39 /* LookingForDevicesViewController.swift */; }; C0BF19A32938FB490044D942 /* TokenContractViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BF19A22938FB490044D942 /* TokenContractViewController.swift */; }; C0C6B5CD28CA25BD00368AEA /* PublicBakerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5CC28CA25BD00368AEA /* PublicBakerCell.swift */; }; - C0C6B5CF28CA27AF00368AEA /* StakeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5CE28CA27AF00368AEA /* StakeViewController.swift */; }; - C0C6B5D128CA2A6B00368AEA /* StakeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5D028CA2A6B00368AEA /* StakeViewModel.swift */; }; + C0C6B5CF28CA27AF00368AEA /* ChooseBakerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5CE28CA27AF00368AEA /* ChooseBakerViewController.swift */; }; + C0C6B5D128CA2A6B00368AEA /* ChooseBakerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5D028CA2A6B00368AEA /* ChooseBakerViewModel.swift */; }; + C0C6D9B32CF8C6F100ABB0A7 /* SlideSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6D9B22CF8C6F100ABB0A7 /* SlideSegue.swift */; }; C0C7A0FE2955B8CF00ADCA51 /* NoContactsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C7A0FD2955B8CF00ADCA51 /* NoContactsCell.swift */; }; C0C7DFB829BF34ED00F60E0C /* SideMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0C7DFB729BF34ED00F60E0C /* SideMenu.storyboard */; }; C0C7DFBA29BF37FF00F60E0C /* SideMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C7DFB929BF37FF00F60E0C /* SideMenuViewController.swift */; }; @@ -353,6 +363,12 @@ C0DAF38C29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DAF38A29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.swift */; }; C0DAF38D29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0DAF38B29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.xib */; }; C0DB48172785C6DD00D3B4F9 /* FadeSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DB48162785C6DD00D3B4F9 /* FadeSegue.swift */; }; + C0DB694B2CEE40D3000C1A17 /* StakeAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DB694A2CEE40D3000C1A17 /* StakeAmountViewController.swift */; }; + C0DB694D2CEF567A000C1A17 /* StakeConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DB694C2CEF567A000C1A17 /* StakeConfirmViewController.swift */; }; + C0DB69502CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DB694E2CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.swift */; }; + C0DB69512CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0DB694F2CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.xib */; }; + C0DB69542CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DB69522CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.swift */; }; + C0DB69552CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0DB69532CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.xib */; }; C0E2317D29897BB5007BC79D /* SendTokenConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E2317C29897BB5007BC79D /* SendTokenConfirmViewController.swift */; }; C0E44BAC2A41FDEE00C2A7C0 /* WatchWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E44BAB2A41FDEE00C2A7C0 /* WatchWalletViewController.swift */; }; C0E4EF3E294105E3007C69CA /* TokenDetailsMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E4EF3D294105E3007C69CA /* TokenDetailsMessageCell.swift */; }; @@ -460,7 +476,7 @@ C008D4122A979B5B000B4503 /* AccountButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountButtonCell.swift; sourceTree = ""; }; C008D4142A979D22000B4503 /* AccountReceiveAssetsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountReceiveAssetsCell.swift; sourceTree = ""; }; C008D4162A979D3A000B4503 /* AccountDiscoverCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDiscoverCell.swift; sourceTree = ""; }; - C009308E28CB3DC500763885 /* StakeHeadingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeHeadingCell.swift; sourceTree = ""; }; + C009308E28CB3DC500763885 /* ChooseBakerHeadingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseBakerHeadingCell.swift; sourceTree = ""; }; C009CD822A1CD7C600CFB88C /* Data+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+extensions.swift"; sourceTree = ""; }; C009F88A28195B9C007EA8A8 /* AddressTypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressTypeViewController.swift; sourceTree = ""; }; C009F88C28197F07007EA8A8 /* TezosDomainValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TezosDomainValidator.swift; sourceTree = ""; }; @@ -489,9 +505,7 @@ C0172A072A98EC6400163179 /* OnrampViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnrampViewController.swift; sourceTree = ""; }; C0172A092A98EE4D00163179 /* TitleSubtitleImageContainerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleSubtitleImageContainerCell.swift; sourceTree = ""; }; C01A633E297842C100278689 /* TokenDetailsChartCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsChartCell.xib; sourceTree = ""; }; - C01A6342297845D000278689 /* TokenDetailsBalanceAndBakerCell_baker.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsBalanceAndBakerCell_baker.xib; sourceTree = ""; }; - C01A6344297846FF00278689 /* TokenDetailsBalanceAndBakerCell_nobaker.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsBalanceAndBakerCell_nobaker.xib; sourceTree = ""; }; - C01A63462978471000278689 /* TokenDetailsBalanceAndBakerCell_nostaking.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsBalanceAndBakerCell_nostaking.xib; sourceTree = ""; }; + C01A6342297845D000278689 /* TokenDetailsBalanceCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsBalanceCell.xib; sourceTree = ""; }; C01A6348297847AA00278689 /* TokenDetailsSendCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsSendCell.xib; sourceTree = ""; }; C01A634A297847FB00278689 /* TokenDetailsStakingRewardsCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsStakingRewardsCell.xib; sourceTree = ""; }; C01A634C2978480700278689 /* TokenDetailsActivityHeaderCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsActivityHeaderCell.xib; sourceTree = ""; }; @@ -516,6 +530,7 @@ C025891E292D1154000D59F6 /* HiddenTokenCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenTokenCell.swift; sourceTree = ""; }; C0258920292D1166000D59F6 /* FavouriteTokenCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavouriteTokenCell.swift; sourceTree = ""; }; C026747A2AB1D17100B3538E /* Test_09_SideMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Test_09_SideMenu.swift; sourceTree = ""; }; + C02810412CFA0D9E005EA5E8 /* StakeOnboardingContainerNavController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeOnboardingContainerNavController.swift; sourceTree = ""; }; C02914352A6589D900A8AF08 /* CreatePasscodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePasscodeViewController.swift; sourceTree = ""; }; C02914372A6589EE00A8AF08 /* ConfirmPasscodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmPasscodeViewController.swift; sourceTree = ""; }; C02A4FD927DA421100F42DE4 /* Date+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+extensions.swift"; sourceTree = ""; }; @@ -535,7 +550,7 @@ C031D3EC27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectiblesDetailsViewModel.swift; sourceTree = ""; }; C031D3EE27D114E300EABBE6 /* CollectiblesDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectiblesDetailsViewController.swift; sourceTree = ""; }; C0321C7C2811792D0013E72B /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; - C034F2C22A6A913B000F4C4C /* ConfirmStakeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmStakeViewController.swift; sourceTree = ""; }; + C034F2C22A6A913B000F4C4C /* ChooseBakerConfirmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseBakerConfirmViewController.swift; sourceTree = ""; }; C034F2C42A6AD2E5000F4C4C /* BakerDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BakerDetailsViewController.swift; sourceTree = ""; }; C03646242A30D4CD00F3F5C8 /* CollectibleDetailCreatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectibleDetailCreatorCell.swift; sourceTree = ""; }; C03646252A30D4CD00F3F5C8 /* CollectibleDetailCreatorCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CollectibleDetailCreatorCell.xib; sourceTree = ""; }; @@ -570,11 +585,21 @@ C049E73D28819E2800887B64 /* DiscoverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverViewController.swift; sourceTree = ""; }; C049E73F28819E3800887B64 /* DiscoverViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverViewModel.swift; sourceTree = ""; }; C04B20A42930E1AF00C008CD /* ActivityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityService.swift; sourceTree = ""; }; + C04BA1ED2CF4CFAF00951249 /* LearnMoreSectionHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearnMoreSectionHeaderCell.swift; sourceTree = ""; }; + C04BA1EF2CF4CFBE00951249 /* LearnMoreItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearnMoreItemCell.swift; sourceTree = ""; }; + C04BA1F12CF4D02A00951249 /* TokenDetailsLearnMoreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsLearnMoreViewController.swift; sourceTree = ""; }; + C04BA1F32CF4D04A00951249 /* TokenDetailsLearnMoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsLearnMoreViewModel.swift; sourceTree = ""; }; + C04BA1F52CF765B900951249 /* StakeOnboardingContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeOnboardingContainerViewController.swift; sourceTree = ""; }; + C04BA1F72CF766DA00951249 /* PageIndicatorContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageIndicatorContainerView.swift; sourceTree = ""; }; + C04D98542CE64938009491BD /* TokenDetailsBakerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsBakerCell.swift; sourceTree = ""; }; + C04D98552CE64938009491BD /* TokenDetailsBakerCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsBakerCell.xib; sourceTree = ""; }; + C04D98582CE65884009491BD /* TokenDetailsStakeBalanceCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsStakeBalanceCell.swift; sourceTree = ""; }; + C04D98592CE65884009491BD /* TokenDetailsStakeBalanceCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsStakeBalanceCell.xib; sourceTree = ""; }; C04E0EB12ADE7EE7001BF56F /* UpdateWarningCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateWarningCell.swift; sourceTree = ""; }; C04E0EB32ADE9216001BF56F /* RequiredUpdateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequiredUpdateViewController.swift; sourceTree = ""; }; C04E0EB52ADE92CA001BF56F /* AppUpdateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateService.swift; sourceTree = ""; }; C04E285F293F940E00DC4171 /* TokenDetailsChartCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsChartCell.swift; sourceTree = ""; }; - C04E2863293F94C500DC4171 /* TokenDetailsBalanceAndBakerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsBalanceAndBakerCell.swift; sourceTree = ""; }; + C04E2863293F94C500DC4171 /* TokenDetailsBalanceCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsBalanceCell.swift; sourceTree = ""; }; C04E2865293F94D700DC4171 /* TokenDetailsSendCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsSendCell.swift; sourceTree = ""; }; C04E2867293F94EA00DC4171 /* TokenDetailsStakingRewardsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsStakingRewardsCell.swift; sourceTree = ""; }; C04E2869293F950900DC4171 /* TokenDetailsActivityHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsActivityHeaderCell.swift; sourceTree = ""; }; @@ -752,8 +777,9 @@ C0BBF17D2C4149D000138C39 /* LookingForDevicesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LookingForDevicesViewController.swift; sourceTree = ""; }; C0BF19A22938FB490044D942 /* TokenContractViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenContractViewController.swift; sourceTree = ""; }; C0C6B5CC28CA25BD00368AEA /* PublicBakerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicBakerCell.swift; sourceTree = ""; }; - C0C6B5CE28CA27AF00368AEA /* StakeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeViewController.swift; sourceTree = ""; }; - C0C6B5D028CA2A6B00368AEA /* StakeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeViewModel.swift; sourceTree = ""; }; + C0C6B5CE28CA27AF00368AEA /* ChooseBakerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseBakerViewController.swift; sourceTree = ""; }; + C0C6B5D028CA2A6B00368AEA /* ChooseBakerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseBakerViewModel.swift; sourceTree = ""; }; + C0C6D9B22CF8C6F100ABB0A7 /* SlideSegue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideSegue.swift; sourceTree = ""; }; C0C7A0FD2955B8CF00ADCA51 /* NoContactsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoContactsCell.swift; sourceTree = ""; }; C0C7DFB729BF34ED00F60E0C /* SideMenu.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SideMenu.storyboard; sourceTree = ""; }; C0C7DFB929BF37FF00F60E0C /* SideMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuViewController.swift; sourceTree = ""; }; @@ -785,6 +811,12 @@ C0DAF38A29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostnetWarningCollectionViewCell.swift; sourceTree = ""; }; C0DAF38B29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GhostnetWarningCollectionViewCell.xib; sourceTree = ""; }; C0DB48162785C6DD00D3B4F9 /* FadeSegue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadeSegue.swift; sourceTree = ""; }; + C0DB694A2CEE40D3000C1A17 /* StakeAmountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeAmountViewController.swift; sourceTree = ""; }; + C0DB694C2CEF567A000C1A17 /* StakeConfirmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeConfirmViewController.swift; sourceTree = ""; }; + C0DB694E2CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsSmallHeadingCell.swift; sourceTree = ""; }; + C0DB694F2CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsSmallHeadingCell.xib; sourceTree = ""; }; + C0DB69522CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsPendingUnstakeCell.swift; sourceTree = ""; }; + C0DB69532CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsPendingUnstakeCell.xib; sourceTree = ""; }; C0E2317C29897BB5007BC79D /* SendTokenConfirmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTokenConfirmViewController.swift; sourceTree = ""; }; C0E44BAB2A41FDEE00C2A7C0 /* WatchWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchWalletViewController.swift; sourceTree = ""; }; C0E4EF3D294105E3007C69CA /* TokenDetailsMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsMessageCell.swift; sourceTree = ""; }; @@ -1207,6 +1239,8 @@ C0FE432D2AD6D44400312940 /* CustomAVPlayerViewController.swift */, C057E3872BE2D03700189234 /* CustomNavigationController.swift */, C0CA53AC2C4FE123003AACFF /* GradientView.swift */, + C04BA1F72CF766DA00951249 /* PageIndicatorContainerView.swift */, + C0C6D9B22CF8C6F100ABB0A7 /* SlideSegue.swift */, ); path = Controls; sourceTree = ""; @@ -1503,7 +1537,7 @@ C0C6B5C728CA257500368AEA /* Cells */ = { isa = PBXGroup; children = ( - C009308E28CB3DC500763885 /* StakeHeadingCell.swift */, + C009308E28CB3DC500763885 /* ChooseBakerHeadingCell.swift */, C0C6B5CC28CA25BD00368AEA /* PublicBakerCell.swift */, ); path = Cells; @@ -1598,11 +1632,15 @@ children = ( C0C6B5C728CA257500368AEA /* Cells */, C0F91C6928BFB3200081E8E8 /* Stake.storyboard */, - C0C6B5CE28CA27AF00368AEA /* StakeViewController.swift */, - C0C6B5D028CA2A6B00368AEA /* StakeViewModel.swift */, + C0C6B5CE28CA27AF00368AEA /* ChooseBakerViewController.swift */, + C0C6B5D028CA2A6B00368AEA /* ChooseBakerViewModel.swift */, C0717B1D2A697D3D007F9419 /* EnterCustomBakerViewController.swift */, - C034F2C22A6A913B000F4C4C /* ConfirmStakeViewController.swift */, + C034F2C22A6A913B000F4C4C /* ChooseBakerConfirmViewController.swift */, C034F2C42A6AD2E5000F4C4C /* BakerDetailsViewController.swift */, + C0DB694A2CEE40D3000C1A17 /* StakeAmountViewController.swift */, + C0DB694C2CEF567A000C1A17 /* StakeConfirmViewController.swift */, + C04BA1F52CF765B900951249 /* StakeOnboardingContainerViewController.swift */, + C02810412CFA0D9E005EA5E8 /* StakeOnboardingContainerNavController.swift */, ); path = Stake; sourceTree = ""; @@ -1625,6 +1663,8 @@ C0FBC890292C12E200B29921 /* FavouriteBalancesViewController.swift */, C0FBC894292C163600B29921 /* FavouriteBalancesViewModel.swift */, C0BF19A22938FB490044D942 /* TokenContractViewController.swift */, + C04BA1F12CF4D02A00951249 /* TokenDetailsLearnMoreViewController.swift */, + C04BA1F32CF4D04A00951249 /* TokenDetailsLearnMoreViewModel.swift */, ); path = Account; sourceTree = ""; @@ -1643,12 +1683,18 @@ C090A6722B0BA1C000F50C76 /* TokenDetailsHeaderCell.swift */, C04E285F293F940E00DC4171 /* TokenDetailsChartCell.swift */, C01A633E297842C100278689 /* TokenDetailsChartCell.xib */, - C04E2863293F94C500DC4171 /* TokenDetailsBalanceAndBakerCell.swift */, - C01A6342297845D000278689 /* TokenDetailsBalanceAndBakerCell_baker.xib */, - C01A6344297846FF00278689 /* TokenDetailsBalanceAndBakerCell_nobaker.xib */, - C01A63462978471000278689 /* TokenDetailsBalanceAndBakerCell_nostaking.xib */, + C04E2863293F94C500DC4171 /* TokenDetailsBalanceCell.swift */, + C01A6342297845D000278689 /* TokenDetailsBalanceCell.xib */, C04E2865293F94D700DC4171 /* TokenDetailsSendCell.swift */, C01A6348297847AA00278689 /* TokenDetailsSendCell.xib */, + C04D98542CE64938009491BD /* TokenDetailsBakerCell.swift */, + C04D98552CE64938009491BD /* TokenDetailsBakerCell.xib */, + C04D98582CE65884009491BD /* TokenDetailsStakeBalanceCell.swift */, + C04D98592CE65884009491BD /* TokenDetailsStakeBalanceCell.xib */, + C0DB694E2CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.swift */, + C0DB694F2CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.xib */, + C0DB69522CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.swift */, + C0DB69532CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.xib */, C04E2867293F94EA00DC4171 /* TokenDetailsStakingRewardsCell.swift */, C01A634A297847FB00278689 /* TokenDetailsStakingRewardsCell.xib */, C04E2869293F950900DC4171 /* TokenDetailsActivityHeaderCell.swift */, @@ -1661,6 +1707,8 @@ C008D4122A979B5B000B4503 /* AccountButtonCell.swift */, C008D4142A979D22000B4503 /* AccountReceiveAssetsCell.swift */, C008D4162A979D3A000B4503 /* AccountDiscoverCell.swift */, + C04BA1ED2CF4CFAF00951249 /* LearnMoreSectionHeaderCell.swift */, + C04BA1EF2CF4CFBE00951249 /* LearnMoreItemCell.swift */, ); path = Cells; sourceTree = ""; @@ -1738,7 +1786,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1250; - LastUpgradeCheck = 1600; + LastUpgradeCheck = 1610; TargetAttributes = { C0B0363E269EE2070071ACD0 = { CreatedOnToolsVersion = 12.5; @@ -1802,12 +1850,12 @@ C0BACA512A0BA82E00FADE47 /* CollectiblesCollectionSinglePageCell.xib in Resources */, C054BCA22A680058006BDFBB /* RecoveryPhrase.storyboard in Resources */, C01372B329B206FF0083E297 /* MenuHeaderCell.xib in Resources */, + C04D98572CE64938009491BD /* TokenDetailsBakerCell.xib in Resources */, C0B372882B0CC13500159266 /* CollectibleDetailQuantityCell.xib in Resources */, C08C973D2908165D00249959 /* CollectibleDetailAttributeItemCell.xib in Resources */, C0EA19DF29096E5900E6B40D /* CollectibleDetailAttributeHeaderCell.xib in Resources */, C03708B72A604186002170BF /* MessageCollectionViewCell.xib in Resources */, C01A634F2978481100278689 /* ActivityItemCell.xib in Resources */, - C01A63472978471000278689 /* TokenDetailsBalanceAndBakerCell_nostaking.xib in Resources */, C036462B2A30D4EA00F3F5C8 /* CollectibleDetailPricesCell.xib in Resources */, C0FB90452B8375D70032C8CE /* EmptyCollectionCell.xib in Resources */, C0FBC885292BCEEF00B29921 /* Figtree-Regular.ttf in Resources */, @@ -1820,8 +1868,9 @@ C0C7DFB829BF34ED00F60E0C /* SideMenu.storyboard in Resources */, C0EA19C129096D9400E6B40D /* CollectibleDetailImageCell.xib in Resources */, C008B9BB2965D2CA00B17D96 /* CollectibleDetailAVCell.xib in Resources */, + C0DB69512CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.xib in Resources */, C05B0A302A03F9E3005AA803 /* CollectiblesCollectionHeaderSmallCell.xib in Resources */, - C01A63452978470000278689 /* TokenDetailsBalanceAndBakerCell_nobaker.xib in Resources */, + C04D985B2CE65884009491BD /* TokenDetailsStakeBalanceCell.xib in Resources */, C05B0A382A03FC6D005AA803 /* CollectiblesCollectionItemLargeWithTextCell.xib in Resources */, C01A6355297848B200278689 /* TokenDetailsActivityHeaderCell_footer.xib in Resources */, C01372AF29B206E00083E297 /* MenuChoiceCell.xib in Resources */, @@ -1840,6 +1889,7 @@ C0D6A9342955C231002BE8DA /* SendAddressType.storyboard in Resources */, C050365D271ED8E600E7A664 /* Onboarding.storyboard in Resources */, C000FA532A02A08600A59861 /* CollectiblesCollectionCell.xib in Resources */, + C0DB69552CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.xib in Resources */, C01A63512978481C00278689 /* TokenDetailsLoadingCell.xib in Resources */, C0239B9A2886B92500E0C973 /* Defi.storyboard in Resources */, C0DAF38929F13F6C006F05A9 /* GhostnetWarningCell.xib in Resources */, @@ -1848,7 +1898,7 @@ C06BC53E2A5EE41B00A0D979 /* SearchResultCell.xib in Resources */, C0DAF38D29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.xib in Resources */, C0411B4F29DEC2A500778E1E /* TextFieldSuggestionAccessoryViewCell.xib in Resources */, - C01A6343297845D000278689 /* TokenDetailsBalanceAndBakerCell_baker.xib in Resources */, + C01A6343297845D000278689 /* TokenDetailsBalanceCell.xib in Resources */, C03BF88E2A279836003BD343 /* ActivityItemBatchCell.xib in Resources */, C0678DDB27205FE300DEF1CB /* InfoPlist.strings in Resources */, C0F91C6A28BFB3200081E8E8 /* Stake.storyboard in Resources */, @@ -1891,6 +1941,7 @@ C0FA74D82A4AF79300CA845B /* LoadingGroupModeCell.swift in Sources */, C0638AAC2B9B2A22009AA870 /* JailbreakWarningViewController.swift in Sources */, C0FB90442B8375D70032C8CE /* EmptyCollectionCell.swift in Sources */, + C0DB694B2CEE40D3000C1A17 /* StakeAmountViewController.swift in Sources */, C0172A042A98AD5400163179 /* UITableViewCellButtonDelegate.swift in Sources */, C04E0EB42ADE9216001BF56F /* RequiredUpdateViewController.swift in Sources */, C0239BA12886C5C600E0C973 /* DefiViewModel.swift in Sources */, @@ -1898,7 +1949,7 @@ C090A6732B0BA1C000F50C76 /* TokenDetailsHeaderCell.swift in Sources */, C0B372872B0CC13500159266 /* CollectibleDetailQuantityCell.swift in Sources */, C0BB8BA1293F5E6200C0E1DD /* CustomisableButton.swift in Sources */, - C009308F28CB3DC500763885 /* StakeHeadingCell.swift in Sources */, + C009308F28CB3DC500763885 /* ChooseBakerHeadingCell.swift in Sources */, C06E0D5F287C3E02007A580B /* WalletConnectViewController.swift in Sources */, C00D6EB42A2E1B490060812A /* ThemeChoiceCell.swift in Sources */, C0081FD627D8FE2300F7FEFF /* ActivityViewController.swift in Sources */, @@ -1913,6 +1964,7 @@ C08694E227BD1BB1000A4909 /* DiscoverCell.swift in Sources */, C09860AA27C3C24E00F888AF /* SendToViewController.swift in Sources */, C06EA1A626A56C6E006029CF /* OnboardingPageViewController.swift in Sources */, + C04BA1F62CF765B900951249 /* StakeOnboardingContainerViewController.swift in Sources */, C06683FF296726C000FBAA7B /* CMTime+extensions.swift in Sources */, C082BBC728941F2B002B1E42 /* RemoveLiquidityViewController.swift in Sources */, C0742C53297ADC8E005D6DB0 /* MenuViewController.swift in Sources */, @@ -1937,6 +1989,7 @@ C0C7DFBF29BF70DD00F60E0C /* SideMenuViewModel.swift in Sources */, C04E2860293F940E00DC4171 /* TokenDetailsChartCell.swift in Sources */, C064D4D32B87B13E008F5947 /* SideMenuLookupViewController.swift in Sources */, + C0DB69542CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.swift in Sources */, C06C529B29FC00C30073A2FE /* RenameWalletGroupdViewController.swift in Sources */, C08694EA27BD5F7D000A4909 /* CoinGeckoService.swift in Sources */, C04B20A52930E1AF00C008CD /* ActivityService.swift in Sources */, @@ -1987,8 +2040,9 @@ C02A4FDA27DA421100F42DE4 /* Date+extensions.swift in Sources */, C0248CEE2AA21B5900B1F63C /* String+extensions_shared.swift in Sources */, C003CECD27FDED5D00F64B4C /* CKRecord+extensions.swift in Sources */, + C04D985A2CE65884009491BD /* TokenDetailsStakeBalanceCell.swift in Sources */, C06AEDFD2C00997E005CFDAA /* AddAccountViewController.swift in Sources */, - C04E2864293F94C500DC4171 /* TokenDetailsBalanceAndBakerCell.swift in Sources */, + C04E2864293F94C500DC4171 /* TokenDetailsBalanceCell.swift in Sources */, C044E1312AAF4CB40085652F /* SideMenuSettingsViewModel.swift in Sources */, C08FE23A2AD4343500327BF9 /* BackupViewController.swift in Sources */, C001D5A82A027EA10089EC7A /* CollectiblesFavouritesViewController.swift in Sources */, @@ -1996,6 +2050,7 @@ C01AE5DC28A26C44008F93E8 /* AddLiquidityConfirmViewController.swift in Sources */, C0B8C8A129DC2AD200C8AFD5 /* FaceIdViewController.swift in Sources */, C073C0DE29E6CADC0064FBEF /* LoadingContainerCell.swift in Sources */, + C02810422CFA0D9E005EA5E8 /* StakeOnboardingContainerNavController.swift in Sources */, C0411B4729DEBFAD00778E1E /* TextFieldSuggestionAccessoryView.swift in Sources */, C0B03643269EE2070071ACD0 /* AppDelegate.swift in Sources */, C022DA462B69433400F61CD1 /* SendBatchDetailsViewController.swift in Sources */, @@ -2007,6 +2062,7 @@ C05B0A332A03FAC9005AA803 /* CollectiblesCollectionHeaderMediumCell.swift in Sources */, C065AF0F2AB09C270061CC64 /* SideMenuBackupViewController.swift in Sources */, C025891D292CD985000D59F6 /* TokenStateService.swift in Sources */, + C0DB69502CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.swift in Sources */, C05848A8280EF5CC007933CA /* ThemePickerViewController.swift in Sources */, C0BF19A32938FB490044D942 /* TokenContractViewController.swift in Sources */, C0CA53AF2C5028DD003AACFF /* SideMenuCell.swift in Sources */, @@ -2023,6 +2079,7 @@ C0E6BCBB2A332FE900F86577 /* BalanceService.swift in Sources */, C0FBC895292C163600B29921 /* FavouriteBalancesViewModel.swift in Sources */, C034F2C52A6AD2E5000F4C4C /* BakerDetailsViewController.swift in Sources */, + C04BA1F22CF4D02A00951249 /* TokenDetailsLearnMoreViewController.swift in Sources */, C008D4172A979D3A000B4503 /* AccountDiscoverCell.swift in Sources */, C001D5AA2A027EB40089EC7A /* CollectiblesRecentsViewController.swift in Sources */, C0FE411C2A5306FA0022EB6D /* ConnectedAppCell.swift in Sources */, @@ -2050,8 +2107,11 @@ C044E1372AAF66F80085652F /* SideMenuSecurityViewModel.swift in Sources */, C0D6D50628B66B07001B8130 /* EditFeesViewController.swift in Sources */, C08C973F2908192A00249959 /* UICollectionViewCell+extensions.swift in Sources */, + C04BA1EE2CF4CFAF00951249 /* LearnMoreSectionHeaderCell.swift in Sources */, + C0C6D9B32CF8C6F100ABB0A7 /* SlideSegue.swift in Sources */, C07DE53A28BCFD2800C32D42 /* AddLiquidityViewModel.swift in Sources */, C02D7CAE27BEB240006A8E39 /* CurrencyViewController.swift in Sources */, + C04BA1F82CF766EA00951249 /* PageIndicatorContainerView.swift in Sources */, C0EA19C029096D9400E6B40D /* CollectibleDetailImageCell.swift in Sources */, C0172A062A98B01C00163179 /* CGSize+extensions.swift in Sources */, C08694E627BD1D94000A4909 /* AccountViewModel.swift in Sources */, @@ -2104,8 +2164,9 @@ C049E73C2881838E00887B64 /* AccountsViewModel.swift in Sources */, C083E6572A6EB79300B3BEBE /* URLFromString.swift in Sources */, C049E74028819E3800887B64 /* DiscoverViewModel.swift in Sources */, - C0C6B5CF28CA27AF00368AEA /* StakeViewController.swift in Sources */, + C0C6B5CF28CA27AF00368AEA /* ChooseBakerViewController.swift in Sources */, C03BF88D2A279836003BD343 /* ActivityItemBatchCell.swift in Sources */, + C04BA1F02CF4CFBE00951249 /* LearnMoreItemCell.swift in Sources */, C01372B229B206FF0083E297 /* MenuHeaderCell.swift in Sources */, C06AEDFF2C00998D005CFDAA /* AddAccountViewModel.swift in Sources */, C08F6913273ABD7300CCB6E3 /* SwapTokenViewModel.swift in Sources */, @@ -2118,10 +2179,11 @@ C01E10EA2BF6185C004A8244 /* MigrationService.swift in Sources */, C0D6C04329DDB93800D890ED /* ImportWalletViewController.swift in Sources */, C03DA8842BFF3B2400B6F46C /* SentryBreadcrumb+extensions.swift in Sources */, + C04D98562CE64938009491BD /* TokenDetailsBakerCell.swift in Sources */, C0F5A8DE29ED85580061DBBD /* EditWalletViewController.swift in Sources */, C0FE432E2AD6D44400312940 /* CustomAVPlayerViewController.swift in Sources */, C02F3CB62BFF77A900FA6383 /* AddWalletViewModel.swift in Sources */, - C0C6B5D128CA2A6B00368AEA /* StakeViewModel.swift in Sources */, + C0C6B5D128CA2A6B00368AEA /* ChooseBakerViewModel.swift in Sources */, C031D3E927D114A600EABBE6 /* TokenDetailsViewController.swift in Sources */, C09860AE27C3C26B00F888AF /* SendTokenAmountViewController.swift in Sources */, C0D01CF329AE4A82007A2468 /* WalletConnectService.swift in Sources */, @@ -2146,6 +2208,7 @@ C003CECB27FDE9C900F64B4C /* CloudKitService.swift in Sources */, C031D3ED27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift in Sources */, C065AF112AB0A6690061CC64 /* SideMenuBackupViewModel.swift in Sources */, + C04BA1F42CF4D04A00951249 /* TokenDetailsLearnMoreViewModel.swift in Sources */, C06E0D65287C3E66007A580B /* WalletConnectSignViewController.swift in Sources */, C04E0EB62ADE92CA001BF56F /* AppUpdateService.swift in Sources */, C0BAFEB328A165110045832E /* ChooseLiquidityTokenViewController.swift in Sources */, @@ -2155,6 +2218,7 @@ C049B5B126A07A1000F1C5E0 /* NetworkChooserViewController.swift in Sources */, C001D5A42A027E710089EC7A /* CollectiblesCollectionsViewController.swift in Sources */, C0D6B94B294B84FE004F2F2D /* HiddenCollectiblesViewController.swift in Sources */, + C0DB694D2CEF567A000C1A17 /* StakeConfirmViewController.swift in Sources */, C08694E427BD1D86000A4909 /* AccountViewController.swift in Sources */, C083E65D2A6EC91500B3BEBE /* DiscoverShowMoreCell.swift in Sources */, C082BBC528941F1A002B1E42 /* AddLiquidityViewController.swift in Sources */, @@ -2171,7 +2235,7 @@ C0EA19D629096E3200E6B40D /* CollectibleDetailSendCell.swift in Sources */, C0172A082A98EC6400163179 /* OnrampViewController.swift in Sources */, C0B2D4C02A70108300BC8DFF /* DiscoverFeaturedItemCell.swift in Sources */, - C034F2C32A6A913B000F4C4C /* ConfirmStakeViewController.swift in Sources */, + C034F2C32A6A913B000F4C4C /* ChooseBakerConfirmViewController.swift in Sources */, C0FCBB852B0D03F8001E01F9 /* CollectibleAttributeDetailViewController.swift in Sources */, C025891F292D1154000D59F6 /* HiddenTokenCell.swift in Sources */, C09635D127CE3CF80095EDBF /* UINavigationController+extensions.swift in Sources */, @@ -2797,7 +2861,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kukai-wallet/kukai-core-swift"; requirement = { - branch = develop; + branch = feature/bakers_and_staking; kind = branch; }; }; diff --git a/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 74258068..0a20d85c 100644 --- a/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -69,8 +69,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kukai-wallet/kukai-core-swift", "state" : { - "branch" : "develop", - "revision" : "192d9cd46876e493a113d81833cac1d1969056bf" + "branch" : "feature/bakers_and_staking", + "revision" : "67b9f749b1e14d536877e9b6ec806c90fb33c43c" } }, { diff --git a/Kukai Mobile.xcodeproj/xcshareddata/xcschemes/Kukai Mobile Beta.xcscheme b/Kukai Mobile.xcodeproj/xcshareddata/xcschemes/Kukai Mobile Beta.xcscheme index b088b660..325f1344 100644 --- a/Kukai Mobile.xcodeproj/xcshareddata/xcschemes/Kukai Mobile Beta.xcscheme +++ b/Kukai Mobile.xcodeproj/xcshareddata/xcschemes/Kukai Mobile Beta.xcscheme @@ -1,6 +1,6 @@ - + - + @@ -61,13 +61,13 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell.swift deleted file mode 100644 index 7283b135..00000000 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// TokenDetailsBalanceAndBakerCell.swift -// Kukai Mobile -// -// Created by Simon Mcloughlin on 06/12/2022. -// - -import UIKit - -class TokenDetailsBalanceAndBakerCell: UITableViewCell { - - @IBOutlet weak var tokenIcon: UIImageView! - @IBOutlet weak var balance: UILabel! - @IBOutlet weak var value: UILabel! - @IBOutlet weak var bakerHeading: UILabel? - @IBOutlet weak var bakerButton: CustomisableButton? - - func setup(data: TokenDetailsBalanceAndBakerData) { - if data.isDelegated { - bakerButton?.customButtonType = .none - bakerButton?.setTitle(data.bakerName + " ", for: .normal) - bakerButton?.setTitleColor(.colorNamed("Txt6"), for: .normal) - - } else { - bakerButton?.customButtonType = .secondary - } - - if data.isStaked { - bakerHeading?.text = "Staked" - } else { - bakerHeading?.text = data.isDelegated ? "Delegated" : "Not Delegated" - } - - balance.text = data.balance - balance.accessibilityIdentifier = "token-detials-balance" - value.text = data.value - value.accessibilityIdentifier = "token-detials-balance-value" - bakerButton?.accessibilityIdentifier = "token-detials-baker-button" - } -} diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nobaker.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nobaker.xib deleted file mode 100644 index e9da62b7..00000000 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nobaker.xib +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - Figtree-Bold - - - Figtree-SemiBold - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nostaking.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nostaking.xib deleted file mode 100644 index 20c74cc1..00000000 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nostaking.xib +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - Figtree-Bold - - - Figtree-SemiBold - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.swift new file mode 100644 index 00000000..e170070d --- /dev/null +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.swift @@ -0,0 +1,30 @@ +// +// TokenDetailsBalanceAndBakerCell.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 06/12/2022. +// + +import UIKit +import KukaiCoreSwift + +class TokenDetailsBalanceCell: UITableViewCell { + + @IBOutlet weak var tokenIcon: UIImageView! + @IBOutlet weak var balance: UILabel! + @IBOutlet weak var value: UILabel! + @IBOutlet weak var availableBalance: UILabel! + @IBOutlet weak var availableValue: UILabel! + + func setup(data: TokenDetailsBalanceData) { + balance.text = data.balance + balance.accessibilityIdentifier = "token-detials-balance" + value.text = data.value + value.accessibilityIdentifier = "token-detials-balance-value" + + availableBalance.text = data.availableBalance + availableBalance.accessibilityIdentifier = "token-detials-available-balance" + availableValue.text = data.availableValue + availableValue.accessibilityIdentifier = "token-detials-available-balance-value" + } +} diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_baker.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib similarity index 68% rename from Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_baker.xib rename to Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib index 7d7b351d..7945fdd9 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_baker.xib +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib @@ -1,9 +1,9 @@ - + - + @@ -18,7 +18,7 @@ - + @@ -26,47 +26,29 @@ - - - - + + + + + + - - + + @@ -113,24 +110,12 @@ - - - - - - - - - - - - diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.swift new file mode 100644 index 00000000..a02799ee --- /dev/null +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.swift @@ -0,0 +1,26 @@ +// +// TokenDetailsPendingUnstakeCell.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 22/11/2024. +// + +import UIKit + +class TokenDetailsPendingUnstakeCell: UITableViewCell { + + @IBOutlet weak var containerView: GradientView! + @IBOutlet weak var amountLabel: UILabel! + @IBOutlet weak var symbolLabel: UILabel! + @IBOutlet weak var fiatLabel: UILabel! + @IBOutlet weak var timeLabel: UILabel! + + public func setup(data: PendingUnstakeData) { + containerView.gradientType = .tableViewCell + + amountLabel.text = data.amount.normalisedRepresentation + symbolLabel.text = "XTZ" + fiatLabel.text = data.fiat + timeLabel.text = data.timeRemaining + } +} diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.xib new file mode 100644 index 00000000..79b61341 --- /dev/null +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.xib @@ -0,0 +1,113 @@ + + + + + + + + + + + + + Figtree-Bold + + + Figtree-SemiBold + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsSendCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsSendCell.xib index 6edd85bd..f9b204f7 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsSendCell.xib +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsSendCell.xib @@ -1,9 +1,9 @@ - + - + @@ -15,15 +15,15 @@ - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.swift index ef6c1590..c8c60939 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.swift +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.swift @@ -11,12 +11,12 @@ import KukaiCoreSwift class TokenDetailsStakingRewardsCell: UITableViewCell { @IBOutlet weak var containerView: GradientView! - @IBOutlet weak var infoButton: CustomisableButton! @IBOutlet weak var lastBakerIcon: UIImageView! @IBOutlet weak var lastBaker: UILabel! - @IBOutlet weak var lastAmountTitle: UILabel! - @IBOutlet weak var lastAmount: UILabel! + @IBOutlet weak var lastDelegationAmountTitle: UILabel! + @IBOutlet weak var lastDelegationAmount: UILabel! + @IBOutlet weak var lastStakeAmount: UILabel! @IBOutlet weak var lastTimeTitle: UILabel! @IBOutlet weak var lastTime: UILabel! @IBOutlet weak var lastCycleTitle: UILabel! @@ -24,7 +24,8 @@ class TokenDetailsStakingRewardsCell: UITableViewCell { @IBOutlet weak var nextBakerIcon: UIImageView! @IBOutlet weak var nextBaker: UILabel! - @IBOutlet weak var nextAmount: UILabel! + @IBOutlet weak var nextDelegationAmount: UILabel! + @IBOutlet weak var nextStakeAmount: UILabel! @IBOutlet weak var nextTime: UILabel! @IBOutlet weak var nextCycle: UILabel! @@ -41,11 +42,13 @@ class TokenDetailsStakingRewardsCell: UITableViewCell { if let previousReward = data.previousReward { MediaProxyService.load(url: previousReward.bakerLogo, to: lastBakerIcon, withCacheType: .permanent, fallback: UIImage.unknownToken()) - let percentage = Decimal(previousReward.fee * 100).rounded(scale: 2, roundingMode: .bankers) + let delegationPercentage = Decimal(previousReward.delegateFee * 100).rounded(scale: 2, roundingMode: .bankers) + let stakingPercentage = Decimal(previousReward.stakeFee * 100).rounded(scale: 2, roundingMode: .bankers) lastBaker.text = previousReward.bakerAlias - lastAmountTitle.text = "Amount (fee)" - lastAmount.text = previousReward.amount.normalisedRepresentation + " (\(percentage)%)" + lastDelegationAmountTitle.text = "Delegation \nAmount (fee)" + lastDelegationAmount.text = previousReward.delegateAmount.normalisedRepresentation + " (\(delegationPercentage)%)" + lastStakeAmount.text = previousReward.stakeAmount.normalisedRepresentation + " (\(stakingPercentage)%)" lastTimeTitle.text = "Time" lastTime.text = previousReward.dateOfPayment.timeAgoDisplay() lastCycleTitle.text = "Cycle" @@ -54,11 +57,13 @@ class TokenDetailsStakingRewardsCell: UITableViewCell { } else if let previousReward = data.estimatedPreviousReward { MediaProxyService.load(url: previousReward.bakerLogo, to: lastBakerIcon, withCacheType: .permanent, fallback: UIImage.unknownToken()) - let percentage = Decimal(previousReward.fee * 100).rounded(scale: 2, roundingMode: .bankers) + let delegationPercentage = Decimal(previousReward.delegateFee * 100).rounded(scale: 2, roundingMode: .bankers) + let stakingPercentage = Decimal(previousReward.stakeFee * 100).rounded(scale: 2, roundingMode: .bankers) lastBaker.text = previousReward.bakerAlias - lastAmountTitle.text = "Est Amount (fee)" - lastAmount.text = previousReward.amount.normalisedRepresentation + " (\(percentage)%)" + lastDelegationAmountTitle.text = "Delegation \nEst Amount (fee)" + lastDelegationAmount.text = previousReward.delegateAmount.normalisedRepresentation + " (\(delegationPercentage)%)" + lastStakeAmount.text = previousReward.stakeAmount.normalisedRepresentation + " (\(stakingPercentage)%)" lastTimeTitle.text = "Est Time" lastTime.text = previousReward.dateOfPayment.timeAgoDisplay() lastCycleTitle.text = "Est Cycle" @@ -68,7 +73,7 @@ class TokenDetailsStakingRewardsCell: UITableViewCell { lastBakerIcon.image = UIImage.unknownToken() lastBaker.text = "N/A" - lastAmount.text = "N/A" + lastDelegationAmount.text = "N/A" lastTime.text = "N/A" lastCycle.text = "N/A" } @@ -76,10 +81,12 @@ class TokenDetailsStakingRewardsCell: UITableViewCell { if let nextReward = data.estimatedNextReward { MediaProxyService.load(url: nextReward.bakerLogo, to: nextBakerIcon, withCacheType: .permanent, fallback: UIImage.unknownToken()) - let percentage = Decimal(nextReward.fee * 100).rounded(scale: 2, roundingMode: .bankers) + let delegationPercentage = Decimal(nextReward.delegateFee * 100).rounded(scale: 2, roundingMode: .bankers) + let stakingPercentage = Decimal(nextReward.stakeFee * 100).rounded(scale: 2, roundingMode: .bankers) nextBaker.text = nextReward.bakerAlias - nextAmount.text = nextReward.amount.normalisedRepresentation + " (\(percentage)%)" + nextDelegationAmount.text = nextReward.delegateAmount.normalisedRepresentation + " (\(delegationPercentage)%)" + nextStakeAmount.text = nextReward.stakeAmount.normalisedRepresentation + " (\(stakingPercentage)%)" nextTime.text = nextReward.dateOfPayment.timeAgoDisplay() nextCycle.text = nextReward.cycle == 0 ? "N/A" : nextReward.cycle.description @@ -87,7 +94,7 @@ class TokenDetailsStakingRewardsCell: UITableViewCell { nextBakerIcon.image = UIImage.unknownToken() nextBaker.text = "N/A" - nextAmount.text = "N/A" + nextDelegationAmount.text = "N/A" nextTime.text = "N/A" nextCycle.text = "N/A" } diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.xib index 3599d315..61f7c172 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.xib +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.xib @@ -1,9 +1,9 @@ - + - + @@ -18,46 +18,18 @@ - - + + - + - + - - - + @@ -95,16 +67,37 @@ - + - + + + + + + - + - + - + @@ -186,16 +179,37 @@ - + - + + + + + + - + - + - + @@ -249,19 +263,15 @@ - - - - + - - + @@ -282,32 +292,27 @@ - - - + + + - + + - + - - - - - - diff --git a/Kukai Mobile/Modules/Account/TokenDetailsLearnMoreViewController.swift b/Kukai Mobile/Modules/Account/TokenDetailsLearnMoreViewController.swift new file mode 100644 index 00000000..d2c19eac --- /dev/null +++ b/Kukai Mobile/Modules/Account/TokenDetailsLearnMoreViewController.swift @@ -0,0 +1,79 @@ +// +// TokenDetailsLearnMoreViewController.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 25/11/2024. +// + +import UIKit +import Combine + +class TokenDetailsLearnMoreViewController: UIViewController { + + @IBOutlet weak var tableView: UITableView! + + public let viewModel = TokenDetailsLearnMoreViewModel() + + private var bag = [AnyCancellable]() + private let sectionFooterSpacer = UIView() + + override func viewDidLoad() { + super.viewDidLoad() + GradientView.add(toView: self.view, withType: .fullScreenBackground) + + // Setup data + viewModel.makeDataSource(withTableView: tableView) + tableView.dataSource = viewModel.dataSource + tableView.delegate = self + tableView.sectionHeaderTopPadding = 0 + tableView.sectionHeaderHeight = 0 + tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0.1, height: 0.1)) + sectionFooterSpacer.backgroundColor = .clear + + viewModel.$state.sink { [weak self] state in + guard let self = self else { return } + + switch state { + case .loading: + let _ = "" + + case .success(_): + let _ = "" + + case .failure(_, let message): + self.windowError(withTitle: "error".localized(), description: message) + } + }.store(in: &bag) + + DependencyManager.shared.$networkDidChange + .dropFirst() + .sink { [weak self] _ in + self?.viewModel.refresh(animate: true) + }.store(in: &bag) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + viewModel.refresh(animate: true) + } +} + +extension TokenDetailsLearnMoreViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + return sectionFooterSpacer + } + + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return 4 + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + if let res = viewModel.segue(forIndexPath: indexPath) { + self.performSegue(withIdentifier: res, sender: nil) + } + } +} diff --git a/Kukai Mobile/Modules/Account/TokenDetailsLearnMoreViewModel.swift b/Kukai Mobile/Modules/Account/TokenDetailsLearnMoreViewModel.swift new file mode 100644 index 00000000..1f69cd88 --- /dev/null +++ b/Kukai Mobile/Modules/Account/TokenDetailsLearnMoreViewModel.swift @@ -0,0 +1,95 @@ +// +// TokenDetailsLearnMoreViewModel.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 25/11/2024. +// + +import UIKit +import KukaiCoreSwift + +struct LearnMoreHeaderData: Hashable { + let id = UUID() + let title: String +} + +struct LearnMoreItemData: Hashable { + let id = UUID() + let title: String + let segueId: String +} + +class TokenDetailsLearnMoreViewModel: ViewModel, UITableViewDiffableDataSourceHandler { + + typealias SectionEnum = Int + typealias CellDataType = AnyHashableSendable + + var dataSource: UITableViewDiffableDataSource? = nil + + func makeDataSource(withTableView tableView: UITableView) { + + dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, item in + + if let obj = item.base as? LearnMoreHeaderData, let cell = tableView.dequeueReusableCell(withIdentifier: "LearnMoreSectionHeaderCell", for: indexPath) as? LearnMoreSectionHeaderCell { + cell.titleLabel.text = obj.title + return cell + + } else if let obj = item.base as? LearnMoreItemData, let cell = tableView.dequeueReusableCell(withIdentifier: "LearnMoreItemCell", for: indexPath) as? LearnMoreItemCell { + cell.titleLabel.text = obj.title + return cell + + } else { + return UITableViewCell() + } + }) + + dataSource?.defaultRowAnimation = .fade + } + + func refresh(animate: Bool, successMessage: String? = nil) { + guard let ds = dataSource else { + state = .failure(KukaiError.internalApplicationError(error: ViewModelError.dataSourceNotCreated), "Unable to process data at this time") + return + } + + // Build snapshot + + var snapshot = NSDiffableDataSourceSnapshot() + let data: [[AnyHashableSendable]] = [ + [.init(LearnMoreHeaderData(title: "Delegating"))], + [.init(LearnMoreItemData(title: "What is Delegating?", segueId: "stake-learn-more-1"))], + [.init(LearnMoreItemData(title: "What is a Baker?", segueId: "stake-learn-more-2"))], + [.init(LearnMoreItemData(title: "What is Governance?", segueId: "stake-learn-more-3"))], + [.init(LearnMoreItemData(title: "What is a Cycle?", segueId: "stake-learn-more-4"))], + [.init(LearnMoreItemData(title: "Are there any risks? (Delegating)", segueId: "stake-learn-more-5"))], + [.init(LearnMoreItemData(title: "How do I get my rewards? (Delegating)", segueId: "stake-learn-more-6"))], + + [.init(LearnMoreHeaderData(title: "Staking"))], + [.init(LearnMoreItemData(title: "What is Staking?", segueId: "stake-learn-more-7"))], + [.init(LearnMoreItemData(title: "Are there any risks? (Staking)", segueId: "stake-learn-more-8"))], + [.init(LearnMoreItemData(title: "How do I get my rewards? (Staking)", segueId: "stake-learn-more-9"))], + + [.init(LearnMoreHeaderData(title: "Monitor your Baker"))], + [.init(LearnMoreItemData(title: "No free space in Baker", segueId: "stake-learn-more-10"))], + [.init(LearnMoreItemData(title: "Baker not voting", segueId: "stake-learn-more-11"))], + ] + + snapshot.appendSections(Array(0.. String? { + if let obj = dataSource?.itemIdentifier(for: forIndexPath)?.base as? LearnMoreItemData { + return obj.segueId + } + + return nil + } +} diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift index a8de2412..e01b53df 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift @@ -197,19 +197,52 @@ class TokenDetailsViewController: UIViewController, UITableViewDelegate { extension TokenDetailsViewController: TokenDetailsViewModelDelegate { - func setBakerTapped() { - self.performSegue(withIdentifier: "stake", sender: nil) - } - func sendTapped() { self.performSegue(withIdentifier: "send", sender: nil) } - func stakingRewardsInfoTapped() { - self.performSegue(withIdentifier: "stakingInfo", sender: nil) - } - func launchExternalBrowser(withURL url: URL) { UIApplication.shared.open(url) } } + +extension TokenDetailsViewController: TokenDetailsBakerDelegate { + + func changeTapped() { + + if viewModel.isNewBakerFlow() { + self.performSegue(withIdentifier: "stake-onboarding", sender: nil) + } else { + self.performSegue(withIdentifier: "choose-baker", sender: nil) + } + } + + func learnTapped() { + self.performSegue(withIdentifier: "learn-more", sender: nil) + } +} + +extension TokenDetailsViewController: TokenDetailsStakeBalanceDelegate { + + func stakeTapped() { + TransactionService.shared.currentTransactionType = .stake + TransactionService.shared.stakeData.chosenBaker = viewModel.baker + TransactionService.shared.stakeData.chosenToken = viewModel.token + self.performSegue(withIdentifier: "stake-amount", sender: nil) + } + + func unstakeTapped() { + TransactionService.shared.currentTransactionType = .unstake + TransactionService.shared.unstakeData.chosenBaker = viewModel.baker + TransactionService.shared.unstakeData.chosenToken = viewModel.token + self.performSegue(withIdentifier: "stake-amount", sender: nil) + } + + func finalizeTapped() { + self.showLoadingView() + viewModel.createFinaliseOperations { [weak self] errorMessage in + self?.loadingViewHideActivityAndFade() + self?.performSegue(withIdentifier: "confirm-stake", sender: nil) + } + } +} diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift index a70b7b02..95cc5dfe 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift @@ -7,6 +7,8 @@ import UIKit import KukaiCoreSwift +import Combine +import OSLog struct AllChartData: Hashable { let id = UUID() @@ -46,14 +48,12 @@ struct TokenDetailsButtonData: Hashable, Identifiable { let hasMoreButton: Bool } -struct TokenDetailsBalanceAndBakerData: Hashable, Identifiable { +struct TokenDetailsBalanceData: Hashable, Identifiable { let id = UUID() let balance: String let value: String - let isDelegationPossible: Bool - let isDelegated: Bool - let isStaked: Bool - let bakerName: String + let availableBalance: String + let availableValue: String } struct TokenDetailsSendData: Hashable { @@ -61,6 +61,27 @@ struct TokenDetailsSendData: Hashable { var isDisabled: Bool } +struct TokenDetailsBakerData: Hashable { + let bakerIcon: URL? + let bakerName: String? + let bakerApy: Decimal + let votingParticipation: [Bool] + let freeSpace: Decimal + let enoughSpaceForBalance: Bool + let bakerChangeDisabled: Bool +} + +struct TokenDetailsStakeData: Hashable { + let stakedBalance: String + let stakedValue: String + let finalizeBalance: String + let finalizeValue: String + let canStake: Bool + let canUnstake: Bool + let canFinalize: Bool + let buttonsDisabled: Bool +} + struct TokenDetailsActivityHeader: Hashable, Identifiable { let id = UUID() let header: Bool @@ -70,12 +91,21 @@ struct TokenDetailsMessageData: Hashable { let message: String } +struct TokenDetailsSmallSectionHeader: Hashable { + let message: String +} + +struct PendingUnstakeData: Hashable { + let id = UUID() + let amount: XTZAmount + let fiat: String + let timeRemaining: String +} + @objc protocol TokenDetailsViewModelDelegate: AnyObject { - func setBakerTapped() func sendTapped() - func stakingRewardsInfoTapped() func launchExternalBrowser(withURL url: URL) } @@ -92,12 +122,16 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { private var currentChartRange: TokenDetailsChartCellRange = .day private let chartDateFormatter = DateFormatter(withFormat: "MMM dd HH:mm a") private var initialChartLoad = true + private var onlineXTZFetchGroup = DispatchGroup() + private var bag = [AnyCancellable]() // Set by VC - weak var delegate: TokenDetailsViewModelDelegate? = nil + weak var delegate: (TokenDetailsViewModelDelegate & TokenDetailsBakerDelegate & TokenDetailsStakeBalanceDelegate)? = nil var token: Token? = nil + var baker: TzKTBaker? = nil var tokenFiatPrice = "" + var needsToLoadOnlineXTZData = false // Set by VM var currentSnapshot = NSDiffableDataSourceSnapshot() @@ -109,16 +143,36 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { var chartData = AllChartData(day: [], week: [], month: [], year: []) var chartDataUnsucessful = false var buttonData: TokenDetailsButtonData? = nil - var balanceAndBakerData: TokenDetailsBalanceAndBakerData? = nil + var balanceData: TokenDetailsBalanceData? = nil var sendData = TokenDetailsSendData(isBuyTez: false, isDisabled: false) - var stakingRewardLoadingData = LoadingData() - var stakingRewardData: AggregateRewardInformation? = nil + var bakerData: TokenDetailsBakerData? = nil + var stakeData: TokenDetailsStakeData? = nil + var pendingUnstakes: [PendingUnstakeData] = [] + var onlineDataLoading = LoadingData() + var rewardData: AggregateRewardInformation? = nil var activityHeaderData = TokenDetailsActivityHeader(header: true) var activityFooterData = TokenDetailsActivityHeader(header: false) var activityItems: [TzKTTransactionGroup] = [] var noItemsData = TokenDetailsMessageData(message: "No items avaialble at this time, check again later") + var finaliseableAmount: TokenAmount = .zero() + + override init() { + super.init() + + DependencyManager.shared.$addressRefreshed + .dropFirst() + .sink { [weak self] address in + let selectedAddress = DependencyManager.shared.selectedWalletAddress ?? "" + if self?.dataSource != nil && selectedAddress == address { + self?.refresh(animate: true) + } + }.store(in: &bag) + } + deinit { + bag.forEach({ $0.cancel() }) + } @@ -126,15 +180,17 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { func makeDataSource(withTableView tableView: UITableView) { tableView.register(UINib(nibName: "TokenDetailsChartCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsChartCell") - tableView.register(UINib(nibName: "TokenDetailsBalanceAndBakerCell_baker", bundle: nil), forCellReuseIdentifier: "TokenDetailsBalanceAndBakerCell_baker") - tableView.register(UINib(nibName: "TokenDetailsBalanceAndBakerCell_nobaker", bundle: nil), forCellReuseIdentifier: "TokenDetailsBalanceAndBakerCell_nobaker") - tableView.register(UINib(nibName: "TokenDetailsBalanceAndBakerCell_nostaking", bundle: nil), forCellReuseIdentifier: "TokenDetailsBalanceAndBakerCell_nostaking") + tableView.register(UINib(nibName: "TokenDetailsBalanceCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsBalanceCell") tableView.register(UINib(nibName: "TokenDetailsSendCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsSendCell") + tableView.register(UINib(nibName: "TokenDetailsBakerCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsBakerCell") + tableView.register(UINib(nibName: "TokenDetailsStakeBalanceCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsStakeBalanceCell") tableView.register(UINib(nibName: "TokenDetailsStakingRewardsCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsStakingRewardsCell") tableView.register(UINib(nibName: "TokenDetailsActivityHeaderCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsActivityHeaderCell") tableView.register(UINib(nibName: "TokenDetailsActivityHeaderCell_footer", bundle: nil), forCellReuseIdentifier: "TokenDetailsActivityHeaderCell_footer") tableView.register(UINib(nibName: "TokenDetailsLoadingCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsLoadingCell") tableView.register(UINib(nibName: "TokenDetailsMessageCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsMessageCell") + tableView.register(UINib(nibName: "TokenDetailsPendingUnstakeCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsPendingUnstakeCell") + tableView.register(UINib(nibName: "TokenDetailsSmallHeadingCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsSmallHeadingCell") tableView.register(UINib(nibName: "ActivityItemCell", bundle: nil), forCellReuseIdentifier: "ActivityItemCell") @@ -157,36 +213,46 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { cell.setup(delegate: self, chartController: self.chartController, allChartData: obj) return cell - } else if let obj = item.base as? TokenDetailsBalanceAndBakerData { - let reuse = obj.isDelegationPossible ? (obj.isDelegated ? "TokenDetailsBalanceAndBakerCell_baker" : "TokenDetailsBalanceAndBakerCell_nobaker") : "TokenDetailsBalanceAndBakerCell_nostaking" - - if let cell = tableView.dequeueReusableCell(withIdentifier: reuse, for: indexPath) as? TokenDetailsBalanceAndBakerCell { - - if let tokenURL = self.tokenHeaderData.tokenURL { - MediaProxyService.load(url: tokenURL, to: cell.tokenIcon, withCacheType: .permanent, fallback: UIImage.unknownToken()) - - } else { - cell.tokenIcon.image = self.tokenHeaderData.tokenImage - } - - if DependencyManager.shared.selectedWalletMetadata?.isWatchOnly == false { - cell.bakerButton?.addTarget(self.delegate, action: #selector(TokenDetailsViewModelDelegate.setBakerTapped), for: .touchUpInside) - } - cell.setup(data: obj) + } else if let obj = item.base as? TokenDetailsBalanceData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsBalanceCell", for: indexPath) as? TokenDetailsBalanceCell { + if let tokenURL = self.tokenHeaderData.tokenURL { + MediaProxyService.load(url: tokenURL, to: cell.tokenIcon, withCacheType: .permanent, fallback: UIImage.unknownToken()) - return cell + } else { + cell.tokenIcon.image = self.tokenHeaderData.tokenImage } + + cell.setup(data: obj) + return cell + } else if let obj = item.base as? TokenDetailsSendData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsSendCell", for: indexPath) as? TokenDetailsSendCell { cell.sendButton?.addTarget(self.delegate, action: #selector(TokenDetailsViewModelDelegate.sendTapped), for: .touchUpInside) cell.setup(data: obj) return cell + } else if let obj = item.base as? TokenDetailsBakerData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsBakerCell", for: indexPath) as? TokenDetailsBakerCell { + cell.setup(data: obj) + cell.delegate = self.delegate + return cell + + } else if let obj = item.base as? TokenDetailsStakeData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsStakeBalanceCell", for: indexPath) as? TokenDetailsStakeBalanceCell { + cell.setup(data: obj) + cell.delegate = self.delegate + return cell + + } else if let obj = item.base as? TokenDetailsSmallSectionHeader, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsSmallHeadingCell", for: indexPath) as? TokenDetailsSmallHeadingCell { + cell.headingLabel.text = obj.message + return cell + + } else if let obj = item.base as? PendingUnstakeData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsPendingUnstakeCell", for: indexPath) as? TokenDetailsPendingUnstakeCell { + cell.setup(data: obj) + return cell + } else if let _ = item.base as? LoadingData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsLoadingCell", for: indexPath) as? TokenDetailsLoadingCell { cell.setup() return cell } else if let obj = item.base as? AggregateRewardInformation, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsStakingRewardsCell", for: indexPath) as? TokenDetailsStakingRewardsCell { - cell.infoButton.addTarget(self.delegate, action: #selector(TokenDetailsViewModelDelegate.stakingRewardsInfoTapped), for: .touchUpInside) + //cell.infoButton.addTarget(self.delegate, action: #selector(TokenDetailsViewModelDelegate.stakingRewardsInfoTapped), for: .touchUpInside) cell.setup(data: obj) return cell @@ -219,36 +285,36 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { return } - loadTokenData(token: token) + let isWatchWallet = DependencyManager.shared.selectedWalletMetadata?.isWatchOnly ?? false + + // Immediately load balance, logo, buttons and placeholder chart + loadOfflineData(token: token) sendData.isBuyTez = (token.isXTZ() && token.balance == .zero()) - sendData.isDisabled = DependencyManager.shared.selectedWalletMetadata?.isWatchOnly ?? false + sendData.isDisabled = isWatchWallet var data: [AnyHashableSendable] = [ .init(tokenHeaderData), .init(chartData), - .init(balanceAndBakerData), + .init(balanceData), .init(sendData) ] - // TODO: remove testnet check in future when remote serivce supports ghostnet - if balanceAndBakerData?.isDelegationPossible == true && balanceAndBakerData?.isDelegated == true && DependencyManager.shared.currentNetworkType != .ghostnet { - data.append(.init(stakingRewardLoadingData)) - } - - - - // Activity data gets loaded as part of token balances - var activitySection: [AnyHashableSendable] = [.init(activityHeaderData)] - self.activityItems = DependencyManager.shared.activityService.filterSendReceive(forToken: token, count: 5) - if activityItems.count == 0 { - activitySection.append(.init(self.noItemsData)) + if token.isXTZ() { + // If XTZ, user has a blance, and we have a delegate set, then we need to fetch more data before displaying anything else + // Otherwise load the baker onboarding flow, if user has a balance + if DependencyManager.shared.balanceService.account.delegate != nil { + self.needsToLoadOnlineXTZData = !sendData.isBuyTez + data.append(.init(onlineDataLoading)) + + } else if !sendData.isBuyTez { + data.append(.init(TokenDetailsBakerData(bakerIcon: nil, bakerName: nil, bakerApy: 0, votingParticipation: [], freeSpace: 0, enoughSpaceForBalance: false, bakerChangeDisabled: isWatchWallet) )) + } } else { - activitySection.append(contentsOf: activityItems.map({ .init($0) })) - activitySection.append(.init(self.activityFooterData)) + + // If its not XTZ, then load the activity items (if any) and move on + data.append(contentsOf: loadActivitySection(token: token)) } - data.append(contentsOf: activitySection) - // Build snapshot @@ -260,16 +326,17 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { self.state = .success(nil) - // Trigger remote data fetching + + // After UI is updated, fetch the data for chart and reload that 1 cell loadChartData(token: token) { [weak self] result in guard let self = self else { return } self.initialChartLoad = false switch result { case .success(let data): - self.currentSnapshot.deleteItems([.init(self.chartData)]) + self.currentSnapshot.deleteItems([.init(self.chartData)]) self.chartData = data - self.currentSnapshot.insertItems([.init(self.chartData)], afterItem: .init(self.tokenHeaderData)) + self.currentSnapshot.insertItems([.init(self.chartData)], afterItem: .init(self.tokenHeaderData)) self.calculatePriceChange(point: nil) self.weakTokenHeaderCell?.changePriceDisplay(data: self.tokenHeaderData) @@ -278,32 +345,45 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { self.state = .success(nil) case .failure(_): - self.currentSnapshot.deleteItems([.init(self.chartData)]) + self.currentSnapshot.deleteItems([.init(self.chartData)]) self.chartDataUnsucessful = true self.chartData = AllChartData(day: [], week: [], month: [], year: []) - self.currentSnapshot.insertItems([.init(self.chartData)], afterItem: .init(self.tokenHeaderData)) + self.currentSnapshot.insertItems([.init(self.chartData)], afterItem: .init(self.tokenHeaderData)) ds.apply(self.currentSnapshot, animatingDifferences: true) self.state = .success(nil) } } - - // TODO: remove testnet check in future when remote serivce supports ghostnet - if balanceAndBakerData?.isDelegationPossible == true && balanceAndBakerData?.isDelegated == true && DependencyManager.shared.currentNetworkType != .ghostnet { - loadBakerData { [weak self] result in + + + // At the same time, if we should, load all the other XTZ related content, like baker, staking view, delegation/staking rewards, etc + if self.needsToLoadOnlineXTZData { + loadOnlineXTZData(token: token) { [weak self] error in + if let err = error { + self?.state = .failure(err, err.rpcErrorString ?? err.description) + } + guard let self = self else { return } - switch result { - case .success(let data): - self.currentSnapshot.deleteItems([.init(self.stakingRewardLoadingData)]) - self.stakingRewardData = data - self.currentSnapshot.insertItems([.init(data)], afterItem: .init(self.sendData)) - - ds.apply(self.currentSnapshot, animatingDifferences: true) - - case .failure(let error): - self.state = .failure(error, "Unable to get baker data") + self.currentSnapshot.deleteItems([.init(self.onlineDataLoading)]) + + var newData: [AnyHashableSendable] = [.init(self.bakerData), .init(self.stakeData)] + + if pendingUnstakes.count > 0 { + newData.append(.init(TokenDetailsSmallSectionHeader(message: "Pending Unstake Requests"))) + newData.append(contentsOf: pendingUnstakes.map({ .init($0) })) + } + + if let rewardData = rewardData { + newData.append(.init(TokenDetailsSmallSectionHeader(message: "Delegation & Staking Rewards"))) + newData.append(.init(rewardData)) } + + newData.append(contentsOf: loadActivitySection(token: token)) + self.currentSnapshot.insertItems(newData, afterItem: .init(self.sendData)) + + ds.apply(self.currentSnapshot, animatingDifferences: true) + self.state = .success(nil) } } } @@ -316,11 +396,12 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { let _ = DiskService.delete(fileName: TokenDetailsViewModel.bakerRewardsCacheFilename) } - func loadTokenData(token: Token) { + func loadOfflineData(token: Token) { self.token = token self.tokenHeaderData.tokenName = token.symbol let tokenBalance = DependencyManager.shared.coinGeckoService.format(decimal: token.balance.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) + let availableTokenBalance = DependencyManager.shared.coinGeckoService.format(decimal: token.availableBalance.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) if token.isXTZ() { self.tokenHeaderData.tokenImage = UIImage.tezosToken() @@ -330,13 +411,14 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { tokenFiatPrice = DependencyManager.shared.coinGeckoService.format(decimal: fiatPerToken, numberStyle: .currency, maximumFractionDigits: 2) self.tokenHeaderData.fiatAmount = tokenFiatPrice - let account = DependencyManager.shared.balanceService.account - let xtzValue = (token.balance as? XTZAmount ?? .zero()) * fiatPerToken + let xtzValue = token.balance * fiatPerToken let tokenValue = DependencyManager.shared.coinGeckoService.format(decimal: xtzValue, numberStyle: .currency, maximumFractionDigits: 2) - let bakerString = (account.delegate?.alias ?? account.delegate?.address.truncateTezosAddress() ?? "") + " " + + let availableXtzValue = token.availableBalance * fiatPerToken + let availableValue = DependencyManager.shared.coinGeckoService.format(decimal: availableXtzValue, numberStyle: .currency, maximumFractionDigits: 2) buttonData = TokenDetailsButtonData(isFavourited: true, canBeUnFavourited: false, isHidden: false, canBeHidden: false, canBePurchased: true, canBeViewedOnline: false, hasMoreButton: false) - balanceAndBakerData = TokenDetailsBalanceAndBakerData(balance: tokenBalance, value: tokenValue, isDelegationPossible: true, isDelegated: (account.delegate != nil), isStaked: account.xtzStakedBalance > .zero(), bakerName: bakerString) + balanceData = TokenDetailsBalanceData(balance: tokenBalance, value: tokenValue, availableBalance: availableTokenBalance, availableValue: availableValue) } else { self.tokenHeaderData.tokenURL = token.thumbnailURL @@ -375,40 +457,166 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { tokenBalanceValueString = DependencyManager.shared.coinGeckoService.format(decimal: xtzPrice, numberStyle: .currency, maximumFractionDigits: 2) } - balanceAndBakerData = TokenDetailsBalanceAndBakerData(balance: tokenBalance, value: tokenBalanceValueString, isDelegationPossible: false, isDelegated: false, isStaked: false, bakerName: "") + balanceData = TokenDetailsBalanceData(balance: tokenBalance, value: tokenBalanceValueString, availableBalance: tokenBalance, availableValue: tokenBalanceValueString) } else { let dashedString = DependencyManager.shared.coinGeckoService.dashedCurrencyString() tokenHeaderData.fiatAmount = dashedString - balanceAndBakerData = TokenDetailsBalanceAndBakerData(balance: tokenBalance, value: dashedString, isDelegationPossible: false, isDelegated: false, isStaked: false, bakerName: "") + balanceData = TokenDetailsBalanceData(balance: tokenBalance, value: dashedString, availableBalance: tokenBalance, availableValue: dashedString) } } } - func loadBakerData(completion: @escaping ((Result) -> Void)) { - let account = DependencyManager.shared.balanceService.account - guard let delegate = account.delegate else { - completion(Result.failure(KukaiError.unknown(withString: "Can't find baker details"))) + func loadActivitySection(token: Token) -> [AnyHashableSendable] { + var data: [AnyHashableSendable] = [.init(activityHeaderData)] + self.activityItems = DependencyManager.shared.activityService.filterSendReceive(forToken: token, count: 5) + + if activityItems.count == 0 { + data.append(.init(self.noItemsData)) + + } else { + data.append(contentsOf: activityItems.map({ .init($0) })) + data.append(.init(self.activityFooterData)) + } + + return data + } + + func loadOnlineXTZData(token: Token, completion: @escaping ((KukaiError?) -> Void)) { + guard let delegate = DependencyManager.shared.balanceService.account.delegate else { + completion(nil) return } + let isWatchWallet = DependencyManager.shared.selectedWalletMetadata?.isWatchOnly ?? false + let account = DependencyManager.shared.balanceService.account + var votingParticipation: [Bool] = [] + + + // Get fresh baker data, as rewards are cached for an entire cycle and free space could change very regularly + onlineXTZFetchGroup.enter() + DependencyManager.shared.tzktClient.bakerConfig(forAddress: delegate.address) { [weak self] result in + guard let res = try? result.get() else { + self?.onlineXTZFetchGroup.leave() + return + } + + self?.baker = res + self?.onlineXTZFetchGroup.leave() + } + + + // Fetch all the pending unstake items + onlineXTZFetchGroup.enter() + DependencyManager.shared.tzktClient.pendingStakingUpdates(forAddress: account.walletAddress, ofType: "unstake") { [weak self] result in + guard let res = try? result.get() else { + self?.onlineXTZFetchGroup.leave() + return + } + + let fiatPerToken = DependencyManager.shared.coinGeckoService.selectedCurrencyRatePerXTZ + self?.pendingUnstakes = res.map { item in + let xtzAmount = item.xtzAmount + let xtzValue = xtzAmount * fiatPerToken + let xtzValueString = DependencyManager.shared.coinGeckoService.format(decimal: xtzValue, numberStyle: .currency, maximumFractionDigits: 2) + + return PendingUnstakeData(amount: item.xtzAmount, fiat: xtzValueString, timeRemaining: item.dateTime.timeAgoDisplay()) + } + + self?.onlineXTZFetchGroup.leave() + } + + + // Get rewards data from cache or remote + onlineXTZFetchGroup.enter() if let bakerRewardCache = DiskService.read(type: AggregateRewardInformation.self, fromFileName: TokenDetailsViewModel.bakerRewardsCacheFilename), !bakerRewardCache.isOutOfDate(), !bakerRewardCache.moreThan1CycleBetweenPreiousAndNext() { - completion(Result.success(bakerRewardCache)) + self.rewardData = bakerRewardCache + onlineXTZFetchGroup.leave() } else { - DependencyManager.shared.tzktClient.estimateLastAndNextReward(forAddress: account.walletAddress, delegate: delegate) { result in + DependencyManager.shared.tzktClient.estimateLastAndNextReward(forAddress: account.walletAddress, delegate: delegate) { [weak self] result in if let res = try? result.get() { let _ = DiskService.write(encodable: res, toFileName: TokenDetailsViewModel.bakerRewardsCacheFilename) - completion(Result.success(res)) + self?.rewardData = res } else { - completion(Result.failure(result.getFailure())) + Logger.app.error("Error fetching baker data: \(result.getFailure())") + } + + self?.onlineXTZFetchGroup.leave() + } + } + + + // Certain things only work or make sense on mainnet + if DependencyManager.shared.currentNetworkType != .ghostnet { + + // Check voting participation + onlineXTZFetchGroup.enter() + DependencyManager.shared.tzktClient.checkBakerVoteParticipation(forAddress: delegate.address) {[weak self] result in + guard let res = try? result.get() else { + self?.onlineXTZFetchGroup.leave() + return } + + votingParticipation = res + self?.onlineXTZFetchGroup.leave() } } + + + + // Fire completion when everything is done + onlineXTZFetchGroup.notify(queue: .global(qos: .background)) { [weak self] in + guard let baker = self?.baker else { + DispatchQueue.main.async { completion(KukaiError.unknown(withString: "Unable to fetch information about the current baker. Please try again later")) } + return + } + + let fiatPerToken = DependencyManager.shared.coinGeckoService.selectedCurrencyRatePerXTZ + let isStaking = account.xtzStakedBalance > .zero() + + let bakerString = delegate.alias ?? delegate.address.truncateTezosAddress() + let freeSpace = isStaking ? baker.staking.freeSpace : baker.delegation.freeSpace + let enoughSpace = isStaking ? account.availableBalance < XTZAmount(fromNormalisedAmount: baker.staking.freeSpace) : account.xtzBalance < XTZAmount(fromNormalisedAmount: baker.delegation.freeSpace) + + let delegationApy = Decimal(baker.delegation.estimatedApy) + let stakingApy = Decimal(baker.staking.estimatedApy) + let percentOfFundsStaked = ((account.xtzStakedBalance.toNormalisedDecimal() ?? 0) / (account.xtzBalance.toNormalisedDecimal() ?? 0)) + let percentOfFundsDelegated = 1 - percentOfFundsStaked + let estimatedApy = (((delegationApy * percentOfFundsDelegated) + (stakingApy * percentOfFundsStaked)) * 100).rounded(scale: 2, roundingMode: .bankers) + + self?.bakerData = TokenDetailsBakerData(bakerIcon: baker.logo, bakerName: bakerString, bakerApy: estimatedApy, votingParticipation: votingParticipation, freeSpace: freeSpace, enoughSpaceForBalance: enoughSpace, bakerChangeDisabled: isWatchWallet) + + + + let stakeBalance = DependencyManager.shared.coinGeckoService.format(decimal: token.stakedBalance.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) + let stakeXtzValue = (token.stakedBalance as? XTZAmount ?? .zero()) * fiatPerToken + let stakeValue = DependencyManager.shared.coinGeckoService.format(decimal: stakeXtzValue, numberStyle: .currency, maximumFractionDigits: 2) + + let totalAmountOfPendingUnstake = self?.pendingUnstakes.map({ $0.amount }).reduce(.zero(), +) + let actualFinaliseableAmount = token.unstakedBalance - (totalAmountOfPendingUnstake ?? .zero()) + self?.finaliseableAmount = actualFinaliseableAmount + let finaliseBalance = DependencyManager.shared.coinGeckoService.format(decimal: actualFinaliseableAmount.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) + let finaliseXtzValue = actualFinaliseableAmount * fiatPerToken + let finaliseValue = DependencyManager.shared.coinGeckoService.format(decimal: finaliseXtzValue, numberStyle: .currency, maximumFractionDigits: 2) + + // We need to prevent users from staking their entire balance so that they have enough balance to unstake + // User can also only stake if the baker has enough free space for them + let canStake = (account.availableBalance > XTZAmount(fromNormalisedAmount: 1) && enoughSpace) + let canUnstake = token.stakedBalance > .zero() + let canFinalize = actualFinaliseableAmount > .zero() + + self?.stakeData = TokenDetailsStakeData(stakedBalance: stakeBalance, stakedValue: stakeValue, finalizeBalance: finaliseBalance, finalizeValue: finaliseValue, canStake: canStake, canUnstake: canUnstake, canFinalize: canFinalize, buttonsDisabled: isWatchWallet) + + DispatchQueue.main.async { completion(nil) } + } } + + + // MARK: - Chart func loadChartData(token: Token, completion: @escaping ((Result) -> Void)) { @@ -541,6 +749,37 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { func chartRangeChanged(to: TokenDetailsChartCellRange) { currentChartRange = to } + + func isNewBakerFlow() -> Bool { + return bakerData?.bakerName == nil + } + + func createFinaliseOperations(completion: @escaping ((String?) -> Void)) { + guard let selectedWalletMetadata = DependencyManager.shared.selectedWalletMetadata else { + completion("error-no-destination".localized()) + return + } + + TransactionService.shared.currentTransactionType = .finaliseUnstake + TransactionService.shared.finaliseUnstakeData.chosenAmount = finaliseableAmount + TransactionService.shared.finaliseUnstakeData.chosenBaker = baker + TransactionService.shared.finaliseUnstakeData.chosenToken = token + let operations = OperationFactory.finaliseUnstakeOperation(from: selectedWalletMetadata.address) + + // Estimate the cost of the operation (ideally display this to a user first and let them confirm) + DependencyManager.shared.tezosNodeClient.estimate(operations: operations, walletAddress: selectedWalletMetadata.address, base58EncodedPublicKey: selectedWalletMetadata.bas58EncodedPublicKey) { estimationResult in + + switch estimationResult { + case .success(let estimationResult): + TransactionService.shared.currentOperationsAndFeesData = TransactionService.OperationsAndFeesData(estimatedOperations: estimationResult.operations) + TransactionService.shared.currentForgedString = estimationResult.forgedString + completion(nil) + + case .failure(let estimationError): + completion(estimationError.description) + } + } + } } diff --git a/Kukai Mobile/Modules/Activity/ActivityViewModel.swift b/Kukai Mobile/Modules/Activity/ActivityViewModel.swift index 0a6147e4..b775df30 100644 --- a/Kukai Mobile/Modules/Activity/ActivityViewModel.swift +++ b/Kukai Mobile/Modules/Activity/ActivityViewModel.swift @@ -136,7 +136,7 @@ class ActivityViewModel: ViewModel, UITableViewDiffableDataSourceHandler { let currentAddress = DependencyManager.shared.selectedWalletAddress ?? "" let confirmed = DependencyManager.shared.activityService.transactionGroups - var pending = DependencyManager.shared.activityService.pendingTransactionGroups.filter({ $0.transactions.first?.sender.address == currentAddress }) + var pending = DependencyManager.shared.activityService.pendingTransactionGroups.filter({ $0.transactions.first?.sender?.address == currentAddress }) pending.append(contentsOf: confirmed) pending = pending.sorted { groupLeft, groupRight in diff --git a/Kukai Mobile/Modules/Activity/Cells/ActivityItemCell.swift b/Kukai Mobile/Modules/Activity/Cells/ActivityItemCell.swift index 770fe9f2..fb185d63 100644 --- a/Kukai Mobile/Modules/Activity/Cells/ActivityItemCell.swift +++ b/Kukai Mobile/Modules/Activity/Cells/ActivityItemCell.swift @@ -261,10 +261,10 @@ class ActivityItemCell: UITableViewCell, UITableViewCellImageDownloading, Activi destinationIcon.image = UIImage(named: record.iconName) } } else { - let record = LookupService.shared.lookupFor(address: tx.sender.address) + let record = LookupService.shared.lookupFor(address: tx.sender?.address ?? "") if record.type == .address { destinationIconStackView.isHidden = true - destinationLabel.text = tx.sender.alias ?? record.displayText.truncateTezosAddress() + destinationLabel.text = tx.sender?.alias ?? record.displayText.truncateTezosAddress() } else { destinationIconStackView.isHidden = false diff --git a/Kukai Mobile/Modules/Collectibles/Cells/List/CollectiblesCollectionCell.swift b/Kukai Mobile/Modules/Collectibles/Cells/List/CollectiblesCollectionCell.swift index 6f4d4ffe..87aa5725 100644 --- a/Kukai Mobile/Modules/Collectibles/Cells/List/CollectiblesCollectionCell.swift +++ b/Kukai Mobile/Modules/Collectibles/Cells/List/CollectiblesCollectionCell.swift @@ -82,26 +82,6 @@ class CollectiblesCollectionCell: UICollectionViewCell, UITableViewCellImageDown } } - // TODO: gradient - /* - override func layoutSubviews() { - super.layoutSubviews() - - if self.previousGradientBounds.width != self.contentView.bounds.width { - addGradientBackground() // Strange loading issue. First time it loads these cells, width is 50% of what it should be - } - } - - public func addGradientBackground() { - contentView.customCornerRadius = 8 - contentView.maskToBounds = true - gradientLayer?.removeFromSuperlayer() - gradientLayer = self.contentView.addGradientPanelRows(withFrame: self.contentView.bounds) - - self.previousGradientBounds = self.contentView.bounds - } - */ - override func prepareForReuse() { for imageView in imageViews { imageView.image = nil diff --git a/Kukai Mobile/Modules/Fees/FeeInfoViewController.swift b/Kukai Mobile/Modules/Fees/FeeInfoViewController.swift index 313fee0f..2af3617a 100644 --- a/Kukai Mobile/Modules/Fees/FeeInfoViewController.swift +++ b/Kukai Mobile/Modules/Fees/FeeInfoViewController.swift @@ -8,9 +8,15 @@ import UIKit class FeeInfoViewController: UIViewController { - + + @IBInspectable var addGradient: Bool = true + override func viewDidLoad() { super.viewDidLoad() - GradientView.add(toView: self.view, withType: .fullScreenBackground) + self.view.backgroundColor = .clear + + if addGradient { + GradientView.add(toView: self.view, withType: .fullScreenBackground) + } } } diff --git a/Kukai Mobile/Modules/Home/Base.lproj/Home.storyboard b/Kukai Mobile/Modules/Home/Base.lproj/Home.storyboard index 5371a6be..d89c22e8 100644 --- a/Kukai Mobile/Modules/Home/Base.lproj/Home.storyboard +++ b/Kukai Mobile/Modules/Home/Base.lproj/Home.storyboard @@ -1551,10 +1551,10 @@ - + - + diff --git a/Kukai Mobile/Modules/Send/Base.lproj/Send.storyboard b/Kukai Mobile/Modules/Send/Base.lproj/Send.storyboard index cc8a24a2..3486bdd5 100644 --- a/Kukai Mobile/Modules/Send/Base.lproj/Send.storyboard +++ b/Kukai Mobile/Modules/Send/Base.lproj/Send.storyboard @@ -467,115 +467,80 @@ - + - - + + - - + + + + + + + + + - - - + + - + + - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + + + + + + + - + + + + + + + + - - - - + + + + @@ -591,25 +556,25 @@ - + @@ -717,7 +682,7 @@ - + @@ -897,7 +859,7 @@ - + @@ -950,123 +912,64 @@ - + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - - + + + - - - - - + + + + + + + + + + - - - - + + + + + + @@ -1082,281 +985,128 @@ - + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - - + + + - - - - - + + + + + + + + + + + - - - - - - - + + + + + + + + + + + @@ -1580,37 +1330,26 @@ + + - - - - - - - - - - - + - - - - - - - - - - + + + + + + + - + @@ -1623,116 +1362,81 @@ - - + + - - + + - - + + + + + + + + + - - - + + - + + - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + + + + + + + - + + + + + + + + - - - - + + + + @@ -1748,7 +1452,7 @@ - + @@ -1761,25 +1465,25 @@ - + - + - + - + - + - + - + - + @@ -1970,15 +1674,15 @@ - - + + - + + - - - + + @@ -1991,11 +1695,11 @@ - - + + @@ -2030,9 +1734,9 @@ - - - + + + @@ -2041,11 +1745,8 @@ - - - @@ -2106,7 +1807,7 @@ - + @@ -2158,124 +1859,65 @@ - - + + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - - + + + - - - - - + + + + + + + + + + - - - - + + + + + + @@ -2289,121 +1931,85 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - + - - - + + + - - - + + - + + - - + - + + + + + + + + - + + + + + + + + - - - - + + + + @@ -2451,16 +2057,16 @@ - + - + @@ -2493,7 +2099,7 @@ - + @@ -2744,24 +2350,18 @@ - - - - - - - + + + + - - - - - - + + + @@ -2853,7 +2453,7 @@ - + @@ -2905,124 +2505,65 @@ - - + + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - - + + + - - - - - + + + + + + + + + + - - - - + + + + + + @@ -3038,165 +2579,10 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -3263,16 +2649,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - - - + + @@ -3496,37 +2943,29 @@ - - - - - - - - - - - - + + + + - - - - + + + + - + @@ -3847,7 +3286,7 @@ - + @@ -3899,124 +3338,65 @@ - - + + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - - + + + - - - - - + + + + + + + + + + - - - - + + + + + + @@ -4032,7 +3412,7 @@ - + @@ -4072,7 +3452,7 @@ - + @@ -4344,13 +3724,10 @@ - - - - - - - + + + + @@ -4610,9 +3987,6 @@ - - - diff --git a/Kukai Mobile/Modules/Send/SendAbstractConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendAbstractConfirmViewController.swift index 83db28d0..84919a1a 100644 --- a/Kukai Mobile/Modules/Send/SendAbstractConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendAbstractConfirmViewController.swift @@ -99,13 +99,25 @@ class SendAbstractConfirmViewController: UIViewController { func dismissAndReturn(collapseOnly: Bool) { DispatchQueue.main.async { [weak self] in - self?.dismiss(animated: true, completion: { - UIViewController.removeLoadingView() - UIViewController.removeLoadingModal() - }) + let topMostNavigationController = ((self?.presentationController?.presentingViewController as? UINavigationController)?.presentationController?.presentingViewController as? UINavigationController) + let isDuringStakeOnboardingFlow = topMostNavigationController?.viewControllers.contains(where: { $0 is StakeOnboardingContainerViewController }) ?? false + // Only directly dismiss the current confirmation screen if its not part of the stake onboarding flow, as that requires a special dismiss to avoid temporary screens popping up + if !(isDuringStakeOnboardingFlow && collapseOnly == false) { + self?.dismiss(animated: true, completion: { + UIViewController.removeLoadingView() + UIViewController.removeLoadingModal() + }) + } + + // If we are part of stake onboarding flow we want to just dismiss the entire thing in one action. Otherwise we need two types of animations if collapseOnly == false { - (self?.presentingViewController as? UINavigationController)?.popToHome() + if isDuringStakeOnboardingFlow { + topMostNavigationController?.dismiss(animated: true) + + } else { + (self?.presentingViewController as? UINavigationController)?.popToHome() + } } } } diff --git a/Kukai Mobile/Modules/Send/SendBatchConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendBatchConfirmViewController.swift index b53f7259..cf0addbb 100644 --- a/Kukai Mobile/Modules/Send/SendBatchConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendBatchConfirmViewController.swift @@ -23,26 +23,15 @@ class SendBatchConfirmViewController: SendAbstractConfirmViewController, SlideBu // From @IBOutlet weak var fromContainer: UIView! - - @IBOutlet weak var fromStackViewSocial: UIStackView! - @IBOutlet weak var fromSocialIcon: UIImageView! - @IBOutlet weak var fromSocialAlias: UILabel! - @IBOutlet weak var fromSocialAddress: UILabel! - - @IBOutlet weak var fromStackViewRegular: UIStackView! - @IBOutlet weak var fromRegularAddress: UILabel! + @IBOutlet weak var fromIcon: UIImageView! + @IBOutlet weak var fromAlias: UILabel! + @IBOutlet weak var fromAddress: UILabel! // Send - @IBOutlet weak var largeDisplayStackView: UIStackView! - @IBOutlet weak var largeDisplayIcon: UIImageView! - @IBOutlet weak var largeDisplayAmount: UILabel! - @IBOutlet weak var largeDisplaySymbol: UILabel! - @IBOutlet weak var largeDisplayFiat: UILabel! - - @IBOutlet weak var smallDisplayStackView: UIStackView! - @IBOutlet weak var smallDisplayIcon: UIImageView! - @IBOutlet weak var smallDisplayAmount: UILabel! - @IBOutlet weak var smallDisplayFiat: UILabel! + @IBOutlet weak var tokenIcon: UIImageView! + @IBOutlet weak var tokenAmount: UILabel! + @IBOutlet weak var tokenSymbol: UILabel! + @IBOutlet weak var tokenFiat: UILabel! // To @IBOutlet weak var toBatchView: UIView! @@ -93,17 +82,6 @@ class SendBatchConfirmViewController: SendAbstractConfirmViewController, SlideBu connectedAppURL = smallIconURL } - let media = TransactionService.walletMedia(forWalletMetadata: walletMetadataForRequestedAccount, ofSize: .size_22) - if let subtitle = media.subtitle { - fromStackViewRegular.isHidden = true - fromSocialAlias.text = media.title - fromSocialIcon.image = media.image - fromSocialAddress.text = subtitle - } else { - fromStackViewSocial.isHidden = true - fromRegularAddress.text = media.title - } - } else { self.isWalletConnectOp = false self.currentBatchData = TransactionService.shared.batchData @@ -111,7 +89,24 @@ class SendBatchConfirmViewController: SendAbstractConfirmViewController, SlideBu connectedAppMetadataStackView.isHidden = true connectedAppLabel.isHidden = true - fromContainer.isHidden = true + } + + // From + guard let selectedMetadata = selectedMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.dismissBottomSheet() + return + } + + let media = TransactionService.walletMedia(forWalletMetadata: selectedMetadata, ofSize: .size_22) + if let subtitle = media.subtitle { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.text = subtitle + } else { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.isHidden = true } @@ -208,40 +203,16 @@ class SendBatchConfirmViewController: SendAbstractConfirmViewController, SlideBu func updateAmountDisplay() { guard let token = self.currentBatchData.mainDisplayToken, let amount = self.currentBatchData.mainDisplayAmount else { - largeDisplayStackView.isHidden = true - smallDisplayIcon.image = UIImage.unknownToken() - smallDisplayAmount.text = "0" - smallDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: Token.xtz(), ofAmount: TokenAmount.zero()) + tokenIcon.image = UIImage.unknownToken() + tokenAmount.text = "0" + tokenFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: Token.xtz(), ofAmount: TokenAmount.zero()) return } - let approxSizeOfOccupiedSpace: CGFloat = 180 // Yes "magic number", deal with it, very unique business logic at play - let remainder = UIScreen.main.bounds.width - approxSizeOfOccupiedSpace let amountText = DependencyManager.shared.coinGeckoService.format(decimal: amount.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) - - var amountWidth = amountText.widthOfString(usingFont: largeDisplayAmount.font) - amountWidth.round(.up) - - var symbolWidth = token.symbol.widthOfString(usingFont: largeDisplaySymbol.font) - symbolWidth.round(.up) - - if (amountWidth + symbolWidth) > remainder { - - // Display with more room for long length numbers - largeDisplayStackView.isHidden = true - smallDisplayIcon.addTokenIcon(token: token) - smallDisplayAmount.text = amountText + " " + token.symbol - smallDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: amount) - } else { - - // Display with less room and more detail - smallDisplayStackView.isHidden = true - largeDisplayIcon.addTokenIcon(token: token) - largeDisplayAmount.text = amountText - - largeDisplaySymbol.text = token.symbol - largeDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: amount) - } + tokenIcon.addTokenIcon(token: token) + tokenAmount.text = amountText + " " + token.symbol + tokenFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: amount) } func updateFees(isFirstCall: Bool = false) { diff --git a/Kukai Mobile/Modules/Send/SendCollectibleAmountViewController.swift b/Kukai Mobile/Modules/Send/SendCollectibleAmountViewController.swift index 2a6476e4..ad34523f 100644 --- a/Kukai Mobile/Modules/Send/SendCollectibleAmountViewController.swift +++ b/Kukai Mobile/Modules/Send/SendCollectibleAmountViewController.swift @@ -12,13 +12,10 @@ import KukaiCoreSwift class SendCollectibleAmountViewController: UIViewController { @IBOutlet weak var scrollView: UIScrollView! - @IBOutlet weak var toStackViewSocial: UIStackView! - @IBOutlet weak var toStackViewRegular: UIStackView! @IBOutlet weak var addressIcon: UIImageView! @IBOutlet weak var addressAliasLabel: UILabel! @IBOutlet weak var addressLabel: UILabel! - @IBOutlet weak var regularAddressLabel: UILabel! @IBOutlet weak var collectibleImage: UIImageView! @IBOutlet weak var collectibleName: UILabel! @@ -46,14 +43,14 @@ class SendCollectibleAmountViewController: UIViewController { // To section if let alias = TransactionService.shared.sendData.destinationAlias { - toStackViewRegular.isHidden = true - addressAliasLabel.text = alias addressIcon.image = TransactionService.shared.sendData.destinationIcon + addressAliasLabel.text = alias addressLabel.text = TransactionService.shared.sendData.destination?.truncateTezosAddress() } else { - toStackViewSocial.isHidden = true - regularAddressLabel.text = TransactionService.shared.sendData.destination?.truncateTezosAddress() + addressIcon.image = TransactionService.shared.sendData.destinationIcon + addressAliasLabel.text = TransactionService.shared.sendData.destination?.truncateTezosAddress() + addressLabel.isHidden = true } diff --git a/Kukai Mobile/Modules/Send/SendCollectibleConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendCollectibleConfirmViewController.swift index e73f9eeb..617eca17 100644 --- a/Kukai Mobile/Modules/Send/SendCollectibleConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendCollectibleConfirmViewController.swift @@ -23,14 +23,9 @@ class SendCollectibleConfirmViewController: SendAbstractConfirmViewController, S // From @IBOutlet weak var fromContainer: UIView! - - @IBOutlet weak var fromStackViewSocial: UIStackView! - @IBOutlet weak var fromSocialIcon: UIImageView! - @IBOutlet weak var fromSocialAlias: UILabel! - @IBOutlet weak var fromSocialAddress: UILabel! - - @IBOutlet weak var fromStackViewRegular: UIStackView! - @IBOutlet weak var fromRegularAddress: UILabel! + @IBOutlet weak var fromIcon: UIImageView! + @IBOutlet weak var fromAlias: UILabel! + @IBOutlet weak var fromAddress: UILabel! // Send @IBOutlet weak var collectibleImage: UIImageView! @@ -40,13 +35,9 @@ class SendCollectibleConfirmViewController: SendAbstractConfirmViewController, S @IBOutlet weak var quantityStackView: UIStackView! @IBOutlet weak var collectibleQuantityLabel: UILabel! - @IBOutlet weak var toStackViewSocial: UIStackView! - @IBOutlet weak var socialIcon: UIImageView! - @IBOutlet weak var socialAlias: UILabel! - @IBOutlet weak var socialAddress: UILabel! - - @IBOutlet weak var toStackViewRegular: UIStackView! - @IBOutlet weak var regularAddress: UILabel! + @IBOutlet weak var toIcon: UIImageView! + @IBOutlet weak var toAlias: UILabel! + @IBOutlet weak var toAddress: UILabel! @IBOutlet weak var feeValueLabel: UILabel! @IBOutlet weak var feeButton: CustomisableButton! @@ -89,17 +80,6 @@ class SendCollectibleConfirmViewController: SendAbstractConfirmViewController, S connectedAppURL = smallIconURL } - let media = TransactionService.walletMedia(forWalletMetadata: walletMetadataForRequestedAccount, ofSize: .size_22) - if let subtitle = media.subtitle { - fromStackViewRegular.isHidden = true - fromSocialAlias.text = media.title - fromSocialIcon.image = media.image - fromSocialAddress.text = subtitle - } else { - fromStackViewSocial.isHidden = true - fromRegularAddress.text = media.title - } - } else { self.isWalletConnectOp = false self.currentSendData = TransactionService.shared.sendData @@ -107,7 +87,6 @@ class SendCollectibleConfirmViewController: SendAbstractConfirmViewController, S connectedAppMetadataStackView.isHidden = true connectedAppLabel.isHidden = true - fromContainer.isHidden = true } @@ -115,18 +94,35 @@ class SendCollectibleConfirmViewController: SendAbstractConfirmViewController, S updateAmountDisplay() + // From + guard let selectedMetadata = selectedMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.dismissBottomSheet() + return + } + + let media = TransactionService.walletMedia(forWalletMetadata: selectedMetadata, ofSize: .size_22) + if let subtitle = media.subtitle { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.text = subtitle + } else { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.isHidden = true + } + + // Destination view configuration if let alias = currentSendData.destinationAlias { - // social display - toStackViewRegular.isHidden = true - socialAlias.text = alias - socialIcon.image = currentSendData.destinationIcon - socialAddress.text = currentSendData.destination?.truncateTezosAddress() + toIcon.image = currentSendData.destinationIcon + toAlias.text = alias + toAddress.text = currentSendData.destination?.truncateTezosAddress() } else { - // basic display - toStackViewSocial.isHidden = true - regularAddress.text = currentSendData.destination?.truncateTezosAddress() + toIcon.image = currentSendData.destinationIcon + toAlias.text = currentSendData.destination?.truncateTezosAddress() + toAddress.isHidden = true } diff --git a/Kukai Mobile/Modules/Send/SendGenericConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendGenericConfirmViewController.swift index fb2a4227..aefc1ea8 100644 --- a/Kukai Mobile/Modules/Send/SendGenericConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendGenericConfirmViewController.swift @@ -23,26 +23,9 @@ class SendGenericConfirmViewController: SendAbstractConfirmViewController, Slide // From @IBOutlet weak var fromContainer: UIView! - - @IBOutlet weak var fromStackViewSocial: UIStackView! - @IBOutlet weak var fromSocialIcon: UIImageView! - @IBOutlet weak var fromSocialAlias: UILabel! - @IBOutlet weak var fromSocialAddress: UILabel! - - @IBOutlet weak var fromStackViewRegular: UIStackView! - @IBOutlet weak var fromRegularAddress: UILabel! - - // Send - @IBOutlet weak var largeDisplayStackView: UIStackView! - @IBOutlet weak var largeDisplayIcon: UIImageView! - @IBOutlet weak var largeDisplayAmount: UILabel! - @IBOutlet weak var largeDisplaySymbol: UILabel! - @IBOutlet weak var largeDisplayFiat: UILabel! - - @IBOutlet weak var smallDisplayStackView: UIStackView! - @IBOutlet weak var smallDisplayIcon: UIImageView! - @IBOutlet weak var smallDisplayAmount: UILabel! - @IBOutlet weak var smallDisplayFiat: UILabel! + @IBOutlet weak var fromIcon: UIImageView! + @IBOutlet weak var fromAlias: UILabel! + @IBOutlet weak var fromAddress: UILabel! // Operation @IBOutlet weak var moreButton: CustomisableButton! @@ -90,17 +73,6 @@ class SendGenericConfirmViewController: SendAbstractConfirmViewController, Slide connectedAppURL = smallIconURL } - let media = TransactionService.walletMedia(forWalletMetadata: walletMetadataForRequestedAccount, ofSize: .size_22) - if let subtitle = media.subtitle { - fromStackViewRegular.isHidden = true - fromSocialAlias.text = media.title - fromSocialIcon.image = media.image - fromSocialAddress.text = subtitle - } else { - fromStackViewSocial.isHidden = true - fromRegularAddress.text = media.title - } - } else { self.isWalletConnectOp = false self.currentSendData = TransactionService.shared.sendData @@ -108,7 +80,25 @@ class SendGenericConfirmViewController: SendAbstractConfirmViewController, Slide connectedAppMetadataStackView.isHidden = true connectedAppLabel.isHidden = true - fromContainer.isHidden = true + } + + + // From + guard let selectedMetadata = selectedMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.dismissBottomSheet() + return + } + + let media = TransactionService.walletMedia(forWalletMetadata: selectedMetadata, ofSize: .size_22) + if let subtitle = media.subtitle { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.text = subtitle + } else { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.isHidden = true } diff --git a/Kukai Mobile/Modules/Send/SendTokenAmountViewController.swift b/Kukai Mobile/Modules/Send/SendTokenAmountViewController.swift index 4686e217..7a30b43b 100644 --- a/Kukai Mobile/Modules/Send/SendTokenAmountViewController.swift +++ b/Kukai Mobile/Modules/Send/SendTokenAmountViewController.swift @@ -9,14 +9,10 @@ import UIKit import KukaiCoreSwift class SendTokenAmountViewController: UIViewController { - - @IBOutlet weak var toStackViewSocial: UIStackView! - @IBOutlet weak var toStackViewRegular: UIStackView! @IBOutlet weak var addressIcon: UIImageView! @IBOutlet weak var addressAliasLabel: UILabel! @IBOutlet weak var addressLabel: UILabel! - @IBOutlet weak var regularAddressLabel: UILabel! @IBOutlet weak var balanceLabel: UILabel! @IBOutlet weak var inputContainer: UIView! @@ -44,14 +40,14 @@ class SendTokenAmountViewController: UIViewController { // To section if let alias = TransactionService.shared.sendData.destinationAlias { - toStackViewRegular.isHidden = true - addressAliasLabel.text = alias addressIcon.image = TransactionService.shared.sendData.destinationIcon + addressAliasLabel.text = alias addressLabel.text = TransactionService.shared.sendData.destination?.truncateTezosAddress() } else { - toStackViewSocial.isHidden = true - regularAddressLabel.text = TransactionService.shared.sendData.destination?.truncateTezosAddress() + addressIcon.image = TransactionService.shared.sendData.destinationIcon + addressAliasLabel.text = TransactionService.shared.sendData.destination?.truncateTezosAddress() + addressLabel.isHidden = true } diff --git a/Kukai Mobile/Modules/Send/SendTokenConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendTokenConfirmViewController.swift index d9548e40..58dc008d 100644 --- a/Kukai Mobile/Modules/Send/SendTokenConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendTokenConfirmViewController.swift @@ -23,35 +23,20 @@ class SendTokenConfirmViewController: SendAbstractConfirmViewController, SlideBu // From @IBOutlet weak var fromContainer: UIView! - - @IBOutlet weak var fromStackViewSocial: UIStackView! - @IBOutlet weak var fromSocialIcon: UIImageView! - @IBOutlet weak var fromSocialAlias: UILabel! - @IBOutlet weak var fromSocialAddress: UILabel! - - @IBOutlet weak var fromStackViewRegular: UIStackView! - @IBOutlet weak var fromRegularAddress: UILabel! + @IBOutlet weak var fromIcon: UIImageView! + @IBOutlet weak var fromAlias: UILabel! + @IBOutlet weak var fromAddress: UILabel! // Send - @IBOutlet weak var largeDisplayStackView: UIStackView! - @IBOutlet weak var largeDisplayIcon: UIImageView! - @IBOutlet weak var largeDisplayAmount: UILabel! - @IBOutlet weak var largeDisplaySymbol: UILabel! - @IBOutlet weak var largeDisplayFiat: UILabel! - - @IBOutlet weak var smallDisplayStackView: UIStackView! - @IBOutlet weak var smallDisplayIcon: UIImageView! - @IBOutlet weak var smallDisplayAmount: UILabel! - @IBOutlet weak var smallDisplayFiat: UILabel! + @IBOutlet weak var tokenIcon: UIImageView! + @IBOutlet weak var tokenAmount: UILabel! + @IBOutlet weak var tokenSymbol: UILabel! + @IBOutlet weak var tokenFiat: UILabel! // To - @IBOutlet weak var toStackViewSocial: UIStackView! - @IBOutlet weak var toSocialIcon: UIImageView! - @IBOutlet weak var toSocialAlias: UILabel! - @IBOutlet weak var toSocialAddress: UILabel! - - @IBOutlet weak var toStackViewRegular: UIStackView! - @IBOutlet weak var toRegularAddress: UILabel! + @IBOutlet weak var toIcon: UIImageView! + @IBOutlet weak var toAlias: UILabel! + @IBOutlet weak var toAddress: UILabel! // Fee @IBOutlet weak var feeValueLabel: UILabel! @@ -97,17 +82,6 @@ class SendTokenConfirmViewController: SendAbstractConfirmViewController, SlideBu connectedAppURL = smallIconURL } - let media = TransactionService.walletMedia(forWalletMetadata: walletMetadataForRequestedAccount, ofSize: .size_22) - if let subtitle = media.subtitle { - fromStackViewRegular.isHidden = true - fromSocialAlias.text = media.title - fromSocialIcon.image = media.image - fromSocialAddress.text = subtitle - } else { - fromStackViewSocial.isHidden = true - fromRegularAddress.text = media.title - } - } else { self.isWalletConnectOp = false self.currentSendData = TransactionService.shared.sendData @@ -115,22 +89,38 @@ class SendTokenConfirmViewController: SendAbstractConfirmViewController, SlideBu connectedAppMetadataStackView.isHidden = true connectedAppLabel.isHidden = true - fromContainer.isHidden = true + } + + + // From + guard let selectedMetadata = selectedMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.dismissBottomSheet() + return + } + + let media = TransactionService.walletMedia(forWalletMetadata: selectedMetadata, ofSize: .size_22) + if let subtitle = media.subtitle { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.text = subtitle + } else { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.isHidden = true } // Destination view configuration if let alias = currentSendData.destinationAlias { - // social display - toStackViewRegular.isHidden = true - toSocialAlias.text = alias - toSocialIcon.image = currentSendData.destinationIcon - toSocialAddress.text = currentSendData.destination?.truncateTezosAddress() + toIcon.image = currentSendData.destinationIcon + toAlias.text = alias + toAddress.text = currentSendData.destination?.truncateTezosAddress() } else { - // basic display - toStackViewSocial.isHidden = true - toRegularAddress.text = currentSendData.destination?.truncateTezosAddress() + toIcon.image = currentSendData.destinationIcon + toAlias.text = currentSendData.destination?.truncateTezosAddress() + toAddress.isHidden = true } @@ -209,40 +199,18 @@ class SendTokenConfirmViewController: SendAbstractConfirmViewController, SlideBu func updateAmountDisplay(withValue value: TokenAmount) { guard let token = currentSendData.chosenToken else { - largeDisplayStackView.isHidden = true - smallDisplayIcon.image = UIImage.unknownToken() - smallDisplayAmount.text = "0" - smallDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: Token.xtz(), ofAmount: TokenAmount.zero()) + tokenIcon.image = UIImage.unknownToken() + tokenAmount.text = "0" + tokenFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: Token.xtz(), ofAmount: TokenAmount.zero()) return } - let approxSizeOfOccupiedSpace: CGFloat = 180 // Yes "magic number", deal with it, very unique business logic at play - let remainder = UIScreen.main.bounds.width - approxSizeOfOccupiedSpace let amountText = DependencyManager.shared.coinGeckoService.format(decimal: value.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: value.decimalPlaces) - var amountWidth = amountText.widthOfString(usingFont: largeDisplayAmount.font) - amountWidth.round(.up) - - var symbolWidth = token.symbol.widthOfString(usingFont: largeDisplaySymbol.font) - symbolWidth.round(.up) - - if (amountWidth + symbolWidth) > remainder { - - // Display with more room for long length numbers - largeDisplayStackView.isHidden = true - smallDisplayIcon.addTokenIcon(token: token) - smallDisplayAmount.text = amountText + " " + token.symbol - smallDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: value) - } else { - - // Display with less room and more detail - smallDisplayStackView.isHidden = true - largeDisplayIcon.addTokenIcon(token: token) - largeDisplayAmount.text = amountText - - largeDisplaySymbol.text = token.symbol - largeDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: value) - } + tokenIcon.addTokenIcon(token: token) + tokenAmount.text = amountText + tokenSymbol.text = token.symbol + tokenFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: value) } func updateFees(isFirstCall: Bool = false) { diff --git a/Kukai Mobile/Modules/Stake/BakerDetailsViewController.swift b/Kukai Mobile/Modules/Stake/BakerDetailsViewController.swift index 14c452e0..70c6e311 100644 --- a/Kukai Mobile/Modules/Stake/BakerDetailsViewController.swift +++ b/Kukai Mobile/Modules/Stake/BakerDetailsViewController.swift @@ -17,7 +17,7 @@ class BakerDetailsViewController: UIViewController { @IBOutlet weak var rewardslabel: UILabel! @IBOutlet weak var freeLabel: UILabel! - @IBOutlet weak var stakeButton: CustomisableButton! + @IBOutlet weak var delegateButton: CustomisableButton! var dimBackground: Bool = false @@ -25,7 +25,7 @@ class BakerDetailsViewController: UIViewController { super.viewDidLoad() GradientView.add(toView: self.view, withType: .fullScreenBackground) - stakeButton.customButtonType = .primary + delegateButton.customButtonType = .primary } override func viewWillAppear(_ animated: Bool) { @@ -35,7 +35,7 @@ class BakerDetailsViewController: UIViewController { return } - stakeButton.isHidden = DependencyManager.shared.balanceService.account.delegate?.address == baker.address + delegateButton.isHidden = DependencyManager.shared.balanceService.account.delegate?.address == baker.address MediaProxyService.load(url: baker.logo, to: bakerIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) @@ -50,9 +50,9 @@ class BakerDetailsViewController: UIViewController { self.dismissBottomSheet() } - @IBAction func stakeTapped(_ sender: Any) { - let parent = ((self.presentationController?.presentingViewController as? UINavigationController)?.viewControllers.last as? StakeViewController) - parent?.stakeTapped() + @IBAction func delegateTapped(_ sender: Any) { + let parent = ((self.presentationController?.presentingViewController as? UINavigationController)?.viewControllers.last as? ChooseBakerViewController) + parent?.delegateTapped() self.dismissBottomSheet() } } diff --git a/Kukai Mobile/Modules/Stake/Cells/StakeHeadingCell.swift b/Kukai Mobile/Modules/Stake/Cells/ChooseBakerHeadingCell.swift similarity index 69% rename from Kukai Mobile/Modules/Stake/Cells/StakeHeadingCell.swift rename to Kukai Mobile/Modules/Stake/Cells/ChooseBakerHeadingCell.swift index 728a93c7..5a2c2217 100644 --- a/Kukai Mobile/Modules/Stake/Cells/StakeHeadingCell.swift +++ b/Kukai Mobile/Modules/Stake/Cells/ChooseBakerHeadingCell.swift @@ -1,5 +1,5 @@ // -// StakeHeadingCell.swift +// ChooseBakerHeadingCell.swift // Kukai Mobile // // Created by Simon Mcloughlin on 09/09/2022. @@ -7,7 +7,7 @@ import UIKit -class StakeHeadingCell: UITableViewCell { +class ChooseBakerHeadingCell: UITableViewCell { @IBOutlet weak var headingLabel: UILabel! @IBOutlet weak var actionTitleLabel: UILabel? diff --git a/Kukai Mobile/Modules/Stake/ConfirmStakeViewController.swift b/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift similarity index 85% rename from Kukai Mobile/Modules/Stake/ConfirmStakeViewController.swift rename to Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift index b811bd15..d11a502b 100644 --- a/Kukai Mobile/Modules/Stake/ConfirmStakeViewController.swift +++ b/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift @@ -1,5 +1,5 @@ // -// ConfirmStakeViewController.swift +// ChooseBakerConfirmViewController.swift // Kukai Mobile // // Created by Simon Mcloughlin on 21/07/2023. @@ -10,8 +10,9 @@ import KukaiCoreSwift import ReownWalletKit import os.log -class ConfirmStakeViewController: SendAbstractConfirmViewController, SlideButtonDelegate, EditFeesViewControllerDelegate { +class ChooseBakerConfirmViewController: SendAbstractConfirmViewController, SlideButtonDelegate, EditFeesViewControllerDelegate { + @IBOutlet var scrollView: UIScrollView! @IBOutlet weak var closeButton: CustomisableButton! // Connected app @@ -20,7 +21,13 @@ class ConfirmStakeViewController: SendAbstractConfirmViewController, SlideButton @IBOutlet weak var connectedAppNameLabel: UILabel! @IBOutlet weak var connectedAppMetadataStackView: UIStackView! - @IBOutlet weak var containerView: GradientView! + // From + @IBOutlet weak var fromContainer: UIView! + @IBOutlet weak var fromIcon: UIImageView! + @IBOutlet weak var fromAlias: UILabel! + @IBOutlet weak var fromAddress: UILabel! + + // Baker @IBOutlet weak var confirmBakerAddView: UIView! @IBOutlet weak var bakerAddIcon: UIImageView! @IBOutlet weak var bakerAddNameLabel: UILabel! @@ -86,6 +93,25 @@ class ConfirmStakeViewController: SendAbstractConfirmViewController, SlideButton } + // From + guard let selectedMetadata = selectedMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.dismissBottomSheet() + return + } + + let media = TransactionService.walletMedia(forWalletMetadata: selectedMetadata, ofSize: .size_22) + if let subtitle = media.subtitle { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.text = subtitle + } else { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.isHidden = true + } + + // Baker info config if self.currentDelegateData.isAdd == true { confirmBakerRemoveView.isHidden = true @@ -110,14 +136,14 @@ class ConfirmStakeViewController: SendAbstractConfirmViewController, SlideButton // Fees and amount view config slideErrorStackView.isHidden = true + feeValueLabel.accessibilityIdentifier = "fee-amount" + feeButton.customButtonType = .secondary slideButton.delegate = self } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - containerView.gradientType = .tableViewCell - updateFees(isFirstCall: true) } @@ -215,14 +241,19 @@ class ConfirmStakeViewController: SendAbstractConfirmViewController, SlideButton } } -extension ConfirmStakeViewController: BottomSheetCustomCalculateProtocol { +extension ChooseBakerConfirmViewController: BottomSheetCustomCalculateProtocol { func bottomSheetHeight() -> CGFloat { viewDidLoad() + scrollView.setNeedsLayout() view.setNeedsLayout() + scrollView.layoutIfNeeded() view.layoutIfNeeded() - return view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height + var height = view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height + height += scrollView.contentSize.height + + return height } } diff --git a/Kukai Mobile/Modules/Stake/StakeViewController.swift b/Kukai Mobile/Modules/Stake/ChooseBakerViewController.swift similarity index 77% rename from Kukai Mobile/Modules/Stake/StakeViewController.swift rename to Kukai Mobile/Modules/Stake/ChooseBakerViewController.swift index 38869c33..a667d15a 100644 --- a/Kukai Mobile/Modules/Stake/StakeViewController.swift +++ b/Kukai Mobile/Modules/Stake/ChooseBakerViewController.swift @@ -9,15 +9,17 @@ import UIKit import KukaiCoreSwift import Combine -class StakeViewController: UIViewController { +class ChooseBakerViewController: UIViewController { @IBOutlet weak var tableView: UITableView! - private let viewModel = StakeViewModel() + private var viewModel = ChooseBakerViewModel() private var cancellable: AnyCancellable? private let footerView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 2)) private let blankView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 0.1)) + public static let notificationNameBakerChosen = Notification.Name("notification-baker-chosen") + override func viewDidLoad() { super.viewDidLoad() GradientView.add(toView: self.view, withType: .fullScreenBackground) @@ -47,7 +49,10 @@ class StakeViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - viewModel.refresh(animate: false) + + if viewModel.bakers.count == 0 { + viewModel.refresh(animate: false) + } } public func enteredCustomBaker(address: String) { @@ -56,9 +61,10 @@ class StakeViewController: UIViewController { if address == "" { let currentDelegate = DependencyManager.shared.balanceService.account.delegate let name = currentDelegate?.alias ?? currentDelegate?.address.truncateTezosAddress() ?? "" - let baker = TzKTBaker(address: "", name: name) + let baker = TzKTBaker(address: currentDelegate?.address ?? "", name: name) TransactionService.shared.delegateData.chosenBaker = baker + TransactionService.shared.stakeData.chosenBaker = baker TransactionService.shared.delegateData.isAdd = false } else { @@ -66,11 +72,13 @@ class StakeViewController: UIViewController { if let foundBaker = viewModel.bakerFor(address: address) { TransactionService.shared.delegateData.chosenBaker = foundBaker + TransactionService.shared.stakeData.chosenBaker = foundBaker TransactionService.shared.delegateData.isAdd = true } else { let baker = TzKTBaker(address: address, name: address.truncateTezosAddress()) TransactionService.shared.delegateData.chosenBaker = baker + TransactionService.shared.stakeData.chosenBaker = baker TransactionService.shared.delegateData.isAdd = true } } @@ -78,11 +86,14 @@ class StakeViewController: UIViewController { createOperationsAndConfirm(toAddress: address) } - public func stakeTapped() { + public func delegateTapped() { self.showLoadingView() if let baker = TransactionService.shared.delegateData.chosenBaker { - createOperationsAndConfirm(toAddress: baker.address) + // Add a breif delay so that multiple animations can finish + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in + self?.createOperationsAndConfirm(toAddress: baker.address) + } } } @@ -92,7 +103,13 @@ class StakeViewController: UIViewController { return } - let operations = OperationFactory.delegateOperation(to: toAddress, from: selectedWallet.address) + var operations: [KukaiCoreSwift.Operation] = [] + if TransactionService.shared.delegateData.isAdd == true { + operations = OperationFactory.delegateOperation(to: toAddress, from: selectedWallet.address) + } else { + operations = OperationFactory.undelegateOperation(address: selectedWallet.address) + } + DependencyManager.shared.tezosNodeClient.estimate(operations: operations, walletAddress: selectedWallet.address, base58EncodedPublicKey: selectedWallet.publicKeyBase58encoded()) { [weak self] estimationResult in self?.hideLoadingView() @@ -109,7 +126,7 @@ class StakeViewController: UIViewController { } } -extension StakeViewController: UITableViewDelegate { +extension ChooseBakerViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0 @@ -127,6 +144,7 @@ extension StakeViewController: UITableViewDelegate { if let baker = viewModel.bakerFor(indexPath: indexPath) { TransactionService.shared.delegateData.chosenBaker = baker + TransactionService.shared.stakeData.chosenBaker = baker TransactionService.shared.delegateData.isAdd = true self.performSegue(withIdentifier: "details", sender: nil) diff --git a/Kukai Mobile/Modules/Stake/StakeViewModel.swift b/Kukai Mobile/Modules/Stake/ChooseBakerViewModel.swift similarity index 79% rename from Kukai Mobile/Modules/Stake/StakeViewModel.swift rename to Kukai Mobile/Modules/Stake/ChooseBakerViewModel.swift index 107ed14a..24e5d8a1 100644 --- a/Kukai Mobile/Modules/Stake/StakeViewModel.swift +++ b/Kukai Mobile/Modules/Stake/ChooseBakerViewModel.swift @@ -10,12 +10,12 @@ import KukaiCoreSwift import Combine import OSLog -struct StakeHeaderData: Hashable { +struct ChooseBakerHeaderData: Hashable { let title: String let actionTitle: String? } -class StakeViewModel: ViewModel, UITableViewDiffableDataSourceHandler { +class ChooseBakerViewModel: ViewModel, UITableViewDiffableDataSourceHandler { typealias SectionEnum = Int typealias CellDataType = AnyHashableSendable @@ -32,15 +32,6 @@ class StakeViewModel: ViewModel, UITableViewDiffableDataSourceHandler { override init() { super.init() - - DependencyManager.shared.$addressRefreshed - .dropFirst() - .sink { [weak self] address in - let selectedAddress = DependencyManager.shared.selectedWalletAddress ?? "" - if self?.dataSource != nil && selectedAddress == address { - self?.refresh(animate: true) - } - }.store(in: &bag) } deinit { @@ -58,12 +49,12 @@ class StakeViewModel: ViewModel, UITableViewDiffableDataSourceHandler { cell.setup(withBaker: obj) return cell - } else if let obj = item.base as? StakeHeaderData, obj.actionTitle != nil, let cell = tableView.dequeueReusableCell(withIdentifier: "StakeHeadingAndActionCell", for: indexPath) as? StakeHeadingCell { + } else if let obj = item.base as? ChooseBakerHeaderData, obj.actionTitle != nil, let cell = tableView.dequeueReusableCell(withIdentifier: "ChooseBakerHeadingAndActionCell", for: indexPath) as? ChooseBakerHeadingCell { cell.headingLabel.text = obj.title cell.actionTitleLabel?.text = obj.actionTitle return cell - } else if let obj = item.base as? StakeHeaderData, let cell = tableView.dequeueReusableCell(withIdentifier: "StakeHeadingCell", for: indexPath) as? StakeHeadingCell { + } else if let obj = item.base as? ChooseBakerHeaderData, let cell = tableView.dequeueReusableCell(withIdentifier: "ChooseBakerHeadingCell", for: indexPath) as? ChooseBakerHeadingCell { cell.headingLabel.text = obj.title return cell @@ -114,7 +105,7 @@ class StakeViewModel: ViewModel, UITableViewDiffableDataSourceHandler { if currentDelegate != nil { self?.currentSnapshot.appendSections([0, 1]) - self?.currentSnapshot.appendItems([.init(StakeHeaderData(title: "CURRENT BAKER", actionTitle: nil))], toSection: 0) + self?.currentSnapshot.appendItems([.init(ChooseBakerHeaderData(title: "CURRENT BAKER", actionTitle: nil))], toSection: 0) if let currentBaker = currentBaker { self?.currentSnapshot.appendItems([.init(currentBaker)], toSection: 1) @@ -129,7 +120,7 @@ class StakeViewModel: ViewModel, UITableViewDiffableDataSourceHandler { } self?.currentSnapshot.appendSections(Array(nextSectionIndex..<(sortedResults.count + nextSectionIndex + 1))) - self?.currentSnapshot.appendItems([.init(StakeHeaderData(title: "SELECT BAKER", actionTitle: "Enter Custom Baker"))], toSection: nextSectionIndex) + self?.currentSnapshot.appendItems([.init(ChooseBakerHeaderData(title: "SELECT BAKER", actionTitle: "Enter Custom Baker"))], toSection: nextSectionIndex) nextSectionIndex += 1 for baker in sortedResults { @@ -159,7 +150,7 @@ class StakeViewModel: ViewModel, UITableViewDiffableDataSourceHandler { } func isEnterCustom(indexPath: IndexPath) -> Bool { - if let obj = dataSource?.itemIdentifier(for: indexPath)?.base as? StakeHeaderData { + if let obj = dataSource?.itemIdentifier(for: indexPath)?.base as? ChooseBakerHeaderData { return obj.actionTitle != nil } diff --git a/Kukai Mobile/Modules/Stake/EnterCustomBakerViewController.swift b/Kukai Mobile/Modules/Stake/EnterCustomBakerViewController.swift index 604464fc..ada6d567 100644 --- a/Kukai Mobile/Modules/Stake/EnterCustomBakerViewController.swift +++ b/Kukai Mobile/Modules/Stake/EnterCustomBakerViewController.swift @@ -18,6 +18,7 @@ class EnterCustomBakerViewController: UIViewController, EnterAddressComponentDel enterAddressComponent.headerLabel.text = "Baker:" enterAddressComponent.updateAvilableAddressTypes([.tezosAddress, .tezosDomain]) enterAddressComponent.delegate = self + enterAddressComponent.allowEmpty = true // undelegate by entering blank into field } func validatedInput(entered: String, validAddress: Bool, ofType: AddressType) { @@ -28,7 +29,7 @@ class EnterCustomBakerViewController: UIViewController, EnterAddressComponentDel DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in if ofType == .tezosAddress { - let parent = ((self?.presentationController?.presentingViewController as? UINavigationController)?.viewControllers.last as? StakeViewController) + let parent = ((self?.presentationController?.presentingViewController as? UINavigationController)?.viewControllers.last as? ChooseBakerViewController) parent?.enteredCustomBaker(address: entered) self?.dismissBottomSheet() @@ -50,7 +51,7 @@ class EnterCustomBakerViewController: UIViewController, EnterAddressComponentDel return } - let parent = ((self?.presentationController?.presentingViewController as? UINavigationController)?.viewControllers.last as? StakeViewController) + let parent = ((self?.presentationController?.presentingViewController as? UINavigationController)?.viewControllers.last as? ChooseBakerViewController) parent?.enteredCustomBaker(address: res.address) self?.hideLoadingView() self?.dismissBottomSheet() diff --git a/Kukai Mobile/Modules/Stake/Stake.storyboard b/Kukai Mobile/Modules/Stake/Stake.storyboard index ca0cc34e..fdc72c24 100644 --- a/Kukai Mobile/Modules/Stake/Stake.storyboard +++ b/Kukai Mobile/Modules/Stake/Stake.storyboard @@ -1,11 +1,12 @@ - + - + + @@ -24,7 +25,7 @@ - + @@ -33,7 +34,7 @@ - + @@ -59,7 +60,7 @@ - + @@ -246,13 +247,13 @@ - + - + @@ -292,7 +293,205 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -330,7 +529,6 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - @@ -470,7 +668,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -501,16 +699,16 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + - - + @@ -551,124 +749,33 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + - - + + - - + + - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - + - - + + @@ -677,60 +784,60 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - + + + + @@ -829,76 +895,671 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + - + - - + + @@ -946,32 +1607,32 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - + + - + - - + + - - - - - - - - - - - + + + + + + + + + + - - + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - - + + - + - + - - + + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1086,21 +3310,33 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + + + + + + + + + + + + diff --git a/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift b/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift new file mode 100644 index 00000000..740a2a3c --- /dev/null +++ b/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift @@ -0,0 +1,186 @@ +// +// StakeAmountViewController.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 20/11/2024. +// + +import UIKit +import KukaiCoreSwift + +class StakeAmountViewController: UIViewController { + + @IBOutlet weak var bakerIcon: UIImageView! + @IBOutlet weak var bakerNameLabel: UILabel! + @IBOutlet weak var bakerSplitValueLabel: UILabel! + @IBOutlet weak var bakerSpaceValueLabel: UILabel! + @IBOutlet weak var bakerRewardsValueLabel: UILabel! + + @IBOutlet weak var tokenNameLabel: UILabel! + @IBOutlet weak var tokenBalanceTitleLabel: UILabel! + @IBOutlet weak var tokenBalanceLabel: UILabel! + @IBOutlet weak var tokenIcon: UIImageView! + @IBOutlet weak var tokenSysmbolLabel: UILabel! + @IBOutlet weak var textfield: ValidatorTextField! + @IBOutlet weak var fiatLabel: UILabel! + @IBOutlet weak var maxButton: UIButton! + + @IBOutlet weak var warningLabel: UILabel! + @IBOutlet weak var errorLabel: UILabel! + + @IBOutlet weak var reviewButton: CustomisableButton! + + private var selectedToken: Token? = nil + private var selectedBaker: TzKTBaker? = nil + private var isStake = true + private var maxAmount: TokenAmount? = nil + + var dimBackground: Bool = true + + override func viewDidLoad() { + super.viewDidLoad() + GradientView.add(toView: self.view, withType: .fullScreenBackground) + + isStake = TransactionService.shared.currentTransactionType == .stake + selectedToken = isStake ? TransactionService.shared.stakeData.chosenToken : TransactionService.shared.unstakeData.chosenToken + selectedBaker = isStake ? TransactionService.shared.stakeData.chosenBaker : TransactionService.shared.unstakeData.chosenBaker + + guard let token = selectedToken, let baker = selectedBaker else { + self.windowError(withTitle: "error".localized(), description: "error-no-token".localized()) + self.dismissBottomSheet() + return + } + + self.title = isStake ? "Stake Amount" : "Unstake Amount" + self.tokenBalanceTitleLabel.text = isStake ? "Balance" : "Staked Balance" + + // To section + MediaProxyService.load(url: baker.logo, to: bakerIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) + bakerNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() + bakerSplitValueLabel.text = "\(baker.staking.fee * 100)%" + bakerSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.staking.freeSpace, decimalPlaces: token.decimalPlaces, includeThousand: true, maximumFractionDigits: 0) + bakerRewardsValueLabel.text = "\(baker.staking.estimatedApy * 100)%" + + + // Token data + tokenBalanceLabel.text = isStake ? token.availableBalance.normalisedRepresentation : token.stakedBalance.normalisedRepresentation + tokenSysmbolLabel.text = token.symbol + fiatLabel?.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: .zero()) + tokenIcon.addTokenIcon(token: token) + + + // Textfield + maxAmount = isStake ? (token.availableBalance - TokenAmount(fromNormalisedAmount: 1, decimalPlaces: token.decimalPlaces)) : token.stakedBalance + textfield.validatorTextFieldDelegate = self + textfield.validator = TokenAmountValidator(balanceLimit: maxAmount ?? .zero(), decimalPlaces: token.decimalPlaces) + textfield.addDoneToolbar() + textfield.numericAndSeperatorOnly = true + + errorLabel.isHidden = true + warningLabel.isHidden = true + reviewButton.customButtonType = .primary + reviewButton.isEnabled = false + } + + @IBAction func closeTapped(_ sender: Any) { + self.dismissBottomSheet() + } + + @IBAction func reviewTapped(_ sender: Any) { + self.textfield.resignFirstResponder() + estimateFeeAndNavigate() + } + + @IBAction func maxTapped(_ sender: Any) { + textfield.text = maxAmount?.normalisedRepresentation + let _ = textfield.revalidateTextfield() + } + + func estimateFeeAndNavigate() { + guard let selectedWalletMetadata = DependencyManager.shared.selectedWalletMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-destination".localized()) + return + } + + if let token = selectedToken, let amount = TokenAmount(fromNormalisedAmount: textfield.text ?? "", decimalPlaces: token.decimalPlaces) { + self.showLoadingView() + + let operations = isStake ? OperationFactory.stakeOperation(from: selectedWalletMetadata.address, amount: amount) : OperationFactory.unstakeOperation(from: selectedWalletMetadata.address, amount: amount) + if isStake { TransactionService.shared.stakeData.chosenAmount = amount } else { TransactionService.shared.unstakeData.chosenAmount = amount } + + // Estimate the cost of the operation (ideally display this to a user first and let them confirm) + DependencyManager.shared.tezosNodeClient.estimate(operations: operations, walletAddress: selectedWalletMetadata.address, base58EncodedPublicKey: selectedWalletMetadata.bas58EncodedPublicKey) { [weak self] estimationResult in + + switch estimationResult { + case .success(let estimationResult): + TransactionService.shared.currentOperationsAndFeesData = TransactionService.OperationsAndFeesData(estimatedOperations: estimationResult.operations) + TransactionService.shared.currentForgedString = estimationResult.forgedString + self?.loadingViewHideActivityAndFade() + self?.performSegue(withIdentifier: "confirm", sender: nil) + + case .failure(let estimationError): + self?.hideLoadingView() + self?.windowError(withTitle: "error".localized(), description: estimationError.description) + } + } + } + } +} + +extension StakeAmountViewController: ValidatorTextFieldDelegate { + + func textFieldDidBeginEditing(_ textField: UITextField) { + + } + + func textFieldDidEndEditing(_ textField: UITextField) { + + } + + func textFieldShouldClear(_ textField: UITextField) -> Bool { + return true + } + + func validated(_ validated: Bool, textfield: ValidatorTextField, forText text: String) { + guard let token = selectedToken else { + return + } + + if validated, let textDecimal = Decimal(string: text) { + self.errorLabel.isHidden = true + self.validateMaxXTZ(input: text) + self.fiatLabel?.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: TokenAmount(fromNormalisedAmount: textDecimal, decimalPlaces: token.decimalPlaces)) + self.reviewButton.isEnabled = true + + } else if text != "" { + errorLabel.isHidden = false + self.fiatLabel?.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: .zero()) + self.reviewButton.isEnabled = false + self.warningLabel.isHidden = true + + } else { + self.errorLabel.isHidden = true + self.warningLabel.isHidden = true + self.fiatLabel?.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: .zero()) + } + } + + func doneOrReturnTapped(isValid: Bool, textfield: ValidatorTextField, forText text: String?) { + + } + + func validateMaxXTZ(input: String) { + if selectedToken?.isXTZ() == true, let inputAmount = XTZAmount(fromNormalisedAmount: input, decimalPlaces: 6), maxAmount == inputAmount, isStake { + warningLabel.isHidden = false + } else { + warningLabel.isHidden = true + } + } +} + +extension StakeAmountViewController: BottomSheetCustomCalculateProtocol { + + func bottomSheetHeight() -> CGFloat { + return 400 + } +} diff --git a/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift b/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift new file mode 100644 index 00000000..7bcc977e --- /dev/null +++ b/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift @@ -0,0 +1,356 @@ +// +// StakeConfirmViewController.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 21/11/2024. +// + +import UIKit +import KukaiCoreSwift +import ReownWalletKit +import os.log + +class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButtonDelegate, EditFeesViewControllerDelegate { + + @IBOutlet var scrollView: UIScrollView! + @IBOutlet weak var closeButton: CustomisableButton! + @IBOutlet weak var titleLabel: UILabel! + + // Connected app + @IBOutlet weak var connectedAppLabel: UILabel! + @IBOutlet weak var connectedAppIcon: UIImageView! + @IBOutlet weak var connectedAppNameLabel: UILabel! + @IBOutlet weak var connectedAppMetadataStackView: UIStackView! + + // From + @IBOutlet weak var fromContainer: UIView! + @IBOutlet weak var fromIcon: UIImageView! + @IBOutlet weak var fromAlias: UILabel! + @IBOutlet weak var fromAddress: UILabel! + + // Baker + @IBOutlet weak var bakerIcon: UIImageView! + @IBOutlet weak var bakerNameLabel: UILabel! + @IBOutlet weak var bakerSplitValueLabel: UILabel! + @IBOutlet weak var bakerSpaceValueLabel: UILabel! + @IBOutlet weak var bakerRewardsValueLabel: UILabel! + + // Stake + @IBOutlet weak var actionTitleLabel: UILabel! + @IBOutlet weak var tokenIcon: UIImageView! + @IBOutlet weak var tokenAmount: UILabel! + @IBOutlet weak var tokenSymbol: UILabel! + @IBOutlet weak var tokenFiat: UILabel! + + // Fee + @IBOutlet weak var feeValueLabel: UILabel! + @IBOutlet weak var feeButton: CustomisableButton! + @IBOutlet weak var slideErrorStackView: UIStackView! + @IBOutlet weak var errorLabel: UILabel! + @IBOutlet weak var slideButton: SlideButton! + @IBOutlet weak var testnetWarningView: UIView! + + private var selectedToken: Token? = nil + private var selectedBaker: TzKTBaker? = nil + private var isStake = true + private var isSendingMaxTez = false + + var dimBackground: Bool = true + + override func viewDidLoad() { + super.viewDidLoad() + GradientView.add(toView: self.view, withType: .fullScreenBackground) + + if DependencyManager.shared.currentNetworkType != .ghostnet { + testnetWarningView.isHidden = true + } + + // This screen handles Stake, Unstake, and Finalise Unstake, with minimal differences + switch TransactionService.shared.currentTransactionType { + case .stake: + self.titleLabel.text = "Confirm Stake" + self.actionTitleLabel.text = "Stake:" + self.selectedToken = TransactionService.shared.stakeData.chosenToken + self.selectedBaker = TransactionService.shared.stakeData.chosenBaker + + case .unstake: + self.titleLabel.text = "Confirm Unstake" + self.actionTitleLabel.text = "Unstake:" + self.selectedToken = TransactionService.shared.unstakeData.chosenToken + self.selectedBaker = TransactionService.shared.unstakeData.chosenBaker + + default: + self.titleLabel.text = "Confirm Finalise" + self.actionTitleLabel.text = "Finalise:" + self.selectedToken = TransactionService.shared.finaliseUnstakeData.chosenToken + self.selectedBaker = TransactionService.shared.finaliseUnstakeData.chosenBaker + } + + guard let baker = selectedBaker else { + self.windowError(withTitle: "error".localized(), description: "error-no-token".localized()) + self.dismissBottomSheet() + return + } + + + // Handle wallet connect data + if let currentTopic = TransactionService.shared.walletConnectOperationData.request?.topic, + let session = WalletKit.instance.getSessions().first(where: { $0.topic == currentTopic }) { + + guard let account = WalletConnectService.accountFromRequest(TransactionService.shared.walletConnectOperationData.request), + let walletMetadataForRequestedAccount = DependencyManager.shared.walletList.metadata(forAddress: account) else { + self.windowError(withTitle: "error".localized(), description: "error-no-account".localized()) + self.handleRejection() + return + } + + self.isWalletConnectOp = true + self.selectedMetadata = walletMetadataForRequestedAccount + self.connectedAppNameLabel.text = session.peer.name + + if let iconString = session.peer.icons.first, let iconUrl = URL(string: iconString) { + let smallIconURL = MediaProxyService.url(fromUri: iconUrl, ofFormat: MediaProxyService.Format.icon.rawFormat()) + connectedAppURL = smallIconURL + } + + } else { + self.isWalletConnectOp = false + self.selectedMetadata = DependencyManager.shared.selectedWalletMetadata + + connectedAppMetadataStackView.isHidden = true + connectedAppLabel.isHidden = true + } + + + // From + guard let selectedMetadata = selectedMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.dismissBottomSheet() + return + } + + let media = TransactionService.walletMedia(forWalletMetadata: selectedMetadata, ofSize: .size_22) + if let subtitle = media.subtitle { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.text = subtitle + } else { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.isHidden = true + } + + + // Baker info config + bakerNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() + if baker.name == nil && baker.delegation.fee == 0 && baker.delegation.capacity == 0 && baker.delegation.estimatedApy == 0 { + bakerSplitValueLabel.text = "N/A" + bakerSpaceValueLabel.text = "N/A" + bakerRewardsValueLabel.text = "N/A" + + } else { + bakerSplitValueLabel.text = (Decimal(baker.delegation.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0) + " XTZ" + bakerRewardsValueLabel.text = Decimal(baker.delegation.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + } + + + // Fees and amount view config + slideErrorStackView.isHidden = true + feeValueLabel.accessibilityIdentifier = "fee-amount" + feeButton.customButtonType = .secondary + + slideButton.delegate = self + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + updateFees(isFirstCall: true) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if let connectedAppURL = connectedAppURL { + MediaProxyService.load(url: connectedAppURL, to: self.connectedAppIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) + } else { + self.connectedAppIcon.image = UIImage.unknownToken() + } + + MediaProxyService.load(url: self.selectedBaker?.logo, to: bakerIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) + } + + private func selectedOperationsAndFees() -> [KukaiCoreSwift.Operation] { + if isWalletConnectOp { + return TransactionService.shared.currentRemoteOperationsAndFeesData.selectedOperationsAndFees() + + } else { + return TransactionService.shared.currentOperationsAndFeesData.selectedOperationsAndFees() + } + } + + func didCompleteSlide() { + self.blockInteraction(exceptFor: [closeButton]) + self.performAuth() + } + + override func authSuccessful() { + guard let walletAddress = selectedMetadata?.address, let wallet = WalletCacheService().fetchWallet(forAddress: walletAddress) else { + self.unblockInteraction() + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.slideButton.resetSlider() + return + } + + DependencyManager.shared.tezosNodeClient.send(operations: selectedOperationsAndFees(), withWallet: wallet) { [weak self] sendResult in + switch sendResult { + case .success(let opHash): + Logger.app.info("Sent: \(opHash)") + self?.didSend = true + self?.addPendingTransaction(opHash: opHash) + self?.handleApproval(opHash: opHash, slideButton: self?.slideButton) + + case .failure(let sendError): + self?.unblockInteraction() + self?.slideButton?.resetSlider() + + if let message = SendAbstractConfirmViewController.checkForExpectedLedgerErrors(sendError) { + self?.windowError(withTitle: "error".localized(), description: message) + } + } + } + } + + override func authFailure() { + self.unblockInteraction() + self.slideButton.resetSlider() + } + + func updateAmountDisplay(withValue value: TokenAmount) { + guard let token = selectedToken else { + tokenIcon.image = UIImage.unknownToken() + tokenAmount.text = "0" + tokenFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: Token.xtz(), ofAmount: TokenAmount.zero()) + return + } + + let amountText = DependencyManager.shared.coinGeckoService.format(decimal: value.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: value.decimalPlaces) + + tokenIcon.addTokenIcon(token: token) + tokenAmount.text = amountText + tokenSymbol.text = token.symbol + tokenFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: value) + } + + func updateFees(isFirstCall: Bool = false) { + let feesAndData = isWalletConnectOp ? TransactionService.shared.currentRemoteOperationsAndFeesData : TransactionService.shared.currentOperationsAndFeesData + let fee = (feesAndData.fee + feesAndData.maxStorageCost) + var chosenAmount: TokenAmount? = nil + + switch TransactionService.shared.currentTransactionType { + case .stake: + chosenAmount = TransactionService.shared.stakeData.chosenAmount + + case .unstake: + chosenAmount = TransactionService.shared.unstakeData.chosenAmount + + default: + chosenAmount = TransactionService.shared.finaliseUnstakeData.chosenAmount + } + + checkForErrorsAndWarnings(errorStackView: slideErrorStackView, errorLabel: errorLabel, totalFee: fee) + feeValueLabel.text = fee.normalisedRepresentation + " XTZ" + feeButton.setTitle(feesAndData.type.displayName(), for: .normal) + + // Sum of send amount + fee is greater than balance, need to adjust send amount + // For safety, don't allow this logic coming from WC2, as its likely the user is communicating with a smart contract that likely won't accept recieving less than expected XTZ + if !isWalletConnectOp, let token = selectedToken, token.isXTZ(), let amount = chosenAmount, (amount + fee) >= token.availableBalance { + let oneTez = XTZAmount(fromNormalisedAmount: 1) + let updatedValue = ((token.availableBalance - oneTez) - fee) + + if updatedValue < .zero() { + updateAmountDisplay(withValue: .zero()) + slideButton.isUserInteractionEnabled = false + slideButton.alpha = 0.6 + + if isFirstCall { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in + self?.windowError(withTitle: "error-funds-title".localized(), description: String.localized("error-funds-body", withArguments: token.availableBalance.normalisedRepresentation, fee.normalisedRepresentation)) + } + } else { + self.windowError(withTitle: "error-funds-title".localized(), description: String.localized("error-funds-body", withArguments: token.availableBalance.normalisedRepresentation, fee.normalisedRepresentation)) + } + + } else { + updateAmountDisplay(withValue: updatedValue) + slideButton.isUserInteractionEnabled = true + slideButton.alpha = 1 + } + + if isWalletConnectOp { + TransactionService.shared.currentRemoteOperationsAndFeesData.updateXTZAmount(to: updatedValue) + } else { + TransactionService.shared.currentOperationsAndFeesData.updateXTZAmount(to: updatedValue) + } + } else { + updateAmountDisplay(withValue: chosenAmount ?? .zero()) + } + } + + @IBAction func closeTapped(_ sender: Any) { + handleRejection(collapseOnly: true) + } + + func addPendingTransaction(opHash: String) { + var amount: TokenAmount = .zero() + var parameters: [String: String] = [:] + + guard let selectedWalletMetadata = selectedMetadata else { return } + + switch TransactionService.shared.currentTransactionType { + case .stake: + amount = TransactionService.shared.stakeData.chosenAmount ?? .zero() + parameters = ["entrypoint": "stake", "value": "[\"prim\": \"Unit\"]"] + + case .unstake: + amount = TransactionService.shared.unstakeData.chosenAmount ?? .zero() + parameters = ["entrypoint": "unstake", "value": "[\"prim\": \"Unit\"]"] + + default: + amount = TransactionService.shared.finaliseUnstakeData.chosenAmount ?? .zero() + parameters = ["entrypoint": "finalize_unstake", "value": "[\"prim\": \"Unit\"]"] + } + + let currentOps = selectedOperationsAndFees() + let counter = Decimal(string: currentOps.last?.counter ?? "0") ?? 0 + let addPendingResult = DependencyManager.shared.activityService.addPending(opHash: opHash, + type: .transaction, + counter: counter, + fromWallet: selectedWalletMetadata, + destinationAddress: selectedWalletMetadata.address, + destinationAlias: nil, + xtzAmount: amount, + parameters: parameters, + primaryToken: selectedToken) + + DependencyManager.shared.activityService.addUniqueAddressToPendingOperation(address: selectedWalletMetadata.address) + Logger.app.info("Recorded pending transaction: \(addPendingResult)") + } +} + +extension StakeConfirmViewController: BottomSheetCustomCalculateProtocol { + + func bottomSheetHeight() -> CGFloat { + viewDidLoad() + + scrollView.setNeedsLayout() + view.setNeedsLayout() + scrollView.layoutIfNeeded() + view.layoutIfNeeded() + + var height = view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height + height += scrollView.contentSize.height + + return height + } +} diff --git a/Kukai Mobile/Modules/Stake/StakeOnboardingContainerNavController.swift b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerNavController.swift new file mode 100644 index 00000000..c0d34f24 --- /dev/null +++ b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerNavController.swift @@ -0,0 +1,17 @@ +// +// StakeOnboardingContainerNavController.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 29/11/2024. +// + +import UIKit + +class StakeOnboardingContainerNavController: UINavigationController, BottomSheetCustomCalculateProtocol { + + var dimBackground = true + + func bottomSheetHeight() -> CGFloat { + return view.bounds.height * 0.75 + } +} diff --git a/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift new file mode 100644 index 00000000..cbd8a458 --- /dev/null +++ b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift @@ -0,0 +1,162 @@ +// +// StakeOnboardingContainerViewController.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 27/11/2024. +// + +import UIKit +import KukaiCoreSwift +import Combine + +class StakeOnboardingContainerViewController: UIViewController { + + @IBOutlet weak var pageIndicator1: PageIndicatorContainerView! + @IBOutlet weak var progressSegment1: UIProgressView! + @IBOutlet weak var pageIndicator2: PageIndicatorContainerView! + @IBOutlet weak var progressSegment2: UIProgressView! + @IBOutlet weak var pageIndicator3: PageIndicatorContainerView! + @IBOutlet weak var progressSegment3: UIProgressView! + @IBOutlet weak var pageIndicator4: PageIndicatorContainerView! + @IBOutlet weak var progressSegment4: UIProgressView! + @IBOutlet weak var pageIndicator5: PageIndicatorContainerView! + @IBOutlet weak var actionButton: CustomisableButton! + + @IBOutlet weak var navigationContainerView: UIView! + private var childNavigationController: UINavigationController? = nil + private var currentChildViewController: UIViewController? = nil + private var bag = [AnyCancellable]() + private var currentStep: String = "" + + override func viewDidLoad() { + super.viewDidLoad() + GradientView.add(toView: self.view, withType: .fullScreenBackground) + + actionButton.customButtonType = .primary + + DependencyManager.shared.activityService.$addressesWithPendingOperation + .dropFirst() + .sink { [weak self] addresses in + guard let address = DependencyManager.shared.selectedWalletAddress else { + return + } + + DispatchQueue.main.async { [weak self] in + if addresses.contains([address]) { + self?.showLoadingView() + self?.updateLoadingViewStatusLabel(message: "Waiting for transaction to complete \n\nThis should only take a few seconds") + + } else { + self?.hideLoadingView() + self?.handleOperationComplete() + } + } + }.store(in: &bag) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + } + + @IBAction func closeTapped(_ sender: Any) { + self.navigationController?.popToDetails() + } + + func setProgressSegmentComplete(_ view: UIProgressView?) { + UIView.animate(withDuration: 0.7) { + view?.setProgress(1, animated: true) + } + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == "embed", let dest = segue.destination as? UINavigationController { + childNavigationController = dest + } + } + + @IBAction func actionButtonTapped(_ sender: Any) { + guard let childNav = childNavigationController, let currentChildVc = childNav.viewControllers.last else { + self.windowError(withTitle: "error".localized(), description: "Unknown error") + return + } + + currentChildViewController = currentChildVc + currentStep = currentChildVc.title ?? "" + + switch currentChildVc.title { + case "step1": + currentChildVc.performSegue(withIdentifier: "next", sender: nil) + self.pageIndicator1.setInprogress(pageNumber: 1) + + case "step2": + if handlePageControllerNext(vc: currentChildVc) == true { + actionButton.setTitle("Choose Baker", for: .normal) + self.pageIndicator1.setComplete() + self.setProgressSegmentComplete(self.progressSegment1) + self.pageIndicator2.setInprogress(pageNumber: 2) + } + + case "step3": + self.performSegue(withIdentifier: "chooseBaker", sender: nil) + + case "step4": + currentChildVc.performSegue(withIdentifier: "next", sender: nil) + + case "step5": + if handlePageControllerNext(vc: currentChildVc) == true { + actionButton.setTitle("Stake", for: .normal) + self.pageIndicator3.setComplete() + self.setProgressSegmentComplete(self.progressSegment3) + self.pageIndicator4.setInprogress(pageNumber: 4) + } + + case "step6": + TransactionService.shared.currentTransactionType = .stake + TransactionService.shared.stakeData.chosenToken = Token.xtz(withAmount: DependencyManager.shared.balanceService.account.xtzBalance) + // chosenBaker will be set inside the the delegation flow + self.performSegue(withIdentifier: "stake", sender: nil) + + case "step7": + self.navigationController?.popToDetails() + + default: + self.windowError(withTitle: "error".localized(), description: "Unknown error") + } + } + + private func handleOperationComplete() { + switch currentStep { + case "step3": + self.pageIndicator2.setComplete() + self.setProgressSegmentComplete(self.progressSegment2) + self.pageIndicator3.setInprogress(pageNumber: 3) + self.currentChildViewController?.performSegue(withIdentifier: "next", sender: nil) + self.actionButton.setTitle("Next", for: .normal) + + case "step6": + self.pageIndicator4.setComplete() + self.setProgressSegmentComplete(self.progressSegment4) + self.pageIndicator5.setInprogress(pageNumber: 5) + self.currentChildViewController?.performSegue(withIdentifier: "next", sender: nil) + self.actionButton.setTitle("Done", for: .normal) + + default: + self.windowError(withTitle: "error".localized(), description: "Unknown error") + } + } + + private func handlePageControllerNext(vc: UIViewController) -> Bool? { + guard let pageController = (vc as? OnboardingPageViewController), let pageControl = pageController.pageControl else { + self.windowError(withTitle: "error".localized(), description: "Unknown error") + return nil + } + + if pageControl.currentPage == pageControl.numberOfPages-1 { + vc.performSegue(withIdentifier: "next", sender: nil) + return true + } else { + pageController.scrollTo(index: (pageController.pageControl?.currentPage ?? 0) + 1) + return false + } + } +} diff --git a/Kukai Mobile/Services/CoinGeckoService.swift b/Kukai Mobile/Services/CoinGeckoService.swift index a0c87521..92f0c6b5 100644 --- a/Kukai Mobile/Services/CoinGeckoService.swift +++ b/Kukai Mobile/Services/CoinGeckoService.swift @@ -348,7 +348,7 @@ public class CoinGeckoService { return outputString } - func formatLargeTokenDisplay(_ num: Decimal, decimalPlaces: Int) -> String { + func formatLargeTokenDisplay(_ num: Decimal, decimalPlaces: Int, includeThousand: Bool = false, maximumFractionDigits: Int = 3) -> String { var reducedNumber: Decimal = 0 var reducedNumberSymbol: String? = nil @@ -364,7 +364,11 @@ public class CoinGeckoService { case 1_000_000...: reducedNumber = num / 1_000_000 reducedNumberSymbol = "m" - + + case 1_000... where includeThousand: + reducedNumber = num / 1_000 + reducedNumberSymbol = "k" + case 0...: reducedNumber = num @@ -374,7 +378,7 @@ public class CoinGeckoService { var stringToReturn = "" if let symbol = reducedNumberSymbol { - stringToReturn = format(decimal: reducedNumber, numberStyle: .decimal, maximumFractionDigits: 3) + stringToReturn = format(decimal: reducedNumber, numberStyle: .decimal, maximumFractionDigits: maximumFractionDigits) stringToReturn += symbol } else { diff --git a/Kukai Mobile/Services/TransactionService.swift b/Kukai Mobile/Services/TransactionService.swift index c2d66948..7b96e0ff 100644 --- a/Kukai Mobile/Services/TransactionService.swift +++ b/Kukai Mobile/Services/TransactionService.swift @@ -18,6 +18,9 @@ public class TransactionService { public enum TransactionType { case send case delegate + case stake + case unstake + case finaliseUnstake case exchange case addLiquidity case removeLiquidity @@ -183,6 +186,24 @@ public class TransactionService { var isAdd: Bool? } + public struct StakeData { + var chosenBaker: TzKTBaker? + var chosenToken: Token? // Incase coming from a dApp for another account + var chosenAmount: TokenAmount? + } + + public struct UnstakeData { + var chosenBaker: TzKTBaker? + var chosenToken: Token? // Incase coming from a dApp for another account + var chosenAmount: TokenAmount? + } + + public struct FinaliseUnstakeData { + var chosenBaker: TzKTBaker? + var chosenToken: Token? // Incase coming from a dApp for another account + var chosenAmount: TokenAmount? + } + public struct ExchangeData { var selectedExchangeAndToken: DipDupExchange? var calculationResult: DexSwapCalculationResult? @@ -253,6 +274,9 @@ public class TransactionService { public var sendData: SendData public var delegateData: DelegateData + public var stakeData: StakeData + public var unstakeData: UnstakeData + public var finaliseUnstakeData: FinaliseUnstakeData public var exchangeData: ExchangeData public var liquidityDetails: LiquidityDetails public var addLiquidityData: AddLiquidityData @@ -268,6 +292,9 @@ public class TransactionService { self.sendData = SendData(chosenToken: nil, chosenNFT: nil, chosenAmount: nil, destination: nil, destinationAlias: nil, destinationIcon: nil) self.delegateData = DelegateData(chosenBaker: nil, isAdd: nil) + self.stakeData = StakeData(chosenBaker: nil) + self.unstakeData = UnstakeData(chosenBaker: nil) + self.finaliseUnstakeData = FinaliseUnstakeData(chosenBaker: nil) self.exchangeData = ExchangeData(selectedExchangeAndToken: nil, calculationResult: nil, isXtzToToken: nil, fromAmount: nil, toAmount: nil, exchangeRateString: nil) self.liquidityDetails = LiquidityDetails(selectedPosition: nil) self.addLiquidityData = AddLiquidityData(selectedExchangeAndToken: nil, calculationResult: nil, token1: nil, token2: nil) @@ -303,6 +330,9 @@ public class TransactionService { self.sendData = SendData(chosenToken: nil, chosenNFT: nil, chosenAmount: nil, destination: nil, destinationAlias: nil, destinationIcon: nil) self.delegateData = DelegateData(chosenBaker: nil, isAdd: nil) + self.stakeData = StakeData(chosenBaker: nil, chosenToken: nil, chosenAmount: nil) + self.unstakeData = UnstakeData(chosenBaker: nil) + self.finaliseUnstakeData = FinaliseUnstakeData(chosenBaker: nil) self.exchangeData = ExchangeData(selectedExchangeAndToken: nil, calculationResult: nil, isXtzToToken: nil, fromAmount: nil, toAmount: nil, exchangeRateString: nil) self.liquidityDetails = LiquidityDetails(selectedPosition: nil) self.addLiquidityData = AddLiquidityData(selectedExchangeAndToken: nil, calculationResult: nil, token1: nil, token2: nil)