Skip to content

Commit 0226a14

Browse files
author
Jon Petersson
committed
Create view with custom lists to edit
1 parent 09d7368 commit 0226a14

24 files changed

+590
-175
lines changed

ios/MullvadSettings/CustomListRepository.swift

+7-24
Original file line numberDiff line numberDiff line change
@@ -28,28 +28,25 @@ public enum CustomRelayListError: LocalizedError, Equatable {
2828
}
2929

3030
public struct CustomListRepository: CustomListRepositoryProtocol {
31-
public var publisher: AnyPublisher<[CustomList], Never> {
32-
passthroughSubject.eraseToAnyPublisher()
33-
}
34-
3531
private let logger = Logger(label: "CustomListRepository")
36-
private let passthroughSubject = PassthroughSubject<[CustomList], Never>()
3732

3833
private let settingsParser: SettingsParser = {
3934
SettingsParser(decoder: JSONDecoder(), encoder: JSONEncoder())
4035
}()
4136

4237
public init() {}
4338

44-
public func create(_ name: String, locations: [RelayLocation]) throws -> CustomList {
39+
public func save(list: CustomList) throws {
4540
var lists = fetchAll()
46-
if lists.contains(where: { $0.name == name }) {
41+
42+
if let index = lists.firstIndex(where: { $0.id == list.id }) {
43+
lists[index] = list
44+
try write(lists)
45+
} else if lists.contains(where: { $0.name == list.name }) {
4746
throw CustomRelayListError.duplicateName
4847
} else {
49-
let item = CustomList(id: UUID(), name: name, locations: locations)
50-
lists.append(item)
48+
lists.append(list)
5149
try write(lists)
52-
return item
5350
}
5451
}
5552

@@ -72,18 +69,6 @@ public struct CustomListRepository: CustomListRepositoryProtocol {
7269
public func fetchAll() -> [CustomList] {
7370
(try? read()) ?? []
7471
}
75-
76-
public func update(_ list: CustomList) {
77-
do {
78-
var lists = fetchAll()
79-
if let index = lists.firstIndex(where: { $0.id == list.id }) {
80-
lists[index] = list
81-
try write(lists)
82-
}
83-
} catch {
84-
logger.error(error: error)
85-
}
86-
}
8772
}
8873

8974
extension CustomListRepository {
@@ -97,7 +82,5 @@ extension CustomListRepository {
9782
let data = try settingsParser.produceUnversionedPayload(list)
9883

9984
try SettingsManager.store.write(data, for: .customRelayLists)
100-
101-
passthroughSubject.send(list)
10285
}
10386
}

ios/MullvadSettings/CustomListRepositoryProtocol.swift

+3-12
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,9 @@ import Combine
1010
import Foundation
1111
import MullvadTypes
1212
public protocol CustomListRepositoryProtocol {
13-
/// Publisher that propagates a snapshot of persistent store upon modifications.
14-
var publisher: AnyPublisher<[CustomList], Never> { get }
15-
16-
/// Persist modified custom list locating existing entry by id.
17-
/// - Parameter list: persistent custom list model.
18-
func update(_ list: CustomList)
13+
/// Save a custom list. If the list doesn't already exist, it must have a unique name.
14+
/// - Parameter list: a custom list.
15+
func save(list: CustomList) throws
1916

2017
/// Delete custom list by id.
2118
/// - Parameter id: an access method id.
@@ -26,12 +23,6 @@ public protocol CustomListRepositoryProtocol {
2623
/// - Returns: a persistent custom list model upon success, otherwise `nil`.
2724
func fetch(by id: UUID) -> CustomList?
2825

29-
/// Create a custom list by unique name.
30-
/// - Parameter name: a custom list name.
31-
/// - Parameter locations: locations in a custom list.
32-
/// - Returns: a persistent custom list model upon success, otherwise throws `Error`.
33-
func create(_ name: String, locations: [RelayLocation]) throws -> CustomList
34-
3526
/// Fetch all custom list.
3627
/// - Returns: all custom list model .
3728
func fetchAll() -> [CustomList]

ios/MullvadTypes/RelayConstraints.swift

+30-11
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ public struct RelayConstraints: Codable, Equatable, CustomDebugStringConvertible
2929
public var filter: RelayConstraint<RelayFilter>
3030

3131
// Added in 2024.1
32-
public var locations: RelayConstraint<RelayLocations>
32+
// Changed from RelayLocations to UserSelectedRelays in 2024.3
33+
public var locations: RelayConstraint<UserSelectedRelays>
3334

3435
public var debugDescription: String {
35-
"RelayConstraints { locations: \(locations), port: \(port) }"
36+
"RelayConstraints { locations: \(locations), port: \(port), filter: \(filter) }"
3637
}
3738

3839
public init(
39-
locations: RelayConstraint<RelayLocations> = .only(RelayLocations(locations: [.country("se")])),
40+
locations: RelayConstraint<UserSelectedRelays> = .only(UserSelectedRelays(locations: [.country("se")])),
4041
port: RelayConstraint<UInt16> = .any,
4142
filter: RelayConstraint<RelayFilter> = .any
4243
) {
@@ -53,27 +54,45 @@ public struct RelayConstraints: Codable, Equatable, CustomDebugStringConvertible
5354
filter = try container.decodeIfPresent(RelayConstraint<RelayFilter>.self, forKey: .filter) ?? .any
5455

5556
// Added in 2024.1
56-
locations = try container.decodeIfPresent(RelayConstraint<RelayLocations>.self, forKey: .locations)
57-
?? Self.migrateLocations(decoder: decoder)
58-
?? .only(RelayLocations(locations: [.country("se")]))
57+
locations = try container.decodeIfPresent(RelayConstraint<UserSelectedRelays>.self, forKey: .locations)
58+
?? Self.migrateRelayLocations(decoder: decoder)
59+
?? Self.migrateRelayLocation(decoder: decoder)
60+
?? .only(UserSelectedRelays(locations: [.country("se")]))
5961
}
6062
}
6163

6264
extension RelayConstraints {
63-
private static func migrateLocations(decoder: Decoder) -> RelayConstraint<RelayLocations>? {
65+
private static func migrateRelayLocations(decoder: Decoder) -> RelayConstraint<UserSelectedRelays>? {
6466
let container = try? decoder.container(keyedBy: CodingKeys.self)
6567

6668
guard
67-
let location = try? container?.decodeIfPresent(RelayConstraint<RelayLocation>.self, forKey: .location)
69+
let relays = try? container?.decodeIfPresent(RelayConstraint<RelayLocations>.self, forKey: .locations)
6870
else {
6971
return nil
7072
}
7173

72-
switch location {
74+
return switch relays {
7375
case .any:
74-
return .any
76+
.any
77+
case let .only(relays):
78+
.only(UserSelectedRelays(locations: relays.locations))
79+
}
80+
}
81+
82+
private static func migrateRelayLocation(decoder: Decoder) -> RelayConstraint<UserSelectedRelays>? {
83+
let container = try? decoder.container(keyedBy: CodingKeys.self)
84+
85+
guard
86+
let relay = try? container?.decodeIfPresent(RelayConstraint<RelayLocation>.self, forKey: .location)
87+
else {
88+
return nil
89+
}
90+
91+
return switch relay {
92+
case .any:
93+
.any
7594
case let .only(location):
76-
return .only(RelayLocations(locations: [location]))
95+
.only(UserSelectedRelays(locations: [location]))
7796
}
7897
}
7998
}

ios/MullvadTypes/RelayLocation.swift

+25
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,31 @@ public enum RelayLocation: Codable, Hashable, CustomDebugStringConvertible {
107107
}
108108
}
109109

110+
public struct UserSelectedRelays: Codable, Equatable {
111+
public let locations: [RelayLocation]
112+
public let customListSelection: CustomListSelection?
113+
114+
public init(locations: [RelayLocation], customListSelection: CustomListSelection? = nil) {
115+
self.locations = locations
116+
self.customListSelection = customListSelection
117+
}
118+
}
119+
120+
extension UserSelectedRelays {
121+
public struct CustomListSelection: Codable, Equatable {
122+
/// The ID of the custom list that the selected relays belong to.
123+
public let listId: UUID
124+
/// Whether the selected relays are subnodes or the custom list itself.
125+
public let isList: Bool
126+
127+
public init(listId: UUID, isList: Bool) {
128+
self.listId = listId
129+
self.isList = isList
130+
}
131+
}
132+
}
133+
134+
/// - Warning: Deprecated, use UserSelectedRelays instead.
110135
public struct RelayLocations: Codable, Equatable {
111136
public let locations: [RelayLocation]
112137
public let customListId: UUID?

ios/MullvadVPN.xcodeproj/project.pbxproj

+8
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,8 @@
578578
7A9CCCC42A96302800DD6A34 /* TunnelCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCB22A96302800DD6A34 /* TunnelCoordinator.swift */; };
579579
7A9FA1422A2E3306000B728D /* CheckboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FA1412A2E3306000B728D /* CheckboxView.swift */; };
580580
7A9FA1442A2E3FE5000B728D /* CheckableSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */; };
581+
7AB2B6702BA1EB8C00B03E3B /* ListCustomListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB2B66E2BA1EB8C00B03E3B /* ListCustomListViewController.swift */; };
582+
7AB2B6712BA1EB8C00B03E3B /* ListCustomListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB2B66F2BA1EB8C00B03E3B /* ListCustomListCoordinator.swift */; };
581583
7AB4CCB92B69097E006037F5 /* IPOverrideTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4CCB82B69097E006037F5 /* IPOverrideTests.swift */; };
582584
7AB4CCBB2B691BBB006037F5 /* IPOverrideInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4CCBA2B691BBB006037F5 /* IPOverrideInteractor.swift */; };
583585
7ABCA5B32A9349F20044A708 /* Routing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A88DCCE2A8FABBE00D2FF0E /* Routing.framework */; };
@@ -1815,6 +1817,8 @@
18151817
7A9CCCB22A96302800DD6A34 /* TunnelCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelCoordinator.swift; sourceTree = "<group>"; };
18161818
7A9FA1412A2E3306000B728D /* CheckboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxView.swift; sourceTree = "<group>"; };
18171819
7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckableSettingsCell.swift; sourceTree = "<group>"; };
1820+
7AB2B66E2BA1EB8C00B03E3B /* ListCustomListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListCustomListViewController.swift; sourceTree = "<group>"; };
1821+
7AB2B66F2BA1EB8C00B03E3B /* ListCustomListCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListCustomListCoordinator.swift; sourceTree = "<group>"; };
18181822
7AB4CCB82B69097E006037F5 /* IPOverrideTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideTests.swift; sourceTree = "<group>"; };
18191823
7AB4CCBA2B691BBB006037F5 /* IPOverrideInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideInteractor.swift; sourceTree = "<group>"; };
18201824
7ABE318C2A1CDD4500DF4963 /* UIFont+Weight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Weight.swift"; sourceTree = "<group>"; };
@@ -3485,6 +3489,8 @@
34853489
7A6389E62B7E42BE008E77E1 /* CustomListViewController.swift */,
34863490
7A6389D32B7E3BD6008E77E1 /* CustomListViewModel.swift */,
34873491
7A6389E42B7E4247008E77E1 /* EditCustomListCoordinator.swift */,
3492+
7AB2B66F2BA1EB8C00B03E3B /* ListCustomListCoordinator.swift */,
3493+
7AB2B66E2BA1EB8C00B03E3B /* ListCustomListViewController.swift */,
34883494
);
34893495
path = CustomLists;
34903496
sourceTree = "<group>";
@@ -5071,6 +5077,7 @@
50715077
58FF9FE22B075BA600E4C97D /* EditAccessMethodSectionIdentifier.swift in Sources */,
50725078
F0C2AEFD2A0BB5CC00986207 /* NotificationProviderIdentifier.swift in Sources */,
50735079
7A58699B2B482FE200640D27 /* UITableViewCell+Disable.swift in Sources */,
5080+
7AB2B6702BA1EB8C00B03E3B /* ListCustomListViewController.swift in Sources */,
50745081
7A9CCCB72A96302800DD6A34 /* RevokedCoordinator.swift in Sources */,
50755082
7A6389F82B864CDF008E77E1 /* LocationNode.swift in Sources */,
50765083
587D96742886D87C00CD8F1C /* DeviceManagementContentView.swift in Sources */,
@@ -5231,6 +5238,7 @@
52315238
583DA21425FA4B5C00318683 /* LocationDataSource.swift in Sources */,
52325239
F050AE602B73A41E003F4EDB /* AllLocationDataSource.swift in Sources */,
52335240
587EB6742714520600123C75 /* PreferencesDataSourceDelegate.swift in Sources */,
5241+
7AB2B6712BA1EB8C00B03E3B /* ListCustomListCoordinator.swift in Sources */,
52345242
582BB1AF229566420055B6EF /* SettingsCell.swift in Sources */,
52355243
7AF9BE8E2A331C7B00DBFEDB /* RelayFilterViewModel.swift in Sources */,
52365244
58F3C0A4249CB069003E76BE /* HeaderBarView.swift in Sources */,

