Add history and settings to watch app

This commit is contained in:
Christoph Hagen 2023-08-09 16:29:18 +02:00
parent e5ea8c4951
commit 5f9af35542
20 changed files with 604 additions and 175 deletions

View File

@ -1,6 +1,7 @@
{
"images" : [
{
"filename" : "sesame.png",
"idiom" : "universal",
"platform" : "watchos",
"size" : "1024x1024"

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -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 {
HStack {
Spacer()
VStack(alignment: .center) {
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)
Image(systemSymbol: .lock)
.resizable()
.aspectRatio(contentMode: .fit)
.fontWeight(.ultraLight)
.padding()
.onTapGesture(perform: mainButtonPressed)
Spacer()
}
.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)")
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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())
}
}

View File

@ -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) {
}
}

View File

@ -11,7 +11,8 @@ struct Sesame_Watch_Watch_AppApp: App {
ContentView()
.environmentObject(keyManagement)
SettingsView()
HistoryView()
.environmentObject(keyManagement)
HistoryView(history: HistoryManager())
}
.tabViewStyle(PageTabViewStyle())
}

View File

@ -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())
}
}

View File

@ -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())
}
}

View File

@ -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")
}
}

View File

@ -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")
}
}

View File

@ -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.")
}
}

View File

@ -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.")
}
}

View File

@ -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.")
}
}

View File

@ -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.")
}
}

View File

@ -1,19 +1,77 @@
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")
}
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()
.previewDevice("Apple Watch Series 7 - 41mm")
.environmentObject(KeyManagement())
}
}

View File

@ -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 = "<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>"; };
@ -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 = "<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 = (
@ -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;
};

View File

@ -67,11 +67,16 @@
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>Sesame.xcscheme_^#shared#^_</key>
<key>Sesame-Watch Watch App.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>