]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
iOS: Rewrite applying runtime configuration
authorRoopesh Chander <roop@roopc.net>
Sat, 9 Feb 2019 21:08:23 +0000 (02:38 +0530)
committerRoopesh Chander <roop@roopc.net>
Sat, 9 Feb 2019 22:05:24 +0000 (03:35 +0530)
To make scrolling smoother while the fields are modified

Signed-off-by: Roopesh Chander <roop@roopc.net>
WireGuard/WireGuard/UI/TunnelViewModel.swift
WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift

index efca5c0fdf45edded5a031d94ac2a1ed4a264846..0657a16da5bb96fa2ff225f7a416d2b539af453d 100644 (file)
@@ -67,17 +67,17 @@ class TunnelViewModel {
 
     static let keyLengthInBase64 = 44
 
-    struct ChangeHandlers {
-        enum FieldChange {
+    struct Changes {
+        enum FieldChange: Equatable {
             case added
             case removed
-            case modified
+            case modified(newValue: String)
         }
 
-        var interfaceChanged: ([InterfaceField: FieldChange]) -> Void
-        var peerChangedAt: (Int, [PeerField: FieldChange]) -> Void
-        var peersRemovedAt: ([Int]) -> Void
-        var peersInsertedAt: ([Int]) -> Void
+        var interfaceChanges: [InterfaceField: FieldChange]
+        var peerChanges: [(peerIndex: Int, changes: [PeerField: FieldChange])]
+        var peersRemovedIndices: [Int]
+        var peersInsertedIndices: [Int]
     }
 
     class InterfaceData {
@@ -217,12 +217,12 @@ class TunnelViewModel {
             }
         }
 
-        func applyConfiguration(other: InterfaceConfiguration, otherName: String, changeHandler: ([InterfaceField: ChangeHandlers.FieldChange]) -> Void) {
+        func applyConfiguration(other: InterfaceConfiguration, otherName: String) -> [InterfaceField: Changes.FieldChange] {
             if scratchpad.isEmpty {
                 populateScratchpad()
             }
             let otherScratchPad = InterfaceData.createScratchPad(from: other, name: otherName)
-            var changes = [InterfaceField: ChangeHandlers.FieldChange]()
+            var changes = [InterfaceField: Changes.FieldChange]()
             for field in InterfaceField.allCases {
                 switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") {
                 case ("", ""):
@@ -233,14 +233,12 @@ class TunnelViewModel {
                     changes[field] = .removed
                 case (let this, let other):
                     if this != other {
-                        changes[field] = .modified
+                        changes[field] = .modified(newValue: other)
                     }
                 }
             }
             scratchpad = otherScratchPad
-            if !changes.isEmpty {
-                changeHandler(changes)
-            }
+            return changes
         }
     }
 
@@ -441,12 +439,12 @@ class TunnelViewModel {
             excludePrivateIPsValue = isOn
         }
 
-        func applyConfiguration(other: PeerConfiguration, peerIndex: Int, changeHandler: (Int, [PeerField: ChangeHandlers.FieldChange]) -> Void) {
+        func applyConfiguration(other: PeerConfiguration) -> [PeerField: Changes.FieldChange] {
             if scratchpad.isEmpty {
                 populateScratchpad()
             }
             let otherScratchPad = PeerData.createScratchPad(from: other)
-            var changes = [PeerField: ChangeHandlers.FieldChange]()
+            var changes = [PeerField: Changes.FieldChange]()
             for field in PeerField.allCases {
                 switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") {
                 case ("", ""):
@@ -457,14 +455,12 @@ class TunnelViewModel {
                     changes[field] = .removed
                 case (let this, let other):
                     if this != other {
-                        changes[field] = .modified
+                        changes[field] = .modified(newValue: other)
                     }
                 }
             }
             scratchpad = otherScratchPad
-            if !changes.isEmpty {
-                changeHandler(peerIndex, changes)
-            }
+            return changes
         }
     }
 
@@ -548,21 +544,20 @@ class TunnelViewModel {
         }
     }
 
-    func applyConfiguration(other: TunnelConfiguration, changeHandlers: ChangeHandlers) {
+    @discardableResult
+    func applyConfiguration(other: TunnelConfiguration) -> Changes {
         // Replaces current data with data from other TunnelConfiguration, ignoring any changes in peer ordering.
-        // Change handler callbacks are processed in the following order, which is designed to work with both the
-        // UITableView way (modify - delete - insert) and the NSTableView way (indices are based on past operations):
-        //   - interfaceChanged
-        //   - peerChangedAt
-        //   - peersRemovedAt
-        //   - peersInsertedAt
 
-        interfaceData.applyConfiguration(other: other.interface, otherName: other.name ?? "", changeHandler: changeHandlers.interfaceChanged)
+        let interfaceChanges = interfaceData.applyConfiguration(other: other.interface, otherName: other.name ?? "")
 
+        var peerChanges = [(peerIndex: Int, changes: [PeerField: Changes.FieldChange])]()
         for otherPeer in other.peers {
             if let peersDataIndex = peersData.firstIndex(where: { $0.publicKey == otherPeer.publicKey }) {
                 let peerData = peersData[peersDataIndex]
-                peerData.applyConfiguration(other: otherPeer, peerIndex: peersDataIndex, changeHandler: changeHandlers.peerChangedAt)
+                let changes = peerData.applyConfiguration(other: otherPeer)
+                if !changes.isEmpty {
+                    peerChanges.append((peerIndex: peersDataIndex, changes: changes))
+                }
             }
         }
 
@@ -573,9 +568,6 @@ class TunnelViewModel {
                 peersData.remove(at: index)
             }
         }
-        if !removedPeerIndices.isEmpty {
-            changeHandlers.peersRemovedAt(removedPeerIndices)
-        }
 
         var addedPeerIndices = [Int]()
         for otherPeer in other.peers {
@@ -586,15 +578,14 @@ class TunnelViewModel {
                 peersData.append(peerData)
             }
         }
-        if !addedPeerIndices.isEmpty {
-            changeHandlers.peersInsertedAt(addedPeerIndices)
-        }
 
         for (index, peer) in peersData.enumerated() {
             peer.index = index
             peer.numberOfPeers = peersData.count
             peer.updateExcludePrivateIPsFieldState()
         }
+
+        return Changes(interfaceChanges: interfaceChanges, peerChanges: peerChanges, peersRemovedIndices: removedPeerIndices, peersInsertedIndices: addedPeerIndices)
     }
 }
 
index 1eb6461921d4c0312167403d261504bc3e07283d..d45f846984e9191ff4f2c1d2a1c09298b4df31b3 100644 (file)
@@ -158,16 +158,21 @@ class TunnelDetailTableViewController: UITableViewController {
         var interfaceFieldIsVisible = self.interfaceFieldIsVisible
         var peerFieldIsVisible = self.peerFieldIsVisible
 
-        func sectionChanged<T>(fields: [T], fieldIsVisible fieldIsVisibleInput: [Bool], tableView: UITableView, section: Int, changes: [T: TunnelViewModel.ChangeHandlers.FieldChange]) {
-            var fieldIsVisible = fieldIsVisibleInput
-            var modifiedIndexPaths = [IndexPath]()
-            for (index, field) in fields.enumerated() where changes[field] == .modified {
-                let row = fieldIsVisible[0 ..< index].filter { $0 }.count
-                modifiedIndexPaths.append(IndexPath(row: row, section: section))
-            }
-            if !modifiedIndexPaths.isEmpty {
-                tableView.reloadRows(at: modifiedIndexPaths, with: .none)
+        func handleSectionFieldsModified<T>(fields: [T], fieldIsVisible: [Bool], section: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
+            for (index, field) in fields.enumerated() {
+                guard let change = changes[field] else { continue }
+                if case .modified(let newValue) = change {
+                    let row = fieldIsVisible[0 ..< index].filter { $0 }.count
+                    let indexPath = IndexPath(row: row, section: section)
+                    if let cell = tableView.cellForRow(at: indexPath) as? KeyValueCell {
+                        cell.value = newValue
+                    }
+                }
             }
+        }
+
+        func handleSectionRowsInsertedOrRemoved<T>(fields: [T], fieldIsVisible fieldIsVisibleInput: [Bool], section: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
+            var fieldIsVisible = fieldIsVisibleInput
 
             var removedIndexPaths = [IndexPath]()
             for (index, field) in fields.enumerated().reversed() where changes[field] == .removed {
@@ -190,30 +195,44 @@ class TunnelDetailTableViewController: UITableViewController {
             }
         }
 
-        let changeHandlers = TunnelViewModel.ChangeHandlers(
-            interfaceChanged: { changes in
-                sectionChanged(fields: TunnelDetailTableViewController.interfaceFields, fieldIsVisible: interfaceFieldIsVisible,
-                               tableView: tableView, section: interfaceSectionIndex, changes: changes)
-            },
-            peerChangedAt: { peerIndex, changes in
-                sectionChanged(fields: TunnelDetailTableViewController.peerFields, fieldIsVisible: peerFieldIsVisible[peerIndex],
-                               tableView: tableView, section: firstPeerSectionIndex + peerIndex, changes: changes)
-            },
-            peersRemovedAt: { peerIndices in
-                let sectionIndices = peerIndices.map { firstPeerSectionIndex + $0 }
-                tableView.deleteSections(IndexSet(sectionIndices), with: .automatic)
-            },
-            peersInsertedAt: { peerIndices in
-                let sectionIndices = peerIndices.map { firstPeerSectionIndex + $0 }
-                tableView.insertSections(IndexSet(sectionIndices), with: .automatic)
-            }
-        )
+        let changes = self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration)
+
+        if !changes.interfaceChanges.isEmpty {
+            handleSectionFieldsModified(fields: TunnelDetailTableViewController.interfaceFields, fieldIsVisible: interfaceFieldIsVisible,
+                                        section: interfaceSectionIndex, changes: changes.interfaceChanges)
+        }
+        for (peerIndex, peerChanges) in changes.peerChanges {
+            handleSectionFieldsModified(fields: TunnelDetailTableViewController.peerFields, fieldIsVisible: peerFieldIsVisible[peerIndex], section: firstPeerSectionIndex + peerIndex, changes: peerChanges)
+        }
+
+        let isAnyInterfaceFieldAddedOrRemoved = changes.interfaceChanges.contains { $0.value == .added || $0.value == .removed }
+        let isAnyPeerFieldAddedOrRemoved = changes.peerChanges.contains { $0.changes.contains { $0.value == .added || $0.value == .removed } }
+        let peersRemovedSectionIndices = changes.peersRemovedIndices.map { firstPeerSectionIndex + $0 }
+        let peersInsertedSectionIndices = changes.peersInsertedIndices.map { firstPeerSectionIndex + $0 }
 
-        tableView.beginUpdates()
-        self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration, changeHandlers: changeHandlers)
-        self.loadSections()
-        self.loadVisibleFields()
-        tableView.endUpdates()
+        if isAnyInterfaceFieldAddedOrRemoved || isAnyPeerFieldAddedOrRemoved || !peersRemovedSectionIndices.isEmpty || !peersInsertedSectionIndices.isEmpty {
+            tableView.beginUpdates()
+            if isAnyInterfaceFieldAddedOrRemoved {
+                handleSectionRowsInsertedOrRemoved(fields: TunnelDetailTableViewController.interfaceFields, fieldIsVisible: interfaceFieldIsVisible, section: interfaceSectionIndex, changes: changes.interfaceChanges)
+            }
+            if isAnyPeerFieldAddedOrRemoved {
+                for (peerIndex, peerChanges) in changes.peerChanges {
+                    handleSectionRowsInsertedOrRemoved(fields: TunnelDetailTableViewController.peerFields, fieldIsVisible: peerFieldIsVisible[peerIndex], section: firstPeerSectionIndex + peerIndex, changes: peerChanges)
+                }
+            }
+            if !peersRemovedSectionIndices.isEmpty {
+                tableView.deleteSections(IndexSet(peersRemovedSectionIndices), with: .automatic)
+            }
+            if !peersInsertedSectionIndices.isEmpty {
+                tableView.insertSections(IndexSet(peersInsertedSectionIndices), with: .automatic)
+            }
+            self.loadSections()
+            self.loadVisibleFields()
+            tableView.endUpdates()
+        } else {
+            self.loadSections()
+            self.loadVisibleFields()
+        }
     }
 
     private func reloadRuntimeConfiguration() {