diff --git a/Sesame-Watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json index 49c81cd..ac477c0 100644 --- a/Sesame-Watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Sesame-Watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "sesame.png", "idiom" : "universal", "platform" : "watchos", "size" : "1024x1024" diff --git a/Sesame-Watch Watch App/Assets.xcassets/AppIcon.appiconset/sesame.png b/Sesame-Watch Watch App/Assets.xcassets/AppIcon.appiconset/sesame.png new file mode 100644 index 0000000..b5acd2c Binary files /dev/null and b/Sesame-Watch Watch App/Assets.xcassets/AppIcon.appiconset/sesame.png differ diff --git a/Sesame-Watch Watch App/ContentView.swift b/Sesame-Watch Watch App/ContentView.swift index 455ee26..dc6e535 100644 --- a/Sesame-Watch Watch App/ContentView.swift +++ b/Sesame-Watch Watch App/ContentView.swift @@ -1,4 +1,5 @@ import SwiftUI +import SFSafeSymbols import CryptoKit struct ContentView: View { @@ -27,65 +28,43 @@ struct ContentView: View { @State var state: ClientState = .noKeyAvailable - @State - private var hasActiveRequest = false - let server = Client() + let history = HistoryManager() + var buttonBackground: Color { state.allowsAction ? .white.opacity(0.2) : .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 { - VStack(alignment: .center) { + HStack { Spacer() - GeometryReader { geo in - HStack(alignment: .center) { - Spacer() - let buttonWidth = min(geo.size.width, geo.size.height) - Text(state.actionText) - .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() - } + VStack(alignment: .center) { + Image(systemSymbol: .lock) + .resizable() + .aspectRatio(contentMode: .fit) + .fontWeight(.ultraLight) + .padding() + .onTapGesture(perform: mainButtonPressed) + .disabled(!state.allowsAction) + Text(state.actionText) + .font(.subheadline) } Spacer() } .background(state.color) .animation(.easeInOut, value: state.color) + .onAppear { + if keyManager.hasAllKeys, state == .noKeyAvailable { + state = .ready + } + } } func mainButtonPressed() { @@ -97,8 +76,9 @@ 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 + compensationTime, + time: sentTime.timestamp + timeCompensation, id: count, device: deviceId) let message = content.authenticate(using: key) @@ -138,7 +118,11 @@ struct ContentView: View { } private func save(historyItem: HistoryItem) { - + do { + try history.save(item: historyItem) + } catch { + print("Failed to save item: \(error)") + } } } diff --git a/Sesame-Watch Watch App/HistoryItemDetail.swift b/Sesame-Watch Watch App/HistoryItemDetail.swift new file mode 100644 index 0000000..b72ef97 --- /dev/null +++ b/Sesame-Watch Watch App/HistoryItemDetail.swift @@ -0,0 +1,67 @@ +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) + } +} diff --git a/Sesame-Watch Watch App/HistoryListRow.swift b/Sesame-Watch Watch App/HistoryListRow.swift new file mode 100644 index 0000000..476b3dc --- /dev/null +++ b/Sesame-Watch Watch App/HistoryListRow.swift @@ -0,0 +1,41 @@ +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) + } +} diff --git a/Sesame-Watch Watch App/HistoryView.swift b/Sesame-Watch Watch App/HistoryView.swift index 4037310..a93a503 100644 --- a/Sesame-Watch Watch App/HistoryView.swift +++ b/Sesame-Watch Watch App/HistoryView.swift @@ -1,13 +1,37 @@ import SwiftUI struct HistoryView: View { + + let history: HistoryManagerProtocol + + @State + private var items: [HistoryItem] = [] + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + 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 + } + } } } struct HistoryView_Previews: PreviewProvider { static var previews: some View { - HistoryView() + HistoryView(history: HistoryManagerMock()) } } diff --git a/Sesame-Watch Watch App/KeyManagement.swift b/Sesame-Watch Watch App/KeyManagement.swift deleted file mode 100644 index 8558a1f..0000000 --- a/Sesame-Watch Watch App/KeyManagement.swift +++ /dev/null @@ -1,121 +0,0 @@ -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) { - - } -} diff --git a/Sesame-Watch Watch App/Sesame_WatchApp.swift b/Sesame-Watch Watch App/Sesame_WatchApp.swift index 849c66d..bef560b 100644 --- a/Sesame-Watch Watch App/Sesame_WatchApp.swift +++ b/Sesame-Watch Watch App/Sesame_WatchApp.swift @@ -11,7 +11,8 @@ struct Sesame_Watch_Watch_AppApp: App { ContentView() .environmentObject(keyManagement) SettingsView() - HistoryView() + .environmentObject(keyManagement) + HistoryView(history: HistoryManager()) } .tabViewStyle(PageTabViewStyle()) } diff --git a/Sesame-Watch Watch App/Settings/SettingsKeyInputView.swift b/Sesame-Watch Watch App/Settings/SettingsKeyInputView.swift new file mode 100644 index 0000000..f7a7781 --- /dev/null +++ b/Sesame-Watch Watch App/Settings/SettingsKeyInputView.swift @@ -0,0 +1,77 @@ +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()) + } +} diff --git a/Sesame-Watch Watch App/Settings/SettingsKeyItemLink.swift b/Sesame-Watch Watch App/Settings/SettingsKeyItemLink.swift new file mode 100644 index 0000000..2c4cc54 --- /dev/null +++ b/Sesame-Watch Watch App/Settings/SettingsKeyItemLink.swift @@ -0,0 +1,47 @@ +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()) + } +} diff --git a/Sesame-Watch Watch App/Settings/SettingsListTextItem.swift b/Sesame-Watch Watch App/Settings/SettingsListTextItem.swift new file mode 100644 index 0000000..1efc714 --- /dev/null +++ b/Sesame-Watch Watch App/Settings/SettingsListTextItem.swift @@ -0,0 +1,31 @@ +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") + } +} diff --git a/Sesame-Watch Watch App/Settings/SettingsListToggleItem.swift b/Sesame-Watch Watch App/Settings/SettingsListToggleItem.swift new file mode 100644 index 0000000..4f9c940 --- /dev/null +++ b/Sesame-Watch Watch App/Settings/SettingsListToggleItem.swift @@ -0,0 +1,28 @@ +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") + } +} diff --git a/Sesame-Watch Watch App/Settings/SettingsNumberInputView.swift b/Sesame-Watch Watch App/Settings/SettingsNumberInputView.swift new file mode 100644 index 0000000..7d30b9e --- /dev/null +++ b/Sesame-Watch Watch App/Settings/SettingsNumberInputView.swift @@ -0,0 +1,45 @@ +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.") + } +} diff --git a/Sesame-Watch Watch App/Settings/SettingsNumberItemLink.swift b/Sesame-Watch Watch App/Settings/SettingsNumberItemLink.swift new file mode 100644 index 0000000..5e5f331 --- /dev/null +++ b/Sesame-Watch Watch App/Settings/SettingsNumberItemLink.swift @@ -0,0 +1,30 @@ +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.") + } +} diff --git a/Sesame-Watch Watch App/Settings/SettingsTextInputView.swift b/Sesame-Watch Watch App/Settings/SettingsTextInputView.swift new file mode 100644 index 0000000..3290952 --- /dev/null +++ b/Sesame-Watch Watch App/Settings/SettingsTextInputView.swift @@ -0,0 +1,33 @@ +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.") + } +} diff --git a/Sesame-Watch Watch App/Settings/SettingsTextItemLink.swift b/Sesame-Watch Watch App/Settings/SettingsTextItemLink.swift new file mode 100644 index 0000000..6a81eec --- /dev/null +++ b/Sesame-Watch Watch App/Settings/SettingsTextItemLink.swift @@ -0,0 +1,30 @@ +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.") + } +} diff --git a/Sesame-Watch Watch App/SettingsView.swift b/Sesame-Watch Watch App/SettingsView.swift index 2f084af..1af38e4 100644 --- a/Sesame-Watch Watch App/SettingsView.swift +++ b/Sesame-Watch Watch App/SettingsView.swift @@ -1,13 +1,70 @@ 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 { - ScrollView { - VStack { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + 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) } + .navigationTitle("Settings") } - .navigationTitle("Settings") } } @@ -15,5 +72,6 @@ struct SettingsView_Previews: PreviewProvider { static var previews: some View { SettingsView() .previewDevice("Apple Watch Series 7 - 41mm") + .environmentObject(KeyManagement()) } } diff --git a/Sesame.xcodeproj/project.pbxproj b/Sesame.xcodeproj/project.pbxproj index b17d741..f1def8c 100644 --- a/Sesame.xcodeproj/project.pbxproj +++ b/Sesame.xcodeproj/project.pbxproj @@ -23,7 +23,6 @@ 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 */; }; @@ -39,6 +38,18 @@ 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 */; }; @@ -72,8 +83,17 @@ 88E197B329EDC9BC00BF1D19 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 88E197B529EDC9BD00BF1D19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 88E197B829EDC9BD00BF1D19 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 88E197C129EDCB0900BF1D19 /* KeyManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManagement.swift; sourceTree = ""; }; 88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; + E240654A2A8153C6009C1AD8 /* SettingsListTextItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsListTextItem.swift; sourceTree = ""; }; + E240654C2A8155A3009C1AD8 /* SettingsListToggleItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsListToggleItem.swift; sourceTree = ""; }; + E240654E2A8159B7009C1AD8 /* SettingsTextInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTextInputView.swift; sourceTree = ""; }; + E24065502A819066009C1AD8 /* SettingsTextItemLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTextItemLink.swift; sourceTree = ""; }; + E24065522A819614009C1AD8 /* SettingsNumberItemLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsNumberItemLink.swift; sourceTree = ""; }; + E24065542A819663009C1AD8 /* SettingsNumberInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsNumberInputView.swift; sourceTree = ""; }; + E24065572A819AE3009C1AD8 /* SettingsKeyItemLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyItemLink.swift; sourceTree = ""; }; + E24065592A82218D009C1AD8 /* SettingsKeyInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyInputView.swift; sourceTree = ""; }; + E240655D2A822E97009C1AD8 /* HistoryListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryListRow.swift; sourceTree = ""; }; + E240655F2A822ED9009C1AD8 /* HistoryItemDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryItemDetail.swift; sourceTree = ""; }; E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = ""; }; E24EE77327FF95920011CFD2 /* DeviceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceResponse.swift; sourceTree = ""; }; E24EE77827FF95E00011CFD2 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; @@ -166,11 +186,13 @@ 88E197B029EDC9BC00BF1D19 /* Sesame-Watch Watch App */ = { isa = PBXGroup; children = ( + E24065562A819AAD009C1AD8 /* Settings */, 88E197B129EDC9BC00BF1D19 /* Sesame_WatchApp.swift */, 88E197B329EDC9BC00BF1D19 /* ContentView.swift */, 888362332A80F3F90032BBB2 /* SettingsView.swift */, 888362352A80F4420032BBB2 /* HistoryView.swift */, - 88E197C129EDCB0900BF1D19 /* KeyManagement.swift */, + E240655D2A822E97009C1AD8 /* HistoryListRow.swift */, + E240655F2A822ED9009C1AD8 /* HistoryItemDetail.swift */, 88E197B529EDC9BD00BF1D19 /* Assets.xcassets */, 88E197B729EDC9BD00BF1D19 /* Preview Content */, 88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */, @@ -193,6 +215,21 @@ name = Frameworks; sourceTree = ""; }; + 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 = ""; + }; E2C5C1D92806FE4A00769EF6 /* API */ = { isa = PBXGroup; children = ( @@ -351,20 +388,31 @@ 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 */, - 88E197C229EDCB0900BF1D19 /* KeyManagement.swift in Sources */, + E240655C2A822C8E009C1AD8 /* HistoryManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate b/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate index 763be60..bd30498 100644 Binary files a/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate and b/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Sesame.xcodeproj/xcuserdata/ch.xcuserdatad/xcschemes/xcschememanagement.plist b/Sesame.xcodeproj/xcuserdata/ch.xcuserdatad/xcschemes/xcschememanagement.plist index db32b40..2f121ba 100644 --- a/Sesame.xcodeproj/xcuserdata/ch.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Sesame.xcodeproj/xcuserdata/ch.xcuserdatad/xcschemes/xcschememanagement.plist @@ -67,11 +67,16 @@ orderHint 0 - Sesame.xcscheme_^#shared#^_ + Sesame-Watch Watch App.xcscheme_^#shared#^_ orderHint 0 + Sesame.xcscheme_^#shared#^_ + + orderHint + 1 +