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