Search + lastXMin Filter + dynamic text size

This commit is contained in:
relikd
2020-05-13 01:37:50 +02:00
parent 9485d7e9b5
commit 8424c161b9
25 changed files with 865 additions and 211 deletions

View File

@@ -2,7 +2,7 @@ import UIKit
extension UIAlertController {
func presentIn(_ viewController: UIViewController?) {
viewController?.present(self, animated: true, completion: nil)
viewController?.present(self, animated: true)
}
}
@@ -68,7 +68,7 @@ func AlertDeleteLogs(_ domain: String, latest: Timestamp, success: @escaping (_
if idx >= times.count {
success(0)
} else {
success(Timestamp(Date().timeIntervalSince1970) - times[idx])
success(Timestamp.now() - times[idx])
}
}
}

View File

@@ -1,10 +1,16 @@
import Foundation
extension GroupedDomain {
/// Return new `GroupedDomain` by adding `total` and `blocked` counts. Set `lastModified` to the maximum of the two.
static func +(a: GroupedDomain, b: GroupedDomain) -> Self {
GroupedDomain(domain: a.domain, total: a.total + b.total, blocked: a.blocked + b.blocked,
lastModified: max(a.lastModified, b.lastModified), options: a.options ?? b.options )
}
/// Return new `GroupedDomain` by subtracting `total` and `blocked` counts.
static func -(a: GroupedDomain, b: GroupedDomain) -> Self {
GroupedDomain(domain: a.domain, total: a.total - b.total, blocked: a.blocked - b.blocked,
lastModified: a.lastModified, options: a.options )
}
}
extension Array where Element == GroupedDomain {
@@ -26,7 +32,9 @@ extension Recording {
}
extension Timestamp {
/// - Returns: Time string with format `yyyy-MM-dd HH:mm:ss`
func asDateTime() -> String { dateTimeFormat.string(from: self) }
func toDate() -> Date { Date(timeIntervalSince1970: Double(self)) }
static func now() -> Timestamp { Timestamp(Date().timeIntervalSince1970) }
static func past(minutes: Int) -> Timestamp { now() - Timestamp(minutes * 60) }
}

View File

@@ -99,6 +99,17 @@ struct TimeFormat {
static func since(_ date: Date, millis: Bool = false) -> String {
from(Date().timeIntervalSince(date), millis: millis)
}
/// Formatted duration string, e.g., `20 min` or `7 days`
/// - Parameters:
/// - minutes: Duration in minutes
/// - style: Default: `.short`
static func short(minutes: Int, style: DateComponentsFormatter.UnitsStyle = .short) -> String? {
let dcf = DateComponentsFormatter()
dcf.maximumUnitCount = 1
dcf.allowedUnits = [.day, .hour, .minute]
dcf.unitsStyle = style
return dcf.string(from: DateComponents(minute: minutes))
}
}
extension UIColor {

View File

@@ -1,7 +1,8 @@
import Foundation
let NotifyVPNStateChanged = NSNotification.Name("GlassVPNStateChanged") // VPNState!
let NotifyFilterChanged = NSNotification.Name("PSIFilterSettingsChanged") // nil!
let NotifyDNSFilterChanged = NSNotification.Name("PSIDNSFilterSettingsChanged") // nil!
let NotifyDateFilterChanged = NSNotification.Name("PSIDateFilterSettingsChanged") // nil!
let NotifyLogHistoryReset = NSNotification.Name("PSILogHistoryReset") // nil!
let NotifyRecordingChanged = NSNotification.Name("PSIRecordingChanged") // (Recording, deleted: Bool)!

View File

@@ -6,3 +6,36 @@ var currentVPNState: VPNState = .off
public enum VPNState : Int {
case on = 1, inbetween, off
}
struct Pref {
struct DidShowTutorial {
static var Welcome: Bool {
get { UserDefaults.standard.bool(forKey: "didShowTutorialAppWelcome") }
set { UserDefaults.standard.set(newValue, forKey: "didShowTutorialAppWelcome") }
}
static var Recordings: Bool {
get { UserDefaults.standard.bool(forKey: "didShowTutorialRecordings") }
set { UserDefaults.standard.set(newValue, forKey: "didShowTutorialRecordings") }
}
}
struct DateFilter {
static var Kind: DateFilterKind {
get { DateFilterKind(rawValue: UserDefaults.standard.integer(forKey: "dateFilterType"))! }
set { UserDefaults.standard.set(newValue.rawValue, forKey: "dateFilterType") }
}
static var LastXMin: Int {
get { UserDefaults.standard.integer(forKey: "dateFilterLastXMin") }
set { UserDefaults.standard.set(newValue, forKey: "dateFilterLastXMin") }
}
/// Return selected timestamp filter or `nil` if filtering is disabled.
/// - Returns: `Timestamp.now() - LastXMin * 60`
static func lastXMinTimestamp() -> Timestamp? {
if Kind != .LastXMin { return nil }
return Timestamp.past(minutes: Pref.DateFilter.LastXMin)
}
}
}
enum DateFilterKind: Int {
case Off = 0, LastXMin = 1, ABRange = 2;
}

View File

@@ -39,60 +39,96 @@ extension UIRefreshControl {
}
// MARK: - Incremental Update Delegate
protocol IncrementalDataSourceUpdate : UITableViewController {
var dataSource: [GroupedDomain] { get set }
}
extension IncrementalDataSourceUpdate {
func ifDisplayed(_ block: () -> Void) {
DispatchQueue.main.sync {
if self.tableView.window?.isKeyWindow ?? false {
block()
// TODO: custom handling if cell is being edited
} else {
self.tableView.reloadData()
}
}
}
func insertRow(_ obj: GroupedDomain, at index: Int) {
dataSource.insert(obj, at: index)
ifDisplayed {
self.tableView.insertRows(at: [IndexPath(row: index)], with: .left)
}
}
func moveRow(_ obj: GroupedDomain, from: Int, to: Int) {
dataSource.remove(at: from)
dataSource.insert(obj, at: to)
ifDisplayed {
let source = IndexPath(row: from)
let cell = self.tableView.cellForRow(at: source)
cell?.detailTextLabel?.text = obj.detailCellText
self.tableView.moveRow(at: source, to: IndexPath(row: to))
}
}
func replaceRow(_ obj: GroupedDomain, at index: Int) {
dataSource[index] = obj
ifDisplayed {
self.tableView.reloadRows(at: [IndexPath(row: index)], with: .automatic)
}
}
func deleteRow(at index: Int) {
dataSource.remove(at: index)
ifDisplayed {
self.tableView.deleteRows(at: [IndexPath(row: index)], with: .automatic)
}
}
func replaceData(with newData: [GroupedDomain]) {
dataSource = newData
ifDisplayed {
self.tableView.reloadData()
}
}
}
// MARK: TableView extensions
extension IndexPath {
/// Convenience init with `section: 0`
public init(row: Int) { self.init(row: row, section: 0) }
}
extension UITableView {
/// Returns `true` if this `tableView` is the currently frontmost visible
var isFrontmost: Bool { window?.isKeyWindow ?? false }
/// If frontmost window, perform `deleteRows()`; If not, perform `reloadData()`
func safeDeleteRow(_ index: Int, with animation: UITableView.RowAnimation = .automatic) {
isFrontmost ? deleteRows(at: [IndexPath(row: index)], with: animation) : reloadData()
}
/// If frontmost window, perform `reloadRows()`; If not, perform `reloadData()`
func safeReloadRow(_ index: Int, with animation: UITableView.RowAnimation = .automatic) {
isFrontmost ? reloadRows(at: [IndexPath(row: index)], with: animation) : reloadData()
}
/// If frontmost window, perform `insertRows()`; If not, perform `reloadData()`
func safeInsertRow(_ index: Int, with animation: UITableView.RowAnimation = .automatic) {
isFrontmost ? insertRows(at: [IndexPath(row: index)], with: animation) : reloadData()
}
/// If frontmost window, perform `moveRow()`; If not, perform `reloadData()`
func safeMoveRow(_ from: Int, to: Int) {
isFrontmost ? moveRow(at: IndexPath(row: from), to: IndexPath(row: to)) : reloadData()
}
}
// MARK: - Incremental Update Delegate
enum IncrementalDataSourceUpdateOperation {
case ReloadTable, Update, Insert, Delete, Move
}
protocol IncrementalDataSourceUpdate : UITableViewController {
var dataSource: [GroupedDomain] { get set }
func shouldLiveUpdateIncrementalDataSource() -> Bool
/// - Warning: Called on a background thread!
/// - Parameters:
/// - operation: Row update action
/// - row: Which row index is affected? `IndexPath(row: row)`
/// - moveTo: Only set for `Move` operation, otherwise `-1`
func didUpdateIncrementalDataSource(_ operation: IncrementalDataSourceUpdateOperation, row: Int, moveTo: Int)
}
extension IncrementalDataSourceUpdate {
func shouldLiveUpdateIncrementalDataSource() -> Bool { true }
func didUpdateIncrementalDataSource(_: IncrementalDataSourceUpdateOperation, row: Int, moveTo: Int) {}
// TODO: custom handling if cell is being edited
func insertRow(_ obj: GroupedDomain, at index: Int) {
dataSource.insert(obj, at: index)
if shouldLiveUpdateIncrementalDataSource() {
DispatchQueue.main.sync { tableView.safeInsertRow(index, with: .left) }
}
didUpdateIncrementalDataSource(.Insert, row: index, moveTo: -1)
}
func moveRow(_ obj: GroupedDomain, from: Int, to: Int) {
dataSource.remove(at: from)
dataSource.insert(obj, at: to)
if shouldLiveUpdateIncrementalDataSource() {
DispatchQueue.main.sync {
let cell = tableView.cellForRow(at: IndexPath(row: from))
cell?.detailTextLabel?.text = obj.detailCellText
tableView.safeMoveRow(from, to: to)
}
}
didUpdateIncrementalDataSource(.Move, row: from, moveTo: to)
}
func replaceRow(_ obj: GroupedDomain, at index: Int) {
dataSource[index] = obj
if shouldLiveUpdateIncrementalDataSource() {
DispatchQueue.main.sync { tableView.safeReloadRow(index) }
}
didUpdateIncrementalDataSource(.Update, row: index, moveTo: -1)
}
func deleteRow(at index: Int) {
dataSource.remove(at: index)
if shouldLiveUpdateIncrementalDataSource() {
DispatchQueue.main.sync { tableView.safeDeleteRow(index) }
}
didUpdateIncrementalDataSource(.Delete, row: index, moveTo: -1)
}
func replaceData(with newData: [GroupedDomain]) {
dataSource = newData
if shouldLiveUpdateIncrementalDataSource() {
DispatchQueue.main.sync { tableView.reloadData() }
}
didUpdateIncrementalDataSource(.ReloadTable, row: -1, moveTo: -1)
}
}