Compare commits
No commits in common. "5f9af35542fdafbd102e78ce92c2f624da7da3d3" and "f599cb790b0f745e638289781a1730a1e18dd8d5" have entirely different histories.
5f9af35542
...
f599cb790b
@ -1,7 +1,6 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "sesame.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "watchos",
|
||||
"size" : "1024x1024"
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 113 KiB |
@ -1,5 +1,4 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
import CryptoKit
|
||||
|
||||
struct ContentView: View {
|
||||
@ -28,9 +27,10 @@ struct ContentView: View {
|
||||
@State
|
||||
var state: ClientState = .noKeyAvailable
|
||||
|
||||
let server = Client()
|
||||
@State
|
||||
private var hasActiveRequest = false
|
||||
|
||||
let history = HistoryManager()
|
||||
let server = Client()
|
||||
|
||||
var buttonBackground: Color {
|
||||
state.allowsAction ?
|
||||
@ -38,33 +38,54 @@ struct ContentView: View {
|
||||
.black.opacity(0.2)
|
||||
}
|
||||
|
||||
let buttonBorderWidth: CGFloat = 3
|
||||
|
||||
var buttonColor: Color {
|
||||
state.allowsAction ? .white : .gray
|
||||
}
|
||||
|
||||
private let sidePaddingRatio: CGFloat = 0.05
|
||||
private let buttonSizeRatio: CGFloat = 0.9
|
||||
|
||||
private let smallButtonHeight: CGFloat = 50
|
||||
|
||||
private let smallButtonWidth: CGFloat = 120
|
||||
|
||||
private let smallButtonBorderWidth: CGFloat = 1
|
||||
|
||||
var compensationTime: UInt32 {
|
||||
isCompensatingDaylightTime ? 3600 : 0
|
||||
}
|
||||
|
||||
var isPerformingRequests: Bool {
|
||||
hasActiveRequest ||
|
||||
state == .waitingForResponse
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack(alignment: .center) {
|
||||
Image(systemSymbol: .lock)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.fontWeight(.ultraLight)
|
||||
.padding()
|
||||
.onTapGesture(perform: mainButtonPressed)
|
||||
.disabled(!state.allowsAction)
|
||||
Spacer()
|
||||
GeometryReader { geo in
|
||||
HStack(alignment: .center) {
|
||||
Spacer()
|
||||
let buttonWidth = min(geo.size.width, geo.size.height)
|
||||
Text(state.actionText)
|
||||
.font(.subheadline)
|
||||
.frame(width: buttonWidth, height: buttonWidth)
|
||||
.background(buttonBackground)
|
||||
.cornerRadius(buttonWidth / 2)
|
||||
.overlay(RoundedRectangle(cornerRadius: buttonWidth / 2)
|
||||
.stroke(lineWidth: buttonBorderWidth).foregroundColor(buttonColor))
|
||||
.foregroundColor(buttonColor)
|
||||
.font(.title)
|
||||
.disabled(!state.allowsAction)
|
||||
.onTapGesture(perform: mainButtonPressed)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.background(state.color)
|
||||
.animation(.easeInOut, value: state.color)
|
||||
.onAppear {
|
||||
if keyManager.hasAllKeys, state == .noKeyAvailable {
|
||||
state = .ready
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mainButtonPressed() {
|
||||
@ -76,9 +97,8 @@ struct ContentView: View {
|
||||
let count = UInt32(nextMessageCounter)
|
||||
let sentTime = Date()
|
||||
// Add time to compensate that the device is using daylight savings time
|
||||
let timeCompensation: UInt32 = isCompensatingDaylightTime ? 3600 : 0
|
||||
let content = Message.Content(
|
||||
time: sentTime.timestamp + timeCompensation,
|
||||
time: sentTime.timestamp + compensationTime,
|
||||
id: count,
|
||||
device: deviceId)
|
||||
let message = content.authenticate(using: key)
|
||||
@ -118,11 +138,7 @@ struct ContentView: View {
|
||||
}
|
||||
|
||||
private func save(historyItem: HistoryItem) {
|
||||
do {
|
||||
try history.save(item: historyItem)
|
||||
} catch {
|
||||
print("Failed to save item: \(error)")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,67 +0,0 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
private let df: DateFormatter = {
|
||||
let df = DateFormatter()
|
||||
df.dateStyle = .short
|
||||
df.timeStyle = .short
|
||||
df.doesRelativeDateFormatting = true
|
||||
return df
|
||||
}()
|
||||
|
||||
struct HistoryItemDetail: View {
|
||||
|
||||
let item: HistoryItem
|
||||
|
||||
private var entryTime: String {
|
||||
df.string(from: item.requestDate)
|
||||
}
|
||||
|
||||
var counterText: String {
|
||||
let sentCounter = item.request.id
|
||||
let startText = "\(sentCounter)"
|
||||
guard let rCounter = item.responseMessage?.id else {
|
||||
return startText
|
||||
}
|
||||
guard sentCounter + 1 != rCounter && sentCounter != rCounter else {
|
||||
return startText
|
||||
}
|
||||
return "\(sentCounter) -> \(rCounter)"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
SettingsListTextItem(
|
||||
title: "Status",
|
||||
value: item.response?.description ?? "No response")
|
||||
SettingsListTextItem(
|
||||
title: "Date",
|
||||
value: entryTime)
|
||||
SettingsListTextItem(
|
||||
title: "Connection",
|
||||
value: item.usedLocalConnection ? "Local" : "Remote")
|
||||
SettingsListTextItem(
|
||||
title: "Device ID",
|
||||
value: "\(item.request.deviceId!)")
|
||||
SettingsListTextItem(
|
||||
title: "Message Counter",
|
||||
value: counterText)
|
||||
if let time = item.roundTripTime {
|
||||
SettingsListTextItem(
|
||||
title: "Round Trip Time",
|
||||
value: "\(Int(time * 1000)) ms")
|
||||
}
|
||||
if let offset = item.clockOffset {
|
||||
SettingsListTextItem(
|
||||
title: "Clock offset",
|
||||
value: "\(offset) seconds")
|
||||
}
|
||||
}.navigationTitle("Details")
|
||||
}
|
||||
}
|
||||
|
||||
struct HistoryItemDetail_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
HistoryItemDetail(item: .mock)
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
private let df: DateFormatter = {
|
||||
let df = DateFormatter()
|
||||
df.dateStyle = .short
|
||||
df.timeStyle = .short
|
||||
df.doesRelativeDateFormatting = true
|
||||
return df
|
||||
}()
|
||||
|
||||
struct HistoryListRow: View {
|
||||
|
||||
let item: HistoryItem
|
||||
|
||||
private var entryTime: String {
|
||||
df.string(from: item.requestDate)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image(systemSymbol: item.response?.symbol ?? .exclamationmarkTriangle)
|
||||
Text(item.response?.description ?? "No response")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
Text(entryTime)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct HistoryListRow_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
HistoryListRow(item: .mock)
|
||||
}
|
||||
}
|
@ -1,37 +1,13 @@
|
||||
import SwiftUI
|
||||
|
||||
struct HistoryView: View {
|
||||
|
||||
let history: HistoryManagerProtocol
|
||||
|
||||
@State
|
||||
private var items: [HistoryItem] = []
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List(items) { item in
|
||||
NavigationLink {
|
||||
HistoryItemDetail(item: item)
|
||||
} label: {
|
||||
HistoryListRow(item: item)
|
||||
}
|
||||
}
|
||||
.navigationTitle("History")
|
||||
}.onAppear(perform: loadItems)
|
||||
}
|
||||
|
||||
private func loadItems() {
|
||||
Task {
|
||||
let entries = history.loadEntries()
|
||||
DispatchQueue.main.async {
|
||||
items = entries
|
||||
}
|
||||
}
|
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
}
|
||||
|
||||
struct HistoryView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
HistoryView(history: HistoryManagerMock())
|
||||
HistoryView()
|
||||
}
|
||||
}
|
||||
|
121
Sesame-Watch Watch App/KeyManagement.swift
Normal file
121
Sesame-Watch Watch App/KeyManagement.swift
Normal file
@ -0,0 +1,121 @@
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import SwiftUI
|
||||
|
||||
private let localKey: [UInt8] = [
|
||||
0x98, 0x36, 0x91, 0x09, 0x29, 0xa0, 0x54, 0x44,
|
||||
0x03, 0x0c, 0xa5, 0xb4, 0x20, 0x16, 0x10, 0x0d,
|
||||
0xaf, 0x41, 0x9b, 0x26, 0x4f, 0x75, 0xa4, 0x61,
|
||||
0xed, 0x15, 0x0c, 0xb3, 0x06, 0x39, 0x92, 0x59]
|
||||
|
||||
|
||||
private let remoteKey: [UInt8] = [
|
||||
0xfa, 0x23, 0xf6, 0x98, 0xea, 0x87, 0x23, 0xa0,
|
||||
0xa0, 0xbe, 0x9a, 0xdb, 0x31, 0x28, 0xcb, 0x7d,
|
||||
0xd3, 0xa5, 0x7b, 0xf0, 0xc0, 0xeb, 0x45, 0x65,
|
||||
0x4d, 0x94, 0x50, 0x1a, 0x2f, 0x6f, 0xeb, 0x70]
|
||||
|
||||
private let authToken: [UInt8] = {
|
||||
let s = "Y6QzDK5DaFK1w2oEX5OkzoC0nTqP8w5IxpvWAR1mpro="
|
||||
let t = Data(base64Encoded: s.data(using: .utf8)!)!
|
||||
return Array(t)
|
||||
}()
|
||||
|
||||
extension KeyManagement {
|
||||
|
||||
enum KeyType: String, Identifiable, CaseIterable {
|
||||
|
||||
case deviceKey = "sesame-device"
|
||||
case remoteKey = "sesame-remote"
|
||||
case authToken = "sesame-remote-auth"
|
||||
|
||||
var id: String {
|
||||
rawValue
|
||||
}
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .deviceKey:
|
||||
return "Device Key"
|
||||
case .remoteKey:
|
||||
return "Remote Key"
|
||||
case .authToken:
|
||||
return "Authentication Token"
|
||||
}
|
||||
}
|
||||
|
||||
var keyLength: SymmetricKeySize {
|
||||
.bits256
|
||||
}
|
||||
|
||||
var usesHashing: Bool {
|
||||
switch self {
|
||||
case .authToken:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KeyManagement.KeyType: CustomStringConvertible {
|
||||
|
||||
var description: String {
|
||||
displayName
|
||||
}
|
||||
}
|
||||
|
||||
final class KeyManagement: ObservableObject {
|
||||
|
||||
|
||||
@Published
|
||||
private(set) var hasRemoteKey = true
|
||||
|
||||
@Published
|
||||
private(set) var hasDeviceKey = true
|
||||
|
||||
@Published
|
||||
private(set) var hasAuthToken = true
|
||||
|
||||
var hasAllKeys: Bool {
|
||||
hasRemoteKey && hasDeviceKey && hasAuthToken
|
||||
}
|
||||
|
||||
init() {}
|
||||
|
||||
func has(_ type: KeyType) -> Bool {
|
||||
switch type {
|
||||
case .deviceKey:
|
||||
return hasDeviceKey
|
||||
case .remoteKey:
|
||||
return hasRemoteKey
|
||||
case .authToken:
|
||||
return hasAuthToken
|
||||
}
|
||||
}
|
||||
|
||||
func get(_ type: KeyType) -> SymmetricKey? {
|
||||
let bytes: [UInt8] = get(type)
|
||||
return SymmetricKey(data: bytes)
|
||||
}
|
||||
|
||||
private func get(_ type: KeyType) -> [UInt8] {
|
||||
switch type {
|
||||
case .deviceKey:
|
||||
return remoteKey
|
||||
case .remoteKey:
|
||||
return localKey
|
||||
case .authToken:
|
||||
return authToken
|
||||
}
|
||||
}
|
||||
|
||||
func delete(_ type: KeyType) {
|
||||
|
||||
}
|
||||
|
||||
func generate(_ type: KeyType) {
|
||||
|
||||
}
|
||||
}
|
@ -11,8 +11,7 @@ struct Sesame_Watch_Watch_AppApp: App {
|
||||
ContentView()
|
||||
.environmentObject(keyManagement)
|
||||
SettingsView()
|
||||
.environmentObject(keyManagement)
|
||||
HistoryView(history: HistoryManager())
|
||||
HistoryView()
|
||||
}
|
||||
.tabViewStyle(PageTabViewStyle())
|
||||
}
|
||||
|
@ -1,77 +0,0 @@
|
||||
import SwiftUI
|
||||
import CryptoKit
|
||||
|
||||
struct SettingsKeyInputView: View {
|
||||
|
||||
let type: KeyManagement.KeyType
|
||||
|
||||
@State
|
||||
private var text: String = ""
|
||||
|
||||
let footnote: String
|
||||
|
||||
@EnvironmentObject
|
||||
private var keys: KeyManagement
|
||||
|
||||
private var hasKey: Bool {
|
||||
keys.has(type)
|
||||
}
|
||||
|
||||
private var displayText: String {
|
||||
keys.get(type)?.displayString ?? "-"
|
||||
}
|
||||
|
||||
private var copyText: String {
|
||||
guard let key = keys.get(type)?.data else {
|
||||
return ""
|
||||
}
|
||||
guard type.usesHashing else {
|
||||
return key.hexEncoded
|
||||
}
|
||||
return SHA256.hash(data: key).hexEncoded
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
TextField(type.displayName, text: $text)
|
||||
.onSubmit(validateText)
|
||||
.foregroundColor(.accentColor)
|
||||
Text(footnote)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.navigationTitle(type.displayName)
|
||||
.onAppear {
|
||||
if text == "" {
|
||||
text = displayText
|
||||
print("Text inserted")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func validateText() {
|
||||
let cleanText = text.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
guard let keyData = Data(fromHexEncodedString: cleanText) else {
|
||||
print("Invalid key string")
|
||||
return
|
||||
}
|
||||
let keyLength = type.keyLength.bitCount
|
||||
guard keyData.count * 8 == keyLength else {
|
||||
print("Invalid key length \(keyData.count * 8) bits, expected \(keyLength) (Input: '\(text)')")
|
||||
return
|
||||
}
|
||||
keys.save(type, data: keyData)
|
||||
print("Key \(type) saved")
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsKeyInputView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsKeyInputView(
|
||||
type: .remoteKey,
|
||||
footnote: "Some text describing the purpose of the key.")
|
||||
.environmentObject(KeyManagement())
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsKeyItemLink: View {
|
||||
|
||||
let type: KeyManagement.KeyType
|
||||
|
||||
let footnote: String
|
||||
|
||||
@EnvironmentObject
|
||||
private var keys: KeyManagement
|
||||
|
||||
@State
|
||||
private var keyText = "..."
|
||||
|
||||
var body: some View {
|
||||
NavigationLink {
|
||||
SettingsKeyInputView(
|
||||
type: type,
|
||||
footnote: footnote)
|
||||
.environmentObject(keys)
|
||||
} label: {
|
||||
SettingsListTextItem(
|
||||
title: type.displayName,
|
||||
value: keyText)
|
||||
.onAppear(perform: updateKeyText)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
|
||||
private func updateKeyText() {
|
||||
Task {
|
||||
let key = keys.get(type)?.displayString ?? "Not set"
|
||||
DispatchQueue.main.async {
|
||||
keyText = key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsKeyItemLink_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsKeyItemLink(
|
||||
type: .deviceKey,
|
||||
footnote: "Some text describing the purpose of the key.")
|
||||
.environmentObject(KeyManagement())
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsListTextItem: View {
|
||||
|
||||
let title: String
|
||||
|
||||
let value: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text(title)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
}
|
||||
Text(value)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsListTextItem_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsListTextItem(
|
||||
title: "Title",
|
||||
value: "Some longer text")
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsListToggleItem: View {
|
||||
|
||||
let title: String
|
||||
|
||||
@Binding
|
||||
var value: Bool
|
||||
|
||||
let subtitle: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(title, isOn: $value)
|
||||
Text(subtitle)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding()
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsListToggleItem_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsListToggleItem(title: "Toggle", value: .constant(true), subtitle: "Some longer text explaining what the toggle does")
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsNumberInputView: View {
|
||||
|
||||
let title: String
|
||||
|
||||
@Binding
|
||||
var value: Int
|
||||
|
||||
@State
|
||||
private var text: String = ""
|
||||
|
||||
let footnote: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
TextField(title, text: $text)
|
||||
.onSubmit {
|
||||
guard let newValue = Int(text) else {
|
||||
return
|
||||
}
|
||||
value = newValue
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
Text(footnote)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
}
|
||||
.navigationTitle(title)
|
||||
.navigationBarBackButtonHidden(false)
|
||||
.onAppear {
|
||||
text = "\(value)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsNumberInputView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsNumberInputView(
|
||||
title: "Title",
|
||||
value: .constant(0),
|
||||
footnote: "Some more text explaining the purpose of the text field.")
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsNumberItemLink: View {
|
||||
|
||||
let title: String
|
||||
|
||||
@Binding
|
||||
var value: Int
|
||||
|
||||
let footnote: String
|
||||
|
||||
var body: some View {
|
||||
NavigationLink {
|
||||
SettingsNumberInputView(
|
||||
title: title,
|
||||
value: $value,
|
||||
footnote: footnote
|
||||
)
|
||||
} label: {
|
||||
SettingsListTextItem(title: title, value: "\(value)")
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsNumberItemLink_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsNumberItemLink(title: "Title", value: .constant(0), footnote: "Some more text explaining the purpose of the text field.")
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsTextInputView: View {
|
||||
|
||||
let title: String
|
||||
|
||||
@Binding
|
||||
var text: String
|
||||
|
||||
let footnote: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
TextField(title, text: $text)
|
||||
.foregroundColor(.accentColor)
|
||||
Text(footnote)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
}
|
||||
.navigationTitle(title)
|
||||
.navigationBarBackButtonHidden(false)
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsTextInputView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsTextInputView(
|
||||
title: "Title",
|
||||
text: .constant("Text"),
|
||||
footnote: "Some more text explaining the purpose of the text field.")
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsTextItemLink: View {
|
||||
|
||||
let title: String
|
||||
|
||||
@Binding
|
||||
var value: String
|
||||
|
||||
let footnote: String
|
||||
|
||||
var body: some View {
|
||||
NavigationLink {
|
||||
SettingsTextInputView(
|
||||
title: title,
|
||||
text: $value,
|
||||
footnote: footnote
|
||||
)
|
||||
} label: {
|
||||
SettingsListTextItem(title: title, value: value)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsTextItemLink_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsTextItemLink(title: "Title", value: .constant("Some value"), footnote: "Some more text explaining the purpose of the text field.")
|
||||
}
|
||||
}
|
@ -1,77 +1,19 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
|
||||
@AppStorage("server")
|
||||
var serverPath: String = "https://christophhagen.de/sesame/"
|
||||
|
||||
@AppStorage("localIP")
|
||||
var localAddress: String = "192.168.178.104/"
|
||||
|
||||
@AppStorage("counter")
|
||||
var nextMessageCounter: Int = 0
|
||||
|
||||
@AppStorage("compensate")
|
||||
var isCompensatingDaylightTime: Bool = false
|
||||
|
||||
@AppStorage("local")
|
||||
private var useLocalConnection = false
|
||||
|
||||
@AppStorage("deviceId")
|
||||
private var deviceId: Int = 0
|
||||
|
||||
@EnvironmentObject
|
||||
var keys: KeyManagement
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
SettingsTextItemLink(
|
||||
title: "Server url",
|
||||
value: $serverPath,
|
||||
footnote: "The url where the sesame server listens for incoming messages.")
|
||||
SettingsTextItemLink(
|
||||
title: "Local url",
|
||||
value: $localAddress,
|
||||
footnote: "The url where the device can be reached directly on the local WiFi network.")
|
||||
SettingsListToggleItem(
|
||||
title: "Local connection",
|
||||
value: $useLocalConnection,
|
||||
subtitle: "Attempt to communicate directly with the device, which requires a WiFi connection on the same network.")
|
||||
SettingsNumberItemLink(
|
||||
title: "Device ID",
|
||||
value: $deviceId,
|
||||
footnote: "The device ID is unique for each remote device, and is assigned by the system administrator.")
|
||||
SettingsNumberItemLink(
|
||||
title: "Message counter",
|
||||
value: $nextMessageCounter,
|
||||
footnote: "The message counter is increased after every message to the device, and used to prevent replay attacks.")
|
||||
SettingsListToggleItem(
|
||||
title: "Daylight savings",
|
||||
value: $isCompensatingDaylightTime,
|
||||
subtitle: "Compensate timestamps if the remote has daylight savings time wrongly set.")
|
||||
SettingsKeyItemLink(
|
||||
type: .deviceKey,
|
||||
footnote: "Some text describing the purpose of the key.")
|
||||
.environmentObject(keys)
|
||||
SettingsKeyItemLink(
|
||||
type: .remoteKey,
|
||||
footnote: "Some text describing the purpose of the key.")
|
||||
.environmentObject(keys)
|
||||
SettingsKeyItemLink(
|
||||
type: .authToken,
|
||||
footnote: "Some text describing the purpose of the key.")
|
||||
.environmentObject(keys)
|
||||
ScrollView {
|
||||
VStack {
|
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsView()
|
||||
.previewDevice("Apple Watch Series 7 - 41mm")
|
||||
.environmentObject(KeyManagement())
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
88E197B429EDC9BC00BF1D19 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197B329EDC9BC00BF1D19 /* ContentView.swift */; };
|
||||
88E197B629EDC9BD00BF1D19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88E197B529EDC9BD00BF1D19 /* Assets.xcassets */; };
|
||||
88E197B929EDC9BD00BF1D19 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88E197B829EDC9BD00BF1D19 /* Preview Assets.xcassets */; };
|
||||
88E197C229EDCB0900BF1D19 /* KeyManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197C129EDCB0900BF1D19 /* KeyManagement.swift */; };
|
||||
88E197C429EDCC8900BF1D19 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CC27A465F500D6E650 /* Client.swift */; };
|
||||
88E197C729EDCCBD00BF1D19 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CC27A465F500D6E650 /* Client.swift */; };
|
||||
88E197C829EDCCCE00BF1D19 /* ClientState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45C827A43D7900D6E650 /* ClientState.swift */; };
|
||||
@ -38,18 +39,6 @@
|
||||
88E197D729EDCFE800BF1D19 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */; };
|
||||
88E197D829EDD13B00BF1D19 /* SymmetricKey+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */; };
|
||||
88E197D929EDD14D00BF1D19 /* HistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28DED34281EB17600259690 /* HistoryItem.swift */; };
|
||||
E240654B2A8153C6009C1AD8 /* SettingsListTextItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240654A2A8153C6009C1AD8 /* SettingsListTextItem.swift */; };
|
||||
E240654D2A8155A3009C1AD8 /* SettingsListToggleItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240654C2A8155A3009C1AD8 /* SettingsListToggleItem.swift */; };
|
||||
E240654F2A8159B7009C1AD8 /* SettingsTextInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240654E2A8159B7009C1AD8 /* SettingsTextInputView.swift */; };
|
||||
E24065512A819066009C1AD8 /* SettingsTextItemLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24065502A819066009C1AD8 /* SettingsTextItemLink.swift */; };
|
||||
E24065532A819614009C1AD8 /* SettingsNumberItemLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24065522A819614009C1AD8 /* SettingsNumberItemLink.swift */; };
|
||||
E24065552A819663009C1AD8 /* SettingsNumberInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24065542A819663009C1AD8 /* SettingsNumberInputView.swift */; };
|
||||
E24065582A819AE3009C1AD8 /* SettingsKeyItemLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24065572A819AE3009C1AD8 /* SettingsKeyItemLink.swift */; };
|
||||
E240655A2A82218D009C1AD8 /* SettingsKeyInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24065592A82218D009C1AD8 /* SettingsKeyInputView.swift */; };
|
||||
E240655B2A822397009C1AD8 /* KeyManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45C4279F4BBE00D6E650 /* KeyManagement.swift */; };
|
||||
E240655C2A822C8E009C1AD8 /* HistoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28DED36281EC7FB00259690 /* HistoryManager.swift */; };
|
||||
E240655E2A822E97009C1AD8 /* HistoryListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240655D2A822E97009C1AD8 /* HistoryListRow.swift */; };
|
||||
E24065602A822ED9009C1AD8 /* HistoryItemDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240655F2A822ED9009C1AD8 /* HistoryItemDetail.swift */; };
|
||||
E24EE77227FDCCC00011CFD2 /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */; };
|
||||
E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77327FF95920011CFD2 /* DeviceResponse.swift */; };
|
||||
E24EE77727FF95C00011CFD2 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = E24EE77627FF95C00011CFD2 /* NIOCore */; };
|
||||
@ -83,17 +72,8 @@
|
||||
88E197B329EDC9BC00BF1D19 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
88E197B529EDC9BD00BF1D19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
88E197B829EDC9BD00BF1D19 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
88E197C129EDCB0900BF1D19 /* KeyManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManagement.swift; sourceTree = "<group>"; };
|
||||
88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; };
|
||||
E240654A2A8153C6009C1AD8 /* SettingsListTextItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsListTextItem.swift; sourceTree = "<group>"; };
|
||||
E240654C2A8155A3009C1AD8 /* SettingsListToggleItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsListToggleItem.swift; sourceTree = "<group>"; };
|
||||
E240654E2A8159B7009C1AD8 /* SettingsTextInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTextInputView.swift; sourceTree = "<group>"; };
|
||||
E24065502A819066009C1AD8 /* SettingsTextItemLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTextItemLink.swift; sourceTree = "<group>"; };
|
||||
E24065522A819614009C1AD8 /* SettingsNumberItemLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsNumberItemLink.swift; sourceTree = "<group>"; };
|
||||
E24065542A819663009C1AD8 /* SettingsNumberInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsNumberInputView.swift; sourceTree = "<group>"; };
|
||||
E24065572A819AE3009C1AD8 /* SettingsKeyItemLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyItemLink.swift; sourceTree = "<group>"; };
|
||||
E24065592A82218D009C1AD8 /* SettingsKeyInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyInputView.swift; sourceTree = "<group>"; };
|
||||
E240655D2A822E97009C1AD8 /* HistoryListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryListRow.swift; sourceTree = "<group>"; };
|
||||
E240655F2A822ED9009C1AD8 /* HistoryItemDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryItemDetail.swift; sourceTree = "<group>"; };
|
||||
E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; };
|
||||
E24EE77327FF95920011CFD2 /* DeviceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceResponse.swift; sourceTree = "<group>"; };
|
||||
E24EE77827FF95E00011CFD2 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; };
|
||||
@ -186,13 +166,11 @@
|
||||
88E197B029EDC9BC00BF1D19 /* Sesame-Watch Watch App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E24065562A819AAD009C1AD8 /* Settings */,
|
||||
88E197B129EDC9BC00BF1D19 /* Sesame_WatchApp.swift */,
|
||||
88E197B329EDC9BC00BF1D19 /* ContentView.swift */,
|
||||
888362332A80F3F90032BBB2 /* SettingsView.swift */,
|
||||
888362352A80F4420032BBB2 /* HistoryView.swift */,
|
||||
E240655D2A822E97009C1AD8 /* HistoryListRow.swift */,
|
||||
E240655F2A822ED9009C1AD8 /* HistoryItemDetail.swift */,
|
||||
88E197C129EDCB0900BF1D19 /* KeyManagement.swift */,
|
||||
88E197B529EDC9BD00BF1D19 /* Assets.xcassets */,
|
||||
88E197B729EDC9BD00BF1D19 /* Preview Content */,
|
||||
88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */,
|
||||
@ -215,21 +193,6 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E24065562A819AAD009C1AD8 /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E24065502A819066009C1AD8 /* SettingsTextItemLink.swift */,
|
||||
E24065522A819614009C1AD8 /* SettingsNumberItemLink.swift */,
|
||||
E24065542A819663009C1AD8 /* SettingsNumberInputView.swift */,
|
||||
E240654E2A8159B7009C1AD8 /* SettingsTextInputView.swift */,
|
||||
E240654A2A8153C6009C1AD8 /* SettingsListTextItem.swift */,
|
||||
E240654C2A8155A3009C1AD8 /* SettingsListToggleItem.swift */,
|
||||
E24065572A819AE3009C1AD8 /* SettingsKeyItemLink.swift */,
|
||||
E24065592A82218D009C1AD8 /* SettingsKeyInputView.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E2C5C1D92806FE4A00769EF6 /* API */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -388,31 +351,20 @@
|
||||
files = (
|
||||
888362342A80F3F90032BBB2 /* SettingsView.swift in Sources */,
|
||||
88E197B429EDC9BC00BF1D19 /* ContentView.swift in Sources */,
|
||||
E24065532A819614009C1AD8 /* SettingsNumberItemLink.swift in Sources */,
|
||||
888362362A80F4420032BBB2 /* HistoryView.swift in Sources */,
|
||||
E240654F2A8159B7009C1AD8 /* SettingsTextInputView.swift in Sources */,
|
||||
88E197D329EDCE6E00BF1D19 /* MessageResult.swift in Sources */,
|
||||
88E197D529EDCE8800BF1D19 /* ServerMessage.swift in Sources */,
|
||||
88E197D129EDCE5F00BF1D19 /* Data+Extensions.swift in Sources */,
|
||||
E240655A2A82218D009C1AD8 /* SettingsKeyInputView.swift in Sources */,
|
||||
88E197D229EDCE6600BF1D19 /* RouteAPI.swift in Sources */,
|
||||
E24065512A819066009C1AD8 /* SettingsTextItemLink.swift in Sources */,
|
||||
88E197D729EDCFE800BF1D19 /* Date+Extensions.swift in Sources */,
|
||||
88E197C829EDCCCE00BF1D19 /* ClientState.swift in Sources */,
|
||||
E24065602A822ED9009C1AD8 /* HistoryItemDetail.swift in Sources */,
|
||||
88E197B229EDC9BC00BF1D19 /* Sesame_WatchApp.swift in Sources */,
|
||||
E24065582A819AE3009C1AD8 /* SettingsKeyItemLink.swift in Sources */,
|
||||
88E197C929EDCCE100BF1D19 /* Message.swift in Sources */,
|
||||
88E197D929EDD14D00BF1D19 /* HistoryItem.swift in Sources */,
|
||||
E240654B2A8153C6009C1AD8 /* SettingsListTextItem.swift in Sources */,
|
||||
88E197C729EDCCBD00BF1D19 /* Client.swift in Sources */,
|
||||
88E197D429EDCE7600BF1D19 /* UInt32+Extensions.swift in Sources */,
|
||||
E240655B2A822397009C1AD8 /* KeyManagement.swift in Sources */,
|
||||
E240654D2A8155A3009C1AD8 /* SettingsListToggleItem.swift in Sources */,
|
||||
E24065552A819663009C1AD8 /* SettingsNumberInputView.swift in Sources */,
|
||||
E240655E2A822E97009C1AD8 /* HistoryListRow.swift in Sources */,
|
||||
88E197D829EDD13B00BF1D19 /* SymmetricKey+Extensions.swift in Sources */,
|
||||
E240655C2A822C8E009C1AD8 /* HistoryManager.swift in Sources */,
|
||||
88E197C229EDCB0900BF1D19 /* KeyManagement.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
Binary file not shown.
@ -67,16 +67,11 @@
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>Sesame-Watch Watch App.xcscheme_^#shared#^_</key>
|
||||
<key>Sesame.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>Sesame.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -27,8 +27,8 @@ struct DeviceResponse {
|
||||
}
|
||||
|
||||
/// Shorthand property for an invalid message.
|
||||
static var invalidMessageSize: DeviceResponse {
|
||||
.init(event: .invalidMessageSize)
|
||||
static var invalidMessageData: DeviceResponse {
|
||||
.init(event: .invalidMessageData)
|
||||
}
|
||||
|
||||
/// Shorthand property for missing body data.
|
||||
@ -83,8 +83,8 @@ struct DeviceResponse {
|
||||
/// Get the reponse encoded in bytes.
|
||||
var encoded: Data {
|
||||
guard let message = response else {
|
||||
return event.encoded
|
||||
return Data([event.rawValue])
|
||||
}
|
||||
return event.encoded + message.encoded
|
||||
return Data([event.rawValue]) + message.encoded
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ enum MessageResult: UInt8 {
|
||||
/// A socket event on the device was unexpected (not binary data)
|
||||
case unexpectedSocketEvent = 2
|
||||
|
||||
/// The size of the payload (i.e. message) was invalid
|
||||
case invalidMessageSize = 3
|
||||
/// The size of the payload (i.e. message) was invalid, or the data could not be read
|
||||
case invalidMessageData = 3
|
||||
|
||||
/// The transmitted message could not be authenticated using the key
|
||||
case messageAuthenticationFailed = 4
|
||||
@ -44,10 +44,6 @@ enum MessageResult: UInt8 {
|
||||
|
||||
/// The device is connected
|
||||
case deviceConnected = 15
|
||||
|
||||
case invalidUrlParameter = 20
|
||||
|
||||
case invalidResponseAuthentication = 21
|
||||
}
|
||||
|
||||
extension MessageResult: CustomStringConvertible {
|
||||
@ -58,7 +54,7 @@ extension MessageResult: CustomStringConvertible {
|
||||
return "The device received unexpected text"
|
||||
case .unexpectedSocketEvent:
|
||||
return "Unexpected socket event for the device"
|
||||
case .invalidMessageSize:
|
||||
case .invalidMessageData:
|
||||
return "Invalid message data"
|
||||
case .messageAuthenticationFailed:
|
||||
return "Message authentication failed"
|
||||
@ -80,17 +76,6 @@ extension MessageResult: CustomStringConvertible {
|
||||
return "Another operation is in progress"
|
||||
case .deviceConnected:
|
||||
return "The device is connected"
|
||||
case .invalidUrlParameter:
|
||||
return "The url parameter could not be found"
|
||||
case .invalidResponseAuthentication:
|
||||
return "The response could not be authenticated"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MessageResult {
|
||||
|
||||
var encoded: Data {
|
||||
Data([rawValue])
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ struct ServerMessage {
|
||||
|
||||
static let authTokenSize = SHA256.byteCount
|
||||
|
||||
static let length = authTokenSize + Message.length
|
||||
|
||||
let authToken: Data
|
||||
|
||||
let message: Message
|
||||
@ -20,7 +22,30 @@ struct ServerMessage {
|
||||
self.message = message
|
||||
}
|
||||
|
||||
/**
|
||||
Decode a message from a byte buffer.
|
||||
The buffer must contain at least `ServerMessage.length` bytes, or it will return `nil`.
|
||||
- Parameter buffer: The buffer containing the bytes.
|
||||
*/
|
||||
init?(decodeFrom buffer: ByteBuffer) {
|
||||
guard let data = buffer.getBytes(at: 0, length: ServerMessage.length) else {
|
||||
return nil
|
||||
}
|
||||
self.authToken = Data(data.prefix(ServerMessage.authTokenSize))
|
||||
self.message = Message(decodeFrom: Data(data.dropFirst(ServerMessage.authTokenSize)))
|
||||
}
|
||||
|
||||
var encoded: Data {
|
||||
authToken + message.encoded
|
||||
}
|
||||
|
||||
static func token(from buffer: ByteBuffer) -> Data? {
|
||||
guard buffer.readableBytes == authTokenSize else {
|
||||
return nil
|
||||
}
|
||||
guard let bytes = buffer.getBytes(at: 0, length: authTokenSize) else {
|
||||
return nil
|
||||
}
|
||||
return Data(bytes)
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ final class Client {
|
||||
var request = URLRequest(url: url)
|
||||
request.httpBody = data
|
||||
request.httpMethod = "POST"
|
||||
request.timeoutInterval = 10
|
||||
return await requestAndDecode(request)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
enum ConnectionError {
|
||||
case serverNotReached
|
||||
@ -98,8 +97,7 @@ enum ClientState {
|
||||
self = .openSesame
|
||||
case .messageDeviceInvalid:
|
||||
self = .messageRejected(.invalidDeviceId)
|
||||
case .noBodyData, .invalidMessageSize, .textReceived, .unexpectedSocketEvent, .invalidUrlParameter, .invalidResponseAuthentication:
|
||||
print("Unexpected internal error: \(keyResult)")
|
||||
case .noBodyData, .invalidMessageData, .textReceived, .unexpectedSocketEvent:
|
||||
self = .internalError(keyResult.description)
|
||||
case .deviceNotConnected:
|
||||
self = .deviceNotAvailable(.deviceDisconnected)
|
||||
@ -244,7 +242,7 @@ extension ClientState {
|
||||
}
|
||||
case .openSesame:
|
||||
return 17
|
||||
case .internalError:
|
||||
case .internalError(_):
|
||||
return 18
|
||||
}
|
||||
}
|
||||
@ -296,26 +294,3 @@ extension ClientState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ClientState {
|
||||
|
||||
@available(iOS 16, *)
|
||||
var symbol: SFSymbol {
|
||||
switch self {
|
||||
case .deviceNotAvailable:
|
||||
return .wifiExclamationmark
|
||||
case .internalError:
|
||||
return .applewatchSlash
|
||||
case .noKeyAvailable:
|
||||
return .lockTrianglebadgeExclamationmark
|
||||
case .openSesame:
|
||||
return .lockOpen
|
||||
case .messageRejected:
|
||||
return .nosign
|
||||
case .responseRejected:
|
||||
return .exclamationmarkTriangle
|
||||
case .requestingStatus, .ready, .waitingForResponse:
|
||||
return .wifiExclamationmark
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,19 +54,23 @@ struct HistoryListItem: View {
|
||||
HStack {
|
||||
if let roundTripText {
|
||||
Image(systemSymbol: entry.usedLocalConnection ? .wifi : .network)
|
||||
//Image(systemSymbol: .arrowUpArrowDownCircle)
|
||||
Text(roundTripText)
|
||||
.font(.subheadline)
|
||||
}
|
||||
//Spacer()
|
||||
Image(systemSymbol: .personalhotspot)
|
||||
Text(counterText)
|
||||
.font(.subheadline)
|
||||
if let timeOffsetText {
|
||||
//Spacer()
|
||||
Image(systemSymbol: .stopwatch)
|
||||
Text(timeOffsetText)
|
||||
.font(.subheadline)
|
||||
}
|
||||
}.foregroundColor(.secondary)
|
||||
}
|
||||
//.padding()
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user