Skip to content

Commit

Permalink
Combine parts of LocationDataSource and AddLocationDataSource to one
Browse files Browse the repository at this point in the history
  • Loading branch information
Jon Petersson authored and buggmagnet committed Apr 3, 2024
1 parent 92ee1ee commit 3e4f72e
Show file tree
Hide file tree
Showing 22 changed files with 288 additions and 312 deletions.
4 changes: 4 additions & 0 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@
7A6389EB2B7FAD7A008E77E1 /* SettingsFieldValidationErrorContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6389EA2B7FAD7A008E77E1 /* SettingsFieldValidationErrorContentView.swift */; };
7A6389ED2B7FADA1008E77E1 /* SettingsFieldValidationErrorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6389EC2B7FADA1008E77E1 /* SettingsFieldValidationErrorConfiguration.swift */; };
7A6389F82B864CDF008E77E1 /* LocationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6389F72B864CDF008E77E1 /* LocationNode.swift */; };
7A6652B82BB44C3E0042D848 /* LocationDiffableDataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6652B62BB44B120042D848 /* LocationDiffableDataSourceProtocol.swift */; };
7A6B4F592AB8412E00123853 /* TunnelMonitorTimings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */; };
7A6F2FA52AFA3CB2006D0856 /* AccountExpiryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */; };
7A6F2FA72AFBB9AE006D0856 /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */; };
Expand Down Expand Up @@ -1788,6 +1789,7 @@
7A6389EA2B7FAD7A008E77E1 /* SettingsFieldValidationErrorContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFieldValidationErrorContentView.swift; sourceTree = "<group>"; };
7A6389EC2B7FADA1008E77E1 /* SettingsFieldValidationErrorConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFieldValidationErrorConfiguration.swift; sourceTree = "<group>"; };
7A6389F72B864CDF008E77E1 /* LocationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationNode.swift; sourceTree = "<group>"; };
7A6652B62BB44B120042D848 /* LocationDiffableDataSourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDiffableDataSourceProtocol.swift; sourceTree = "<group>"; };
7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorTimings.swift; sourceTree = "<group>"; };
7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiryTests.swift; sourceTree = "<group>"; };
7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiry.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2434,6 +2436,7 @@
F050AE4D2B70D7F8003F4EDB /* LocationCellViewModel.swift */,
583DA21325FA4B5C00318683 /* LocationDataSource.swift */,
F050AE5D2B739A73003F4EDB /* LocationDataSourceProtocol.swift */,
7A6652B62BB44B120042D848 /* LocationDiffableDataSourceProtocol.swift */,
7A6389F72B864CDF008E77E1 /* LocationNode.swift */,
F050AE512B70DFC0003F4EDB /* LocationSection.swift */,
F0BE65362B9F136A005CC385 /* LocationSectionHeaderView.swift */,
Expand Down Expand Up @@ -5324,6 +5327,7 @@
58607A4D2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift in Sources */,
58C8191829FAA2C400DEB1B4 /* NotificationConfiguration.swift in Sources */,
58FF9FE82B07650A00E4C97D /* ButtonCellContentConfiguration.swift in Sources */,
7A6652B82BB44C3E0042D848 /* LocationDiffableDataSourceProtocol.swift in Sources */,
5827B0A82B0F49EF00CCBBA1 /* ProxyConfigurationInteractorProtocol.swift in Sources */,
7A5869B92B56E7F000640D27 /* IPOverrideViewControllerDelegate.swift in Sources */,
586C0D7A2B039CE300E7CDD7 /* ShadowsocksCipherPicker.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"originHash" : "c15149b2d59d9e9c72375f65339c04f41a19943e1117e682df27fc9f943fdc56",
"pins" : [
{
"identity" : "swift-log",
Expand All @@ -19,5 +18,5 @@
}
}
],
"version" : 3
"version" : 2
}
10 changes: 1 addition & 9 deletions ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,6 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
*/
private let secondaryNavigationContainer = RootContainerViewController()

private var customListRepository: CustomListRepositoryProtocol {
#if DEBUG
InMemoryCustomListRepository()
#else
CustomListRepository()
#endif
}

/// Posts `preferredAccountNumber` notification when user inputs the account number instead of voucher code
private let preferredAccountNumberSubject = PassthroughSubject<String, Never>()

Expand Down Expand Up @@ -719,7 +711,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
navigationController: navigationController,
tunnelManager: tunnelManager,
relayCacheTracker: relayCacheTracker,
customListRepository: customListRepository
customListRepository: CustomListRepository()
)

