7a74cfa75e
- Add remote-client-ios: full native UIKit app with Objects/View/UILayout/Functions pattern - Widget hierarchy: BaseWidget → Fader, Toggle, Transport, Title, Feedback, LogWidget - LoggingVC: child VC embedded in LogWidget for live WS message inspection - Codable model: ModelPresetLoadToRemoteDevice with snake_case decoder strategy - Layout persistence: UserDefaults keyed by preset_uuid, fully client-side - WS logging: dedicated WS In/Out floating panels on desktop app - Remove HTTP server and JS client (replaced by native iOS app) - Add Xcode noise to .gitignore (xcuserstate, xcuserdata) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
158 lines
5.6 KiB
Swift
158 lines
5.6 KiB
Swift
import UIKit
|
|
|
|
class ObjectsViewController: UIViewController {
|
|
|
|
// MARK: - WebSocket
|
|
var wsTask: URLSessionWebSocketTask?
|
|
var urlSession: URLSession?
|
|
|
|
// MARK: - Connection State
|
|
enum ConnectionState {
|
|
case notStarted, connecting, connected, disconnected
|
|
}
|
|
var connectionState: ConnectionState = .notStarted
|
|
|
|
// MARK: - State
|
|
var presetName: String = ""
|
|
var presetUuid: String = ""
|
|
var isLocked: Bool = true
|
|
|
|
// MARK: - Widgets
|
|
var widgets: [String: BaseWidget] = [:]
|
|
var feedbackWidgets: [FeedbackWidget] = []
|
|
|
|
// MARK: - Layout
|
|
var layout: [String: CGRect] = [:]
|
|
|
|
let containerView: UIView = {
|
|
let view = UIView()
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.backgroundColor = UIColor(white: 0.1, alpha: 1)
|
|
return view
|
|
}()
|
|
|
|
// MARK: - Toolbar
|
|
lazy var connectButton: UIButton = {
|
|
let btn = UIButton(type: .system)
|
|
btn.translatesAutoresizingMaskIntoConstraints = false
|
|
btn.setTitle("Connect", for: .normal)
|
|
btn.setTitleColor(.black, for: .normal)
|
|
btn.backgroundColor = UIColor(red: 0, green: 0.9, blue: 0.46, alpha: 1)
|
|
btn.layer.cornerRadius = 4
|
|
btn.contentEdgeInsets = UIEdgeInsets(top: 6, left: 14, bottom: 6, right: 14)
|
|
btn.addTarget(self, action: #selector(self.connectTapped), for: .touchUpInside)
|
|
return btn
|
|
}()
|
|
|
|
lazy var arrangeButton: UIButton = {
|
|
let btn = UIButton(type: .system)
|
|
btn.translatesAutoresizingMaskIntoConstraints = false
|
|
btn.setTitle("Arrange", for: .normal)
|
|
btn.setTitleColor(.lightGray, for: .normal)
|
|
btn.backgroundColor = UIColor(white: 0.15, alpha: 1)
|
|
btn.layer.borderWidth = 1
|
|
btn.layer.borderColor = UIColor(white: 0.27, alpha: 1).cgColor
|
|
btn.layer.cornerRadius = 4
|
|
btn.contentEdgeInsets = UIEdgeInsets(top: 6, left: 14, bottom: 6, right: 14)
|
|
btn.addTarget(self, action: #selector(self.arrangeTapped), for: .touchUpInside)
|
|
return btn
|
|
}()
|
|
|
|
lazy var lockButton: UIButton = {
|
|
let btn = UIButton(type: .system)
|
|
btn.translatesAutoresizingMaskIntoConstraints = false
|
|
btn.setTitle("Lock / Save", for: .normal)
|
|
btn.setTitleColor(.black, for: .normal)
|
|
btn.backgroundColor = UIColor(red: 0, green: 0.9, blue: 0.46, alpha: 1)
|
|
btn.layer.cornerRadius = 4
|
|
btn.contentEdgeInsets = UIEdgeInsets(top: 6, left: 14, bottom: 6, right: 14)
|
|
btn.addTarget(self, action: #selector(self.lockTapped), for: .touchUpInside)
|
|
return btn
|
|
}()
|
|
|
|
// MARK: - Connection Fields
|
|
let ipField: UITextField = {
|
|
let tf = UITextField()
|
|
tf.translatesAutoresizingMaskIntoConstraints = false
|
|
tf.text = "127.0.0.1"
|
|
tf.textColor = .lightGray
|
|
tf.font = .monospacedSystemFont(ofSize: 13, weight: .regular)
|
|
tf.backgroundColor = UIColor(white: 0.12, alpha: 1)
|
|
tf.layer.borderWidth = 1
|
|
tf.layer.borderColor = UIColor(white: 0.27, alpha: 1).cgColor
|
|
tf.layer.cornerRadius = 4
|
|
tf.keyboardType = .decimalPad
|
|
tf.textAlignment = .center
|
|
tf.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: 0))
|
|
tf.leftViewMode = .always
|
|
tf.rightView = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: 0))
|
|
tf.rightViewMode = .always
|
|
return tf
|
|
}()
|
|
|
|
let portField: UITextField = {
|
|
let tf = UITextField()
|
|
tf.translatesAutoresizingMaskIntoConstraints = false
|
|
tf.text = "8765"
|
|
tf.textColor = .lightGray
|
|
tf.font = .monospacedSystemFont(ofSize: 13, weight: .regular)
|
|
tf.backgroundColor = UIColor(white: 0.12, alpha: 1)
|
|
tf.layer.borderWidth = 1
|
|
tf.layer.borderColor = UIColor(white: 0.27, alpha: 1).cgColor
|
|
tf.layer.cornerRadius = 4
|
|
tf.keyboardType = .numberPad
|
|
tf.textAlignment = .center
|
|
tf.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: 0))
|
|
tf.leftViewMode = .always
|
|
tf.rightView = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: 0))
|
|
tf.rightViewMode = .always
|
|
return tf
|
|
}()
|
|
|
|
// MARK: - Logging
|
|
lazy var loggingView: LoggingView = LoggingView()
|
|
|
|
lazy var logWidget: LogWidget = {
|
|
let w = LogWidget(uid: "log-widget", frame: LogWidget.defaultFrame())
|
|
return w
|
|
}()
|
|
|
|
lazy var showLoggingButton: UIButton = {
|
|
let btn = UIButton(type: .system)
|
|
btn.translatesAutoresizingMaskIntoConstraints = false
|
|
btn.setTitle("Show Logging", for: .normal)
|
|
btn.setTitleColor(.lightGray, for: .normal)
|
|
btn.backgroundColor = UIColor(white: 0.15, alpha: 1)
|
|
btn.layer.borderWidth = 1
|
|
btn.layer.borderColor = UIColor(white: 0.27, alpha: 1).cgColor
|
|
btn.layer.cornerRadius = 4
|
|
btn.contentEdgeInsets = UIEdgeInsets(top: 6, left: 14, bottom: 6, right: 14)
|
|
btn.addTarget(self, action: #selector(self.showLoggingTapped), for: .touchUpInside)
|
|
return btn
|
|
}()
|
|
|
|
// MARK: - Labels
|
|
let statusLabel: UILabel = {
|
|
let lbl = UILabel()
|
|
lbl.translatesAutoresizingMaskIntoConstraints = false
|
|
lbl.text = "not started"
|
|
lbl.textColor = UIColor(white: 0.33, alpha: 1)
|
|
lbl.font = .systemFont(ofSize: 11)
|
|
return lbl
|
|
}()
|
|
|
|
let presetLabel: UILabel = {
|
|
let lbl = UILabel()
|
|
lbl.translatesAutoresizingMaskIntoConstraints = false
|
|
lbl.text = ""
|
|
lbl.textColor = UIColor(white: 0.33, alpha: 1)
|
|
lbl.font = .systemFont(ofSize: 11)
|
|
return lbl
|
|
}()
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
}
|
|
|
|
}
|