Sort order
This commit is contained in:
@@ -49,7 +49,7 @@
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="domainFilter" modalTransitionStyle="crossDissolve" id="r7v-PM-PrR" customClass="VCDateFilter" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="QBv-5g-BTH">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="320"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<navigationBar hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jAM-LN-evh">
|
||||
@@ -61,7 +61,7 @@
|
||||
</items>
|
||||
</navigationBar>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pEc-vv-7Ts">
|
||||
<rect key="frame" x="8" y="64" width="233.5" height="217.5"/>
|
||||
<rect key="frame" x="8" y="64" width="233.5" height="391.5"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="UNT-qn-2cg">
|
||||
<rect key="frame" x="8" y="8" width="217.5" height="32"/>
|
||||
@@ -70,20 +70,20 @@
|
||||
<segment title="Date Range"/>
|
||||
</segments>
|
||||
<connections>
|
||||
<action selector="didChangeSegment:" destination="r7v-PM-PrR" eventType="valueChanged" id="cxI-lR-J7y"/>
|
||||
<action selector="didChangeFilterBy:" destination="r7v-PM-PrR" eventType="valueChanged" id="kM6-QE-ZGV"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Show entries no older than" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UBq-oH-pKp">
|
||||
<rect key="frame" x="10" y="55" width="213.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="gEf-Ra-RyA">
|
||||
<rect key="frame" x="10" y="83.5" width="213.5" height="124"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="gEf-Ra-RyA">
|
||||
<rect key="frame" x="10" y="47" width="213.5" height="334.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Show entries no older than" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UBq-oH-pKp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="213.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ucF-MH-iRP">
|
||||
<rect key="frame" x="0.0" y="0.0" width="213.5" height="50"/>
|
||||
<rect key="frame" x="0.0" y="35.5" width="213.5" height="50"/>
|
||||
<subviews>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="qhe-6d-hGB">
|
||||
<rect key="frame" x="-2" y="0.0" width="155.5" height="51"/>
|
||||
@@ -111,8 +111,14 @@
|
||||
<constraint firstItem="qhe-6d-hGB" firstAttribute="top" secondItem="ucF-MH-iRP" secondAttribute="top" id="eJC-d4-zg0"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Show entries within range" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rtf-o1-gk6">
|
||||
<rect key="frame" x="0.0" y="100.5" width="213.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9As-hA-MKt">
|
||||
<rect key="frame" x="0.0" y="50" width="213.5" height="74"/>
|
||||
<rect key="frame" x="0.0" y="136" width="213.5" height="74"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="From:" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wAd-o2-PHY">
|
||||
<rect key="frame" x="0.0" y="6.5" width="44" height="20.5"/>
|
||||
@@ -159,21 +165,54 @@
|
||||
<constraint firstItem="wAd-o2-PHY" firstAttribute="leading" secondItem="9As-hA-MKt" secondAttribute="leading" id="zgR-pJ-vFs"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Order by" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9Fe-5F-TVt">
|
||||
<rect key="frame" x="0.0" y="225" width="213.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cWy-un-IHC">
|
||||
<rect key="frame" x="0.0" y="260.5" width="213.5" height="74"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="UKE-MR-kRJ">
|
||||
<rect key="frame" x="-2" y="0.0" width="217.5" height="36"/>
|
||||
<segments>
|
||||
<segment title="Date"/>
|
||||
<segment title="Name"/>
|
||||
<segment title="Count"/>
|
||||
</segments>
|
||||
</segmentedControl>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="eG2-a4-zm5">
|
||||
<rect key="frame" x="-2" y="43" width="217.5" height="32"/>
|
||||
<segments>
|
||||
<segment title="Ascending"/>
|
||||
<segment title="Descending"/>
|
||||
</segments>
|
||||
</segmentedControl>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="eG2-a4-zm5" firstAttribute="top" secondItem="UKE-MR-kRJ" secondAttribute="bottom" constant="8" symbolic="YES" id="6oC-bZ-XdM"/>
|
||||
<constraint firstItem="eG2-a4-zm5" firstAttribute="leading" secondItem="cWy-un-IHC" secondAttribute="leading" constant="-2" id="7R0-qB-J0u"/>
|
||||
<constraint firstAttribute="bottom" secondItem="eG2-a4-zm5" secondAttribute="bottom" id="JbN-vA-Rd5"/>
|
||||
<constraint firstItem="UKE-MR-kRJ" firstAttribute="top" secondItem="cWy-un-IHC" secondAttribute="top" id="L21-Kf-g2d"/>
|
||||
<constraint firstAttribute="trailing" secondItem="eG2-a4-zm5" secondAttribute="trailing" constant="-2" id="cbD-H9-e1Q"/>
|
||||
<constraint firstItem="UKE-MR-kRJ" firstAttribute="leading" secondItem="cWy-un-IHC" secondAttribute="leading" constant="-2" id="lKB-g4-asw"/>
|
||||
<constraint firstAttribute="trailing" secondItem="UKE-MR-kRJ" secondAttribute="trailing" constant="-2" id="xIa-X2-0Lp"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="UNT-qn-2cg" firstAttribute="top" secondItem="pEc-vv-7Ts" secondAttribute="top" constant="8" id="Awu-uv-9wF"/>
|
||||
<constraint firstItem="UBq-oH-pKp" firstAttribute="top" secondItem="UNT-qn-2cg" secondAttribute="bottom" constant="16" id="FDO-1V-ffl"/>
|
||||
<constraint firstItem="UNT-qn-2cg" firstAttribute="leading" secondItem="pEc-vv-7Ts" secondAttribute="leading" constant="8" id="Icx-YR-5bc"/>
|
||||
<constraint firstItem="UBq-oH-pKp" firstAttribute="leading" secondItem="pEc-vv-7Ts" secondAttribute="leading" constant="10" id="KRb-xo-A9i"/>
|
||||
<constraint firstItem="gEf-Ra-RyA" firstAttribute="top" secondItem="UBq-oH-pKp" secondAttribute="bottom" constant="8" symbolic="YES" id="QPi-aa-6ff"/>
|
||||
<constraint firstItem="gEf-Ra-RyA" firstAttribute="top" secondItem="UNT-qn-2cg" secondAttribute="bottom" constant="8" symbolic="YES" id="QPi-aa-6ff"/>
|
||||
<constraint firstItem="UNT-qn-2cg" firstAttribute="trailing" secondItem="pEc-vv-7Ts" secondAttribute="trailing" constant="-8" id="Sof-6L-T2D"/>
|
||||
<constraint firstItem="gEf-Ra-RyA" firstAttribute="bottom" secondItem="pEc-vv-7Ts" secondAttribute="bottom" constant="-10" id="TMx-5J-z2P"/>
|
||||
<constraint firstItem="gEf-Ra-RyA" firstAttribute="leading" secondItem="UBq-oH-pKp" secondAttribute="leading" id="U6l-7M-bm4"/>
|
||||
<constraint firstItem="gEf-Ra-RyA" firstAttribute="trailing" secondItem="UBq-oH-pKp" secondAttribute="trailing" id="YKE-TR-fTB"/>
|
||||
<constraint firstItem="UBq-oH-pKp" firstAttribute="trailing" secondItem="pEc-vv-7Ts" secondAttribute="trailing" constant="-10" id="yZd-eO-85k"/>
|
||||
<constraint firstItem="gEf-Ra-RyA" firstAttribute="leading" secondItem="pEc-vv-7Ts" secondAttribute="leading" constant="10" id="U6l-7M-bm4"/>
|
||||
<constraint firstItem="gEf-Ra-RyA" firstAttribute="trailing" secondItem="pEc-vv-7Ts" secondAttribute="trailing" constant="-10" id="YKE-TR-fTB"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
@@ -213,16 +252,18 @@
|
||||
</connections>
|
||||
</view>
|
||||
<extendedEdge key="edgesForExtendedLayout" bottom="YES"/>
|
||||
<size key="freeformSize" width="320" height="320"/>
|
||||
<connections>
|
||||
<outlet property="buttonRangeEnd" destination="IG3-Wc-UI4" id="wAd-ca-bVQ"/>
|
||||
<outlet property="buttonRangeStart" destination="FVD-kB-91w" id="HbX-Vl-uBE"/>
|
||||
<outlet property="durationLabel" destination="ika-su-PZQ" id="1Br-vu-xir"/>
|
||||
<outlet property="durationSlider" destination="qhe-6d-hGB" id="wph-zX-WIz"/>
|
||||
<outlet property="durationTitle" destination="UBq-oH-pKp" id="BEd-Lo-a2v"/>
|
||||
<outlet property="durationView" destination="ucF-MH-iRP" id="TCI-Pp-drf"/>
|
||||
<outlet property="filterBy" destination="UNT-qn-2cg" id="M1J-n8-LHq"/>
|
||||
<outlet property="orderbyAsc" destination="eG2-a4-zm5" id="II1-hc-pyZ"/>
|
||||
<outlet property="orderbyType" destination="UKE-MR-kRJ" id="fK7-dW-MLd"/>
|
||||
<outlet property="rangeTitle" destination="rtf-o1-gk6" id="2DY-xP-VOg"/>
|
||||
<outlet property="rangeView" destination="9As-hA-MKt" id="0Mq-Gi-nF6"/>
|
||||
<outlet property="sectionTitle" destination="UBq-oH-pKp" id="JG9-aM-n6e"/>
|
||||
<outlet property="segmentControl" destination="UNT-qn-2cg" id="JKs-2W-IRd"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="xTS-RW-xLN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
@@ -232,7 +273,7 @@
|
||||
</connections>
|
||||
</tapGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="700" y="-1800"/>
|
||||
<point key="canvasLocation" x="700" y="-1950"/>
|
||||
</scene>
|
||||
<!--Domains-->
|
||||
<scene sceneID="MN1-aZ-cZt">
|
||||
|
||||
@@ -105,7 +105,7 @@ class FilterPipeline<T> {
|
||||
pipeline.remove(at: i)
|
||||
if i == pipeline.count {
|
||||
// only if we don't reset other layers we can assure `toLessRestrictive`
|
||||
display?.reset(toLessRestrictive: lastLayerIndices())
|
||||
display?.apply(lessRestrictive: lastLayerIndices())
|
||||
} else {
|
||||
resetFilters(startingAt: i)
|
||||
}
|
||||
@@ -128,6 +128,15 @@ class FilterPipeline<T> {
|
||||
reloadTableCells()
|
||||
}
|
||||
|
||||
/// Will reverse the current display order without resorting. This is faster than setting a new sorting `predicate`.
|
||||
/// However, the `predicate` must be dynamic and support a sort order flag.
|
||||
/// - Warning: Make sure `predicate` does reflect the change or it will lead to data inconsistency!
|
||||
func reverseSorting() {
|
||||
// TODO: use semaphore to prevent concurrent edits
|
||||
display?.reverseOrder()
|
||||
reloadTableCells()
|
||||
}
|
||||
|
||||
/// Re-built filter and display sorting order.
|
||||
/// - Parameter index: Must be: `index <= pipeline.count`
|
||||
private func resetFilters(startingAt index: Int = 0) {
|
||||
@@ -344,6 +353,7 @@ class PipelineSorting<T> {
|
||||
|
||||
/// Create a fresh, already sorted, display order projection.
|
||||
/// - Parameter predicate: Return `true` if first element should be sorted before second element.
|
||||
/// - Complexity: O(*n* log *n*), where *n* is the length of the `filter`.
|
||||
required init(_ predicate: @escaping Predicate, pipe: FilterPipeline<T>) {
|
||||
comperator = { [unowned pipe] in
|
||||
predicate(pipe.dataSource[$0], pipe.dataSource[$1])
|
||||
@@ -351,6 +361,12 @@ class PipelineSorting<T> {
|
||||
reset(to: pipe.lastLayerIndices())
|
||||
}
|
||||
|
||||
/// - Warning: Make sure `predicate` does reflect the change. Or it will lead to data inconsistency.
|
||||
/// - Complexity: O(*n*), where *n* is the length of the `filter`.
|
||||
fileprivate func reverseOrder() {
|
||||
projection.reverse()
|
||||
}
|
||||
|
||||
/// Replace current `projection` with new filter indices and apply sorting.
|
||||
/// - Complexity: O(*n* log *n*), where *n* is the length of the `filter`.
|
||||
fileprivate func reset(to filterIndices: [Int]) {
|
||||
@@ -367,7 +383,7 @@ class PipelineSorting<T> {
|
||||
/// After removing a layer of filtering the previous layers are less restrictive and thus contain more indices.
|
||||
/// Therefore, the difference between both index sets will be inserted into the projection.
|
||||
/// - Complexity: O(*m* log *n*), where *m* is the difference to the previous layer and *n* is the length of the `projection`.
|
||||
fileprivate func reset(toLessRestrictive filterIndices: [Int]) {
|
||||
fileprivate func apply(lessRestrictive filterIndices: [Int]) {
|
||||
for x in filterIndices.difference(toSubset: projection.sorted(), compare: (<)) {
|
||||
insertNew(x)
|
||||
}
|
||||
|
||||
@@ -13,19 +13,20 @@ class GroupedDomainDataSource {
|
||||
private let parent: String?
|
||||
let pipeline: FilterPipeline<GroupedDomain>
|
||||
private lazy var search = SearchBarManager(on: pipeline.delegate!.tableView)
|
||||
private var currentOrder: DateFilterOrderBy = .Date
|
||||
private var orderAsc = false
|
||||
|
||||
init(withDelegate tvc: FilterPipelineDelegate, parent p: String?) {
|
||||
parent = p
|
||||
pipeline = .init(withDelegate: tvc)
|
||||
pipeline.setDataSource { [unowned self] in self.dataSourceCallback() }
|
||||
pipeline.setSorting {
|
||||
$0.lastModified > $1.lastModified
|
||||
}
|
||||
resetSortingOrder(force: true)
|
||||
if #available(iOS 10.0, *) {
|
||||
tvc.tableView.refreshControl = UIRefreshControl(call: #selector(reloadFromSource), on: self)
|
||||
}
|
||||
NotifyLogHistoryReset.observe(call: #selector(reloadFromSource), on: self)
|
||||
NotifyDNSFilterChanged.observe(call: #selector(didChangeDomainFilter), on: self)
|
||||
NotifySortOrderChanged.observe(call: #selector(didChangeSortOrder), on: self)
|
||||
NotifySyncInsert.observe(call: #selector(syncInsert), on: self)
|
||||
NotifySyncRemove.observe(call: #selector(syncRemove), on: self)
|
||||
}
|
||||
@@ -43,6 +44,30 @@ class GroupedDomainDataSource {
|
||||
return log
|
||||
}
|
||||
|
||||
/// Read user defaults and apply new sorting order. Either by setting a new or reversing the current.
|
||||
/// - Parameter force: If `true` set new sorting even if the type does not differ.
|
||||
private func resetSortingOrder(force: Bool = false) {
|
||||
let orderDidChange = (orderAsc =? Pref.DateFilter.OrderAsc)
|
||||
if currentOrder =? Pref.DateFilter.OrderBy || force {
|
||||
switch currentOrder {
|
||||
case .Date:
|
||||
pipeline.setSorting { [unowned self] in
|
||||
self.orderAsc ? $0.lastModified < $1.lastModified : $0.lastModified > $1.lastModified
|
||||
}
|
||||
case .Name:
|
||||
pipeline.setSorting { [unowned self] in
|
||||
self.orderAsc ? $0.domain < $1.domain : $0.domain > $1.domain
|
||||
}
|
||||
case .Count:
|
||||
pipeline.setSorting { [unowned self] in
|
||||
self.orderAsc ? $0.total < $1.total : $0.total > $1.total
|
||||
}
|
||||
}
|
||||
} else if orderDidChange {
|
||||
pipeline.reverseSorting()
|
||||
}
|
||||
}
|
||||
|
||||
/// Pause recurring background updates to force reload `dataSource`.
|
||||
/// Callback fired on user action `pull-to-refresh`, or another background task triggered `NotifyLogHistoryReset`.
|
||||
/// - Parameter sender: May be either `UIRefreshControl` or `Notification`
|
||||
@@ -63,7 +88,7 @@ class GroupedDomainDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback fired when user editslist of `blocked` or `ignored` domains in settings. (`NotifyDNSFilterChanged` notification)
|
||||
/// Callback fired when user edits list of `blocked` or `ignored` domains in settings. (`NotifyDNSFilterChanged` notification)
|
||||
@objc private func didChangeDomainFilter(_ notification: Notification) {
|
||||
guard let domain = notification.object as? String else {
|
||||
reloadFromSource()
|
||||
@@ -76,6 +101,11 @@ class GroupedDomainDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback fired when user changes date filter settings. (`NotifySortOrderChanged` notification)
|
||||
@objc private func didChangeSortOrder(_ notification: Notification) {
|
||||
resetSortingOrder()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Table View Data Source
|
||||
|
||||
|
||||
@@ -26,3 +26,16 @@ extension UIEdgeInsets {
|
||||
self.init(top: top ?? all, left: left ?? all, bottom: bottom ?? all, right: right ?? all)
|
||||
}
|
||||
}
|
||||
|
||||
infix operator =? : ComparisonPrecedence
|
||||
extension Equatable {
|
||||
/// Assign a new value to `lhs` if the `newValue` differs from the previous value. Return whether the new value was set.
|
||||
/// - Returns: `true` if `lhs` was overwritten with another value
|
||||
static func =?(lhs: inout Self, newValue: Self) -> Bool {
|
||||
if lhs != newValue {
|
||||
lhs = newValue
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import Foundation
|
||||
let NotifyVPNStateChanged = NSNotification.Name("GlassVPNStateChanged") // VPNState!
|
||||
let NotifyDNSFilterChanged = NSNotification.Name("PSIDNSFilterSettingsChanged") // domain: String?
|
||||
let NotifyDateFilterChanged = NSNotification.Name("PSIDateFilterSettingsChanged") // nil!
|
||||
let NotifySortOrderChanged = NSNotification.Name("PSIDateFilterSortOrderChanged") // nil!
|
||||
let NotifyLogHistoryReset = NSNotification.Name("PSILogHistoryReset") // domain: String?
|
||||
let NotifySyncInsert = NSNotification.Name("PSISyncInsert") // SQLiteRowRange!
|
||||
let NotifySyncRemove = NSNotification.Name("PSISyncRemove") // SQLiteRowRange!
|
||||
|
||||
@@ -8,24 +8,40 @@ public enum VPNState : Int {
|
||||
}
|
||||
|
||||
struct Pref {
|
||||
static func Int(_ key: String) -> Int { UserDefaults.standard.integer(forKey: key) }
|
||||
static func Int(_ val: Int, _ key: String) { UserDefaults.standard.set(val, forKey: key) }
|
||||
static func Bool(_ key: String) -> Bool { UserDefaults.standard.bool(forKey: key) }
|
||||
static func Bool(_ val: Bool, _ key: String) { UserDefaults.standard.set(val, forKey: key) }
|
||||
|
||||
struct DidShowTutorial {
|
||||
static var Welcome: Bool {
|
||||
get { UserDefaults.standard.bool(forKey: "didShowTutorialAppWelcome") }
|
||||
set { UserDefaults.standard.set(newValue, forKey: "didShowTutorialAppWelcome") }
|
||||
get { Pref.Bool("didShowTutorialAppWelcome") }
|
||||
set { Pref.Bool(newValue, "didShowTutorialAppWelcome") }
|
||||
}
|
||||
static var Recordings: Bool {
|
||||
get { UserDefaults.standard.bool(forKey: "didShowTutorialRecordings") }
|
||||
set { UserDefaults.standard.set(newValue, forKey: "didShowTutorialRecordings") }
|
||||
get { Pref.Bool("didShowTutorialRecordings") }
|
||||
set { Pref.Bool(newValue, "didShowTutorialRecordings") }
|
||||
}
|
||||
}
|
||||
struct DateFilter {
|
||||
static var Kind: DateFilterKind {
|
||||
get { DateFilterKind(rawValue: UserDefaults.standard.integer(forKey: "dateFilterType"))! }
|
||||
set { UserDefaults.standard.set(newValue.rawValue, forKey: "dateFilterType") }
|
||||
get { DateFilterKind(rawValue: Pref.Int("dateFilterType"))! }
|
||||
set { Pref.Int(newValue.rawValue, "dateFilterType") }
|
||||
}
|
||||
/// Default: `0` (disabled)
|
||||
static var LastXMin: Int {
|
||||
get { UserDefaults.standard.integer(forKey: "dateFilterLastXMin") }
|
||||
set { UserDefaults.standard.set(newValue, forKey: "dateFilterLastXMin") }
|
||||
get { Pref.Int("dateFilterLastXMin") }
|
||||
set { Pref.Int(newValue, "dateFilterLastXMin") }
|
||||
}
|
||||
/// default: `.Date`
|
||||
static var OrderBy: DateFilterOrderBy {
|
||||
get { DateFilterOrderBy(rawValue: Pref.Int("dateFilterOderType"))! }
|
||||
set { Pref.Int(newValue.rawValue, "dateFilterOderType") }
|
||||
}
|
||||
/// default: `false` (Desc)
|
||||
static var OrderAsc: Bool {
|
||||
get { Pref.Bool("dateFilterOderAsc") }
|
||||
set { Pref.Bool(newValue, "dateFilterOderAsc") }
|
||||
}
|
||||
|
||||
/// Return selected timestamp filter or `nil` if filtering is disabled.
|
||||
@@ -39,3 +55,6 @@ struct Pref {
|
||||
enum DateFilterKind: Int {
|
||||
case Off = 0, LastXMin = 1, ABRange = 2;
|
||||
}
|
||||
enum DateFilterOrderBy: Int {
|
||||
case Date = 0, Name = 1, Count = 2;
|
||||
}
|
||||
|
||||
@@ -4,27 +4,32 @@ import UIKit
|
||||
|
||||
class VCDateFilter: UIViewController, UIGestureRecognizerDelegate {
|
||||
|
||||
@IBOutlet private var segmentControl: UISegmentedControl!
|
||||
@IBOutlet private var sectionTitle: UILabel!
|
||||
@IBOutlet private var filterBy: UISegmentedControl!
|
||||
|
||||
// entries no older than
|
||||
@IBOutlet private var durationTitle: UILabel!
|
||||
@IBOutlet private var durationView: UIView!
|
||||
@IBOutlet private var durationSlider: UISlider!
|
||||
@IBOutlet private var durationLabel: UILabel!
|
||||
private let durationTimes = [0, 1, 20, 60, 360, 720, 1440, 2880, 4320, 10080]
|
||||
|
||||
// entries within range
|
||||
@IBOutlet private var rangeTitle: UILabel!
|
||||
@IBOutlet private var rangeView: UIView!
|
||||
@IBOutlet private var buttonRangeStart: UIButton!
|
||||
@IBOutlet private var buttonRangeEnd: UIButton!
|
||||
|
||||
// order by
|
||||
@IBOutlet private var orderbyType: UISegmentedControl!
|
||||
@IBOutlet private var orderbyAsc: UISegmentedControl!
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
segmentControl.selectedSegmentIndex = (Pref.DateFilter.Kind == .ABRange ? 1 : 0)
|
||||
didChangeSegment(segmentControl)
|
||||
segmentControl.setEnabled(false, forSegmentAt: 1) // TODO: until range filter is ready
|
||||
filterBy.selectedSegmentIndex = (Pref.DateFilter.Kind == .ABRange ? 1 : 0)
|
||||
didChangeFilterBy(filterBy)
|
||||
filterBy.setEnabled(false, forSegmentAt: 1) // TODO: until range filter is ready
|
||||
|
||||
durationSlider.tag = -1 // otherwise wont update because `tag == 0`
|
||||
durationSlider.value = Float(durationTimes.firstIndex(of: Pref.DateFilter.LastXMin) ?? 0) / 9
|
||||
@@ -36,16 +41,17 @@ class VCDateFilter: UIViewController, UIGestureRecognizerDelegate {
|
||||
b.removeLast(3)
|
||||
buttonRangeStart.setTitle(a, for: .normal)
|
||||
buttonRangeEnd.setTitle(b, for: .normal)
|
||||
|
||||
orderbyType.selectedSegmentIndex = Pref.DateFilter.OrderBy.rawValue
|
||||
orderbyAsc.selectedSegmentIndex = (Pref.DateFilter.OrderAsc ? 0 : 1)
|
||||
}
|
||||
|
||||
@IBAction private func didChangeSegment(_ sender: UISegmentedControl) {
|
||||
durationView.isHidden = (sender.selectedSegmentIndex != 0)
|
||||
rangeView.isHidden = (sender.selectedSegmentIndex != 1)
|
||||
switch sender.selectedSegmentIndex {
|
||||
case 0: sectionTitle.text = "Show entries no older than"
|
||||
case 1: sectionTitle.text = "Show entries within range"
|
||||
default: break
|
||||
}
|
||||
@IBAction private func didChangeFilterBy(_ sender: UISegmentedControl) {
|
||||
let firstSelected = (sender.selectedSegmentIndex == 0)
|
||||
durationTitle.isHidden = !firstSelected
|
||||
durationView.isHidden = !firstSelected
|
||||
rangeTitle.isHidden = firstSelected
|
||||
rangeView.isHidden = firstSelected
|
||||
}
|
||||
|
||||
@IBAction private func durationSliderChanged(_ sender: UISlider) {
|
||||
@@ -65,16 +71,28 @@ class VCDateFilter: UIViewController, UIGestureRecognizerDelegate {
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
||||
if gestureRecognizer.view == touch.view {
|
||||
let newXMin = durationSlider.tag
|
||||
let newKind: DateFilterKind
|
||||
if segmentControl.selectedSegmentIndex == 1 {
|
||||
newKind = .ABRange
|
||||
} else if newXMin > 0 {
|
||||
newKind = .LastXMin
|
||||
} else {
|
||||
newKind = .Off
|
||||
let filterType: DateFilterKind
|
||||
let orderType: DateFilterOrderBy
|
||||
|
||||
switch filterBy.selectedSegmentIndex {
|
||||
case 0: filterType = (newXMin > 0) ? .LastXMin : .Off
|
||||
case 1: filterType = .ABRange
|
||||
default: preconditionFailure()
|
||||
}
|
||||
if Pref.DateFilter.Kind != newKind || Pref.DateFilter.LastXMin != newXMin {
|
||||
Pref.DateFilter.Kind = newKind
|
||||
switch orderbyType.selectedSegmentIndex {
|
||||
case 0: orderType = .Date
|
||||
case 1: orderType = .Name
|
||||
case 2: orderType = .Count
|
||||
default: preconditionFailure()
|
||||
}
|
||||
let orderAsc = (orderbyAsc.selectedSegmentIndex == 0)
|
||||
if Pref.DateFilter.OrderBy != orderType || Pref.DateFilter.OrderAsc != orderAsc {
|
||||
Pref.DateFilter.OrderBy = orderType
|
||||
Pref.DateFilter.OrderAsc = orderAsc
|
||||
NotifySortOrderChanged.post()
|
||||
}
|
||||
if Pref.DateFilter.Kind != filterType || Pref.DateFilter.LastXMin != newXMin {
|
||||
Pref.DateFilter.Kind = filterType
|
||||
Pref.DateFilter.LastXMin = newXMin
|
||||
NotifyDateFilterChanged.post()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user