Recordings interface
This commit is contained in:
74
main/Recordings/TVCPreviousRecords.swift
Normal file
74
main/Recordings/TVCPreviousRecords.swift
Normal file
@@ -0,0 +1,74 @@
|
||||
import UIKit
|
||||
|
||||
class TVCPreviousRecords: UITableViewController {
|
||||
private var dataSource: [Recording] = []
|
||||
|
||||
override func viewDidLoad() {
|
||||
dataSource = DBWrp.listOfRecordings().reversed() // newest on top
|
||||
NotifyRecordingChanged.observe(call: #selector(recordingDidChange(_:)), on: self)
|
||||
}
|
||||
|
||||
func stopRecording(_ record: Recording?) {
|
||||
guard let r = record?.stoppedCopy() else {
|
||||
return
|
||||
}
|
||||
insertNewRecord(r)
|
||||
editRecord(r, isNewRecording: true)
|
||||
}
|
||||
|
||||
@objc private func recordingDidChange(_ notification: Notification) {
|
||||
let (new, deleted) = notification.object as! (Recording, Bool)
|
||||
if let i = dataSource.firstIndex(where: { $0.start == new.start }) {
|
||||
if deleted {
|
||||
dataSource.remove(at: i)
|
||||
tableView.deleteRows(at: [IndexPath(row: i)], with: .automatic)
|
||||
} else {
|
||||
dataSource[i] = new
|
||||
tableView.reloadRows(at: [IndexPath(row: i)], with: .automatic)
|
||||
}
|
||||
} else if !deleted {
|
||||
insertNewRecord(new)
|
||||
}
|
||||
}
|
||||
|
||||
private func insertNewRecord(_ record: Recording) {
|
||||
dataSource.insert(record, at: 0)
|
||||
tableView.insertRows(at: [IndexPath(row: 0)], with: .top)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table View Delegate
|
||||
|
||||
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
||||
editRecord(dataSource[indexPath.row])
|
||||
}
|
||||
|
||||
private func editRecord(_ record: Recording, isNewRecording: Bool = false) {
|
||||
performSegue(withIdentifier: "editRecordSegue", sender: (record, isNewRecording))
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if segue.identifier == "editRecordSegue" {
|
||||
let (record, newlyCreated) = sender as! (Recording, Bool)
|
||||
let target = segue.destination as! VCEditRecording
|
||||
target.record = record
|
||||
target.deleteOnCancel = newlyCreated
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table View Data Source
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
dataSource.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PreviousRecordCell")!
|
||||
let x = dataSource[indexPath.row]
|
||||
cell.textLabel?.text = x.title ?? x.fallbackTitle
|
||||
cell.textLabel?.textColor = (x.title == nil) ? .systemGray : nil
|
||||
cell.detailTextLabel?.text = "at \(x.start.asDateTime()), duration: \(x.durationString ?? "?")"
|
||||
return cell
|
||||
}
|
||||
}
|
||||
68
main/Recordings/VCEditRecording.swift
Normal file
68
main/Recordings/VCEditRecording.swift
Normal file
@@ -0,0 +1,68 @@
|
||||
import UIKit
|
||||
|
||||
class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate {
|
||||
var record: Recording!
|
||||
var deleteOnCancel: Bool = false
|
||||
|
||||
@IBOutlet private var buttonCancel: UIBarButtonItem!
|
||||
@IBOutlet private var buttonSave: UIBarButtonItem!
|
||||
@IBOutlet private var inputTitle: UITextField!
|
||||
@IBOutlet private var inputNotes: UITextView!
|
||||
@IBOutlet private var inputDetails: UITextView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
if deleteOnCancel { // mark as destructive
|
||||
buttonCancel.tintColor = .systemRed
|
||||
}
|
||||
inputTitle.placeholder = record.fallbackTitle
|
||||
inputTitle.text = record.title
|
||||
inputNotes.text = record.notes
|
||||
inputDetails.text = """
|
||||
Start:\t\t\(record.start.asDateTime())
|
||||
End:\t\t\(record.stop?.asDateTime() ?? "?")
|
||||
Duration:\t\(record.durationString ?? "?")
|
||||
"""
|
||||
}
|
||||
|
||||
func textFieldDidChangeSelection(_ _: UITextField) { validateInput() }
|
||||
func textViewDidChange(_ _: UITextView) { validateInput() }
|
||||
|
||||
private func validateInput() {
|
||||
let changed = (inputTitle.text != record.title ?? "" || inputNotes.text != record.notes ?? "")
|
||||
buttonSave.isEnabled = changed
|
||||
}
|
||||
|
||||
@IBAction func didTapSave(_ sender: UIBarButtonItem) {
|
||||
if deleteOnCancel { // aka newly created
|
||||
// if remains true, `viewDidDisappear` will delete the record
|
||||
deleteOnCancel = false
|
||||
// TODO: copy db entries in new table for editing
|
||||
}
|
||||
QLog.Debug("updating record \(record.start)")
|
||||
record.title = (inputTitle.text == "") ? nil : inputTitle.text
|
||||
record.notes = (inputNotes.text == "") ? nil : inputNotes.text
|
||||
dismiss(animated: true) {
|
||||
DBWrp.recordingUpdate(self.record)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func didTapCancel(_ sender: UIBarButtonItem) {
|
||||
QLog.Debug("discard edit of record \(record.start)")
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
if deleteOnCancel {
|
||||
QLog.Debug("deleting record \(record.start)")
|
||||
DBWrp.recordingDelete(record)
|
||||
deleteOnCancel = false
|
||||
}
|
||||
}
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
if textField == inputTitle {
|
||||
return inputNotes.becomeFirstResponder()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
73
main/Recordings/VCRecordings.swift
Normal file
73
main/Recordings/VCRecordings.swift
Normal file
@@ -0,0 +1,73 @@
|
||||
import UIKit
|
||||
|
||||
class VCRecordings: UIViewController {
|
||||
private var currentRecording: Recording?
|
||||
private var recordingTimer: Timer?
|
||||
|
||||
@IBOutlet private var timeLabel: UILabel!
|
||||
@IBOutlet private var startButton: UIButton!
|
||||
|
||||
override func viewDidLoad() {
|
||||
// Duplicate font attributes but set monospace
|
||||
let traits = timeLabel.font.fontDescriptor.object(forKey: .traits) as? [UIFontDescriptor.TraitKey: Any] ?? [:]
|
||||
let weight = traits[.weight] as? CGFloat ?? UIFont.Weight.regular.rawValue
|
||||
timeLabel.font = UIFont.monospacedDigitSystemFont(ofSize: timeLabel.font.pointSize, weight: UIFont.Weight(rawValue: weight))
|
||||
// hide timer if not running
|
||||
updateUI(setRecording: false, animated: false)
|
||||
currentRecording = DBWrp.recordingGetCurrent()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
if currentRecording != nil { startTimer(animate: false) }
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
stopTimer(animate: false)
|
||||
}
|
||||
|
||||
@IBAction private func recordingButtonTapped(_ sender: UIButton) {
|
||||
if recordingTimer == nil {
|
||||
currentRecording = DBWrp.recordingStartNew()
|
||||
startTimer(animate: true)
|
||||
} else {
|
||||
stopTimer(animate: true)
|
||||
DBWrp.recordingStopAll()
|
||||
(children.first as! TVCPreviousRecords).stopRecording(currentRecording!)
|
||||
currentRecording = nil // otherwise it will restart
|
||||
}
|
||||
}
|
||||
|
||||
private func startTimer(animate: Bool) {
|
||||
guard let r = currentRecording, r.stop == nil else {
|
||||
return
|
||||
}
|
||||
recordingTimer = Timer.repeating(0.173, call: #selector(timerCallback(_:)), on: self, userInfo: r.start.toDate())
|
||||
updateUI(setRecording: true, animated: animate)
|
||||
}
|
||||
|
||||
@objc private func timerCallback(_ sender: Timer) {
|
||||
timeLabel.text = TimeFormat.since(sender.userInfo as! Date, millis: true)
|
||||
}
|
||||
|
||||
private func stopTimer(animate: Bool) {
|
||||
recordingTimer?.invalidate()
|
||||
recordingTimer = nil
|
||||
updateUI(setRecording: false, animated: animate)
|
||||
}
|
||||
|
||||
private func updateUI(setRecording: Bool, animated: Bool) {
|
||||
let title = setRecording ? "Stop Recording" : "Start New Recording"
|
||||
let color = setRecording ? UIColor.systemRed : nil
|
||||
let yT = setRecording ? 0 : -timeLabel.frame.height
|
||||
let yB = (setRecording ? 1 : 0.5) * (startButton.superview!.frame.height - startButton.frame.height)
|
||||
if !animated { // else title will flash
|
||||
startButton.titleLabel?.text = title
|
||||
}
|
||||
UIView.animate(withDuration: animated ? 0.3 : 0) {
|
||||
self.timeLabel.frame.origin.y = yT
|
||||
self.startButton.frame.origin.y = yB
|
||||
self.startButton.setTitle(title, for: .normal)
|
||||
self.startButton.setTitleColor(color, for: .normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user