Delete history, allow retry
This commit is contained in:
parent
95ece1ddcc
commit
7a443d51b3
BIN
Banner.key
BIN
Banner.key
Binary file not shown.
@ -1,10 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import ClockKit
|
|
||||||
|
|
||||||
|
|
||||||
class ComplicationController: NSObject, CLKComplicationDataSource {
|
|
||||||
|
|
||||||
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
|
|
||||||
// TODO: Finish implementing this required method.
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,6 +7,9 @@ struct ContentView: View {
|
|||||||
@Binding
|
@Binding
|
||||||
var didLaunchFromComplication: Bool
|
var didLaunchFromComplication: Bool
|
||||||
|
|
||||||
|
@AppStorage("connectionType")
|
||||||
|
var connectionType: ConnectionStrategy = .remoteFirst
|
||||||
|
|
||||||
@AppStorage("server")
|
@AppStorage("server")
|
||||||
var serverPath: String = "https://christophhagen.de/sesame/"
|
var serverPath: String = "https://christophhagen.de/sesame/"
|
||||||
|
|
||||||
@ -19,15 +22,15 @@ struct ContentView: View {
|
|||||||
@AppStorage("compensate")
|
@AppStorage("compensate")
|
||||||
var isCompensatingDaylightTime: Bool = false
|
var isCompensatingDaylightTime: Bool = false
|
||||||
|
|
||||||
@AppStorage("local")
|
|
||||||
private var useLocalConnection = false
|
|
||||||
|
|
||||||
@AppStorage("deviceId")
|
@AppStorage("deviceId")
|
||||||
private var deviceId: Int = 0
|
private var deviceId: Int = 0
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
var keyManager: KeyManagement
|
var keyManager: KeyManagement
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
var history: HistoryManager
|
||||||
|
|
||||||
@State
|
@State
|
||||||
var state: ClientState = .noKeyAvailable
|
var state: ClientState = .noKeyAvailable
|
||||||
|
|
||||||
@ -36,7 +39,32 @@ struct ContentView: View {
|
|||||||
|
|
||||||
let server = Client()
|
let server = Client()
|
||||||
|
|
||||||
let history = HistoryManager()
|
private var firstTryIsLocalConnection: Bool {
|
||||||
|
switch connectionType {
|
||||||
|
case .local, .localFirst:
|
||||||
|
return true
|
||||||
|
case .remote, .remoteFirst:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var hasSecondTry: Bool {
|
||||||
|
switch connectionType {
|
||||||
|
case .localFirst, .remoteFirst:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var secondTryIsLocalConnection: Bool {
|
||||||
|
switch connectionType {
|
||||||
|
case .local, .localFirst:
|
||||||
|
return false
|
||||||
|
case .remote, .remoteFirst:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var buttonBackground: Color {
|
var buttonBackground: Color {
|
||||||
state.allowsAction ?
|
state.allowsAction ?
|
||||||
@ -87,45 +115,42 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mainButtonPressed() {
|
func mainButtonPressed() {
|
||||||
guard let key = keyManager.get(.remoteKey),
|
guard let keys = keyManager.getAllKeys(),
|
||||||
let token = keyManager.get(.authToken)?.data,
|
|
||||||
let deviceId = UInt8(exactly: deviceId) else {
|
let deviceId = UInt8(exactly: deviceId) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let count = UInt32(nextMessageCounter)
|
sendMessage(from: deviceId, using: keys, isFirstTry: true)
|
||||||
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,
|
|
||||||
id: count,
|
|
||||||
device: deviceId)
|
|
||||||
let message = content.authenticate(using: key)
|
|
||||||
let historyItem = HistoryItem(sent: message.content, date: sentTime, local: useLocalConnection)
|
|
||||||
preventStateReset()
|
|
||||||
state = .waitingForResponse
|
|
||||||
print("Sending message \(count)")
|
|
||||||
Task {
|
|
||||||
let (newState, responseMessage) = await send(message, authToken: token)
|
|
||||||
let receivedTime = Date.now
|
|
||||||
state = newState
|
|
||||||
scheduleStateReset()
|
|
||||||
let finishedItem = historyItem.didReceive(response: newState, date: receivedTime, message: responseMessage?.content)
|
|
||||||
guard let key = keyManager.get(.deviceKey) else {
|
|
||||||
save(historyItem: finishedItem.notAuthenticated())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let responseMessage else {
|
|
||||||
save(historyItem: finishedItem)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard responseMessage.isValid(using: key) else {
|
|
||||||
save(historyItem: finishedItem.invalidated())
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nextMessageCounter = Int(responseMessage.content.id)
|
private func sendMessage(from deviceId: UInt8, using keys: KeySet, isFirstTry: Bool) {
|
||||||
save(historyItem: finishedItem)
|
preventStateReset()
|
||||||
|
state = .waitingForResponse
|
||||||
|
let localConnection = isFirstTry ? firstTryIsLocalConnection : secondTryIsLocalConnection
|
||||||
|
Task {
|
||||||
|
let response = await send(
|
||||||
|
count: UInt32(nextMessageCounter),
|
||||||
|
from: deviceId,
|
||||||
|
using: keys,
|
||||||
|
to: server,
|
||||||
|
over: localConnection,
|
||||||
|
while: isCompensatingDaylightTime,
|
||||||
|
localAddress: localAddress,
|
||||||
|
remoteAddress: serverPath)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
state = response.response
|
||||||
|
scheduleStateReset()
|
||||||
|
if let counter = response.responseMessage?.id {
|
||||||
|
nextMessageCounter = Int(counter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
save(historyItem: response)
|
||||||
|
guard isFirstTry, hasSecondTry else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
sendMessage(from: deviceId, using: keys, isFirstTry: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,14 +173,6 @@ struct ContentView: View {
|
|||||||
preventStateReset()
|
preventStateReset()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func send(_ message: Message, authToken: Data) async -> (state: ClientState, response: Message?) {
|
|
||||||
if useLocalConnection {
|
|
||||||
return await server.sendMessageOverLocalNetwork(message, server: localAddress)
|
|
||||||
} else {
|
|
||||||
return await server.send(message, server: serverPath, authToken: authToken)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func save(historyItem: HistoryItem) {
|
private func save(historyItem: HistoryItem) {
|
||||||
do {
|
do {
|
||||||
try history.save(item: historyItem)
|
try history.save(item: historyItem)
|
||||||
@ -165,9 +182,48 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func send(count: UInt32, from deviceId: UInt8, using keys: KeySet, to server: Client, over localConnection: Bool, while compensatingTime: Bool, localAddress: String, remoteAddress: String) async -> HistoryItem {
|
||||||
|
let sentTime = Date()
|
||||||
|
// Add time to compensate that the device is using daylight savings time
|
||||||
|
let timeCompensation: UInt32 = compensatingTime ? 3600 : 0
|
||||||
|
let content = Message.Content(
|
||||||
|
time: sentTime.timestamp + timeCompensation,
|
||||||
|
id: count,
|
||||||
|
device: deviceId)
|
||||||
|
let message = content.authenticate(using: keys.remote)
|
||||||
|
print("Sending message \(count)")
|
||||||
|
let address = localConnection ? localAddress : remoteAddress
|
||||||
|
let (newState, responseMessage) = await send(message, to: server, using: keys.server, local: localConnection, address: address)
|
||||||
|
var historyItem = HistoryItem(
|
||||||
|
sent: message.content,
|
||||||
|
sentDate: sentTime,
|
||||||
|
local: localConnection,
|
||||||
|
response: newState,
|
||||||
|
responseDate: .now,
|
||||||
|
responseMessage: responseMessage?.content)
|
||||||
|
|
||||||
|
guard let responseMessage else {
|
||||||
|
return historyItem
|
||||||
|
}
|
||||||
|
guard responseMessage.isValid(using: keys.device) else {
|
||||||
|
historyItem.response = .responseRejected(.invalidAuthentication)
|
||||||
|
return historyItem
|
||||||
|
}
|
||||||
|
return historyItem
|
||||||
|
}
|
||||||
|
|
||||||
|
private func send(_ message: Message, to server: Client, using authToken: Data, local: Bool, address: String) async -> (state: ClientState, response: Message?) {
|
||||||
|
if local {
|
||||||
|
return await server.sendMessageOverLocalNetwork(message, server: address)
|
||||||
|
} else {
|
||||||
|
return await server.send(message, server: address, authToken: authToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ContentView_Previews: PreviewProvider {
|
struct ContentView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ContentView(didLaunchFromComplication: .constant(false))
|
ContentView(didLaunchFromComplication: .constant(false))
|
||||||
.environmentObject(KeyManagement())
|
.environmentObject(KeyManagement())
|
||||||
|
.environmentObject(HistoryManager())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,10 @@ struct HistoryItemDetail: View {
|
|||||||
|
|
||||||
let item: HistoryItem
|
let item: HistoryItem
|
||||||
|
|
||||||
|
let history: HistoryManagerProtocol
|
||||||
|
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
private var entryTime: String {
|
private var entryTime: String {
|
||||||
df.string(from: item.requestDate)
|
df.string(from: item.requestDate)
|
||||||
}
|
}
|
||||||
@ -33,7 +37,7 @@ struct HistoryItemDetail: View {
|
|||||||
List {
|
List {
|
||||||
SettingsListTextItem(
|
SettingsListTextItem(
|
||||||
title: "Status",
|
title: "Status",
|
||||||
value: item.response?.description ?? "No response")
|
value: item.response.description)
|
||||||
SettingsListTextItem(
|
SettingsListTextItem(
|
||||||
title: "Date",
|
title: "Date",
|
||||||
value: entryTime)
|
value: entryTime)
|
||||||
@ -46,22 +50,41 @@ struct HistoryItemDetail: View {
|
|||||||
SettingsListTextItem(
|
SettingsListTextItem(
|
||||||
title: "Message Counter",
|
title: "Message Counter",
|
||||||
value: counterText)
|
value: counterText)
|
||||||
if let time = item.roundTripTime {
|
|
||||||
SettingsListTextItem(
|
SettingsListTextItem(
|
||||||
title: "Round Trip Time",
|
title: "Round Trip Time",
|
||||||
value: "\(Int(time * 1000)) ms")
|
value: "\(Int(item.roundTripTime * 1000)) ms")
|
||||||
}
|
|
||||||
if let offset = item.clockOffset {
|
if let offset = item.clockOffset {
|
||||||
SettingsListTextItem(
|
SettingsListTextItem(
|
||||||
title: "Clock offset",
|
title: "Clock offset",
|
||||||
value: "\(offset) seconds")
|
value: "\(offset) seconds")
|
||||||
}
|
}
|
||||||
|
Button {
|
||||||
|
delete(item: item)
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Label("Delete", systemSymbol: .trash)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listRowBackground(
|
||||||
|
RoundedRectangle(cornerSize: CGSize(width: 8, height: 8))
|
||||||
|
.fill(.red)
|
||||||
|
)
|
||||||
|
.foregroundColor(.white)
|
||||||
}.navigationTitle("Details")
|
}.navigationTitle("Details")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func delete(item: HistoryItem) {
|
||||||
|
guard history.delete(item: item) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HistoryItemDetail_Previews: PreviewProvider {
|
struct HistoryItemDetail_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
HistoryItemDetail(item: .mock)
|
HistoryItemDetail(item: .mock, history: HistoryManagerMock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,11 @@ struct HistoryListRow: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemSymbol: item.response?.symbol ?? .exclamationmarkTriangle)
|
Image(systemSymbol: item.response.symbol)
|
||||||
Text(item.response?.description ?? "No response")
|
Text(item.response.description)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
Text(entryTime)
|
Text(entryTime)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
|
@ -2,36 +2,66 @@ import SwiftUI
|
|||||||
|
|
||||||
struct HistoryView: View {
|
struct HistoryView: View {
|
||||||
|
|
||||||
let history: HistoryManagerProtocol
|
@ObservedObject
|
||||||
|
var history: HistoryManager
|
||||||
|
|
||||||
@State
|
private var unlockCount: Int {
|
||||||
private var items: [HistoryItem] = []
|
history.entries.count {
|
||||||
|
$0.response == .openSesame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var percentage: Double {
|
||||||
|
guard history.entries.count > 0 else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return Double(unlockCount * 100) / Double(history.entries.count)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationView {
|
||||||
List(items) { item in
|
List {
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("\(history.entries.count) requests")
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.font(.body)
|
||||||
|
Text(String(format: "%.1f %% success", percentage))
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.font(.footnote)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.listRowBackground(Color.clear)
|
||||||
|
|
||||||
|
ForEach(history.entries) { item in
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
HistoryItemDetail(item: item)
|
HistoryItemDetail(item: item, history: history)
|
||||||
} label: {
|
} label: {
|
||||||
HistoryListRow(item: item)
|
HistoryListRow(item: item)
|
||||||
}
|
}
|
||||||
|
.swipeActions(edge: .trailing) {
|
||||||
|
Button {
|
||||||
|
delete(item: item)
|
||||||
|
} label: {
|
||||||
|
Label("Delete", systemSymbol: .trash)
|
||||||
|
}.tint(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("History")
|
.navigationTitle("History")
|
||||||
}.onAppear(perform: loadItems)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadItems() {
|
private func delete(item: HistoryItem) {
|
||||||
Task {
|
guard history.delete(item: item) else {
|
||||||
let entries = history.loadEntries()
|
return
|
||||||
DispatchQueue.main.async {
|
|
||||||
items = entries
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HistoryView_Previews: PreviewProvider {
|
struct HistoryView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
HistoryView(history: HistoryManagerMock())
|
HistoryView(history: HistoryManager())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,6 +5,8 @@ struct Sesame_Watch_Watch_AppApp: App {
|
|||||||
|
|
||||||
let keyManagement = KeyManagement()
|
let keyManagement = KeyManagement()
|
||||||
|
|
||||||
|
let history = HistoryManager()
|
||||||
|
|
||||||
@State
|
@State
|
||||||
var selected: Int = 0
|
var selected: Int = 0
|
||||||
|
|
||||||
@ -16,11 +18,12 @@ struct Sesame_Watch_Watch_AppApp: App {
|
|||||||
TabView(selection: $selected) {
|
TabView(selection: $selected) {
|
||||||
ContentView(didLaunchFromComplication: $didLaunchFromComplication)
|
ContentView(didLaunchFromComplication: $didLaunchFromComplication)
|
||||||
.environmentObject(keyManagement)
|
.environmentObject(keyManagement)
|
||||||
|
.environmentObject(history)
|
||||||
.tag(1)
|
.tag(1)
|
||||||
SettingsView()
|
SettingsView()
|
||||||
.environmentObject(keyManagement)
|
.environmentObject(keyManagement)
|
||||||
.tag(2)
|
.tag(2)
|
||||||
HistoryView(history: HistoryManager())
|
HistoryView(history: history)
|
||||||
.tag(3)
|
.tag(3)
|
||||||
}
|
}
|
||||||
.tabViewStyle(PageTabViewStyle())
|
.tabViewStyle(PageTabViewStyle())
|
||||||
|
@ -20,6 +20,7 @@ struct SettingsTextItemLink: View {
|
|||||||
SettingsListTextItem(title: title, value: value)
|
SettingsListTextItem(title: title, value: value)
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.padding(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,9 @@ import SwiftUI
|
|||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
|
|
||||||
|
@AppStorage("connectionType")
|
||||||
|
var connectionType: ConnectionStrategy = .remoteFirst
|
||||||
|
|
||||||
@AppStorage("server")
|
@AppStorage("server")
|
||||||
var serverPath: String = "https://christophhagen.de/sesame/"
|
var serverPath: String = "https://christophhagen.de/sesame/"
|
||||||
|
|
||||||
@ -14,9 +17,6 @@ struct SettingsView: View {
|
|||||||
@AppStorage("compensate")
|
@AppStorage("compensate")
|
||||||
var isCompensatingDaylightTime: Bool = false
|
var isCompensatingDaylightTime: Bool = false
|
||||||
|
|
||||||
@AppStorage("local")
|
|
||||||
private var useLocalConnection = false
|
|
||||||
|
|
||||||
@AppStorage("deviceId")
|
@AppStorage("deviceId")
|
||||||
private var deviceId: Int = 0
|
private var deviceId: Int = 0
|
||||||
|
|
||||||
@ -26,6 +26,17 @@ struct SettingsView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
List {
|
List {
|
||||||
|
Picker("Connection", selection: $connectionType) {
|
||||||
|
Text(ConnectionStrategy.local.rawValue)
|
||||||
|
.tag(ConnectionStrategy.local)
|
||||||
|
Text(ConnectionStrategy.localFirst.rawValue)
|
||||||
|
.tag(ConnectionStrategy.localFirst)
|
||||||
|
Text(ConnectionStrategy.remote.rawValue)
|
||||||
|
.tag(ConnectionStrategy.remote)
|
||||||
|
Text(ConnectionStrategy.remoteFirst.rawValue)
|
||||||
|
.tag(ConnectionStrategy.remoteFirst)
|
||||||
|
}
|
||||||
|
.padding(.leading)
|
||||||
SettingsTextItemLink(
|
SettingsTextItemLink(
|
||||||
title: "Server url",
|
title: "Server url",
|
||||||
value: $serverPath,
|
value: $serverPath,
|
||||||
@ -34,10 +45,6 @@ struct SettingsView: View {
|
|||||||
title: "Local url",
|
title: "Local url",
|
||||||
value: $localAddress,
|
value: $localAddress,
|
||||||
footnote: "The url where the device can be reached directly on the local WiFi network.")
|
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(
|
SettingsNumberItemLink(
|
||||||
title: "Device ID",
|
title: "Device ID",
|
||||||
value: $deviceId,
|
value: $deviceId,
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
884A45B7279F48C100D6E650 /* SesameApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45B6279F48C100D6E650 /* SesameApp.swift */; };
|
884A45B7279F48C100D6E650 /* SesameApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45B6279F48C100D6E650 /* SesameApp.swift */; };
|
||||||
884A45B9279F48C100D6E650 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45B8279F48C100D6E650 /* ContentView.swift */; };
|
884A45B9279F48C100D6E650 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45B8279F48C100D6E650 /* ContentView.swift */; };
|
||||||
884A45BB279F48C300D6E650 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 884A45BA279F48C300D6E650 /* Assets.xcassets */; };
|
884A45BB279F48C300D6E650 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 884A45BA279F48C300D6E650 /* Assets.xcassets */; };
|
||||||
884A45BE279F48C300D6E650 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 884A45BD279F48C300D6E650 /* Preview Assets.xcassets */; };
|
|
||||||
884A45C5279F4BBE00D6E650 /* KeyManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45C4279F4BBE00D6E650 /* KeyManagement.swift */; };
|
884A45C5279F4BBE00D6E650 /* KeyManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45C4279F4BBE00D6E650 /* KeyManagement.swift */; };
|
||||||
884A45C927A43D7900D6E650 /* ClientState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45C827A43D7900D6E650 /* ClientState.swift */; };
|
884A45C927A43D7900D6E650 /* ClientState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45C827A43D7900D6E650 /* ClientState.swift */; };
|
||||||
884A45CB27A464C000D6E650 /* SymmetricKey+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */; };
|
884A45CB27A464C000D6E650 /* SymmetricKey+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */; };
|
||||||
@ -22,7 +21,6 @@
|
|||||||
88E197B229EDC9BC00BF1D19 /* Sesame_WatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197B129EDC9BC00BF1D19 /* Sesame_WatchApp.swift */; };
|
88E197B229EDC9BC00BF1D19 /* Sesame_WatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197B129EDC9BC00BF1D19 /* Sesame_WatchApp.swift */; };
|
||||||
88E197B429EDC9BC00BF1D19 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197B329EDC9BC00BF1D19 /* ContentView.swift */; };
|
88E197B429EDC9BC00BF1D19 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197B329EDC9BC00BF1D19 /* ContentView.swift */; };
|
||||||
88E197B629EDC9BD00BF1D19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88E197B529EDC9BD00BF1D19 /* Assets.xcassets */; };
|
88E197B629EDC9BD00BF1D19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88E197B529EDC9BD00BF1D19 /* Assets.xcassets */; };
|
||||||
88E197B929EDC9BD00BF1D19 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88E197B829EDC9BD00BF1D19 /* Preview Assets.xcassets */; };
|
|
||||||
88E197C429EDCC8900BF1D19 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CC27A465F500D6E650 /* Client.swift */; };
|
88E197C429EDCC8900BF1D19 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CC27A465F500D6E650 /* Client.swift */; };
|
||||||
88E197C729EDCCBD00BF1D19 /* 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 */; };
|
88E197C829EDCCCE00BF1D19 /* ClientState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45C827A43D7900D6E650 /* ClientState.swift */; };
|
||||||
@ -37,7 +35,6 @@
|
|||||||
88E197D529EDCE8800BF1D19 /* ServerMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1F7281E769F00769EF6 /* ServerMessage.swift */; };
|
88E197D529EDCE8800BF1D19 /* ServerMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1F7281E769F00769EF6 /* ServerMessage.swift */; };
|
||||||
88E197D729EDCFE800BF1D19 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */; };
|
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 */; };
|
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 */; };
|
E240654B2A8153C6009C1AD8 /* SettingsListTextItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240654A2A8153C6009C1AD8 /* SettingsListTextItem.swift */; };
|
||||||
E240654D2A8155A3009C1AD8 /* SettingsListToggleItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240654C2A8155A3009C1AD8 /* SettingsListToggleItem.swift */; };
|
E240654D2A8155A3009C1AD8 /* SettingsListToggleItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240654C2A8155A3009C1AD8 /* SettingsListToggleItem.swift */; };
|
||||||
E240654F2A8159B7009C1AD8 /* SettingsTextInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240654E2A8159B7009C1AD8 /* SettingsTextInputView.swift */; };
|
E240654F2A8159B7009C1AD8 /* SettingsTextInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240654E2A8159B7009C1AD8 /* SettingsTextInputView.swift */; };
|
||||||
@ -54,7 +51,9 @@
|
|||||||
E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77327FF95920011CFD2 /* DeviceResponse.swift */; };
|
E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77327FF95920011CFD2 /* DeviceResponse.swift */; };
|
||||||
E24EE77727FF95C00011CFD2 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = E24EE77627FF95C00011CFD2 /* NIOCore */; };
|
E24EE77727FF95C00011CFD2 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = E24EE77627FF95C00011CFD2 /* NIOCore */; };
|
||||||
E24EE77927FF95E00011CFD2 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77827FF95E00011CFD2 /* Message.swift */; };
|
E24EE77927FF95E00011CFD2 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77827FF95E00011CFD2 /* Message.swift */; };
|
||||||
E268E04D2A852AFE00185913 /* ComplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E268E04C2A852AFE00185913 /* ComplicationController.swift */; };
|
E24F6C6E2A89749A0040F8C4 /* ConnectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24F6C6D2A89749A0040F8C4 /* ConnectionStrategy.swift */; };
|
||||||
|
E24F6C6F2A8974C60040F8C4 /* ConnectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24F6C6D2A89749A0040F8C4 /* ConnectionStrategy.swift */; };
|
||||||
|
E25317562A8A1ABF005A537D /* HistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28DED34281EB17600259690 /* HistoryItem.swift */; };
|
||||||
E268E0822A85302000185913 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E268E0532A852F8E00185913 /* WidgetKit.framework */; };
|
E268E0822A85302000185913 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E268E0532A852F8E00185913 /* WidgetKit.framework */; };
|
||||||
E268E0832A85302000185913 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E268E0552A852F8E00185913 /* SwiftUI.framework */; };
|
E268E0832A85302000185913 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E268E0552A852F8E00185913 /* SwiftUI.framework */; };
|
||||||
E268E0862A85302000185913 /* Sesame_Widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E268E0852A85302000185913 /* Sesame_Widget.swift */; };
|
E268E0862A85302000185913 /* Sesame_Widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E268E0852A85302000185913 /* Sesame_Widget.swift */; };
|
||||||
@ -70,6 +69,8 @@
|
|||||||
E2C5C1DB2806FE8900769EF6 /* RouteAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */; };
|
E2C5C1DB2806FE8900769EF6 /* RouteAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */; };
|
||||||
E2C5C1DD281B3AC400769EF6 /* UInt32+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */; };
|
E2C5C1DD281B3AC400769EF6 /* UInt32+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */; };
|
||||||
E2C5C1F8281E769F00769EF6 /* ServerMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1F7281E769F00769EF6 /* ServerMessage.swift */; };
|
E2C5C1F8281E769F00769EF6 /* ServerMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1F7281E769F00769EF6 /* ServerMessage.swift */; };
|
||||||
|
E2F5DCCA2A88E913002858B9 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F5DCC92A88E913002858B9 /* Array+Extensions.swift */; };
|
||||||
|
E2F5DCCB2A88E976002858B9 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F5DCC92A88E913002858B9 /* Array+Extensions.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -101,7 +102,6 @@
|
|||||||
884A45B6279F48C100D6E650 /* SesameApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SesameApp.swift; sourceTree = "<group>"; };
|
884A45B6279F48C100D6E650 /* SesameApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SesameApp.swift; sourceTree = "<group>"; };
|
||||||
884A45B8279F48C100D6E650 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
884A45B8279F48C100D6E650 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
884A45BA279F48C300D6E650 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
884A45BA279F48C300D6E650 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
884A45BD279F48C300D6E650 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
|
||||||
884A45C4279F4BBE00D6E650 /* KeyManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManagement.swift; sourceTree = "<group>"; };
|
884A45C4279F4BBE00D6E650 /* KeyManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManagement.swift; sourceTree = "<group>"; };
|
||||||
884A45C827A43D7900D6E650 /* ClientState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientState.swift; sourceTree = "<group>"; };
|
884A45C827A43D7900D6E650 /* ClientState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientState.swift; sourceTree = "<group>"; };
|
||||||
884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SymmetricKey+Extensions.swift"; sourceTree = "<group>"; };
|
884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SymmetricKey+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
@ -113,7 +113,6 @@
|
|||||||
88E197B129EDC9BC00BF1D19 /* Sesame_WatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sesame_WatchApp.swift; sourceTree = "<group>"; };
|
88E197B129EDC9BC00BF1D19 /* Sesame_WatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sesame_WatchApp.swift; sourceTree = "<group>"; };
|
||||||
88E197B329EDC9BC00BF1D19 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
|
||||||
88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.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>"; };
|
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>"; };
|
E240654C2A8155A3009C1AD8 /* SettingsListToggleItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsListToggleItem.swift; sourceTree = "<group>"; };
|
||||||
@ -128,7 +127,7 @@
|
|||||||
E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.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>"; };
|
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>"; };
|
E24EE77827FF95E00011CFD2 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; };
|
||||||
E268E04C2A852AFE00185913 /* ComplicationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComplicationController.swift; sourceTree = "<group>"; };
|
E24F6C6D2A89749A0040F8C4 /* ConnectionStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionStrategy.swift; sourceTree = "<group>"; };
|
||||||
E268E0532A852F8E00185913 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
E268E0532A852F8E00185913 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||||
E268E0552A852F8E00185913 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
E268E0552A852F8E00185913 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||||
E268E0812A85302000185913 /* Sesame-WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Sesame-WidgetExtension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
E268E0812A85302000185913 /* Sesame-WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Sesame-WidgetExtension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@ -145,6 +144,7 @@
|
|||||||
E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteAPI.swift; sourceTree = "<group>"; };
|
E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteAPI.swift; sourceTree = "<group>"; };
|
||||||
E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt32+Extensions.swift"; sourceTree = "<group>"; };
|
E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt32+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
E2C5C1F7281E769F00769EF6 /* ServerMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerMessage.swift; sourceTree = "<group>"; };
|
E2C5C1F7281E769F00769EF6 /* ServerMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerMessage.swift; sourceTree = "<group>"; };
|
||||||
|
E2F5DCC92A88E913002858B9 /* Array+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -206,36 +206,24 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E28DED38281EE9CF00259690 /* Info.plist */,
|
E28DED38281EE9CF00259690 /* Info.plist */,
|
||||||
|
884A45BA279F48C300D6E650 /* Assets.xcassets */,
|
||||||
|
E24F6C6C2A89748B0040F8C4 /* Common */,
|
||||||
E2C5C1D92806FE4A00769EF6 /* API */,
|
E2C5C1D92806FE4A00769EF6 /* API */,
|
||||||
884A45B6279F48C100D6E650 /* SesameApp.swift */,
|
884A45B6279F48C100D6E650 /* SesameApp.swift */,
|
||||||
884A45B8279F48C100D6E650 /* ContentView.swift */,
|
884A45B8279F48C100D6E650 /* ContentView.swift */,
|
||||||
E28DED30281EAE9100259690 /* HistoryView.swift */,
|
|
||||||
E28DED32281EB15B00259690 /* HistoryListItem.swift */,
|
|
||||||
E28DED34281EB17600259690 /* HistoryItem.swift */,
|
|
||||||
E28DED36281EC7FB00259690 /* HistoryManager.swift */,
|
|
||||||
E28DED2C281E840B00259690 /* SettingsView.swift */,
|
E28DED2C281E840B00259690 /* SettingsView.swift */,
|
||||||
E28DED2E281E8A0500259690 /* SingleKeyView.swift */,
|
E28DED2E281E8A0500259690 /* SingleKeyView.swift */,
|
||||||
884A45C827A43D7900D6E650 /* ClientState.swift */,
|
E28DED30281EAE9100259690 /* HistoryView.swift */,
|
||||||
884A45CC27A465F500D6E650 /* Client.swift */,
|
E25317542A8A1A07005A537D /* History */,
|
||||||
884A45C4279F4BBE00D6E650 /* KeyManagement.swift */,
|
E25317552A8A1A32005A537D /* Extensions */,
|
||||||
884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */,
|
|
||||||
884A45BA279F48C300D6E650 /* Assets.xcassets */,
|
|
||||||
884A45BC279F48C300D6E650 /* Preview Content */,
|
|
||||||
);
|
);
|
||||||
path = Sesame;
|
path = Sesame;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
884A45BC279F48C300D6E650 /* Preview Content */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
884A45BD279F48C300D6E650 /* Preview Assets.xcassets */,
|
|
||||||
);
|
|
||||||
path = "Preview Content";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
88E197B029EDC9BC00BF1D19 /* Sesame-Watch Watch App */ = {
|
88E197B029EDC9BC00BF1D19 /* Sesame-Watch Watch App */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
88E197B529EDC9BD00BF1D19 /* Assets.xcassets */,
|
||||||
E24065562A819AAD009C1AD8 /* Settings */,
|
E24065562A819AAD009C1AD8 /* Settings */,
|
||||||
88E197B129EDC9BC00BF1D19 /* Sesame_WatchApp.swift */,
|
88E197B129EDC9BC00BF1D19 /* Sesame_WatchApp.swift */,
|
||||||
88E197B329EDC9BC00BF1D19 /* ContentView.swift */,
|
88E197B329EDC9BC00BF1D19 /* ContentView.swift */,
|
||||||
@ -243,22 +231,11 @@
|
|||||||
888362352A80F4420032BBB2 /* HistoryView.swift */,
|
888362352A80F4420032BBB2 /* HistoryView.swift */,
|
||||||
E240655D2A822E97009C1AD8 /* HistoryListRow.swift */,
|
E240655D2A822E97009C1AD8 /* HistoryListRow.swift */,
|
||||||
E240655F2A822ED9009C1AD8 /* HistoryItemDetail.swift */,
|
E240655F2A822ED9009C1AD8 /* HistoryItemDetail.swift */,
|
||||||
88E197B529EDC9BD00BF1D19 /* Assets.xcassets */,
|
|
||||||
88E197B729EDC9BD00BF1D19 /* Preview Content */,
|
|
||||||
88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */,
|
88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */,
|
||||||
E268E04C2A852AFE00185913 /* ComplicationController.swift */,
|
|
||||||
);
|
);
|
||||||
path = "Sesame-Watch Watch App";
|
path = "Sesame-Watch Watch App";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
88E197B729EDC9BD00BF1D19 /* Preview Content */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
88E197B829EDC9BD00BF1D19 /* Preview Assets.xcassets */,
|
|
||||||
);
|
|
||||||
path = "Preview Content";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
88E197CA29EDCD4900BF1D19 /* Frameworks */ = {
|
88E197CA29EDCD4900BF1D19 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -283,12 +260,42 @@
|
|||||||
path = Settings;
|
path = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E24F6C6C2A89748B0040F8C4 /* Common */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
884A45CC27A465F500D6E650 /* Client.swift */,
|
||||||
|
884A45C827A43D7900D6E650 /* ClientState.swift */,
|
||||||
|
E24F6C6D2A89749A0040F8C4 /* ConnectionStrategy.swift */,
|
||||||
|
E28DED36281EC7FB00259690 /* HistoryManager.swift */,
|
||||||
|
884A45C4279F4BBE00D6E650 /* KeyManagement.swift */,
|
||||||
|
);
|
||||||
|
path = Common;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
E25317542A8A1A07005A537D /* History */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E28DED32281EB15B00259690 /* HistoryListItem.swift */,
|
||||||
|
E28DED34281EB17600259690 /* HistoryItem.swift */,
|
||||||
|
);
|
||||||
|
path = History;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
E25317552A8A1A32005A537D /* Extensions */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E2F5DCC92A88E913002858B9 /* Array+Extensions.swift */,
|
||||||
|
884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */,
|
||||||
|
);
|
||||||
|
path = Extensions;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E268E0842A85302000185913 /* Sesame-Widget */ = {
|
E268E0842A85302000185913 /* Sesame-Widget */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E268E0852A85302000185913 /* Sesame_Widget.swift */,
|
|
||||||
E268E0872A85302000185913 /* Assets.xcassets */,
|
|
||||||
E268E0892A85302000185913 /* Info.plist */,
|
E268E0892A85302000185913 /* Info.plist */,
|
||||||
|
E268E0872A85302000185913 /* Assets.xcassets */,
|
||||||
|
E268E0852A85302000185913 /* Sesame_Widget.swift */,
|
||||||
);
|
);
|
||||||
path = "Sesame-Widget";
|
path = "Sesame-Widget";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -428,7 +435,6 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
884A45BE279F48C300D6E650 /* Preview Assets.xcassets in Resources */,
|
|
||||||
884A45BB279F48C300D6E650 /* Assets.xcassets in Resources */,
|
884A45BB279F48C300D6E650 /* Assets.xcassets in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -437,7 +443,6 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
88E197B929EDC9BD00BF1D19 /* Preview Assets.xcassets in Resources */,
|
|
||||||
88E197B629EDC9BD00BF1D19 /* Assets.xcassets in Resources */,
|
88E197B629EDC9BD00BF1D19 /* Assets.xcassets in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -468,12 +473,14 @@
|
|||||||
884A45CB27A464C000D6E650 /* SymmetricKey+Extensions.swift in Sources */,
|
884A45CB27A464C000D6E650 /* SymmetricKey+Extensions.swift in Sources */,
|
||||||
E28DED31281EAE9100259690 /* HistoryView.swift in Sources */,
|
E28DED31281EAE9100259690 /* HistoryView.swift in Sources */,
|
||||||
E24EE77927FF95E00011CFD2 /* Message.swift in Sources */,
|
E24EE77927FF95E00011CFD2 /* Message.swift in Sources */,
|
||||||
|
E2F5DCCA2A88E913002858B9 /* Array+Extensions.swift in Sources */,
|
||||||
E28DED35281EB17600259690 /* HistoryItem.swift in Sources */,
|
E28DED35281EB17600259690 /* HistoryItem.swift in Sources */,
|
||||||
884A45C927A43D7900D6E650 /* ClientState.swift in Sources */,
|
884A45C927A43D7900D6E650 /* ClientState.swift in Sources */,
|
||||||
E28DED33281EB15B00259690 /* HistoryListItem.swift in Sources */,
|
E28DED33281EB15B00259690 /* HistoryListItem.swift in Sources */,
|
||||||
E28DED2D281E840B00259690 /* SettingsView.swift in Sources */,
|
E28DED2D281E840B00259690 /* SettingsView.swift in Sources */,
|
||||||
884A45B7279F48C100D6E650 /* SesameApp.swift in Sources */,
|
884A45B7279F48C100D6E650 /* SesameApp.swift in Sources */,
|
||||||
88E197C429EDCC8900BF1D19 /* Client.swift in Sources */,
|
88E197C429EDCC8900BF1D19 /* Client.swift in Sources */,
|
||||||
|
E24F6C6E2A89749A0040F8C4 /* ConnectionStrategy.swift in Sources */,
|
||||||
884A45C5279F4BBE00D6E650 /* KeyManagement.swift in Sources */,
|
884A45C5279F4BBE00D6E650 /* KeyManagement.swift in Sources */,
|
||||||
E2C5C1F8281E769F00769EF6 /* ServerMessage.swift in Sources */,
|
E2C5C1F8281E769F00769EF6 /* ServerMessage.swift in Sources */,
|
||||||
);
|
);
|
||||||
@ -485,6 +492,7 @@
|
|||||||
files = (
|
files = (
|
||||||
888362342A80F3F90032BBB2 /* SettingsView.swift in Sources */,
|
888362342A80F3F90032BBB2 /* SettingsView.swift in Sources */,
|
||||||
88E197B429EDC9BC00BF1D19 /* ContentView.swift in Sources */,
|
88E197B429EDC9BC00BF1D19 /* ContentView.swift in Sources */,
|
||||||
|
E2F5DCCB2A88E976002858B9 /* Array+Extensions.swift in Sources */,
|
||||||
E24065532A819614009C1AD8 /* SettingsNumberItemLink.swift in Sources */,
|
E24065532A819614009C1AD8 /* SettingsNumberItemLink.swift in Sources */,
|
||||||
888362362A80F4420032BBB2 /* HistoryView.swift in Sources */,
|
888362362A80F4420032BBB2 /* HistoryView.swift in Sources */,
|
||||||
E240654F2A8159B7009C1AD8 /* SettingsTextInputView.swift in Sources */,
|
E240654F2A8159B7009C1AD8 /* SettingsTextInputView.swift in Sources */,
|
||||||
@ -500,7 +508,7 @@
|
|||||||
88E197B229EDC9BC00BF1D19 /* Sesame_WatchApp.swift in Sources */,
|
88E197B229EDC9BC00BF1D19 /* Sesame_WatchApp.swift in Sources */,
|
||||||
E24065582A819AE3009C1AD8 /* SettingsKeyItemLink.swift in Sources */,
|
E24065582A819AE3009C1AD8 /* SettingsKeyItemLink.swift in Sources */,
|
||||||
88E197C929EDCCE100BF1D19 /* Message.swift in Sources */,
|
88E197C929EDCCE100BF1D19 /* Message.swift in Sources */,
|
||||||
88E197D929EDD14D00BF1D19 /* HistoryItem.swift in Sources */,
|
E24F6C6F2A8974C60040F8C4 /* ConnectionStrategy.swift in Sources */,
|
||||||
E240654B2A8153C6009C1AD8 /* SettingsListTextItem.swift in Sources */,
|
E240654B2A8153C6009C1AD8 /* SettingsListTextItem.swift in Sources */,
|
||||||
88E197C729EDCCBD00BF1D19 /* Client.swift in Sources */,
|
88E197C729EDCCBD00BF1D19 /* Client.swift in Sources */,
|
||||||
88E197D429EDCE7600BF1D19 /* UInt32+Extensions.swift in Sources */,
|
88E197D429EDCE7600BF1D19 /* UInt32+Extensions.swift in Sources */,
|
||||||
@ -508,7 +516,7 @@
|
|||||||
E240654D2A8155A3009C1AD8 /* SettingsListToggleItem.swift in Sources */,
|
E240654D2A8155A3009C1AD8 /* SettingsListToggleItem.swift in Sources */,
|
||||||
E24065552A819663009C1AD8 /* SettingsNumberInputView.swift in Sources */,
|
E24065552A819663009C1AD8 /* SettingsNumberInputView.swift in Sources */,
|
||||||
E240655E2A822E97009C1AD8 /* HistoryListRow.swift in Sources */,
|
E240655E2A822E97009C1AD8 /* HistoryListRow.swift in Sources */,
|
||||||
E268E04D2A852AFE00185913 /* ComplicationController.swift in Sources */,
|
E25317562A8A1ABF005A537D /* HistoryItem.swift in Sources */,
|
||||||
88E197D829EDD13B00BF1D19 /* SymmetricKey+Extensions.swift in Sources */,
|
88E197D829EDD13B00BF1D19 /* SymmetricKey+Extensions.swift in Sources */,
|
||||||
E240655C2A822C8E009C1AD8 /* HistoryManager.swift in Sources */,
|
E240655C2A822C8E009C1AD8 /* HistoryManager.swift in Sources */,
|
||||||
);
|
);
|
||||||
@ -657,7 +665,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Sesame/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = H8WR4M6QQ4;
|
DEVELOPMENT_TEAM = H8WR4M6QQ4;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -668,6 +676,7 @@
|
|||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -691,7 +700,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Sesame/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = H8WR4M6QQ4;
|
DEVELOPMENT_TEAM = H8WR4M6QQ4;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -702,6 +711,7 @@
|
|||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -725,7 +735,7 @@
|
|||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Sesame-Watch Watch App/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = H8WR4M6QQ4;
|
DEVELOPMENT_TEAM = H8WR4M6QQ4;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -757,7 +767,7 @@
|
|||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Sesame-Watch Watch App/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = H8WR4M6QQ4;
|
DEVELOPMENT_TEAM = H8WR4M6QQ4;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
Binary file not shown.
10
Sesame/Common/ConnectionStrategy.swift
Normal file
10
Sesame/Common/ConnectionStrategy.swift
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum ConnectionStrategy: String, CaseIterable, Identifiable {
|
||||||
|
case local = "Local"
|
||||||
|
case localFirst = "Local first"
|
||||||
|
case remote = "Remote"
|
||||||
|
case remoteFirst = "Remote first"
|
||||||
|
|
||||||
|
var id: Self { self }
|
||||||
|
}
|
@ -1,14 +1,22 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import CBORCoding
|
import CBORCoding
|
||||||
|
|
||||||
protocol HistoryManagerProtocol {
|
class HistoryManagerBase: ObservableObject {
|
||||||
|
|
||||||
func loadEntries() -> [HistoryItem]
|
@Published
|
||||||
|
var entries: [HistoryItem] = []
|
||||||
func save(item: HistoryItem) throws
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final class HistoryManager: HistoryManagerProtocol {
|
protocol HistoryManagerProtocol: HistoryManagerBase {
|
||||||
|
|
||||||
|
var entries: [HistoryItem] { get }
|
||||||
|
|
||||||
|
func save(item: HistoryItem) throws
|
||||||
|
|
||||||
|
func delete(item: HistoryItem) -> Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
final class HistoryManager: HistoryManagerBase, HistoryManagerProtocol {
|
||||||
|
|
||||||
private let encoder = CBOREncoder(dateEncodingStrategy: .secondsSince1970)
|
private let encoder = CBOREncoder(dateEncodingStrategy: .secondsSince1970)
|
||||||
|
|
||||||
@ -25,11 +33,20 @@ final class HistoryManager: HistoryManagerProtocol {
|
|||||||
|
|
||||||
private let fileUrl: URL
|
private let fileUrl: URL
|
||||||
|
|
||||||
init() {
|
override init() {
|
||||||
self.fileUrl = HistoryManager.documentDirectory.appendingPathComponent("history2.bin")
|
self.fileUrl = HistoryManager.documentDirectory.appendingPathComponent("history2.bin")
|
||||||
|
super.init()
|
||||||
|
Task {
|
||||||
|
print("Loading history...")
|
||||||
|
let all = loadEntries()
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.entries = all
|
||||||
|
print("History loaded (\(self.entries.count) entries)")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadEntries() -> [HistoryItem] {
|
private func loadEntries() -> [HistoryItem] {
|
||||||
guard fm.fileExists(atPath: fileUrl.path) else {
|
guard fm.fileExists(atPath: fileUrl.path) else {
|
||||||
print("No history data found")
|
print("No history data found")
|
||||||
return []
|
return []
|
||||||
@ -65,8 +82,7 @@ final class HistoryManager: HistoryManagerProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func save(item: HistoryItem) throws {
|
func save(item: HistoryItem) throws {
|
||||||
let entryData = try encoder.encode(item)
|
let data = try convertForStorage(item)
|
||||||
let data = Data([UInt8(entryData.count)]) + entryData
|
|
||||||
guard fm.fileExists(atPath: fileUrl.path) else {
|
guard fm.fileExists(atPath: fileUrl.path) else {
|
||||||
try data.write(to: fileUrl)
|
try data.write(to: fileUrl)
|
||||||
print("First history item written (\(data[0]))")
|
print("First history item written (\(data[0]))")
|
||||||
@ -78,15 +94,50 @@ final class HistoryManager: HistoryManagerProtocol {
|
|||||||
try handle.close()
|
try handle.close()
|
||||||
print("History item written (\(data[0]))")
|
print("History item written (\(data[0]))")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func delete(item: HistoryItem) -> Bool {
|
||||||
|
let newItems = entries
|
||||||
|
.filter { $0 != item }
|
||||||
|
|
||||||
|
let data: FlattenSequence<[Data]>
|
||||||
|
do {
|
||||||
|
data = try newItems
|
||||||
|
.map(convertForStorage)
|
||||||
|
.joined()
|
||||||
|
} catch {
|
||||||
|
print("Failed to encode items: \(error)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
try Data(data).write(to: fileUrl)
|
||||||
|
} catch {
|
||||||
|
print("Failed to save items: \(error)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
entries = newItems
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private func convertForStorage(_ item: HistoryItem) throws -> Data {
|
||||||
|
let entryData = try encoder.encode(item)
|
||||||
|
return Data([UInt8(entryData.count)]) + entryData
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class HistoryManagerMock: HistoryManagerProtocol {
|
final class HistoryManagerMock: HistoryManagerBase, HistoryManagerProtocol {
|
||||||
|
|
||||||
func loadEntries() -> [HistoryItem] {
|
override init() {
|
||||||
[.mock]
|
super.init()
|
||||||
|
self.entries = [.mock]
|
||||||
}
|
}
|
||||||
|
|
||||||
func save(item: HistoryItem) throws {
|
func save(item: HistoryItem) throws {
|
||||||
|
entries.append(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func delete(item: HistoryItem) -> Bool {
|
||||||
|
entries = entries.filter { $0 != item }
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,15 @@ import Foundation
|
|||||||
import CryptoKit
|
import CryptoKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
struct KeySet {
|
||||||
|
|
||||||
|
let remote: SymmetricKey
|
||||||
|
|
||||||
|
let device: SymmetricKey
|
||||||
|
|
||||||
|
let server: Data
|
||||||
|
}
|
||||||
|
|
||||||
extension KeyManagement {
|
extension KeyManagement {
|
||||||
|
|
||||||
enum KeyType: String, Identifiable, CaseIterable {
|
enum KeyType: String, Identifiable, CaseIterable {
|
||||||
@ -82,6 +91,9 @@ private struct KeyChain {
|
|||||||
|
|
||||||
var item: CFTypeRef?
|
var item: CFTypeRef?
|
||||||
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
||||||
|
guard status != -25300 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
guard status == errSecSuccess else {
|
guard status == errSecSuccess else {
|
||||||
print("Failed to get \(type): \(status)")
|
print("Failed to get \(type): \(status)")
|
||||||
return nil
|
return nil
|
||||||
@ -137,6 +149,15 @@ final class KeyManagement: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAllKeys() -> KeySet? {
|
||||||
|
guard let remoteKey = get(.remoteKey),
|
||||||
|
let token = get(.authToken)?.data,
|
||||||
|
let deviceKey = get(.deviceKey) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .init(remote: remoteKey, device: deviceKey, server: token)
|
||||||
|
}
|
||||||
|
|
||||||
func get(_ type: KeyType) -> SymmetricKey? {
|
func get(_ type: KeyType) -> SymmetricKey? {
|
||||||
keyChain.load(type)
|
keyChain.load(type)
|
||||||
}
|
}
|
@ -21,7 +21,7 @@ struct ContentView: View {
|
|||||||
@AppStorage("deviceID")
|
@AppStorage("deviceID")
|
||||||
private var deviceID: Int = 0
|
private var deviceID: Int = 0
|
||||||
|
|
||||||
@State
|
@ObservedObject
|
||||||
var keyManager = KeyManagement()
|
var keyManager = KeyManagement()
|
||||||
|
|
||||||
let history = HistoryManager()
|
let history = HistoryManager()
|
||||||
@ -133,7 +133,7 @@ struct ContentView: View {
|
|||||||
.animation(.easeInOut, value: state.color)
|
.animation(.easeInOut, value: state.color)
|
||||||
.sheet(isPresented: $showSettingsSheet) {
|
.sheet(isPresented: $showSettingsSheet) {
|
||||||
SettingsView(
|
SettingsView(
|
||||||
keyManager: $keyManager,
|
keyManager: keyManager,
|
||||||
serverAddress: $serverPath,
|
serverAddress: $serverPath,
|
||||||
localAddress: $localAddress,
|
localAddress: $localAddress,
|
||||||
deviceID: $deviceID,
|
deviceID: $deviceID,
|
||||||
@ -142,7 +142,7 @@ struct ContentView: View {
|
|||||||
useLocalConnection: $useLocalConnection)
|
useLocalConnection: $useLocalConnection)
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showHistorySheet) {
|
.sheet(isPresented: $showHistorySheet) {
|
||||||
HistoryView(manager: history)
|
HistoryView(history: history)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.preferredColorScheme(.dark)
|
.preferredColorScheme(.dark)
|
||||||
|
14
Sesame/Extensions/Array+Extensions.swift
Normal file
14
Sesame/Extensions/Array+Extensions.swift
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Array {
|
||||||
|
|
||||||
|
func count(where closure: (Element) -> Bool) -> Int {
|
||||||
|
var result = 0
|
||||||
|
forEach { element in
|
||||||
|
if closure(element) {
|
||||||
|
result += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ import Foundation
|
|||||||
|
|
||||||
struct HistoryItem {
|
struct HistoryItem {
|
||||||
|
|
||||||
|
|
||||||
/// The sent/received date (local time, not including compensation offset)
|
/// The sent/received date (local time, not including compensation offset)
|
||||||
let requestDate: Date
|
let requestDate: Date
|
||||||
|
|
||||||
@ -11,46 +10,25 @@ struct HistoryItem {
|
|||||||
|
|
||||||
let usedLocalConnection: Bool
|
let usedLocalConnection: Bool
|
||||||
|
|
||||||
let response: ClientState?
|
var response: ClientState
|
||||||
|
|
||||||
let responseMessage: Message.Content?
|
let responseMessage: Message.Content?
|
||||||
|
|
||||||
let responseDate: Date?
|
let responseDate: Date
|
||||||
|
|
||||||
init(sent message: Message.Content, date: Date, local: Bool) {
|
init(sent message: Message.Content, sentDate: Date, local: Bool, response: ClientState, responseDate: Date, responseMessage: Message.Content?) {
|
||||||
self.requestDate = date
|
self.requestDate = sentDate
|
||||||
self.request = message
|
self.request = message
|
||||||
self.responseMessage = nil
|
self.responseMessage = responseMessage
|
||||||
self.response = nil
|
|
||||||
self.responseDate = nil
|
|
||||||
self.usedLocalConnection = local
|
|
||||||
}
|
|
||||||
|
|
||||||
func didReceive(response: ClientState, date: Date?, message: Message.Content?) -> HistoryItem {
|
|
||||||
.init(sent: self, response: response, date: date, message: message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func invalidated() -> HistoryItem {
|
|
||||||
didReceive(response: .responseRejected(.invalidAuthentication), date: responseDate, message: responseMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func notAuthenticated() -> HistoryItem {
|
|
||||||
didReceive(response: .responseRejected(.missingKey), date: responseDate, message: responseMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
private init(sent: HistoryItem, response: ClientState, date: Date?, message: Message.Content?) {
|
|
||||||
self.requestDate = sent.requestDate
|
|
||||||
self.request = sent.request
|
|
||||||
self.responseDate = date
|
|
||||||
self.responseMessage = message
|
|
||||||
self.response = response
|
self.response = response
|
||||||
self.usedLocalConnection = sent.usedLocalConnection
|
self.responseDate = responseDate
|
||||||
|
self.usedLocalConnection = local
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Statistics
|
// MARK: Statistics
|
||||||
|
|
||||||
var roundTripTime: TimeInterval? {
|
var roundTripTime: TimeInterval {
|
||||||
responseDate?.timeIntervalSince(requestDate)
|
responseDate.timeIntervalSince(requestDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
var deviceTime: Date? {
|
var deviceTime: Date? {
|
||||||
@ -68,14 +46,14 @@ struct HistoryItem {
|
|||||||
guard let deviceTime = deviceTime else {
|
guard let deviceTime = deviceTime else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return responseDate?.timeIntervalSince(deviceTime)
|
return responseDate.timeIntervalSince(deviceTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
var clockOffset: Int? {
|
var clockOffset: Int? {
|
||||||
guard let interval = roundTripTime, let deviceTime = deviceTime else {
|
guard let deviceTime = deviceTime else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let estimatedArrival = requestDate.advanced(by: interval / 2)
|
let estimatedArrival = requestDate.advanced(by: roundTripTime / 2)
|
||||||
return Int(deviceTime.timeIntervalSince(estimatedArrival))
|
return Int(deviceTime.timeIntervalSince(estimatedArrival))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +103,12 @@ extension HistoryItem {
|
|||||||
static var mock: HistoryItem {
|
static var mock: HistoryItem {
|
||||||
let content = Message.Content(time: Date.now.timestamp, id: 123, device: 0)
|
let content = Message.Content(time: Date.now.timestamp, id: 123, device: 0)
|
||||||
let content2 = Message.Content(time: (Date.now + 1).timestamp, id: 124, device: 0)
|
let content2 = Message.Content(time: (Date.now + 1).timestamp, id: 124, device: 0)
|
||||||
return .init(sent: content, date: .now, local: false)
|
return .init(
|
||||||
.didReceive(response: .openSesame, date: .now + 2, message: content2)
|
sent: content,
|
||||||
|
sentDate: .now,
|
||||||
|
local: false,
|
||||||
|
response: .openSesame,
|
||||||
|
responseDate: .now + 2,
|
||||||
|
responseMessage: content2)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,11 +16,8 @@ struct HistoryListItem: View {
|
|||||||
df.string(from: entry.requestDate)
|
df.string(from: entry.requestDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
var roundTripText: String? {
|
var roundTripText: String {
|
||||||
guard let time = entry.roundTripTime else {
|
"\(Int(entry.roundTripTime * 1000)) ms"
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return "\(Int(time * 1000)) ms"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var counterText: String {
|
var counterText: String {
|
||||||
@ -46,17 +43,15 @@ struct HistoryListItem: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(entry.response?.description ?? "")
|
Text(entry.response.description)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(entryTime)
|
Text(entryTime)
|
||||||
}.padding(.bottom, 1)
|
}.padding(.bottom, 1)
|
||||||
HStack {
|
HStack {
|
||||||
if let roundTripText {
|
|
||||||
Image(systemSymbol: entry.usedLocalConnection ? .wifi : .network)
|
Image(systemSymbol: entry.usedLocalConnection ? .wifi : .network)
|
||||||
Text(roundTripText)
|
Text(roundTripText)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
}
|
|
||||||
Image(systemSymbol: .personalhotspot)
|
Image(systemSymbol: .personalhotspot)
|
||||||
Text(counterText)
|
Text(counterText)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
@ -2,20 +2,59 @@ import SwiftUI
|
|||||||
|
|
||||||
struct HistoryView: View {
|
struct HistoryView: View {
|
||||||
|
|
||||||
let manager: HistoryManagerProtocol
|
let history: HistoryManagerProtocol
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var items: [HistoryItem] = []
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var unlockCount = 0
|
||||||
|
|
||||||
|
private var percentage: Double {
|
||||||
|
guard items.count > 0 else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return Double(unlockCount * 100) / Double(items.count)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List(manager.loadEntries()) { entry in
|
List {
|
||||||
|
HStack {
|
||||||
|
Text("\(items.count) requests")
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.font(.body)
|
||||||
|
Spacer()
|
||||||
|
Text(String(format: "%d successful (%.1f %%)", unlockCount, percentage))
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.font(.footnote)
|
||||||
|
}
|
||||||
|
ForEach(items) {entry in
|
||||||
HistoryListItem(entry: entry)
|
HistoryListItem(entry: entry)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.navigationTitle("History")
|
.navigationTitle("History")
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func load() {
|
||||||
|
Task {
|
||||||
|
let entries = history.loadEntries()
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
items = entries
|
||||||
|
unlockCount = items.count {
|
||||||
|
$0.response == .openSesame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HistoryView_Previews: PreviewProvider {
|
struct HistoryView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
HistoryView(manager: HistoryManagerMock())
|
HistoryView(history: HistoryManagerMock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,8 +2,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
|
|
||||||
@Binding
|
let keyManager: KeyManagement
|
||||||
var keyManager: KeyManagement
|
|
||||||
|
|
||||||
@Binding
|
@Binding
|
||||||
var serverAddress: String
|
var serverAddress: String
|
||||||
@ -88,7 +87,7 @@ struct SettingsView: View {
|
|||||||
}.padding(.vertical, 8)
|
}.padding(.vertical, 8)
|
||||||
ForEach(KeyManagement.KeyType.allCases) { keyType in
|
ForEach(KeyManagement.KeyType.allCases) { keyType in
|
||||||
SingleKeyView(
|
SingleKeyView(
|
||||||
keyManager: $keyManager,
|
keyManager: keyManager,
|
||||||
type: keyType)
|
type: keyType)
|
||||||
}
|
}
|
||||||
Toggle(isOn: $isCompensatingDaylightTime) {
|
Toggle(isOn: $isCompensatingDaylightTime) {
|
||||||
@ -157,7 +156,7 @@ struct SettingsView: View {
|
|||||||
struct SettingsView_Previews: PreviewProvider {
|
struct SettingsView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
SettingsView(
|
SettingsView(
|
||||||
keyManager: .constant(KeyManagement()),
|
keyManager: KeyManagement(),
|
||||||
serverAddress: .constant("https://example.com"),
|
serverAddress: .constant("https://example.com"),
|
||||||
localAddress: .constant("192.168.178.42"),
|
localAddress: .constant("192.168.178.42"),
|
||||||
deviceID: .constant(0),
|
deviceID: .constant(0),
|
||||||
|
@ -6,8 +6,7 @@ struct SingleKeyView: View {
|
|||||||
@State
|
@State
|
||||||
private var needRefresh = false
|
private var needRefresh = false
|
||||||
|
|
||||||
@Binding
|
let keyManager: KeyManagement
|
||||||
var keyManager: KeyManagement
|
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var showEditWindow = false
|
private var showEditWindow = false
|
||||||
@ -74,7 +73,7 @@ struct SingleKeyView: View {
|
|||||||
TextField("Key data", text: $keyText)
|
TextField("Key data", text: $keyText)
|
||||||
.lineLimit(4)
|
.lineLimit(4)
|
||||||
.font(.system(.body, design: .monospaced))
|
.font(.system(.body, design: .monospaced))
|
||||||
.foregroundColor(.black)
|
.foregroundColor(.primary)
|
||||||
Button("Save", action: saveKey)
|
Button("Save", action: saveKey)
|
||||||
Button("Cancel", role: .cancel, action: {})
|
Button("Cancel", role: .cancel, action: {})
|
||||||
}, message: {
|
}, message: {
|
||||||
@ -101,7 +100,7 @@ struct SingleKeyView: View {
|
|||||||
struct SingleKeyView_Previews: PreviewProvider {
|
struct SingleKeyView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
SingleKeyView(
|
SingleKeyView(
|
||||||
keyManager: .constant(KeyManagement()),
|
keyManager: KeyManagement(),
|
||||||
type: .deviceKey)
|
type: .deviceKey)
|
||||||
.previewLayout(.fixed(width: 350, height: 100))
|
.previewLayout(.fixed(width: 350, height: 100))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user