diff --git a/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json
new file mode 100644
index 0000000..26454ca
--- /dev/null
+++ b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json
@@ -0,0 +1,25 @@
+{
+ "images" : [
+ {
+ "idiom" : "watch",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : "<=145"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : ">183"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "auto-scaling" : "auto"
+ }
+}
diff --git a/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Contents.json
new file mode 100644
index 0000000..e8b3252
--- /dev/null
+++ b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Contents.json
@@ -0,0 +1,53 @@
+{
+ "assets" : [
+ {
+ "filename" : "Circular.imageset",
+ "idiom" : "watch",
+ "role" : "circular"
+ },
+ {
+ "filename" : "Extra Large.imageset",
+ "idiom" : "watch",
+ "role" : "extra-large"
+ },
+ {
+ "filename" : "Graphic Bezel.imageset",
+ "idiom" : "watch",
+ "role" : "graphic-bezel"
+ },
+ {
+ "filename" : "Graphic Circular.imageset",
+ "idiom" : "watch",
+ "role" : "graphic-circular"
+ },
+ {
+ "filename" : "Graphic Corner.imageset",
+ "idiom" : "watch",
+ "role" : "graphic-corner"
+ },
+ {
+ "filename" : "Graphic Extra Large.imageset",
+ "idiom" : "watch",
+ "role" : "graphic-extra-large"
+ },
+ {
+ "filename" : "Graphic Large Rectangular.imageset",
+ "idiom" : "watch",
+ "role" : "graphic-large-rectangular"
+ },
+ {
+ "filename" : "Modular.imageset",
+ "idiom" : "watch",
+ "role" : "modular"
+ },
+ {
+ "filename" : "Utilitarian.imageset",
+ "idiom" : "watch",
+ "role" : "utilitarian"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json
new file mode 100644
index 0000000..26454ca
--- /dev/null
+++ b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json
@@ -0,0 +1,25 @@
+{
+ "images" : [
+ {
+ "idiom" : "watch",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : "<=145"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : ">183"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "auto-scaling" : "auto"
+ }
+}
diff --git a/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json
new file mode 100644
index 0000000..6e184db
--- /dev/null
+++ b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "images" : [
+ {
+ "idiom" : "watch",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : ">183"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "auto-scaling" : "auto"
+ }
+}
diff --git a/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json
new file mode 100644
index 0000000..6e184db
--- /dev/null
+++ b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "images" : [
+ {
+ "idiom" : "watch",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : ">183"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "auto-scaling" : "auto"
+ }
+}
diff --git a/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json
new file mode 100644
index 0000000..6e184db
--- /dev/null
+++ b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "images" : [
+ {
+ "idiom" : "watch",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : ">183"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "auto-scaling" : "auto"
+ }
+}
diff --git a/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json
new file mode 100644
index 0000000..26454ca
--- /dev/null
+++ b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json
@@ -0,0 +1,25 @@
+{
+ "images" : [
+ {
+ "idiom" : "watch",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : "<=145"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : ">183"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "auto-scaling" : "auto"
+ }
+}
diff --git a/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json
new file mode 100644
index 0000000..6e184db
--- /dev/null
+++ b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "images" : [
+ {
+ "idiom" : "watch",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : ">183"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "auto-scaling" : "auto"
+ }
+}
diff --git a/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json
new file mode 100644
index 0000000..26454ca
--- /dev/null
+++ b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json
@@ -0,0 +1,25 @@
+{
+ "images" : [
+ {
+ "idiom" : "watch",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : "<=145"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : ">183"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "auto-scaling" : "auto"
+ }
+}
diff --git a/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json
new file mode 100644
index 0000000..26454ca
--- /dev/null
+++ b/Sesame-Watch Watch App/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json
@@ -0,0 +1,25 @@
+{
+ "images" : [
+ {
+ "idiom" : "watch",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : "<=145"
+ },
+ {
+ "idiom" : "watch",
+ "scale" : "2x",
+ "screen-width" : ">183"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "auto-scaling" : "auto"
+ }
+}
diff --git a/Sesame-Watch Watch App/ComplicationController.swift b/Sesame-Watch Watch App/ComplicationController.swift
new file mode 100644
index 0000000..30c86dc
--- /dev/null
+++ b/Sesame-Watch Watch App/ComplicationController.swift
@@ -0,0 +1,10 @@
+import Foundation
+import ClockKit
+
+
+class ComplicationController: NSObject, CLKComplicationDataSource {
+
+ func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
+ // TODO: Finish implementing this required method.
+ }
+}
diff --git a/Sesame-Watch Watch App/ContentView.swift b/Sesame-Watch Watch App/ContentView.swift
index dc6e535..f3672ce 100644
--- a/Sesame-Watch Watch App/ContentView.swift
+++ b/Sesame-Watch Watch App/ContentView.swift
@@ -3,6 +3,9 @@ import SFSafeSymbols
import CryptoKit
struct ContentView: View {
+
+ @Binding
+ var didLaunchFromComplication: Bool
@AppStorage("server")
var serverPath: String = "https://christophhagen.de/sesame/"
@@ -27,6 +30,9 @@ struct ContentView: View {
@State
var state: ClientState = .noKeyAvailable
+
+ @State
+ var stateResetTimer: Timer?
let server = Client()
@@ -53,18 +59,31 @@ struct ContentView: View {
.padding()
.onTapGesture(perform: mainButtonPressed)
.disabled(!state.allowsAction)
- Text(state.actionText)
- .font(.subheadline)
+ if state == .waitingForResponse {
+ ProgressView()
+ .progressViewStyle(CircularProgressViewStyle())
+ .frame(width: 20, height: 20)
+ } else {
+ Text(state.actionText)
+ .font(.subheadline)
+ }
}
Spacer()
}
.background(state.color)
.animation(.easeInOut, value: state.color)
.onAppear {
- if keyManager.hasAllKeys, state == .noKeyAvailable {
+ if state == .noKeyAvailable, keyManager.hasAllKeys {
state = .ready
}
}
+ .onChange(of: didLaunchFromComplication) { launched in
+ guard launched else {
+ return
+ }
+ didLaunchFromComplication = false
+ mainButtonPressed()
+ }
}
func mainButtonPressed() {
@@ -83,13 +102,14 @@ struct ContentView: View {
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
- //responseTime = receivedTime
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())
@@ -108,6 +128,25 @@ struct ContentView: View {
save(historyItem: finishedItem)
}
}
+
+ private func preventStateReset() {
+ stateResetTimer?.invalidate()
+ stateResetTimer = nil
+ }
+
+ private func scheduleStateReset() {
+ stateResetTimer?.invalidate()
+ stateResetTimer = Timer.scheduledTimer(withTimeInterval: 8.0, repeats: false) { _ in
+ DispatchQueue.main.async {
+ resetState()
+ }
+ }
+ }
+
+ private func resetState() {
+ state = keyManager.hasAllKeys ? .ready : .noKeyAvailable
+ preventStateReset()
+ }
private func send(_ message: Message, authToken: Data) async -> (state: ClientState, response: Message?) {
if useLocalConnection {
@@ -128,7 +167,7 @@ struct ContentView: View {
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
- ContentView()
+ ContentView(didLaunchFromComplication: .constant(false))
.environmentObject(KeyManagement())
}
}
diff --git a/Sesame-Watch Watch App/Sesame_WatchApp.swift b/Sesame-Watch Watch App/Sesame_WatchApp.swift
index bef560b..8b83d2e 100644
--- a/Sesame-Watch Watch App/Sesame_WatchApp.swift
+++ b/Sesame-Watch Watch App/Sesame_WatchApp.swift
@@ -4,17 +4,30 @@ import SwiftUI
struct Sesame_Watch_Watch_AppApp: App {
let keyManagement = KeyManagement()
+
+ @State
+ var selected: Int = 0
+
+ @State
+ var didLaunchFromComplication = false
var body: some Scene {
WindowGroup {
- TabView {
- ContentView()
+ TabView(selection: $selected) {
+ ContentView(didLaunchFromComplication: $didLaunchFromComplication)
.environmentObject(keyManagement)
+ .tag(1)
SettingsView()
.environmentObject(keyManagement)
+ .tag(2)
HistoryView(history: HistoryManager())
+ .tag(3)
}
.tabViewStyle(PageTabViewStyle())
+ .onOpenURL { url in
+ selected = 0
+ didLaunchFromComplication = true
+ }
}
}
}
diff --git a/Sesame-Widget/Assets.xcassets/AccentColor.colorset/Contents.json b/Sesame-Widget/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/Sesame-Widget/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sesame-Widget/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sesame-Widget/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..49c81cd
--- /dev/null
+++ b/Sesame-Widget/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "watchos",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sesame-Widget/Assets.xcassets/Contents.json b/Sesame-Widget/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Sesame-Widget/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sesame-Widget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/Sesame-Widget/Assets.xcassets/WidgetBackground.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/Sesame-Widget/Assets.xcassets/WidgetBackground.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sesame-Widget/Info.plist b/Sesame-Widget/Info.plist
new file mode 100644
index 0000000..0f118fb
--- /dev/null
+++ b/Sesame-Widget/Info.plist
@@ -0,0 +1,11 @@
+
+
+
+
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.widgetkit-extension
+
+
+
diff --git a/Sesame-Widget/Sesame_Widget.swift b/Sesame-Widget/Sesame_Widget.swift
new file mode 100644
index 0000000..712547b
--- /dev/null
+++ b/Sesame-Widget/Sesame_Widget.swift
@@ -0,0 +1,58 @@
+import WidgetKit
+import SwiftUI
+import SFSafeSymbols
+
+struct Provider: TimelineProvider {
+
+ func placeholder(in context: Context) -> SimpleEntry {
+ SimpleEntry(date: Date())
+ }
+
+ func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
+ let entry = SimpleEntry(date: Date())
+ completion(entry)
+ }
+
+ func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) {
+ let entries = [Entry(date: .now), Entry(date: .distantFuture)]
+ completion(Timeline(entries: entries, policy: .atEnd))
+ }
+}
+
+struct SimpleEntry: TimelineEntry {
+ let date: Date
+}
+
+struct Sesame_WidgetEntryView : View {
+
+ var entry: Provider.Entry
+
+ var body: some View {
+ Image(systemSymbol: .lock)
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+ .padding()
+ .fontWeight(.thin)
+ .widgetURL(URL(string: "sesame:///open")!)
+ }
+}
+
+@main
+struct Sesame_Widget: Widget {
+ let kind: String = "SesameOpen"
+
+ var body: some WidgetConfiguration {
+ StaticConfiguration(kind: kind, provider: Provider()) { entry in
+ Sesame_WidgetEntryView(entry: entry)
+ }
+ .configurationDisplayName("Open")
+ .description("This widget can be used to unlock the door with a single tap.")
+ }
+}
+
+struct Sesame_Widget_Previews: PreviewProvider {
+ static var previews: some View {
+ Sesame_WidgetEntryView(entry: SimpleEntry(date: Date()))
+ .previewContext(WidgetPreviewContext(family: .accessoryRectangular))
+ }
+}
diff --git a/Sesame.xcodeproj/project.pbxproj b/Sesame.xcodeproj/project.pbxproj
index f1def8c..88f95bd 100644
--- a/Sesame.xcodeproj/project.pbxproj
+++ b/Sesame.xcodeproj/project.pbxproj
@@ -54,6 +54,13 @@
E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77327FF95920011CFD2 /* DeviceResponse.swift */; };
E24EE77727FF95C00011CFD2 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = E24EE77627FF95C00011CFD2 /* NIOCore */; };
E24EE77927FF95E00011CFD2 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77827FF95E00011CFD2 /* Message.swift */; };
+ E268E04D2A852AFE00185913 /* ComplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E268E04C2A852AFE00185913 /* ComplicationController.swift */; };
+ E268E0822A85302000185913 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E268E0532A852F8E00185913 /* WidgetKit.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 */; };
+ E268E0882A85302000185913 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E268E0872A85302000185913 /* Assets.xcassets */; };
+ E268E08C2A85302000185913 /* Sesame-WidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E268E0812A85302000185913 /* Sesame-WidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ E268E0912A85318500185913 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = E268E0902A85318500185913 /* SFSafeSymbols */; };
E28DED2D281E840B00259690 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28DED2C281E840B00259690 /* SettingsView.swift */; };
E28DED2F281E8A0500259690 /* SingleKeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28DED2E281E8A0500259690 /* SingleKeyView.swift */; };
E28DED31281EAE9100259690 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28DED30281EAE9100259690 /* HistoryView.swift */; };
@@ -65,6 +72,30 @@
E2C5C1F8281E769F00769EF6 /* ServerMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1F7281E769F00769EF6 /* ServerMessage.swift */; };
/* End PBXBuildFile section */
+/* Begin PBXContainerItemProxy section */
+ E268E08A2A85302000185913 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 884A45AB279F48C100D6E650 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = E268E0802A85302000185913;
+ remoteInfo = "Sesame-WidgetExtension";
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ E268E0662A852F8E00185913 /* Embed Foundation Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ E268E08C2A85302000185913 /* Sesame-WidgetExtension.appex in Embed Foundation Extensions */,
+ );
+ name = "Embed Foundation Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
/* Begin PBXFileReference section */
884A45B3279F48C100D6E650 /* Sesame.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sesame.app; sourceTree = BUILT_PRODUCTS_DIR; };
884A45B6279F48C100D6E650 /* SesameApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SesameApp.swift; sourceTree = ""; };
@@ -97,6 +128,13 @@
E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = ""; };
E24EE77327FF95920011CFD2 /* DeviceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceResponse.swift; sourceTree = ""; };
E24EE77827FF95E00011CFD2 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; };
+ E268E04C2A852AFE00185913 /* ComplicationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComplicationController.swift; sourceTree = ""; };
+ 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; };
+ E268E0812A85302000185913 /* Sesame-WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Sesame-WidgetExtension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
+ E268E0852A85302000185913 /* Sesame_Widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sesame_Widget.swift; sourceTree = ""; };
+ E268E0872A85302000185913 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ E268E0892A85302000185913 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
E28DED2C281E840B00259690 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; };
E28DED2E281E8A0500259690 /* SingleKeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleKeyView.swift; sourceTree = ""; };
E28DED30281EAE9100259690 /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = ""; };
@@ -130,6 +168,16 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ E268E07E2A85302000185913 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E268E0912A85318500185913 /* SFSafeSymbols in Frameworks */,
+ E268E0832A85302000185913 /* SwiftUI.framework in Frameworks */,
+ E268E0822A85302000185913 /* WidgetKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -138,6 +186,7 @@
children = (
884A45B5279F48C100D6E650 /* Sesame */,
88E197B029EDC9BC00BF1D19 /* Sesame-Watch Watch App */,
+ E268E0842A85302000185913 /* Sesame-Widget */,
884A45B4279F48C100D6E650 /* Products */,
88E197CA29EDCD4900BF1D19 /* Frameworks */,
);
@@ -148,6 +197,7 @@
children = (
884A45B3279F48C100D6E650 /* Sesame.app */,
88E197AC29EDC9BC00BF1D19 /* Sesame-Watch Watch App.app */,
+ E268E0812A85302000185913 /* Sesame-WidgetExtension.appex */,
);
name = Products;
sourceTree = "";
@@ -196,6 +246,7 @@
88E197B529EDC9BD00BF1D19 /* Assets.xcassets */,
88E197B729EDC9BD00BF1D19 /* Preview Content */,
88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */,
+ E268E04C2A852AFE00185913 /* ComplicationController.swift */,
);
path = "Sesame-Watch Watch App";
sourceTree = "";
@@ -211,6 +262,8 @@
88E197CA29EDCD4900BF1D19 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ E268E0532A852F8E00185913 /* WidgetKit.framework */,
+ E268E0552A852F8E00185913 /* SwiftUI.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -230,6 +283,16 @@
path = Settings;
sourceTree = "";
};
+ E268E0842A85302000185913 /* Sesame-Widget */ = {
+ isa = PBXGroup;
+ children = (
+ E268E0852A85302000185913 /* Sesame_Widget.swift */,
+ E268E0872A85302000185913 /* Assets.xcassets */,
+ E268E0892A85302000185913 /* Info.plist */,
+ );
+ path = "Sesame-Widget";
+ sourceTree = "";
+ };
E2C5C1D92806FE4A00769EF6 /* API */ = {
isa = PBXGroup;
children = (
@@ -276,10 +339,12 @@
88E197A829EDC9BC00BF1D19 /* Sources */,
88E197A929EDC9BC00BF1D19 /* Frameworks */,
88E197AA29EDC9BC00BF1D19 /* Resources */,
+ E268E0662A852F8E00185913 /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
+ E268E08B2A85302000185913 /* PBXTargetDependency */,
);
name = "Sesame-Watch Watch App";
packageProductDependencies = (
@@ -291,6 +356,26 @@
productReference = 88E197AC29EDC9BC00BF1D19 /* Sesame-Watch Watch App.app */;
productType = "com.apple.product-type.application";
};
+ E268E0802A85302000185913 /* Sesame-WidgetExtension */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = E268E08D2A85302000185913 /* Build configuration list for PBXNativeTarget "Sesame-WidgetExtension" */;
+ buildPhases = (
+ E268E07D2A85302000185913 /* Sources */,
+ E268E07E2A85302000185913 /* Frameworks */,
+ E268E07F2A85302000185913 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "Sesame-WidgetExtension";
+ packageProductDependencies = (
+ E268E0902A85318500185913 /* SFSafeSymbols */,
+ );
+ productName = "Sesame-WidgetExtension";
+ productReference = E268E0812A85302000185913 /* Sesame-WidgetExtension.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -308,6 +393,9 @@
88E197AB29EDC9BC00BF1D19 = {
CreatedOnToolsVersion = 14.3;
};
+ E268E0802A85302000185913 = {
+ CreatedOnToolsVersion = 14.3.1;
+ };
};
};
buildConfigurationList = 884A45AE279F48C100D6E650 /* Build configuration list for PBXProject "Sesame" */;
@@ -330,6 +418,7 @@
targets = (
884A45B2279F48C100D6E650 /* Sesame */,
88E197AB29EDC9BC00BF1D19 /* Sesame-Watch Watch App */,
+ E268E0802A85302000185913 /* Sesame-WidgetExtension */,
);
};
/* End PBXProject section */
@@ -353,6 +442,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ E268E07F2A85302000185913 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E268E0882A85302000185913 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -411,13 +508,30 @@
E240654D2A8155A3009C1AD8 /* SettingsListToggleItem.swift in Sources */,
E24065552A819663009C1AD8 /* SettingsNumberInputView.swift in Sources */,
E240655E2A822E97009C1AD8 /* HistoryListRow.swift in Sources */,
+ E268E04D2A852AFE00185913 /* ComplicationController.swift in Sources */,
88E197D829EDD13B00BF1D19 /* SymmetricKey+Extensions.swift in Sources */,
E240655C2A822C8E009C1AD8 /* HistoryManager.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
+ E268E07D2A85302000185913 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E268E0862A85302000185913 /* Sesame_Widget.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
+/* Begin PBXTargetDependency section */
+ E268E08B2A85302000185913 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = E268E0802A85302000185913 /* Sesame-WidgetExtension */;
+ targetProxy = E268E08A2A85302000185913 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
/* Begin XCBuildConfiguration section */
884A45BF279F48C300D6E650 /* Debug */ = {
isa = XCBuildConfiguration;
@@ -605,6 +719,7 @@
88E197BD29EDC9BD00BF1D19 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@@ -636,6 +751,7 @@
88E197BE29EDC9BD00BF1D19 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@@ -664,6 +780,68 @@
};
name = Release;
};
+ E268E08E2A85302000185913 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = H8WR4M6QQ4;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = "Sesame-Widget/Info.plist";
+ INFOPLIST_KEY_CFBundleDisplayName = "Sesame-Widget";
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ "@executable_path/../../../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "de.christophhagen.Sesame-Watch.watchkitapp.Sesame-Widget";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = watchos;
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = 4;
+ WATCHOS_DEPLOYMENT_TARGET = 9.4;
+ };
+ name = Debug;
+ };
+ E268E08F2A85302000185913 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = H8WR4M6QQ4;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = "Sesame-Widget/Info.plist";
+ INFOPLIST_KEY_CFBundleDisplayName = "Sesame-Widget";
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ "@executable_path/../../../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "de.christophhagen.Sesame-Watch.watchkitapp.Sesame-Widget";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = watchos;
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = 4;
+ WATCHOS_DEPLOYMENT_TARGET = 9.4;
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -694,6 +872,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ E268E08D2A85302000185913 /* Build configuration list for PBXNativeTarget "Sesame-WidgetExtension" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ E268E08E2A85302000185913 /* Debug */,
+ E268E08F2A85302000185913 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
@@ -754,6 +941,11 @@
package = E24EE77527FF95C00011CFD2 /* XCRemoteSwiftPackageReference "swift-nio" */;
productName = NIOCore;
};
+ E268E0902A85318500185913 /* SFSafeSymbols */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 8864665029E5939C004FE2BE /* XCRemoteSwiftPackageReference "SFSafeSymbols" */;
+ productName = SFSafeSymbols;
+ };
/* End XCSwiftPackageProductDependency section */
};
rootObject = 884A45AB279F48C100D6E650 /* Project object */;
diff --git a/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate b/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate
index bd30498..c450514 100644
Binary files a/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate and b/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/Sesame.xcodeproj/xcuserdata/ch.xcuserdatad/xcschemes/xcschememanagement.plist b/Sesame.xcodeproj/xcuserdata/ch.xcuserdatad/xcschemes/xcschememanagement.plist
index 2f121ba..98feb3a 100644
--- a/Sesame.xcodeproj/xcuserdata/ch.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Sesame.xcodeproj/xcuserdata/ch.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -70,12 +70,17 @@
Sesame-Watch Watch App.xcscheme_^#shared#^_
orderHint
- 0
+ 1
+
+ Sesame-WidgetExtension.xcscheme_^#shared#^_
+
+ orderHint
+ 2
Sesame.xcscheme_^#shared#^_
orderHint
- 1
+ 0
diff --git a/Sesame/ClientState.swift b/Sesame/ClientState.swift
index 9fdae4b..746d73c 100644
--- a/Sesame/ClientState.swift
+++ b/Sesame/ClientState.swift
@@ -111,7 +111,57 @@ enum ClientState {
}
var actionText: String {
- "Unlock"
+ switch self {
+ case .noKeyAvailable:
+ return "No key"
+ case .requestingStatus:
+ return "Checking..."
+ case .deviceNotAvailable(let connectionError):
+ switch connectionError {
+ case .serverNotReached:
+ return "Server not found"
+ case .deviceDisconnected:
+ return "Device disconnected"
+ }
+ case .ready:
+ return "Unlock"
+ case .waitingForResponse:
+ return "Unlocking..."
+ case .messageRejected(let rejectionCause):
+ switch rejectionCause {
+ case .invalidDeviceId:
+ return "Invalid device ID"
+ case .invalidCounter:
+ return "Invalid counter"
+ case .invalidTime:
+ return "Invalid timestamp"
+ case .invalidAuthentication:
+ return "Invalid signature"
+ case .timeout:
+ return "Device not responding"
+ case .missingKey:
+ return "Device key missing"
+ }
+ case .responseRejected(let rejectionCause):
+ switch rejectionCause {
+ case .invalidDeviceId:
+ return "Invalid device id (response)"
+ case .invalidCounter:
+ return "Invalid counter (response)"
+ case .invalidTime:
+ return "Invalid time (response)"
+ case .invalidAuthentication:
+ return "Invalid signature (response)"
+ case .timeout:
+ return "Timed out (response)"
+ case .missingKey:
+ return "Missing key (response)"
+ }
+ case .openSesame:
+ return "Unlocked"
+ case .internalError(let string):
+ return string
+ }
}
var requiresDescription: Bool {
@@ -144,7 +194,7 @@ enum ClientState {
var allowsAction: Bool {
switch self {
- case .noKeyAvailable:
+ case .noKeyAvailable, .waitingForResponse:
return false
default:
return true