diff options
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 54 | ||||
| -rw-r--r-- | ios/MullvadVPN/CellFactoryProtocol.swift | 20 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsCellFactory.swift | 113 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsDataSource.swift | 212 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsViewController.swift | 11 |
5 files changed, 225 insertions, 185 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 428c2fe55d..f6755cfec3 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -340,6 +340,8 @@ 58FEEB46260A028D00A621A8 /* GeoJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FEEB45260A028D00A621A8 /* GeoJSON.swift */; }; 58FEEB58260B662E00A621A8 /* AutomaticKeyboardResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */; }; 58FF2C03281BDE02009EF542 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF2C02281BDE02009EF542 /* SettingsManager.swift */; }; + 7AD8490D29BA1EC500878E53 /* SettingsCellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD8490C29BA1EC500878E53 /* SettingsCellFactory.swift */; }; + 7AD8490F29BA26B000878E53 /* CellFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD8490E29BA26B000878E53 /* CellFactoryProtocol.swift */; }; E1187ABC289BBB850024E748 /* OutOfTimeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */; }; E1187ABD289BBB850024E748 /* OutOfTimeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */; }; E158B360285381C60002F069 /* StringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158B35F285381C60002F069 /* StringFormatter.swift */; }; @@ -903,6 +905,8 @@ 58FEEB45260A028D00A621A8 /* GeoJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoJSON.swift; sourceTree = "<group>"; }; 58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomaticKeyboardResponder.swift; sourceTree = "<group>"; }; 58FF2C02281BDE02009EF542 /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = "<group>"; }; + 7AD8490C29BA1EC500878E53 /* SettingsCellFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCellFactory.swift; sourceTree = "<group>"; }; + 7AD8490E29BA26B000878E53 /* CellFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellFactoryProtocol.swift; sourceTree = "<group>"; }; E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeViewController.swift; sourceTree = "<group>"; }; E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeContentView.swift; sourceTree = "<group>"; }; E158B35F285381C60002F069 /* StringFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringFormatter.swift; sourceTree = "<group>"; }; @@ -1374,47 +1378,39 @@ 58CE5E62224146200008646E /* MullvadVPN */ = { isa = PBXGroup; children = ( - 06AC114028F841390037AF9A /* AddressCacheTracker.swift */, 5896CEF126972DEB00B0FAE8 /* AccountContentView.swift */, 587988C628A2A01F00E3DF54 /* AccountDataThrottling.swift */, - 58138E60294871C600684F0C /* DeviceDataThrottling.swift */, 58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */, + 5878A27029091CF20096FC88 /* AccountInteractor.swift */, 58CCA01D2242787B004F3011 /* AccountTextField.swift */, 582AE30F2440A6CA00E6733A /* AccountTokenInput.swift */, - 5878A27029091CF20096FC88 /* AccountInteractor.swift */, 58CCA01722426713004F3011 /* AccountViewController.swift */, - 5867771329097BCD006F721F /* PaymentState.swift */, - 5867771529097C5B006F721F /* ProductState.swift */, + 06AC114028F841390037AF9A /* AddressCacheTracker.swift */, 5868585424054096000B8131 /* AppButton.swift */, 58CE5E63224146200008646E /* AppDelegate.swift */, - 5846226F26E229CD0035F7C2 /* StorePaymentManager */, 58CE5E6A224146210008646E /* Assets.xcassets */, 58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */, 5891BF1B25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift */, + 7AD8490E29BA26B000878E53 /* CellFactoryProtocol.swift */, 587EB669270EFACB00123C75 /* CharacterSet+IPAddress.swift */, 58E511E528DDDEAC00B0BCDE /* CodingErrors+CustomErrorDescription.swift */, - 58B43C1825F77DB60002C8C3 /* TunnelControlView.swift */, - 58C3F4F82964B08300D72515 /* MapViewController.swift */, 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */, - 58CCA00F224249A1004F3011 /* TunnelViewController.swift */, - 5878A27A2909649A0096FC88 /* CustomOverlayRenderer.swift */, - 5878A278290954790096FC88 /* TunnelViewControllerInteractor.swift */, 5871FB95254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift */, 5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */, 582BB1B0229569620055B6EF /* CustomNavigationBar.swift */, + 5878A27A2909649A0096FC88 /* CustomOverlayRenderer.swift */, 5868BD32261DCD2600E6027F /* CustomSplitViewController.swift */, 58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */, 58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */, 58293FB025124117005D0BB5 /* CustomTextField.swift */, 58293FB2251241B3005D0BB5 /* CustomTextView.swift */, 587EB66F27143B6500123C75 /* DataSourceSnapshot.swift */, + 58138E60294871C600684F0C /* DeviceDataThrottling.swift */, 587D96732886D87C00CD8F1C /* DeviceManagementContentView.swift */, 5820EDA8288FE064006BF4E4 /* DeviceManagementInteractor.swift */, 5893716928817A45004EE76C /* DeviceManagementViewController.swift */, 5820EDAA288FF0D2006BF4E4 /* DeviceRowView.swift */, 58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */, - 58B9EB142489139B00095626 /* RESTError+Display.swift */, - 58A8EE5D2976DB00009C0F8D /* StorePaymentManagerError+Display.swift */, 580F8B8528197958002E0998 /* DNSSettings.swift */, 5892A45D265FABFF00890742 /* EmptyTableViewHeaderFooterView.swift */, 58FEEB45260A028D00A621A8 /* GeoJSON.swift */, @@ -1427,6 +1423,8 @@ 583DA21325FA4B5C00318683 /* LocationDataSource.swift */, 58B993B02608A34500BA7811 /* LoginContentView.swift */, 58CE5E65224146200008646E /* LoginViewController.swift */, + 58C3F4F82964B08300D72515 /* MapViewController.swift */, + 58ACA9EC2979569500B5825C /* ModalRootAdaptivePresentationDelegate.swift */, 5866F39B2243B82D00168AE5 /* MullvadVPN.entitlements */, 58906DDF2445C7A5002F0673 /* NEProviderStopReason+Debug.swift */, 5811DE4F239014550011EB53 /* NEVPNStatus+Debug.swift */, @@ -1437,22 +1435,25 @@ 58CC40EE24A601900019D96E /* ObserverList.swift */, 586A950B2901250A007BAF2B /* Operations */, E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */, - E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */, 5867770D29096984006F721F /* OutOfTimeInteractor.swift */, + E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */, + 5867771329097BCD006F721F /* PaymentState.swift */, 584D26C3270C855A004EA533 /* PreferencesDataSource.swift */, 587EB6732714520600123C75 /* PreferencesDataSourceDelegate.swift */, - 58ACF6482655365700ACE4B7 /* PreferencesViewController.swift */, 5871167E2910035700D41AAC /* PreferencesInteractor.swift */, + 58ACF6482655365700ACE4B7 /* PreferencesViewController.swift */, 587EB671271451E300123C75 /* PreferencesViewModel.swift */, + 5878A26E2907E7E00096FC88 /* ProblemReportInteractor.swift */, 58F8AC0D25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift */, 58EF580A25D69D7A00AEBA94 /* ProblemReportSubmissionOverlayView.swift */, 58293FAC2510CA58005D0BB5 /* ProblemReportViewController.swift */, - 5878A26E2907E7E00096FC88 /* ProblemReportInteractor.swift */, + 5867771529097C5B006F721F /* ProductState.swift */, 585DA87526B0249A00B8C587 /* RelayCacheTracker */, 06FAE67828F83CA50033DD93 /* RESTCreateApplePaymentResponse+Localization.swift */, + 58B9EB142489139B00095626 /* RESTError+Display.swift */, 58F1311427E0B2AB007AC5BC /* Result+Extensions.swift */, - 580909D22876D09A0078138D /* RevokedDeviceViewController.swift */, 5878A27C2909657C0096FC88 /* RevokedDeviceInteractor.swift */, + 580909D22876D09A0078138D /* RevokedDeviceViewController.swift */, 587425C02299833500CA2045 /* RootContainerViewController.swift */, 58E25F802837BBBB002CFB2C /* SceneDelegate.swift */, 5888AD82227B11080051EB06 /* SelectLocationCell.swift */, @@ -1462,22 +1463,27 @@ 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */, 5819C2162729595500D6EC38 /* SettingsAddDNSEntryCell.swift */, 582BB1AE229566420055B6EF /* SettingsCell.swift */, + 7AD8490C29BA1EC500878E53 /* SettingsCellFactory.swift */, 58EE2E38272FF814003BFF93 /* SettingsDataSource.swift */, 58EE2E39272FF814003BFF93 /* SettingsDataSourceDelegate.swift */, 584D26C5270C8741004EA533 /* SettingsDNSTextCell.swift */, + 58677711290976FB006F721F /* SettingsInteractor.swift */, + 5867770F290975E8006F721F /* SettingsInteractorFactory.swift */, 580F8B88281A79A7002E0998 /* SettingsManager */, + 58E11187292FA11F009FCA84 /* SettingsMigrationUIHandler.swift */, 58E6771E24ADFE7800AA26E7 /* SettingsNavigationController.swift */, - 5867770F290975E8006F721F /* SettingsInteractorFactory.swift */, 584D26C1270C8542004EA533 /* SettingsStaticTextFooterView.swift */, 58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */, 58CCA01122424D11004F3011 /* SettingsViewController.swift */, - 58677711290976FB006F721F /* SettingsInteractor.swift */, 58BA693023EADA6A009DC256 /* SimulatorTunnelProvider.swift */, 587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */, + 58A8EE592976BFBB009C0F8D /* SKError+Localized.swift */, 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */, 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */, E1FD0DF428AA7CE400299DB4 /* StatusActivityView.swift */, 58EF581025D69DB400AEBA94 /* StatusImageView.swift */, + 5846226F26E229CD0035F7C2 /* StorePaymentManager */, + 58A8EE5D2976DB00009C0F8D /* StorePaymentManagerError+Display.swift */, 5807E2BF2432038B00F5FF30 /* String+Split.swift */, E158B35F285381C60002F069 /* StringFormatter.swift */, 589A454B28DDF5E100565204 /* Swizzle.swift */, @@ -1486,16 +1492,16 @@ 58A99ED2240014A0006599E9 /* TermsOfServiceViewController.swift */, 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */, 589E63DA28F7E9E7005FAB05 /* TransportMonitor */, + 58B43C1825F77DB60002C8C3 /* TunnelControlView.swift */, 5823FA5726CE4A4100283BF8 /* TunnelManager */, + 58CCA00F224249A1004F3011 /* TunnelViewController.swift */, + 5878A278290954790096FC88 /* TunnelViewControllerInteractor.swift */, 5891BF5025E66B1E006D6FB0 /* UIBarButtonItem+KeyboardNavigation.swift */, 587CBFE222807F530028DED3 /* UIColor+Helpers.swift */, 58CCA0152242560B004F3011 /* UIColor+Palette.swift */, 585CA70E25F8C44600B47C62 /* UIMetrics.swift */, - 58F7CA872692E34000FC59FD /* WireguardKeysContentView.swift */, - 58E11187292FA11F009FCA84 /* SettingsMigrationUIHandler.swift */, - 58A8EE592976BFBB009C0F8D /* SKError+Localized.swift */, - 58ACA9EC2979569500B5825C /* ModalRootAdaptivePresentationDelegate.swift */, 584555F32991176200DD0657 /* UIPresentationController+Private.swift */, + 58F7CA872692E34000FC59FD /* WireguardKeysContentView.swift */, ); path = MullvadVPN; sourceTree = "<group>"; @@ -2281,6 +2287,7 @@ buildActionMask = 2147483647; files = ( 58BFA5CC22A7CE1F00A6173D /* ApplicationConfiguration.swift in Sources */, + 7AD8490D29BA1EC500878E53 /* SettingsCellFactory.swift in Sources */, 5891BF5125E66B1E006D6FB0 /* UIBarButtonItem+KeyboardNavigation.swift in Sources */, 58E511E628DDDEAC00B0BCDE /* CodingErrors+CustomErrorDescription.swift in Sources */, 587B75412668FD7800DEF7E9 /* AccountExpirySystemNotificationProvider.swift in Sources */, @@ -2404,6 +2411,7 @@ 58E0A98827C8F46300FE6BDD /* Tunnel.swift in Sources */, 58ACF64F26567A7100ACE4B7 /* CustomSwitchContainer.swift in Sources */, 5857F24324C8662600CF6F47 /* SelectLocationHeaderView.swift in Sources */, + 7AD8490F29BA26B000878E53 /* CellFactoryProtocol.swift in Sources */, 58EE2E3A272FF814003BFF93 /* SettingsDataSource.swift in Sources */, 58B26E1E2943514300D5980C /* InAppNotificationDescriptor.swift in Sources */, 58421032282E42B000F24E46 /* UpdateDeviceDataOperation.swift in Sources */, diff --git a/ios/MullvadVPN/CellFactoryProtocol.swift b/ios/MullvadVPN/CellFactoryProtocol.swift new file mode 100644 index 0000000000..7bf3cbda21 --- /dev/null +++ b/ios/MullvadVPN/CellFactoryProtocol.swift @@ -0,0 +1,20 @@ +// +// CellFactoryProtocol.swift +// MullvadVPN +// +// Created by Jon Petersson on 2023-03-09. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import UIKit + +/// Protocol for creating factories to make ``UITableViewCell``s of various kinds. +/// Typically used in conjunction with a ``UITableViewDiffableDataSource.CellProvider``. +protocol CellFactoryProtocol { + associatedtype ItemIdentifier + + var tableView: UITableView { get } + + func makeCell(for item: ItemIdentifier, indexPath: IndexPath) -> UITableViewCell + func configureCell(_ cell: UITableViewCell, item: ItemIdentifier, indexPath: IndexPath) +} diff --git a/ios/MullvadVPN/SettingsCellFactory.swift b/ios/MullvadVPN/SettingsCellFactory.swift new file mode 100644 index 0000000000..2420004921 --- /dev/null +++ b/ios/MullvadVPN/SettingsCellFactory.swift @@ -0,0 +1,113 @@ +// +// SettingsCellFactory.swift +// MullvadVPN +// +// Created by Jon Petersson on 2023-03-09. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import UIKit + +struct SettingsCellFactory: CellFactoryProtocol { + let tableView: UITableView + private let interactor: SettingsInteractor + + init(tableView: UITableView, interactor: SettingsInteractor) { + self.tableView = tableView + self.interactor = interactor + } + + func makeCell(for item: SettingsDataSource.Item, indexPath: IndexPath) -> UITableViewCell { + let cell: UITableViewCell + + switch item { + case .account: + cell = tableView.dequeueReusableCell( + withIdentifier: SettingsDataSource.CellReuseIdentifiers.accountCell.rawValue, + for: indexPath + ) + default: + cell = tableView.dequeueReusableCell( + withIdentifier: SettingsDataSource.CellReuseIdentifiers.basicCell.rawValue, + for: indexPath + ) + } + + configureCell(cell, item: item, indexPath: indexPath) + + return cell + } + + func configureCell( + _ cell: UITableViewCell, + item: SettingsDataSource.Item, + indexPath: IndexPath + ) { + switch item { + case .account: + guard let cell = cell as? SettingsAccountCell else { return } + + cell.titleLabel.text = NSLocalizedString( + "ACCOUNT_CELL_LABEL", + tableName: "Settings", + value: "Account", + comment: "" + ) + cell.accountExpiryDate = interactor.deviceState.accountData?.expiry + cell.accessibilityIdentifier = "AccountCell" + cell.disclosureType = .chevron + + case .preferences: + guard let cell = cell as? SettingsCell else { return } + + cell.titleLabel.text = NSLocalizedString( + "PREFERENCES_CELL_LABEL", + tableName: "Settings", + value: "Preferences", + comment: "" + ) + cell.detailTitleLabel.text = nil + cell.accessibilityIdentifier = "PreferencesCell" + cell.disclosureType = .chevron + + case .version: + guard let cell = cell as? SettingsCell else { return } + + cell.titleLabel.text = NSLocalizedString( + "APP_VERSION_CELL_LABEL", + tableName: "Settings", + value: "App version", + comment: "" + ) + cell.detailTitleLabel.text = Bundle.main.productVersion + cell.accessibilityIdentifier = nil + cell.disclosureType = .none + + case .problemReport: + guard let cell = cell as? SettingsCell else { return } + + cell.titleLabel.text = NSLocalizedString( + "REPORT_PROBLEM_CELL_LABEL", + tableName: "Settings", + value: "Report a problem", + comment: "" + ) + cell.detailTitleLabel.text = nil + cell.accessibilityIdentifier = nil + cell.disclosureType = .chevron + + case .faq: + guard let cell = cell as? SettingsCell else { return } + + cell.titleLabel.text = NSLocalizedString( + "FAQ_AND_GUIDES_CELL_LABEL", + tableName: "Settings", + value: "FAQ & Guides", + comment: "" + ) + cell.detailTitleLabel.text = nil + cell.accessibilityIdentifier = nil + cell.disclosureType = .externalLink + } + } +} diff --git a/ios/MullvadVPN/SettingsDataSource.swift b/ios/MullvadVPN/SettingsDataSource.swift index 15027f8ae4..96466cf6f2 100644 --- a/ios/MullvadVPN/SettingsDataSource.swift +++ b/ios/MullvadVPN/SettingsDataSource.swift @@ -8,8 +8,11 @@ import UIKit -final class SettingsDataSource: NSObject, UITableViewDataSource, UITableViewDelegate { - private enum CellReuseIdentifiers: String, CaseIterable { +final class SettingsDataSource: UITableViewDiffableDataSource< + SettingsDataSource.Section, + SettingsDataSource.Item +>, UITableViewDelegate { + enum CellReuseIdentifiers: String, CaseIterable { case accountCell case basicCell @@ -48,172 +51,38 @@ final class SettingsDataSource: NSObject, UITableViewDataSource, UITableViewDele case faq } - private var snapshot = DataSourceSnapshot<Section, Item>() private let interactor: SettingsInteractor + private let settingsCellFactory: SettingsCellFactory private var storedAccountData: StoredAccountData? + private weak var tableView: UITableView? weak var delegate: SettingsDataSourceDelegate? - weak var tableView: UITableView? { - didSet { - tableView?.delegate = self - tableView?.dataSource = self - - registerClasses() - } - } - - init(interactor: SettingsInteractor) { + init(tableView: UITableView, interactor: SettingsInteractor) { + self.tableView = tableView self.interactor = interactor - super.init() + let settingsCellFactory = SettingsCellFactory(tableView: tableView, interactor: interactor) + self.settingsCellFactory = settingsCellFactory - interactor.didUpdateDeviceState = { [weak self] deviceState in - self?.didUpdateDeviceState(deviceState) + super.init(tableView: tableView) { tableView, indexPath, itemIdentifier in + settingsCellFactory.makeCell(for: itemIdentifier, indexPath: indexPath) } - storedAccountData = interactor.deviceState.accountData + tableView.delegate = self + registerClasses() updateDataSnapshot() - } - - private func registerClasses() { - CellReuseIdentifiers.allCases.forEach { cellIdentifier in - tableView?.register( - cellIdentifier.reusableViewClass, - forCellReuseIdentifier: cellIdentifier.rawValue - ) - } - - HeaderFooterReuseIdentifier.allCases.forEach { reuseIdentifier in - tableView?.register( - reuseIdentifier.reusableViewClass, - forHeaderFooterViewReuseIdentifier: reuseIdentifier.rawValue - ) - } - } - - private func updateDataSnapshot() { - var newSnapshot = DataSourceSnapshot<Section, Item>() - - if interactor.deviceState.isLoggedIn { - newSnapshot.appendSections([.main]) - newSnapshot.appendItems([.account, .preferences], in: .main) - } - - newSnapshot.appendSections([.version, .problemReport]) - newSnapshot.appendItems([.version], in: .version) - newSnapshot.appendItems([.problemReport, .faq], in: .problemReport) - - snapshot = newSnapshot - } - - // MARK: - UITableViewDataSource - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let sectionIdentifier = snapshot.section(at: section)! - - return snapshot.numberOfItems(in: sectionIdentifier) ?? 0 - } - - func numberOfSections(in tableView: UITableView) -> Int { - return snapshot.numberOfSections() - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let item = snapshot.itemForIndexPath(indexPath)! - - switch item { - case .account: - let cell = tableView.dequeueReusableCell( - withIdentifier: CellReuseIdentifiers.accountCell.rawValue, - for: indexPath - ) as! SettingsAccountCell - cell.titleLabel.text = NSLocalizedString( - "ACCOUNT_CELL_LABEL", - tableName: "Settings", - value: "Account", - comment: "" - ) - cell.accountExpiryDate = interactor.deviceState.accountData?.expiry - cell.accessibilityIdentifier = "AccountCell" - cell.disclosureType = .chevron - return cell - - case .preferences: - let cell = tableView.dequeueReusableCell( - withIdentifier: CellReuseIdentifiers.basicCell.rawValue, - for: indexPath - ) as! SettingsCell - cell.titleLabel.text = NSLocalizedString( - "PREFERENCES_CELL_LABEL", - tableName: "Settings", - value: "Preferences", - comment: "" - ) - cell.detailTitleLabel.text = nil - cell.accessibilityIdentifier = "PreferencesCell" - cell.disclosureType = .chevron - - return cell - - case .version: - let cell = tableView.dequeueReusableCell( - withIdentifier: CellReuseIdentifiers.basicCell.rawValue, - for: indexPath - ) as! SettingsCell - cell.titleLabel.text = NSLocalizedString( - "APP_VERSION_CELL_LABEL", - tableName: "Settings", - value: "App version", - comment: "" - ) - cell.detailTitleLabel.text = Bundle.main.productVersion - cell.accessibilityIdentifier = nil - cell.disclosureType = .none - - return cell - - case .problemReport: - let cell = tableView.dequeueReusableCell( - withIdentifier: CellReuseIdentifiers.basicCell.rawValue, - for: indexPath - ) as! SettingsCell - cell.titleLabel.text = NSLocalizedString( - "REPORT_PROBLEM_CELL_LABEL", - tableName: "Settings", - value: "Report a problem", - comment: "" - ) - cell.detailTitleLabel.text = nil - cell.accessibilityIdentifier = nil - cell.disclosureType = .chevron - - return cell - - case .faq: - let cell = tableView.dequeueReusableCell( - withIdentifier: CellReuseIdentifiers.basicCell.rawValue, - for: indexPath - ) as! SettingsCell - cell.titleLabel.text = NSLocalizedString( - "FAQ_AND_GUIDES_CELL_LABEL", - tableName: "Settings", - value: "FAQ & Guides", - comment: "" - ) - cell.detailTitleLabel.text = nil - cell.accessibilityIdentifier = nil - cell.disclosureType = .externalLink - - return cell + interactor.didUpdateDeviceState = { [weak self] deviceState in + self?.didUpdateDeviceState(deviceState) } + storedAccountData = interactor.deviceState.accountData } // MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { - if case .version = snapshot.itemForIndexPath(indexPath) { + if case .version = itemIdentifier(for: indexPath) { return false } else { return true @@ -221,7 +90,7 @@ final class SettingsDataSource: NSObject, UITableViewDataSource, UITableViewDele } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let item = snapshot.itemForIndexPath(indexPath) else { return } + guard let item = itemIdentifier(for: indexPath) else { return } delegate?.settingsDataSource(self, didSelectItem: item) } @@ -246,6 +115,37 @@ final class SettingsDataSource: NSObject, UITableViewDataSource, UITableViewDele // MARK: - Private + private func registerClasses() { + CellReuseIdentifiers.allCases.forEach { cellIdentifier in + tableView?.register( + cellIdentifier.reusableViewClass, + forCellReuseIdentifier: cellIdentifier.rawValue + ) + } + + HeaderFooterReuseIdentifier.allCases.forEach { reuseIdentifier in + tableView?.register( + reuseIdentifier.reusableViewClass, + forHeaderFooterViewReuseIdentifier: reuseIdentifier.rawValue + ) + } + } + + private func updateDataSnapshot() { + var snapshot = NSDiffableDataSourceSnapshot<Section, Item>() + + if interactor.deviceState.isLoggedIn { + snapshot.appendSections([.main]) + snapshot.appendItems([.account, .preferences], toSection: .main) + } + + snapshot.appendSections([.version, .problemReport]) + snapshot.appendItems([.version], toSection: .version) + snapshot.appendItems([.problemReport, .faq], toSection: .problemReport) + + apply(snapshot) + } + private func didUpdateDeviceState(_ deviceState: DeviceState) { let newAccountData = deviceState.accountData let oldAccountData = storedAccountData @@ -257,15 +157,15 @@ final class SettingsDataSource: NSObject, UITableViewDataSource, UITableViewDele oldAccountData.number == newAccountData.number, oldAccountData.expiry != newAccountData.expiry { - tableView?.performBatchUpdates { - if let indexPath = snapshot.indexPathForItem(.account) { - tableView?.reloadRows(at: [indexPath], with: .none) - } + if let indexPath = indexPath(for: .account), + let cell = tableView?.cellForRow(at: indexPath) + { + settingsCellFactory.configureCell(cell, item: .account, indexPath: indexPath) } + return } updateDataSnapshot() - tableView?.reloadData() } } diff --git a/ios/MullvadVPN/SettingsViewController.swift b/ios/MullvadVPN/SettingsViewController.swift index 2dd09d4d20..e43c960b56 100644 --- a/ios/MullvadVPN/SettingsViewController.swift +++ b/ios/MullvadVPN/SettingsViewController.swift @@ -18,16 +18,15 @@ class SettingsViewController: UITableViewController, SettingsDataSourceDelegate, SFSafariViewControllerDelegate { weak var delegate: SettingsViewControllerDelegate? + private var dataSource: SettingsDataSource? + private let interactor: SettingsInteractor override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } - private let dataSource: SettingsDataSource - init(interactor: SettingsInteractor) { - dataSource = SettingsDataSource(interactor: interactor) - + self.interactor = interactor super.init(style: .grouped) } @@ -55,8 +54,8 @@ class SettingsViewController: UITableViewController, SettingsDataSourceDelegate, tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 60 - dataSource.tableView = tableView - dataSource.delegate = self + dataSource = SettingsDataSource(tableView: tableView, interactor: interactor) + dataSource?.delegate = self } // MARK: - IBActions |
