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