-
Notifications
You must be signed in to change notification settings - Fork 85
/
Copy pathBlockLogViewController.swift
218 lines (181 loc) · 6.91 KB
/
BlockLogViewController.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
//
// BlockLogViewController.swift
// Lockdown
//
// Copyright © 2019 Confirmed Inc. All rights reserved.
//
import UIKit
class BlockLogViewController: BaseViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet var blockDayCounterLabel: UILabel!
// -- SUPPORTING LIVE UPDATES
var timer: Timer?
var kvoObservationToken: Any?
let debouncer = Debouncer(seconds: 0.3)
//
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dayLogTime.count;
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0
}
func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
return false
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "blockLogCell", for: indexPath) as? BlockLogCell else {
return UITableViewCell()
}
cell.time.text = dayLogTime[indexPath.row];
cell.logHost?.text = dayLogHost[indexPath.row];
return cell
}
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
let logHost = dayLogHost[indexPath.row]
let info = TrackerInfoRegistry.shared.info(forTrackerDomain: logHost)
showPopupDialog(title: info.title, message: info.description, acceptButton: "Okay")
}
override func viewDidLoad() {
super.viewDidLoad()
blockDayCounterLabel.text = getDayMetricsString(commas: true)
tableView.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(self.refreshData(_:)), for: .valueChanged)
configureObservers()
refreshData(self)
}
deinit {
timer?.invalidate()
timer = nil
}
func configureObservers() {
kvoObservationToken = defaults.observe(\.LockdownDayLogs, options: [.new, .old]) { [weak self] (defaults, change) in
DispatchQueue.main.async {
self?.debouncer.debounce {
self?.refreshData(defaults)
}
}
}
// timer is used as a backup in case KVO fails for any reason
timer = Timer.scheduledTimer(withTimeInterval: 15.0, repeats: true) { [weak self] (timer) in
self?.refreshData(timer)
}
timer?.tolerance = 3.0
}
@objc func refreshData(_ sender: Any) {
flushBlockLog(log: { _ in })
blockDayCounterLabel.text = getDayMetricsString(commas: true)
if BlockDayLog.shared.isEnabled {
tableView.isHidden = false
blockLogDisabledContainer.isHidden = true
let oldDayLogTime = dayLogTime
dayLogTime = []
dayLogHost = []
if let dayLogs = BlockDayLog.shared.strings?.reversed() {
for log in dayLogs {
let sp = log.components(separatedBy: "_");
if sp.count == 2 {
dayLogTime.append(sp[0]);
dayLogHost.append(sp[1]);
}
}
}
if dayLogTime.count > oldDayLogTime.count, oldDayLogTime != [] {
let diff = dayLogTime.count - oldDayLogTime.count
let indexPaths = (0 ..< diff).map({ IndexPath(row: $0, section: 0) })
tableView.performBatchUpdates {
tableView.insertRows(at: indexPaths, with: .top)
} completion: { (finished) in
return
}
} else {
tableView.reloadData()
}
DispatchQueue.main.async {
self.refreshControl.endRefreshing()
}
} else {
tableView.isHidden = true
blockLogDisabledContainer.isHidden = false
}
}
@IBAction func dismiss() {
self.dismiss(animated: true, completion: {})
}
@IBAction func showMenu() {
let isBlockEnabled = BlockDayLog.shared.isEnabled
let message = """
The block log can be manually cleared or disabled. Disabling the Block Log only disables the log of connections - \
the number of tracking attempts will still be displayed.
"""
showPopupDialog(
title: .localized("Settings"),
message: .localized(message),
buttons: [
.custom(title: isBlockEnabled ? .localized("Disable Block Log") : .localized("Enable Block Log")) {
if isBlockEnabled {
self.showDisableBlockLog()
} else {
self.enableBlockLog()
}
},
.custom(title: .localized("Clear Block Log")) {
BlockDayLog.shared.clear()
defaults.set(0, forKey: kDayMetrics)
self.refreshData(self)
},
.cancel()
])
}
func showDisableBlockLog() {
showPopupDialog(
title: .localized("Disable Block Log?"),
message: .localized("You'll have to reenable it later here to start seeing blocked entries again."),
buttons: [
.destructive(title: .localized("Disable")) {
BlockDayLog.shared.disable(shouldClear: true)
self.refreshData(self)
},
.preferredCancel()
])
}
@IBAction func enableBlockLog() {
BlockDayLog.shared.enable()
self.refreshData(self)
}
var dayLogTime: [String] = []
var dayLogHost: [String] = []
private let refreshControl = UIRefreshControl()
@IBOutlet var tableView: UITableView!
@IBOutlet var blockLogDisabledContainer: UIStackView!
}
fileprivate extension UserDefaults {
@objc
dynamic var LockdownDayLogs: [Any]? {
get {
return array(forKey: "LockdownDayLogs")
}
set {
set(newValue, forKey: "LockdownDayLogs")
}
}
}
// https://stackoverflow.com/a/52338788
// by Frédéric Adda
class Debouncer {
// MARK: - Properties
private let queue = DispatchQueue.main
private var workItem = DispatchWorkItem(block: {})
private var interval: TimeInterval
// MARK: - Initializer
init(seconds: TimeInterval) {
self.interval = seconds
}
// MARK: - Debouncing function
func debounce(action: @escaping (() -> Void)) {
workItem.cancel()
workItem = DispatchWorkItem(block: { action() })
queue.asyncAfter(deadline: .now() + interval, execute: workItem)
}
}