diff --git a/DashWallet.xcodeproj/project.pbxproj b/DashWallet.xcodeproj/project.pbxproj index a64fa4b4a..8e8f52308 100644 --- a/DashWallet.xcodeproj/project.pbxproj +++ b/DashWallet.xcodeproj/project.pbxproj @@ -558,6 +558,8 @@ 754BEA132C0B6BD700E8C93C /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754BEA112C0B6BD700E8C93C /* HomeViewModel.swift */; }; 755049A92C846299008FA7EB /* DWAboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 755049A72C846299008FA7EB /* DWAboutViewController.m */; }; 755049AA2C846299008FA7EB /* DWAboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 755049A72C846299008FA7EB /* DWAboutViewController.m */; }; + 755049AC2C846576008FA7EB /* MenuItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755049AB2C846576008FA7EB /* MenuItemModel.swift */; }; + 755049AD2C846576008FA7EB /* MenuItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755049AB2C846576008FA7EB /* MenuItemModel.swift */; }; 755A22BD2B1385FD001F170D /* IconAttributedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755A22BC2B1385FD001F170D /* IconAttributedText.swift */; }; 755B4B222B0C903500B844F0 /* DWDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755B4B212B0C903500B844F0 /* DWDateFormatter.swift */; }; 755B4B232B0C903500B844F0 /* DWDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755B4B212B0C903500B844F0 /* DWDateFormatter.swift */; }; @@ -2432,6 +2434,7 @@ 754BEA112C0B6BD700E8C93C /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; 755049A72C846299008FA7EB /* DWAboutViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWAboutViewController.m; sourceTree = ""; }; 755049A82C846299008FA7EB /* DWAboutViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWAboutViewController.h; sourceTree = ""; }; + 755049AB2C846576008FA7EB /* MenuItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemModel.swift; sourceTree = ""; }; 755A22BC2B1385FD001F170D /* IconAttributedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconAttributedText.swift; sourceTree = ""; }; 755B4B212B0C903500B844F0 /* DWDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DWDateFormatter.swift; sourceTree = ""; }; 755C32372C358FBD007DA721 /* BackupSeedPhraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupSeedPhraseViewController.swift; sourceTree = ""; }; @@ -4122,6 +4125,7 @@ 2A7A7BCA2347EF7A00451078 /* Security */, 2A7A7BD32348CB4F00451078 /* Settings */, 2A7A7BDA2348DBE700451078 /* Tools */, + 755049AB2C846576008FA7EB /* MenuItemModel.swift */, ); path = Menu; sourceTree = ""; @@ -8943,6 +8947,7 @@ C9F42FB829DFC507001BC549 /* SpendableTransaction.swift in Sources */, 11ED906B29681773003784F9 /* StakingInfoDialogController.swift in Sources */, 0F36937E2919A5DB007F4E91 /* TwoFactorAuthViewController.swift in Sources */, + 755049AC2C846576008FA7EB /* MenuItemModel.swift in Sources */, 75EBAA292BBBE385004488E3 /* ZenLedgerViewModel.swift in Sources */, 471A260A289ACDF70056B7B2 /* Taxes.swift in Sources */, 2AD1CE9722DD0E8E00C99324 /* DWSeedWordModel+DWLayoutSupport.m in Sources */, @@ -9176,6 +9181,7 @@ C9D2C7022A320AA000D15901 /* CrowdNodeWebViewController.swift in Sources */, C9D2C7032A320AA000D15901 /* UIViewController+DWDisplayError.m in Sources */, C9D2C7042A320AA000D15901 /* BRAppleWatchData.m in Sources */, + 755049AD2C846576008FA7EB /* MenuItemModel.swift in Sources */, C9D2C7052A320AA000D15901 /* DWRequestAmountContentView.m in Sources */, C943B3312A408CED00AF23C5 /* DWProfileAboutCellModel.m in Sources */, C943B4C42A40A54600AF23C5 /* DWSearchViewController.m in Sources */, diff --git a/DashWallet/Sources/UI/Menu/MenuItemModel.swift b/DashWallet/Sources/UI/Menu/MenuItemModel.swift new file mode 100644 index 000000000..f58c385fe --- /dev/null +++ b/DashWallet/Sources/UI/Menu/MenuItemModel.swift @@ -0,0 +1,36 @@ +// +// Created by Andrei Ashikhmin +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct MenuItemModel: Identifiable, Equatable { + let id = UUID() + + var title: String + var subtitle: String? = nil + var details: String? = nil + var icon: IconName? = nil + var showInfo: Bool = false + var showChevron: Bool = false + var showToggle: Bool = false + @State var isToggled: Bool = false + var action: (() -> Void)? = nil + + static func == (lhs: MenuItemModel, rhs: MenuItemModel) -> Bool { + lhs.id == rhs.id + } +} diff --git a/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift b/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift index 3ca927f37..4ba781626 100644 --- a/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift +++ b/DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewController.swift @@ -16,6 +16,7 @@ // import UIKit +import SwiftUI @objc(DWSettingsMenuViewControllerDelegate) protocol SettingsMenuViewControllerDelegate: AnyObject { @@ -28,9 +29,6 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle @objc weak var delegate: SettingsMenuViewControllerDelegate? private lazy var model: DWSettingsMenuModel = DWSettingsMenuModel() - private var formController: DWFormTableViewController! - private var localCurrencyCellModel: DWSelectorFormCellModel! - private var switchNetworkCellModel: DWSelectorFormCellModel! init() { super.init(nibName: nil, bundle: nil) @@ -47,10 +45,21 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle view.backgroundColor = .dw_secondaryBackground() - formController = DWFormTableViewController(style: .plain) - formController.setSections(sections, placeholderText: nil) - - dw_embedChild(formController) + let content = SettingsMenuContent( + items: menuItems(), + onLocalCurrencyChange: { [weak self] in + self?.showCurrencySelector() + }, + onNetworkChange: { [weak self] in + self?.showChangeNetwork() + }, + onRescanBlockchain: { [weak self] in + self?.showWarningAboutReclassifiedTransactions() + } + ) + let swiftUIController = UIHostingController(rootView: content) + swiftUIController.view.backgroundColor = .dw_secondaryBackground() + dw_embedChild(swiftUIController) } override var preferredStatusBarStyle: UIStatusBarStyle { @@ -60,7 +69,6 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle // MARK: - LocalCurrencyViewControllerDelegate func localCurrencyViewController(_ controller: DWLocalCurrencyViewController, didSelectCurrency currencyCode: String) { - updateLocalCurrencyCellModel() navigationController?.popViewController(animated: true) } @@ -70,95 +78,71 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle // MARK: - Private - private var items: [DWBaseFormCellModel] { - var items: [DWBaseFormCellModel] = [] - - let localCurrencyCell = DWSelectorFormCellModel(title: NSLocalizedString("Local Currency", comment: "")) - localCurrencyCellModel = localCurrencyCell - updateLocalCurrencyCellModel() - localCurrencyCell.accessoryType = .disclosureIndicator - localCurrencyCell.didSelectBlock = { [weak self] _, _ in - self?.showCurrencySelector() - } - items.append(localCurrencyCell) - - let notificationsCell = DWSwitcherFormCellModel(title: NSLocalizedString("Enable Receive Notifications", comment: "")) - notificationsCell.isOn = model.notificationsEnabled - notificationsCell.didChangeValueBlock = { [weak self] cellModel in - self?.model.notificationsEnabled = cellModel.isOn - } - items.append(notificationsCell) - - let networkCell = DWSelectorFormCellModel(title: NSLocalizedString("Network", comment: "")) - switchNetworkCellModel = networkCell - updateSwitchNetworkCellModel() - networkCell.accessoryType = .disclosureIndicator - networkCell.didSelectBlock = { [weak self] _, indexPath in - guard let self = self else { return } - let tableView = self.formController.tableView! - guard let cell = tableView.cellForRow(at: indexPath) else { return } - self.showChangeNetwork(from: tableView, sourceRect: cell.frame) - } - items.append(networkCell) - - let rescanCell = DWSelectorFormCellModel(title: NSLocalizedString("Rescan Blockchain", comment: "")) - rescanCell.didSelectBlock = { [weak self] _, indexPath in - guard let self = self else { return } - let tableView = self.formController.tableView! - guard let cell = tableView.cellForRow(at: indexPath) else { return } - self.showWarningAboutReclassifiedTransactions(tableView, sourceRect: cell.frame) - } - items.append(rescanCell) - - let aboutCell = DWSelectorFormCellModel(title: NSLocalizedString("About", comment: "")) - aboutCell.accessoryType = .disclosureIndicator - aboutCell.didSelectBlock = { [weak self] _, _ in - self?.showAboutController() - } - items.append(aboutCell) + private func menuItems() -> [MenuItemModel] { + var items: [MenuItemModel] = [ + MenuItemModel( + title: NSLocalizedString("Local Currency", comment: ""), + subtitle: model.localCurrencyCode, + showChevron: true, + action: { [weak self] in + self?.showCurrencySelector() + } + ), + MenuItemModel( + title: NSLocalizedString("Enable Receive Notifications", comment: ""), + showToggle: true, + isToggled: self.model.notificationsEnabled, + action: { [weak self] in + self?.model.notificationsEnabled.toggle() + } + ), + MenuItemModel( + title: NSLocalizedString("Network", comment: ""), + subtitle: model.networkName, + showChevron: true, + action: { [weak self] in + self?.showChangeNetwork() + } + ), + MenuItemModel( + title: NSLocalizedString("Rescan Blockchain", comment: ""), + showChevron: true, + action: { [weak self] in + self?.showWarningAboutReclassifiedTransactions() + } + ), + MenuItemModel( + title: NSLocalizedString("About", comment: ""), + showChevron: true, + action: { [weak self] in + self?.showAboutController() + } + ) + ] #if DASHPAY - let coinJoinCell = DWSelectorFormCellModel(title: NSLocalizedString("CoinJoin", comment: "")) - coinJoinCell.didSelectBlock = { [weak self] _, _ in - self?.showCoinJoinController() - } - items.append(coinJoinCell) - - let votingCell = DWSwitcherFormCellModel(title: "Enable Voting") - votingCell.isOn = VotingPrefsWrapper.getIsEnabled() - votingCell.didChangeValueBlock = { cellModel in - VotingPrefsWrapper.setIsEnabled(value: cellModel.isOn) - } - items.append(votingCell) + items.append(contentsOf: [ + MenuItemModel( + title: NSLocalizedString("CoinJoin", comment: ""), + showChevron: true, + action: { [weak self] in + self?.showCoinJoinController() + } + ), + MenuItemModel( + title: "Enable Voting", + showToggle: true, + isToggled: VotingPrefs.shared.votingEnabled, + action: { + VotingPrefs.shared.votingEnabled.toggle() + } + ) + ]) #endif return items } - private var sections: [DWFormSectionModel] { - return items.map { item in - let section = DWFormSectionModel() - section.items = [item] - return section - } - } - - private func updateLocalCurrencyCellModel() { - localCurrencyCellModel.subTitle = model.localCurrencyCode - } - - private func updateSwitchNetworkCellModel() { - switchNetworkCellModel.subTitle = model.networkName - } - - private func rescanBlockchainAction(from sourceView: UIView, sourceRect: CGRect) { - DWSettingsMenuModel.rescanBlockchainAction(from: self, sourceView: sourceView, sourceRect: sourceRect) { [weak self] confirmed in - if confirmed { - self?.delegate?.settingsMenuViewControllerDidRescanBlockchain(self!) - } - } - } - private func showCurrencySelector() { let controller = DWLocalCurrencyViewController(navigationAppearance: .default, presentationMode: .screen, currencyCode: nil) controller.delegate = self @@ -182,13 +166,13 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle navigationController?.pushViewController(vc, animated: true) } - private func showChangeNetwork(from sourceView: UIView, sourceRect: CGRect) { + private func showChangeNetwork() { let actionSheet = UIAlertController(title: NSLocalizedString("Network", comment: ""), message: nil, preferredStyle: .actionSheet) let mainnetAction = UIAlertAction(title: NSLocalizedString("Mainnet", comment: ""), style: .default) { [weak self] _ in DWSettingsMenuModel.switchToMainnet { success in if success { - self?.updateSwitchNetworkCellModel() + self?.updateView() } } } @@ -196,7 +180,7 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle let testnetAction = UIAlertAction(title: NSLocalizedString("Testnet", comment: ""), style: .default) { [weak self] _ in DWSettingsMenuModel.switchToTestnet { success in if success { - self?.updateSwitchNetworkCellModel() + self?.updateView() } } } @@ -208,21 +192,21 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle actionSheet.addAction(cancelAction) if UIDevice.current.userInterfaceIdiom == .pad { - actionSheet.popoverPresentationController?.sourceView = sourceView - actionSheet.popoverPresentationController?.sourceRect = sourceRect + actionSheet.popoverPresentationController?.sourceView = view + actionSheet.popoverPresentationController?.sourceRect = view.bounds } present(actionSheet, animated: true, completion: nil) } - private func showWarningAboutReclassifiedTransactions(_ sourceView: UIView, sourceRect: CGRect) { + private func showWarningAboutReclassifiedTransactions() { let actionSheet = UIAlertController( title: NSLocalizedString("You will lose all your manually reclassified transactions types", comment: ""), message: NSLocalizedString("If you would like to save manually reclassified types for transactions you should export a CSV transaction file.", comment: ""), preferredStyle: .actionSheet) let continueAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { [weak self] _ in - self?.rescanBlockchainAction(from: sourceView, sourceRect: sourceRect) + self?.rescanBlockchainAction() } let exportAction = UIAlertAction(title: NSLocalizedString("Export CSV", comment: ""), style: .default) { [weak self] _ in @@ -236,13 +220,21 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle actionSheet.addAction(cancelAction) if UIDevice.current.userInterfaceIdiom == .pad { - actionSheet.popoverPresentationController?.sourceView = sourceView - actionSheet.popoverPresentationController?.sourceRect = sourceRect + actionSheet.popoverPresentationController?.sourceView = view + actionSheet.popoverPresentationController?.sourceRect = view.bounds } present(actionSheet, animated: true, completion: nil) } + private func rescanBlockchainAction() { + DWSettingsMenuModel.rescanBlockchainAction(from: self, sourceView: view, sourceRect: view.bounds) { [weak self] confirmed in + if confirmed { + self?.delegate?.settingsMenuViewControllerDidRescanBlockchain(self!) + } + } + } + private func exportTransactionsInCSV() { view.dw_showProgressHUD(withMessage: NSLocalizedString("Generating CSV Report", comment: "")) @@ -263,4 +255,39 @@ class SettingsMenuViewController: UIViewController, DWLocalCurrencyViewControlle self?.dw_displayErrorModally(error) } } + + private func updateView() { + // Trigger a view update + viewDidLoad() + } +} + +struct SettingsMenuContent: View { + var items: [MenuItemModel] + var onLocalCurrencyChange: () -> Void + var onNetworkChange: () -> Void + var onRescanBlockchain: () -> Void + + var body: some View { + List(items) { item in + MenuItem( + title: item.title, + subtitle: item.subtitle, + details: item.details, + icon: item.icon, + showInfo: item.showInfo, + showChevron: item.showChevron, + showToggle: item.showToggle, + isToggled: item.isToggled, + action: item.action + ) + .background(Color.secondaryBackground) + .cornerRadius(8) + .shadow(color: .shadow, radius: 10, x: 0, y: 5) + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } + .listStyle(.plain) + .background(Color.clear) + } } diff --git a/DashWallet/Sources/UI/Menu/Tools/ToolsMenuViewController.swift b/DashWallet/Sources/UI/Menu/Tools/ToolsMenuViewController.swift index a0fc3e770..92b14f481 100644 --- a/DashWallet/Sources/UI/Menu/Tools/ToolsMenuViewController.swift +++ b/DashWallet/Sources/UI/Menu/Tools/ToolsMenuViewController.swift @@ -167,23 +167,6 @@ extension DWImportWalletInfoViewController { } } -struct MenuItemModel: Identifiable, Equatable { - let id = UUID() - - var title: String - var subtitle: String? = nil - var details: String? = nil - var icon: IconName? = nil - var showInfo: Bool = false - var showChevron: Bool = false - var isToggled: Binding? = nil - var action: (() -> Void)? = nil - - static func == (lhs: MenuItemModel, rhs: MenuItemModel) -> Bool { - lhs.id == rhs.id - } -} - struct ToolsMenuContent: View { var items: [MenuItemModel] @State private var showZenLedgerSheet: Bool = false diff --git a/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift b/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift index 257901e2a..136d0b5f5 100644 --- a/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift +++ b/DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift @@ -30,7 +30,8 @@ struct MenuItem: View { var showChevron: Bool = false var dashAmount: Int64? = nil var overrideFiatAmount: String? = nil - var isToggled: Binding? = nil + var showToggle: Bool = false + @State var isToggled: Bool = false var action: (() -> Void)? = nil init(title: String, @@ -43,30 +44,33 @@ struct MenuItem: View { showChevron: Bool = false, dashAmount: Int64? = nil, overrideFiatAmount: String? = nil, - isToggled: Binding? = nil, + showToggle: Bool = false, + isToggled: Bool = false, action: (() -> Void)? = nil ) { - self.init(title: title, - subtitleView: subtitle.map { - AnyView( - Text($0) - .font(.caption) - .lineSpacing(3) - .foregroundColor(.tertiaryText) - .padding(.leading, 4) - .padding(.top, 2) - ) - }, - details: details, - topText: topText, - icon: icon, - secondaryIcon: secondaryIcon, - showInfo: showInfo, - showChevron: showChevron, - dashAmount: dashAmount, - overrideFiatAmount: overrideFiatAmount, - isToggled: isToggled, - action: action + self.init( + title: title, + subtitleView: subtitle.map { + AnyView( + Text($0) + .font(.caption) + .lineSpacing(3) + .foregroundColor(.tertiaryText) + .padding(.leading, 4) + .padding(.top, 2) + ) + }, + details: details, + topText: topText, + icon: icon, + secondaryIcon: secondaryIcon, + showInfo: showInfo, + showChevron: showChevron, + dashAmount: dashAmount, + overrideFiatAmount: overrideFiatAmount, + showToggle: showToggle, + isToggled: isToggled, + action: action ) } @@ -80,7 +84,8 @@ struct MenuItem: View { showChevron: Bool = false, dashAmount: Int64? = nil, overrideFiatAmount: String? = nil, - isToggled: Binding? = nil, + showToggle: Bool = false, + isToggled: Bool = false, action: (() -> Void)? = nil ) { self.title = title @@ -93,7 +98,8 @@ struct MenuItem: View { self.showChevron = showChevron self.dashAmount = dashAmount self.overrideFiatAmount = overrideFiatAmount - self.isToggled = isToggled + self._isToggled = State(initialValue: isToggled) + self.showToggle = showToggle self.action = action } @@ -152,7 +158,7 @@ struct MenuItem: View { if let subtitle = subtitleView { subtitle } - + if let details = details { Text(details) .font(.caption) @@ -164,8 +170,10 @@ struct MenuItem: View { } .frame(maxWidth: .infinity) - if let isToggled = isToggled { - Toggle(isOn: isToggled) { } + if showToggle { + Toggle(isOn: $isToggled) { } + .tint(Color.dashBlue) + .scaleEffect(0.75) .frame(maxWidth: 60) } @@ -196,6 +204,10 @@ struct MenuItem: View { .padding(10) .frame(maxWidth: .infinity, minHeight: 66) .onTapGesture { + if showToggle { + isToggled.toggle() + } + action?() } } @@ -216,6 +228,7 @@ struct MenuItem: View { subtitle: "Easily stake Dash and earn passive income with a few simple steps", icon: .system("faceid"), showInfo: true, - isToggled: .constant(true) + showToggle: true, + isToggled: true ) }