locationCoordinator.didFinish = { [weak self] _ in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Combine
import Foundation
import MullvadSettings
import MullvadTypes
import Routing
Expand Down
141 changes: 42 additions & 99 deletions ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,40 @@
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadSettings
import MullvadTypes
import UIKit

class AddLocationsDataSource: UITableViewDiffableDataSource<LocationSection, LocationCellViewModel> {
private let tableView: UITableView
private let nodes: [LocationNode]
class AddLocationsDataSource:
UITableViewDiffableDataSource<LocationSection, LocationCellViewModel>,
LocationDiffableDataSourceProtocol {
private var customListLocationNode: CustomListLocationNode
private let nodes: [LocationNode]
var didUpdateCustomList: ((CustomListLocationNode) -> Void)?
let tableView: UITableView
let sections: [LocationSection]

init(
tableView: UITableView,
allLocations: [LocationNode],
allLocationNodes: [LocationNode],
customList: CustomList
) {
self.tableView = tableView
self.nodes = allLocations
self.nodes = allLocationNodes

self.customListLocationNode = CustomListLocationNodeBuilder(
customList: customList,
allLocations: self.nodes
).customListLocationNode

let sections: [LocationSection] = [.customLists]
self.sections = sections

super.init(tableView: tableView) { _, indexPath, itemIdentifier in
let cell = tableView.dequeueReusableView(
withIdentifier: LocationSection.allCases[indexPath.section],
withIdentifier: sections[indexPath.section],
for: indexPath
// swiftlint:disable:next force_cast
) as! LocationCell
) as! LocationCell // swiftlint:disable:this force_cast
cell.configure(item: itemIdentifier, behavior: .add)
cell.selectionStyle = .none
return cell
Expand All @@ -47,6 +51,14 @@ class AddLocationsDataSource: UITableViewDiffableDataSource<LocationSection, Loc
reloadWithSelectedLocations()
}

func nodeShowsChildren(_ node: LocationNode) -> Bool {
isLocationInCustomList(node: node)
}

func nodeShouldBeSelected(_ node: LocationNode) -> Bool {
customListLocationNode.children.contains(node)
}

private func reloadWithSelectedLocations() {
var locationsList: [LocationCellViewModel] = []
nodes.forEach { node in
Expand All @@ -62,9 +74,13 @@ class AddLocationsDataSource: UITableViewDiffableDataSource<LocationSection, Loc
return
}

// Walk tree backwards to determine which nodes should be expanded.
node.forEachAncestor { node in
node.showsChildren = true
// Only parents with partially selected children should be expanded.
node.forEachDescendant { descendantNode in
if customListLocationNode.children.contains(descendantNode) {
descendantNode.forEachAncestor { descendantParentNode in
descendantParentNode.showsChildren = true
}
}
}

locationsList.append(contentsOf: recursivelyCreateCellViewModelTree(
Expand All @@ -73,53 +89,7 @@ class AddLocationsDataSource: UITableViewDiffableDataSource<LocationSection, Loc
indentationLevel: 1
))
}
updateDataSnapshot(with: locationsList)
}

private func updateDataSnapshot(
with list: [LocationCellViewModel],
animated: Bool = false,
completion: (() -> Void)? = nil
) {
var snapshot = NSDiffableDataSourceSnapshot<LocationSection, LocationCellViewModel>()

snapshot.appendSections([.customLists])
snapshot.appendItems(list, toSection: .customLists)

apply(snapshot, animatingDifferences: animated, completion: completion)
}

private func recursivelyCreateCellViewModelTree(
for node: LocationNode,
in section: LocationSection,
indentationLevel: Int
) -> [LocationCellViewModel] {
var viewModels = [LocationCellViewModel]()
for childNode in node.children {
viewModels.append(
LocationCellViewModel(
section: .customLists,
node: childNode,
indentationLevel: indentationLevel,
isSelected: customListLocationNode.children.contains(childNode)
)
)

let indentationLevel = indentationLevel + 1

// Walk tree forward to determine which nodes should be expanded.
if isLocationInCustomList(node: childNode) {
viewModels.append(
contentsOf: recursivelyCreateCellViewModelTree(
for: childNode,
in: section,
indentationLevel: indentationLevel
)
)
}
}

return viewModels
updateDataSnapshot(with: [locationsList])
}

private func isLocationInCustomList(node: LocationNode) -> Bool {
Expand All @@ -146,26 +116,23 @@ extension AddLocationsDataSource: UITableViewDelegate {

extension AddLocationsDataSource: LocationCellDelegate {
func toggleExpanding(cell: LocationCell) {
guard let indexPath = tableView.indexPath(for: cell),
let item = itemIdentifier(for: indexPath) else { return }
let isExpanded = item.node.showsChildren

item.node.showsChildren = !isExpanded

var locationList = snapshot().itemIdentifiers

if !isExpanded {
locationList.addSubNodes(from: item, at: indexPath)
} else {
locationList.removeSubNodes(from: item.node)
let items = toggledItems(for: cell).first!.map { item in
var item = item
if containsChild(parent: customListLocationNode, child: item.node) {
item.isSelected = true
}
return item
}

updateDataSnapshot(with: locationList, animated: true, completion: {
self.scroll(to: item, animated: true)
updateDataSnapshot(with: [items], reloadExisting: true, completion: {
if let indexPath = self.tableView.indexPath(for: cell),
let item = self.itemIdentifier(for: indexPath) {
self.scroll(to: item, animated: true)
}
})
}

func toggleSelection(cell: LocationCell) {
func toggleSelecting(cell: LocationCell) {
guard let index = tableView.indexPath(for: cell)?.row else { return }

var locationList = snapshot().itemIdentifiers
Expand All @@ -181,36 +148,12 @@ extension AddLocationsDataSource: LocationCellDelegate {
} else {
customListLocationNode.remove(selectedLocation: item.node, with: locationList)
}
updateDataSnapshot(with: locationList, completion: {
updateDataSnapshot(with: [locationList], completion: {
self.didUpdateCustomList?(self.customListLocationNode)
})
}
}

extension AddLocationsDataSource {
private func scroll(to item: LocationCellViewModel, animated: Bool) {
guard
let visibleIndexPaths = tableView.indexPathsForVisibleRows,
let indexPath = indexPath(for: item)
else { return }

if item.node.children.count > visibleIndexPaths.count {
tableView.scrollToRow(at: indexPath, at: .top, animated: animated)
} else {
if let last = item.node.children.last {
if let lastInsertedIndexPath = self.indexPath(for: LocationCellViewModel(
section: .customLists,
node: last
)),
let lastVisibleIndexPath = visibleIndexPaths.last,
lastInsertedIndexPath >= lastVisibleIndexPath {
tableView.scrollToRow(at: lastInsertedIndexPath, at: .bottom, animated: animated)
}
}
}
}
}

// MARK: - Toggle selection in table view

fileprivate extension [LocationCellViewModel] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,9 @@ class AddLocationsViewController: UIViewController {
tableView.rowHeight = 56
tableView.indicatorStyle = .white
tableView.accessibilityIdentifier = .addLocationsView
tableView.allowsMultipleSelection = true
tableView.tableHeaderView = nil
tableView.sectionHeaderHeight = .zero
return tableView
}()

private lazy var backBarButton: UIBarButtonItem = {
let backBarButton = UIBarButtonItem(
primaryAction: UIAction(
image: UIImage(resource: .iconBack),
handler: { [weak self] _ in
guard let self else { return }
delegate?.didBack()
navigationController?.popViewController(animated: true)
}
)
)
backBarButton.style = .done

return backBarButton
}()

init(
allLocationsNodes: [LocationNode],
customList: CustomList
Expand All @@ -67,11 +48,18 @@ class AddLocationsViewController: UIViewController {
super.viewDidLoad()
tableView.backgroundColor = view.backgroundColor
view.backgroundColor = .secondaryColor
navigationItem.leftBarButtonItem = backBarButton
addConstraints()
setUpDataSource()
}

override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)

if parent == nil {
delegate?.didBack()
}
}

private func addConstraints() {
view.addConstrainedSubviews([tableView]) {
tableView.pinEdgesToSuperview()
Expand All @@ -81,7 +69,7 @@ class AddLocationsViewController: UIViewController {
private func setUpDataSource() {
dataSource = AddLocationsDataSource(
tableView: tableView,
allLocations: nodes.copy(),
allLocationNodes: nodes.copy(),
customList: customList
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ class CustomListDataSourceConfiguration: NSObject {
}

extension CustomListDataSourceConfiguration: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return nil
}

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let sectionIdentifier = dataSource.snapshot().sectionIdentifiers[section]

return switch sectionIdentifier {
case .name:
16
default:
UITableView.automaticDimension
}
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
UIMetrics.SettingsCell.customListsCellHeight
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,6 @@ extension EditCustomListCoordinator: CustomListViewControllerDelegate {

coordinator.start()

coordinator.addChild(coordinator)
addChild(coordinator)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadSettings
import MullvadTypes
import Routing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class ListCustomListCoordinator: Coordinator, Presentable, Presenting {
}

tunnelManager.updateSettings([.relayConstraints(relayConstraints)]) { [weak self] in
self?.tunnelManager.startTunnel()
self?.tunnelManager.reconnectTunnel(selectNewRelay: true)
}
}

Expand Down
Loading

0 comments on commit 3e4f72e

Please sign in to comment.