summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@kvadrat.se>2024-03-26 14:05:54 +0100
committerBug Magnet <marco.nikic@mullvad.net>2024-04-05 09:34:32 +0200
commite2b0e60bef73fadcbdb25057e414b739456c0370 (patch)
tree96651544d06365e3130fe4a2d5b4e3558204486c
parentcf85ff5daf1c656e17fc237464ccbc843ad6e136 (diff)
downloadmullvadvpn-e2b0e60bef73fadcbdb25057e414b739456c0370.tar.xz
mullvadvpn-e2b0e60bef73fadcbdb25057e414b739456c0370.zip
Combine AddLocationCell and LocationCell to one
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj8
-rw-r--r--ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved23
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/AddLocationCell.swift204
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/AddLocationCellViewModel.swift15
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift102
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationCell.swift111
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift35
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift36
8 files changed, 181 insertions, 353 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index e56b72cf8e..632600e08f 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -829,8 +829,6 @@
F02F41A02B9723AF00625A4F /* AddLocationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02F419A2B9723AE00625A4F /* AddLocationsViewController.swift */; };
F02F41A12B9723AF00625A4F /* AddLocationsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02F419B2B9723AE00625A4F /* AddLocationsDataSource.swift */; };
F02F41A22B9723AF00625A4F /* AddLocationsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02F419C2B9723AF00625A4F /* AddLocationsCoordinator.swift */; };
- F02F41A42B9723AF00625A4F /* AddLocationCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02F419E2B9723AF00625A4F /* AddLocationCellViewModel.swift */; };
- F02F41A52B9723AF00625A4F /* AddLocationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02F419F2B9723AF00625A4F /* AddLocationCell.swift */; };
F03580252A13842C00E5DAFD /* IncreasedHitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */; };
F04413612BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */; };
F04413622BA45CE30018A6EE /* CustomListLocationNodeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */; };
@@ -1991,8 +1989,6 @@
F02F419A2B9723AE00625A4F /* AddLocationsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocationsViewController.swift; sourceTree = "<group>"; };
F02F419B2B9723AE00625A4F /* AddLocationsDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocationsDataSource.swift; sourceTree = "<group>"; };
F02F419C2B9723AF00625A4F /* AddLocationsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocationsCoordinator.swift; sourceTree = "<group>"; };
- F02F419E2B9723AF00625A4F /* AddLocationCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocationCellViewModel.swift; sourceTree = "<group>"; };
- F02F419F2B9723AF00625A4F /* AddLocationCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocationCell.swift; sourceTree = "<group>"; };
F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncreasedHitButton.swift; sourceTree = "<group>"; };
F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomListLocationNodeBuilder.swift; sourceTree = "<group>"; };
F04F95A02B21D24400431E08 /* shadowsocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = shadowsocks.h; sourceTree = "<group>"; };
@@ -3520,8 +3516,6 @@
isa = PBXGroup;
children = (
7A6389D72B7E3BD6008E77E1 /* AddCustomListCoordinator.swift */,
- F02F419F2B9723AF00625A4F /* AddLocationCell.swift */,
- F02F419E2B9723AF00625A4F /* AddLocationCellViewModel.swift */,
F02F419C2B9723AF00625A4F /* AddLocationsCoordinator.swift */,
F02F419B2B9723AE00625A4F /* AddLocationsDataSource.swift */,
F02F419A2B9723AE00625A4F /* AddLocationsViewController.swift */,
@@ -5197,7 +5191,6 @@
582BB1B1229569620055B6EF /* UINavigationBar+Appearance.swift in Sources */,
7A9FA1442A2E3FE5000B728D /* CheckableSettingsCell.swift in Sources */,
58ACF6492655365700ACE4B7 /* VPNSettingsViewController.swift in Sources */,
- F02F41A52B9723AF00625A4F /* AddLocationCell.swift in Sources */,
7ABE318D2A1CDD4500DF4963 /* UIFont+Weight.swift in Sources */,
58C774BE29A7A249003A1A56 /* CustomNavigationController.swift in Sources */,
E1FD0DF528AA7CE400299DB4 /* StatusActivityView.swift in Sources */,
@@ -5454,7 +5447,6 @@
7A6389DB2B7E3BD6008E77E1 /* CustomListCellConfiguration.swift in Sources */,
F09A297C2A9F8A9B00EA3B6F /* VoucherTextField.swift in Sources */,
7A5869B72B56B41500640D27 /* IPOverrideTextViewController.swift in Sources */,
- F02F41A42B9723AF00625A4F /* AddLocationCellViewModel.swift in Sources */,
58ACF64B26553C3F00ACE4B7 /* SettingsSwitchCell.swift in Sources */,
7AF9BE952A40461100DBFEDB /* RelayFilterView.swift in Sources */,
7A09C98129D99215000C2CAC /* String+FuzzyMatch.swift in Sources */,
diff --git a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 0000000000..eaa4f1187c
--- /dev/null
+++ b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,23 @@
+{
+ "originHash" : "c15149b2d59d9e9c72375f65339c04f41a19943e1117e682df27fc9f943fdc56",
+ "pins" : [
+ {
+ "identity" : "swift-log",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-log.git",
+ "state" : {
+ "revision" : "173f567a2dfec11d74588eea82cecea555bdc0bc",
+ "version" : "1.4.0"
+ }
+ },
+ {
+ "identity" : "wireguard-apple",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/mullvad/wireguard-apple.git",
+ "state" : {
+ "revision" : "11a00c20dc03f2751db47e94f585c0778c7bde82"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationCell.swift b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationCell.swift
deleted file mode 100644
index e0094f0a9a..0000000000
--- a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationCell.swift
+++ /dev/null
@@ -1,204 +0,0 @@
-//
-// AddLocationCell.swift
-// MullvadVPN
-//
-// Created by Mojgan on 2024-02-29.
-// Copyright © 2024 Mullvad VPN AB. All rights reserved.
-//
-
-import UIKit
-
-protocol AddLocationCellDelegate: AnyObject {
- func toggleExpanding(cell: AddLocationCell)
- func toggleSelection(cell: AddLocationCell)
-}
-
-class AddLocationCell: UITableViewCell {
- weak var delegate: AddLocationCellDelegate?
-
- private let chevronDown = UIImage(resource: .iconChevronDown)
- private let chevronUp = UIImage(resource: .iconChevronUp)
-
- private let locationLabel: UILabel = {
- let label = UILabel()
- label.font = UIFont.systemFont(ofSize: 17)
- label.textColor = .white
- label.numberOfLines = .zero
- label.lineBreakStrategy = []
- return label
- }()
-
- private let checkboxButton: UIButton = {
- let button = UIButton()
- button.setImage(UIImage(systemName: "checkmark.square.fill"), for: .selected)
- button.setImage(UIImage(systemName: "square"), for: .normal)
- button.tintColor = .white
- return button
- }()
-
- private let collapseButton: UIButton = {
- let button = UIButton(type: .custom)
- button.accessibilityIdentifier = .collapseButton
- button.isAccessibilityElement = false
- button.tintColor = .white
- return button
- }()
-
- var isExpanded = false {
- didSet {
- updateCollapseImage()
- updateAccessibilityCustomActions()
- }
- }
-
- var showsCollapseControl = false {
- didSet {
- collapseButton.isHidden = !showsCollapseControl
- updateAccessibilityCustomActions()
- }
- }
-
- override var indentationLevel: Int {
- didSet {
- updateBackgroundColor()
- setLayoutMargins()
- }
- }
-
- override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- setupCell()
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- private func setLayoutMargins() {
- let indentation = CGFloat(indentationLevel) * indentationWidth
-
- var contentMargins = UIMetrics.locationCellLayoutMargins
- contentMargins.leading += indentation
-
- contentView.directionalLayoutMargins = contentMargins
- }
-
- private func setupCell() {
- indentationWidth = UIMetrics.TableView.cellIndentationWidth
-
- backgroundColor = .clear
- contentView.backgroundColor = .clear
-
- backgroundView = UIView()
-
- collapseButton.addTarget(self, action: #selector(handleCollapseButton(_:)), for: .touchUpInside)
- checkboxButton.addTarget(self, action: #selector(toggleCheckboxButton(_:)), for: .touchUpInside)
-
- contentView.addConstrainedSubviews([checkboxButton, locationLabel, collapseButton]) {
- checkboxButton.pinEdgeToSuperviewMargin(.leading(.zero))
- checkboxButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
- checkboxButton.widthAnchor
- .constraint(
- equalToConstant: 44.0
- )
- checkboxButton.heightAnchor.constraint(equalTo: checkboxButton.widthAnchor, multiplier: 1, constant: 0)
-
- locationLabel.leadingAnchor.constraint(
- equalTo: checkboxButton.trailingAnchor,
- constant: 12
- )
-
- locationLabel.trailingAnchor.constraint(lessThanOrEqualTo: collapseButton.leadingAnchor)
- .withPriority(.defaultHigh)
- locationLabel.pinEdgesToSuperviewMargins(PinnableEdges([.top(.zero), .bottom(.zero)]))
-
- collapseButton.widthAnchor
- .constraint(
- equalToConstant: UIMetrics.contentLayoutMargins.leading + UIMetrics
- .contentLayoutMargins.trailing + 24.0
- )
- collapseButton.pinEdgesToSuperviewMargins(.all().excluding(.leading))
- }
-
- updateCollapseImage()
- updateAccessibilityCustomActions()
- updateBackgroundColor()
- setLayoutMargins()
- }
-
- private func updateBackgroundColor() {
- backgroundView?.backgroundColor = backgroundColorForIdentationLevel()
- }
-
- private func backgroundColorForIdentationLevel() -> UIColor {
- switch indentationLevel {
- case 1:
- return UIColor.Cell.Background.indentationLevelOne
- case 2:
- return UIColor.Cell.Background.indentationLevelTwo
- case 3:
- return UIColor.Cell.Background.indentationLevelThree
- default:
- return UIColor.Cell.Background.normal
- }
- }
-
- private func updateCollapseImage() {
- let image = isExpanded ? chevronUp : chevronDown
- collapseButton.setImage(image, for: .normal)
- }
-
- private func updateAccessibilityCustomActions() {
- if showsCollapseControl {
- let actionName = isExpanded
- ? NSLocalizedString(
- "ADD_LOCATIONS_COLLAPSE_ACCESSIBILITY_ACTION",
- tableName: "AddLocationsLocation",
- value: "Collapse location",
- comment: ""
- )
- : NSLocalizedString(
- "ADD_LOCATIONS_EXPAND_ACCESSIBILITY_ACTION",
- tableName: "AddLocationsLocation",
- value: "Expand location",
- comment: ""
- )
-
- accessibilityCustomActions = [
- UIAccessibilityCustomAction(
- name: actionName,
- target: self,
- selector: #selector(toggleCollapseAccessibilityAction)
- ),
- ]
- } else {
- accessibilityCustomActions = nil
- }
- }
-
- // MARK: - actions
-
- @objc private func handleCollapseButton(_ sender: UIControl) {
- delegate?.toggleExpanding(cell: self)
- }
-
- @objc private func toggleCollapseAccessibilityAction() -> Bool {
- delegate?.toggleExpanding(cell: self)
- return true
- }
-
- @objc private func toggleCheckboxButton(_ sender: UIControl) {
- delegate?.toggleSelection(cell: self)
- }
-}
-
-extension AddLocationCell {
- func configure(item: AddLocationCellViewModel) {
- accessibilityIdentifier = item.node.name
- locationLabel.text = item.node.name
- showsCollapseControl = !item.node.children.isEmpty
- isExpanded = item.node.showsChildren
- checkboxButton.isSelected = item.isSelected
- checkboxButton.tintColor = item.isSelected ? .successColor : .white
- }
-}
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationCellViewModel.swift b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationCellViewModel.swift
deleted file mode 100644
index ffc06aea74..0000000000
--- a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationCellViewModel.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-//
-// AddLocationCellViewModel.swift
-// MullvadVPN
-//
-// Created by Mojgan on 2024-03-04.
-// Copyright © 2024 Mullvad VPN AB. All rights reserved.
-//
-
-import Foundation
-
-struct AddLocationCellViewModel: Hashable {
- let node: LocationNode
- var indentationLevel = 0
- var isSelected: Bool
-}
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift
index 3b2125811f..18564b8a12 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift
@@ -11,17 +11,7 @@ import MullvadSettings
import MullvadTypes
import UIKit
-enum AddLocationsSectionIdentifier: String, Hashable, CaseIterable, CellIdentifierProtocol {
- case `default`
-
- var cellClass: AnyClass {
- switch self {
- case .default: AddLocationCell.self
- }
- }
-}
-
-class AddLocationsDataSource: UITableViewDiffableDataSource<AddLocationsSectionIdentifier, AddLocationCellViewModel> {
+class AddLocationsDataSource: UITableViewDiffableDataSource<LocationSection, LocationCellViewModel> {
private let tableView: UITableView
private let nodes: [LocationNode]
private var customListLocationNode: CustomListLocationNode
@@ -42,25 +32,26 @@ class AddLocationsDataSource: UITableViewDiffableDataSource<AddLocationsSectionI
super.init(tableView: tableView) { _, indexPath, itemIdentifier in
let cell = tableView.dequeueReusableView(
- withIdentifier: AddLocationsSectionIdentifier.allCases[indexPath.section],
+ withIdentifier: LocationSection.allCases[indexPath.section],
for: indexPath
// swiftlint:disable:next force_cast
- ) as! AddLocationCell
- cell.configure(item: itemIdentifier)
+ ) as! LocationCell
+ cell.configure(item: itemIdentifier, behavior: .add)
cell.selectionStyle = .none
return cell
}
tableView.delegate = self
- tableView.registerReusableViews(from: AddLocationsSectionIdentifier.self)
+ tableView.registerReusableViews(from: LocationSection.self)
defaultRowAnimation = .fade
reloadWithSelectedLocations()
}
private func reloadWithSelectedLocations() {
- var locationsList: [AddLocationCellViewModel] = []
+ var locationsList: [LocationCellViewModel] = []
nodes.forEach { node in
- let viewModel = AddLocationCellViewModel(
+ let viewModel = LocationCellViewModel(
+ section: .customLists,
node: node,
isSelected: customListLocationNode.children.contains(node)
)
@@ -78,40 +69,36 @@ class AddLocationsDataSource: UITableViewDiffableDataSource<AddLocationsSectionI
locationsList.append(contentsOf: recursivelyCreateCellViewModelTree(
for: node,
- in: .default,
+ in: .customLists,
indentationLevel: 1
))
}
- updateDataSnapshot(with: [locationsList])
+ updateDataSnapshot(with: locationsList)
}
private func updateDataSnapshot(
- with list: [[AddLocationCellViewModel]],
+ with list: [LocationCellViewModel],
animated: Bool = false,
completion: (() -> Void)? = nil
) {
- var snapshot = NSDiffableDataSourceSnapshot<AddLocationsSectionIdentifier, AddLocationCellViewModel>()
- let sections = AddLocationsSectionIdentifier.allCases
+ var snapshot = NSDiffableDataSourceSnapshot<LocationSection, LocationCellViewModel>()
- snapshot.appendSections(sections)
+ snapshot.appendSections([.customLists])
+ snapshot.appendItems(list, toSection: .customLists)
- for (index, section) in sections.enumerated() {
- let items = list[index]
-
- snapshot.appendItems(items, toSection: section)
- }
apply(snapshot, animatingDifferences: animated, completion: completion)
}
private func recursivelyCreateCellViewModelTree(
for node: LocationNode,
- in section: AddLocationsSectionIdentifier,
+ in section: LocationSection,
indentationLevel: Int
- ) -> [AddLocationCellViewModel] {
- var viewModels = [AddLocationCellViewModel]()
+ ) -> [LocationCellViewModel] {
+ var viewModels = [LocationCellViewModel]()
for childNode in node.children {
viewModels.append(
- AddLocationCellViewModel(
+ LocationCellViewModel(
+ section: .customLists,
node: childNode,
indentationLevel: indentationLevel,
isSelected: customListLocationNode.children.contains(childNode)
@@ -145,7 +132,7 @@ class AddLocationsDataSource: UITableViewDiffableDataSource<AddLocationsSectionI
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// swiftlint:disable:next force_cast
- let cell = super.tableView(tableView, cellForRowAt: indexPath) as! AddLocationCell
+ let cell = super.tableView(tableView, cellForRowAt: indexPath) as! LocationCell
cell.delegate = self
return cell
}
@@ -157,8 +144,8 @@ extension AddLocationsDataSource: UITableViewDelegate {
}
}
-extension AddLocationsDataSource: AddLocationCellDelegate {
- func toggleExpanding(cell: AddLocationCell) {
+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
@@ -173,12 +160,12 @@ extension AddLocationsDataSource: AddLocationCellDelegate {
locationList.removeSubNodes(from: item.node)
}
- updateDataSnapshot(with: [locationList], animated: true, completion: {
+ updateDataSnapshot(with: locationList, animated: true, completion: {
self.scroll(to: item, animated: true)
})
}
- func toggleSelection(cell: AddLocationCell) {
+ func toggleSelection(cell: LocationCell) {
guard let index = tableView.indexPath(for: cell)?.row else { return }
var locationList = snapshot().itemIdentifiers
@@ -194,14 +181,14 @@ extension AddLocationsDataSource: AddLocationCellDelegate {
} 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: AddLocationCellViewModel, animated: Bool) {
+ private func scroll(to item: LocationCellViewModel, animated: Bool) {
guard
let visibleIndexPaths = tableView.indexPathsForVisibleRows,
let indexPath = indexPath(for: item)
@@ -211,9 +198,9 @@ extension AddLocationsDataSource {
tableView.scrollToRow(at: indexPath, at: .top, animated: animated)
} else {
if let last = item.node.children.last {
- if let lastInsertedIndexPath = self.indexPath(for: AddLocationCellViewModel(
- node: last,
- isSelected: false
+ if let lastInsertedIndexPath = self.indexPath(for: LocationCellViewModel(
+ section: .customLists,
+ node: last
)),
let lastVisibleIndexPath = visibleIndexPaths.last,
lastInsertedIndexPath >= lastVisibleIndexPath {
@@ -224,34 +211,9 @@ extension AddLocationsDataSource {
}
}
-// MARK: - Toggle expanding
-
-fileprivate extension [AddLocationCellViewModel] {
- mutating func addSubNodes(from item: AddLocationCellViewModel, at indexPath: IndexPath) {
- let row = indexPath.row + 1
- let locations = item.node.children.map {
- AddLocationCellViewModel(node: $0, indentationLevel: item.indentationLevel + 1, isSelected: item.isSelected)
- }
-
- if row < count {
- insert(contentsOf: locations, at: row)
- } else {
- append(contentsOf: locations)
- }
- }
-
- mutating func removeSubNodes(from node: LocationNode) {
- for node in node.children {
- node.showsChildren = false
- removeAll(where: { node == $0.node })
- removeSubNodes(from: node)
- }
- }
-}
-
// MARK: - Toggle selection in table view
-fileprivate extension [AddLocationCellViewModel] {
+fileprivate extension [LocationCellViewModel] {
mutating func deselectAncestors(from node: LocationNode?) {
node?.forEachAncestor { parent in
guard let index = firstIndex(where: { $0.node == parent }) else {
@@ -274,7 +236,7 @@ fileprivate extension [AddLocationCellViewModel] {
// MARK: - Update custom list
fileprivate extension CustomListLocationNode {
- func remove(selectedLocation: LocationNode, with locationList: [AddLocationCellViewModel]) {
+ func remove(selectedLocation: LocationNode, with locationList: [LocationCellViewModel]) {
if let index = children.firstIndex(of: selectedLocation) {
children.remove(at: index)
}
@@ -304,7 +266,7 @@ fileprivate extension CustomListLocationNode {
}
}
- private func addSiblings(from locationList: [AddLocationCellViewModel], for node: LocationNode) {
+ private func addSiblings(from locationList: [LocationCellViewModel], for node: LocationNode) {
guard let parent = node.parent else { return }
parent.children.forEach { child in
// adding siblings if they are already selected in snapshot
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationCell.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationCell.swift
index dfdd791de1..79f847095f 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationCell.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationCell.swift
@@ -8,13 +8,23 @@
import UIKit
+//protocol LocationCellDelegate: AnyObject {
+// func toggle(cell: LocationCell)
+//}
+
protocol LocationCellDelegate: AnyObject {
- func toggle(cell: LocationCell)
+ func toggleExpanding(cell: LocationCell)
+ func toggleSelection(cell: LocationCell)
}
class LocationCell: UITableViewCell {
weak var delegate: LocationCellDelegate?
+ private let container: UIStackView = {
+ let view = UIStackView()
+ return view
+ }()
+
private let locationLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
@@ -38,6 +48,14 @@ class LocationCell: UITableViewCell {
return imageView
}()
+ private let checkboxButton: UIButton = {
+ let button = UIButton()
+ button.setImage(UIImage(systemName: "checkmark.square.fill"), for: .selected)
+ button.setImage(UIImage(systemName: "square"), for: .normal)
+ button.tintColor = .white
+ return button
+ }()
+
private let collapseButton: UIButton = {
let button = UIButton(type: .custom)
button.accessibilityIdentifier = .collapseButton
@@ -46,6 +64,8 @@ class LocationCell: UITableViewCell {
return button
}()
+ private var behavior: LocationCellBehavior = .select
+
private let chevronDown = UIImage(resource: .iconChevronDown)
private let chevronUp = UIImage(resource: .iconChevronUp)
@@ -106,7 +126,7 @@ class LocationCell: UITableViewCell {
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
- updateTickImage()
+ updateLeadingImage()
updateStatusIndicatorColor()
}
@@ -122,6 +142,7 @@ class LocationCell: UITableViewCell {
selectedBackgroundView = UIView()
selectedBackgroundView?.backgroundColor = UIColor.Cell.Background.selected
+ checkboxButton.addTarget(self, action: #selector(toggleCheckboxButton(_:)), for: .touchUpInside)
collapseButton.addTarget(self, action: #selector(handleCollapseButton(_:)), for: .touchUpInside)
[locationLabel, tickImageView, statusIndicator, collapseButton].forEach { subview in
@@ -131,42 +152,62 @@ class LocationCell: UITableViewCell {
updateCollapseImage()
updateAccessibilityCustomActions()
- updateDisabled()
+// updateDisabled()
updateBackgroundColor()
setLayoutMargins()
- NSLayoutConstraint.activate([
- tickImageView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
- tickImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+ contentView.addConstrainedSubviews([
+ tickImageView,
+ statusIndicator,
+ locationLabel,
+ collapseButton,
+ checkboxButton
+ ]) {
+ tickImageView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor)
+ tickImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
- statusIndicator.widthAnchor.constraint(equalToConstant: 16),
- statusIndicator.heightAnchor.constraint(equalTo: statusIndicator.widthAnchor),
- statusIndicator.centerXAnchor.constraint(equalTo: tickImageView.centerXAnchor),
- statusIndicator.centerYAnchor.constraint(equalTo: tickImageView.centerYAnchor),
+ statusIndicator.widthAnchor.constraint(equalToConstant: 16)
+ statusIndicator.heightAnchor.constraint(equalTo: statusIndicator.widthAnchor)
+ statusIndicator.centerXAnchor.constraint(equalTo: tickImageView.centerXAnchor)
+ statusIndicator.centerYAnchor.constraint(equalTo: tickImageView.centerYAnchor)
+
+ checkboxButton.pinEdgeToSuperviewMargin(.leading(.zero))
+ checkboxButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
+ checkboxButton.widthAnchor
+ .constraint(
+ equalToConstant: 44.0
+ )
+ checkboxButton.heightAnchor.constraint(equalTo: checkboxButton.widthAnchor, multiplier: 1, constant: 0)
locationLabel.leadingAnchor.constraint(
equalTo: statusIndicator.trailingAnchor,
constant: 12
- ),
+ )
+
locationLabel.trailingAnchor.constraint(lessThanOrEqualTo: collapseButton.leadingAnchor)
- .withPriority(.defaultHigh),
- locationLabel.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
- locationLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
+ .withPriority(.defaultHigh)
+ locationLabel.pinEdgesToSuperviewMargins(PinnableEdges([.top(.zero), .bottom(.zero)]))
collapseButton.widthAnchor
.constraint(
equalToConstant: UIMetrics.contentLayoutMargins.leading + UIMetrics
- .contentLayoutMargins.trailing + 24
- ),
- collapseButton.topAnchor.constraint(equalTo: contentView.topAnchor),
- collapseButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
- collapseButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- ])
+ .contentLayoutMargins.trailing + 24.0
+ )
+ collapseButton.pinEdgesToSuperviewMargins(.all().excluding(.leading))
+ }
}
- private func updateTickImage() {
- statusIndicator.isHidden = isSelected
- tickImageView.isHidden = !isSelected
+ private func updateLeadingImage() {
+ switch behavior {
+ case .add:
+ checkboxButton.isHidden = false
+ statusIndicator.isHidden = true
+ tickImageView.isHidden = true
+ case .select:
+ checkboxButton.isHidden = true
+ statusIndicator.isHidden = isSelected
+ tickImageView.isHidden = !isSelected
+ }
}
private func updateStatusIndicatorColor() {
@@ -221,11 +262,11 @@ class LocationCell: UITableViewCell {
}
@objc private func handleCollapseButton(_ sender: UIControl) {
- delegate?.toggle(cell: self)
+ delegate?.toggleExpanding(cell: self)
}
@objc private func toggleCollapseAccessibilityAction() -> Bool {
- delegate?.toggle(cell: self)
+ delegate?.toggleExpanding(cell: self)
return true
}
@@ -262,13 +303,31 @@ class LocationCell: UITableViewCell {
accessibilityCustomActions = nil
}
}
+
+ @objc private func toggleCheckboxButton(_ sender: UIControl) {
+ delegate?.toggleSelection(cell: self)
+ }
+}
+
+enum LocationCellBehavior {
+ case add
+ case select
}
extension LocationCell {
- func configureCell(item: LocationCellViewModel) {
+ func configure(item: LocationCellViewModel, behavior: LocationCellBehavior) {
accessibilityIdentifier = item.node.code
locationLabel.text = item.node.name
showsCollapseControl = !item.node.children.isEmpty
isExpanded = item.node.showsChildren
+ checkboxButton.isSelected = item.isSelected
+ checkboxButton.tintColor = item.isSelected ? .successColor : .white
+
+ setBehavior(behavior)
+ }
+
+ private func setBehavior(_ newBehavior: LocationCellBehavior) {
+ self.behavior = newBehavior
+ updateLeadingImage()
}
}
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift
index 2425413fdd..50418e21d8 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift
@@ -12,6 +12,7 @@ struct LocationCellViewModel: Hashable {
let section: LocationSection
let node: LocationNode
var indentationLevel = 0
+ var isSelected: Bool = false
func hash(into hasher: inout Hasher) {
hasher.combine(section)
@@ -20,6 +21,38 @@ struct LocationCellViewModel: Hashable {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.node == rhs.node &&
- lhs.section == rhs.section
+ lhs.section == rhs.section &&
+ lhs.isSelected == rhs.isSelected
+ }
+}
+
+extension [LocationCellViewModel] {
+ mutating func addSubNodes(from item: LocationCellViewModel, at indexPath: IndexPath) {
+ let section = LocationSection.allCases[indexPath.section]
+ let row = indexPath.row + 1
+
+ let locations = item.node.children.map {
+ LocationCellViewModel(
+ section: section,
+ node: $0,
+ indentationLevel: item.indentationLevel + 1,
+ isSelected: item.isSelected
+ )
+ }
+
+ if row < count {
+ insert(contentsOf: locations, at: row)
+ } else {
+ append(contentsOf: locations)
+ }
+ }
+
+ mutating func removeSubNodes(from node: LocationNode) {
+ for node in node.children {
+ node.showsChildren = false
+ removeAll(where: { node == $0.node })
+
+ removeSubNodes(from: node)
+ }
}
}
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift
index 857037e362..9c1ca8ca06 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift
@@ -39,7 +39,7 @@ final class LocationDataSource: UITableViewDiffableDataSource<LocationSection, L
for: indexPath
// swiftlint:disable:next force_cast
) as! LocationCell
- cell.configureCell(item: itemIdentifier)
+ cell.configure(item: itemIdentifier, behavior: .select)
return cell
}
@@ -307,7 +307,11 @@ extension LocationDataSource: UITableViewDelegate {
}
extension LocationDataSource: LocationCellDelegate {
- func toggle(cell: LocationCell) {
+ func toggleSelection(cell: LocationCell) {
+ print("Just put a print statement // Marco")
+ }
+
+ func toggleExpanding(cell: LocationCell) {
guard let indexPath = tableView.indexPath(for: cell),
let item = itemIdentifier(for: indexPath) else { return }
@@ -321,7 +325,7 @@ extension LocationDataSource: LocationCellDelegate {
if !isExpanded {
locationList.addSubNodes(from: item, at: indexPath)
} else {
- locationList.recursivelyRemoveSubNodes(from: item.node)
+ locationList.removeSubNodes(from: item.node)
}
let list = sections.enumerated().map { index, section in
@@ -369,29 +373,3 @@ extension LocationDataSource {
}
}
}
-
-private extension [LocationCellViewModel] {
- mutating func addSubNodes(from item: LocationCellViewModel, at indexPath: IndexPath) {
- let section = LocationSection.allCases[indexPath.section]
- let row = indexPath.row + 1
-
- let locations = item.node.children.map {
- LocationCellViewModel(section: section, node: $0, indentationLevel: item.indentationLevel + 1)
- }
-
- if row < count {
- insert(contentsOf: locations, at: row)
- } else {
- append(contentsOf: locations)
- }
- }
-
- mutating func recursivelyRemoveSubNodes(from node: LocationNode) {
- for node in node.children {
- node.showsChildren = false
- removeAll(where: { node == $0.node })
-
- recursivelyRemoveSubNodes(from: node)
- }
- }
-}