Search + lastXMin Filter + dynamic text size
This commit is contained in:
@@ -1,8 +1,24 @@
|
||||
import UIKit
|
||||
|
||||
class TVCDomains: UITableViewController, IncrementalDataSourceUpdate {
|
||||
class TVCDomains: UITableViewController, IncrementalDataSourceUpdate, UISearchBarDelegate {
|
||||
|
||||
internal var dataSource: [GroupedDomain] = []
|
||||
private func dataSource(at: Int) -> GroupedDomain {
|
||||
dataSource[(searchActive ? searchIndices[at] : at)]
|
||||
}
|
||||
private var searchActive: Bool = false
|
||||
private var searchIndices: [Int] = []
|
||||
private var searchTerm: String?
|
||||
private let searchBar: UISearchBar = {
|
||||
let x = UISearchBar(frame: CGRect.init(x: 0, y: 0, width: 20, height: 10))
|
||||
x.sizeToFit()
|
||||
x.showsCancelButton = true
|
||||
x.autocapitalizationType = .none
|
||||
x.autocorrectionType = .no
|
||||
return x
|
||||
}()
|
||||
@IBOutlet private var filterButton: UIBarButtonItem!
|
||||
@IBOutlet private var filterButtonDetail: UIBarButtonItem!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@@ -12,30 +28,129 @@ class TVCDomains: UITableViewController, IncrementalDataSourceUpdate {
|
||||
NotifyLogHistoryReset.observe(call: #selector(reloadDataSource), on: self)
|
||||
reloadDataSource()
|
||||
DBWrp.dataA_delegate = self
|
||||
searchBar.delegate = self
|
||||
NotifyDateFilterChanged.observe(call: #selector(dateFilterChanged), on: self)
|
||||
dateFilterChanged()
|
||||
}
|
||||
|
||||
@objc func reloadDataSource() {
|
||||
dataSource = DBWrp.listOfDomains()
|
||||
tableView.reloadData()
|
||||
if searchActive {
|
||||
searchBar(searchBar, textDidChange: "")
|
||||
} else {
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if let index = tableView.indexPathForSelectedRow?.row {
|
||||
(segue.destination as? TVCHosts)?.parentDomain = dataSource[index].domain
|
||||
(segue.destination as? TVCHosts)?.parentDomain = dataSource(at: index).domain
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table View Delegate
|
||||
|
||||
override func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int { dataSource.count }
|
||||
override func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int {
|
||||
searchActive ? searchIndices.count : dataSource.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "DomainCell")!
|
||||
let entry = dataSource[indexPath.row]
|
||||
let entry = dataSource(at: indexPath.row)
|
||||
cell.textLabel?.text = entry.domain
|
||||
cell.detailTextLabel?.text = entry.detailCellText
|
||||
cell.imageView?.image = entry.options?.tableRowImage()
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Search
|
||||
|
||||
@IBAction private func searchButtonTapped(_ sender: UIBarButtonItem) {
|
||||
setSearch(hidden: searchActive)
|
||||
}
|
||||
|
||||
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||
setSearch(hidden: true)
|
||||
}
|
||||
|
||||
private func setSearch(hidden: Bool) {
|
||||
searchActive = !hidden
|
||||
searchIndices = []
|
||||
searchTerm = nil
|
||||
searchBar.text = nil
|
||||
tableView.tableHeaderView = hidden ? nil : searchBar
|
||||
if !hidden { searchBar.becomeFirstResponder() }
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
||||
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(performSearch), object: nil)
|
||||
perform(#selector(performSearch), with: nil, afterDelay: 0.3)
|
||||
}
|
||||
|
||||
@objc private func performSearch() {
|
||||
searchTerm = searchBar.text?.lowercased() ?? ""
|
||||
searchIndices = dataSource.enumerated().compactMap {
|
||||
if $1.domain.lowercased().contains(searchTerm!) { return $0 }
|
||||
return nil
|
||||
}
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
func shouldLiveUpdateIncrementalDataSource() -> Bool { !searchActive }
|
||||
|
||||
func didUpdateIncrementalDataSource(_ operation: IncrementalDataSourceUpdateOperation, row: Int, moveTo: Int) {
|
||||
guard searchActive else {
|
||||
return
|
||||
}
|
||||
switch operation {
|
||||
case .ReloadTable:
|
||||
DispatchQueue.main.sync { tableView.reloadData() }
|
||||
case .Insert:
|
||||
if dataSource[row].domain.lowercased().contains(searchTerm ?? "") {
|
||||
searchIndices.insert(row, at: 0)
|
||||
DispatchQueue.main.sync { tableView.safeInsertRow(0, with: .left) }
|
||||
}
|
||||
case .Delete:
|
||||
if let idx = searchIndices.firstIndex(of: row) {
|
||||
searchIndices.remove(at: idx)
|
||||
DispatchQueue.main.sync { tableView.safeDeleteRow(idx) }
|
||||
}
|
||||
case .Update, .Move:
|
||||
if let idx = searchIndices.firstIndex(of: row) {
|
||||
if operation == .Move { searchIndices[idx] = moveTo }
|
||||
DispatchQueue.main.sync { tableView.safeReloadRow(idx) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Filter
|
||||
|
||||
@IBAction private func filterButtonTapped(_ sender: UIBarButtonItem) {
|
||||
let vc = self.storyboard!.instantiateViewController(withIdentifier: "domainFilter")
|
||||
vc.modalPresentationStyle = .custom
|
||||
if #available(iOS 13.0, *) {
|
||||
vc.isModalInPresentation = true
|
||||
}
|
||||
present(vc, animated: true)
|
||||
}
|
||||
|
||||
@objc private func dateFilterChanged() {
|
||||
switch Pref.DateFilter.Kind {
|
||||
case .ABRange: // read start/end time
|
||||
self.filterButtonDetail.title = "A – B"
|
||||
self.filterButton.image = UIImage(named: "filter-filled")
|
||||
case .LastXMin: // most recent
|
||||
let lastXMin = Pref.DateFilter.LastXMin
|
||||
if lastXMin == 0 { fallthrough }
|
||||
self.filterButtonDetail.title = TimeFormat.short(minutes: lastXMin, style: .abbreviated)
|
||||
self.filterButton.image = UIImage(named: "filter-filled")
|
||||
default:
|
||||
self.filterButtonDetail.title = ""
|
||||
self.filterButton.image = UIImage(named: "filter-clear")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
117
main/Requests/VCDateFilter.swift
Normal file
117
main/Requests/VCDateFilter.swift
Normal file
@@ -0,0 +1,117 @@
|
||||
import UIKit
|
||||
|
||||
// TODO: (count > x) filter
|
||||
|
||||
class VCDateFilter: UIViewController, UIGestureRecognizerDelegate {
|
||||
|
||||
@IBOutlet private var segmentControl: UISegmentedControl!
|
||||
@IBOutlet private var sectionTitle: UILabel!
|
||||
|
||||
// entries no older than
|
||||
@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 rangeView: UIView!
|
||||
@IBOutlet private var buttonRangeStart: UIButton!
|
||||
@IBOutlet private var buttonRangeEnd: UIButton!
|
||||
|
||||
|
||||
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
|
||||
|
||||
durationSlider.tag = -1 // otherwise wont update because `tag == 0`
|
||||
durationSlider.value = Float(durationTimes.firstIndex(of: Pref.DateFilter.LastXMin) ?? 0) / 9
|
||||
durationSliderChanged(durationSlider)
|
||||
|
||||
var a = Timestamp(4).asDateTime() // TODO: load from preferences
|
||||
var b = Timestamp.now().asDateTime()
|
||||
a.removeLast(3) // remove seconds
|
||||
b.removeLast(3)
|
||||
buttonRangeStart.setTitle(a, for: .normal)
|
||||
buttonRangeEnd.setTitle(b, for: .normal)
|
||||
}
|
||||
|
||||
@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 durationSliderChanged(_ sender: UISlider) {
|
||||
let i = Int((sender.value + (0.499/9)) * 9) // mid-value-switch
|
||||
guard i >= 0, i <= 9 else { return }
|
||||
sender.value = Float(i) / 9
|
||||
if sender.tag != durationTimes[i] {
|
||||
sender.tag = durationTimes[i]
|
||||
durationLabel.text = (sender.tag == 0 ? "Off" : TimeFormat.short(minutes: sender.tag))
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction private func didTapRangeButton(_ sender: UIButton) {
|
||||
// TODO: show date picker
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if Pref.DateFilter.Kind != newKind || Pref.DateFilter.LastXMin != newXMin {
|
||||
Pref.DateFilter.Kind = newKind
|
||||
Pref.DateFilter.LastXMin = newXMin
|
||||
DBWrp.reloadAfterDateFilterHasChanged()
|
||||
NotifyDateFilterChanged.post()
|
||||
}
|
||||
dismiss(animated: true)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: White Triangle Popup Arrow
|
||||
|
||||
@IBDesignable
|
||||
class PopupTriangle: UIView {
|
||||
@IBInspectable var rotation: CGFloat = 0
|
||||
@IBInspectable var color: UIColor = .black
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
guard let c = UIGraphicsGetCurrentContext() else { return }
|
||||
let w = rect.width, h = rect.height
|
||||
switch rotation {
|
||||
case 90: // right
|
||||
c.lineFromTo(x1: 0, y1: 0, x2: w, y2: h/2)
|
||||
c.addLine(to: CGPoint(x: 0, y: h))
|
||||
case 180: // bottom
|
||||
c.lineFromTo(x1: w, y1: 0, x2: w/2, y2: h)
|
||||
c.addLine(to: CGPoint(x: 0, y: 0))
|
||||
case 270: // left
|
||||
c.lineFromTo(x1: w, y1: h, x2: 0, y2: h/2)
|
||||
c.addLine(to: CGPoint(x: w, y: 0))
|
||||
default: // top
|
||||
c.lineFromTo(x1: 0, y1: h, x2: w/2, y2: 0)
|
||||
c.addLine(to: CGPoint(x: w, y: h))
|
||||
}
|
||||
c.closePath()
|
||||
c.setFillColor(color.cgColor)
|
||||
c.fillPath()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user