ios/MullvadVPN/Coordinators/CustomLists/AddCustomListCoordinator.swift

+13-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import UIKit
1313

1414
class AddCustomListCoordinator: Coordinator, Presentable, Presenting {
1515
let navigationController: UINavigationController
16-
let customListInteractor: CustomListInteractorProtocol
16+
let interactor: CustomListInteractorProtocol
1717

1818
var presentedViewController: UIViewController {
1919
navigationController
@@ -23,10 +23,10 @@ class AddCustomListCoordinator: Coordinator, Presentable, Presenting {
2323

2424
init(
2525
navigationController: UINavigationController,
26-
customListInteractor: CustomListInteractorProtocol
26+
interactor: CustomListInteractorProtocol
2727
) {
2828
self.navigationController = navigationController
29-
self.customListInteractor = customListInteractor
29+
self.interactor = interactor
3030
}
3131

3232
func start() {
@@ -35,7 +35,7 @@ class AddCustomListCoordinator: Coordinator, Presentable, Presenting {
3535
)
3636

3737
let controller = CustomListViewController(
38-
interactor: customListInteractor,
38+
interactor: interactor,
3939
subject: subject,
4040
alertPresenter: AlertPresenter(context: self)
4141
)
@@ -55,16 +55,23 @@ class AddCustomListCoordinator: Coordinator, Presentable, Presenting {
5555
comment: ""
5656
)
5757

58+
controller.navigationItem.leftBarButtonItem = UIBarButtonItem(
59+
systemItem: .cancel,
60+
primaryAction: UIAction(handler: { _ in
61+
self.didFinish?()
62+
})
63+
)
64+
5865
navigationController.pushViewController(controller, animated: false)
5966
}
6067
}
6168

6269
extension AddCustomListCoordinator: CustomListViewControllerDelegate {
63-
func customListDidSave() {
70+
func customListDidSave(_ list: CustomList) {
6471
didFinish?()
6572
}
6673

67-
func customListDidDelete() {
74+
func customListDidDelete(_ list: CustomList) {
6875
// No op.
6976
}
7077

ios/MullvadVPN/Coordinators/CustomLists/CustomListInteractor.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,23 @@
99
import MullvadSettings
1010

1111
protocol CustomListInteractorProtocol {
12-
func createCustomList(viewModel: CustomListViewModel) throws
13-
func updateCustomList(viewModel: CustomListViewModel)
14-
func deleteCustomList(id: UUID)
12+
func fetchAll() -> [CustomList]
13+
func save(viewModel: CustomListViewModel) throws
14+
func delete(id: UUID)
1515
}
1616

1717
struct CustomListInteractor: CustomListInteractorProtocol {
1818
let repository: CustomListRepositoryProtocol
1919

20-
func createCustomList(viewModel: CustomListViewModel) throws {
21-
try _ = repository.create(viewModel.name, locations: viewModel.locations)
20+
func fetchAll() -> [CustomList] {
21+
repository.fetchAll()
2222
}
2323

24-
func updateCustomList(viewModel: CustomListViewModel) {
25-
repository.update(viewModel.customList)
24+
func save(viewModel: CustomListViewModel) throws {
25+
try repository.save(list: viewModel.customList)
2626
}
2727

28-
func deleteCustomList(id: UUID) {
28+
func delete(id: UUID) {
2929
repository.delete(id: id)
3030
}
3131
}

ios/MullvadVPN/Coordinators/CustomLists/CustomListViewController.swift

+6-14
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import MullvadSettings
1111
import UIKit
1212

1313
protocol CustomListViewControllerDelegate: AnyObject {
14-
func customListDidSave()
15-
func customListDidDelete()
14+
func customListDidSave(_ list: CustomList)
15+
func customListDidDelete(_ list: CustomList)
1616
func showLocations()
1717
}
1818

@@ -91,13 +91,6 @@ class CustomListViewController: UIViewController {
9191
}
9292

9393
private func configureNavigationItem() {
94-
navigationItem.leftBarButtonItem = UIBarButtonItem(
95-
systemItem: .cancel,
96-
primaryAction: UIAction(handler: { _ in
97-
self.dismiss(animated: true)
98-
})
99-
)
100-
10194
navigationItem.rightBarButtonItem = saveBarButton
10295
}
10396

@@ -149,8 +142,8 @@ class CustomListViewController: UIViewController {
149142

150143
private func onSave() {
151144
do {
152-
try interactor.createCustomList(viewModel: subject.value)
153-
delegate?.customListDidSave()
145+
try interactor.save(viewModel: subject.value)
146+
delegate?.customListDidSave(subject.value.customList)
154147
} catch {
155148
validationErrors.insert(.name)
156149
dataSourceConfiguration?.set(validationErrors: validationErrors)
@@ -182,9 +175,8 @@ class CustomListViewController: UIViewController {
182175
),
183176
style: .destructive,
184177
handler: {
185-
self.interactor.deleteCustomList(id: self.subject.value.id)
186-
self.dismiss(animated: true)
187-
self.delegate?.customListDidDelete()
178+
self.interactor.delete(id: self.subject.value.id)
179+
self.delegate?.customListDidDelete(self.subject.value.customList)
188180
}
189181
),
190182
AlertAction(

0 commit comments

Comments
 (0)