From 97ae896d6dcc7e044dac0b86e25d888173b97036 Mon Sep 17 00:00:00 2001 From: pacu Date: Wed, 21 Aug 2024 21:31:09 -0300 Subject: [PATCH 1/5] WIP - Companion App --- .../FrostCompanion.xcodeproj/project.pbxproj | 53 ++++++++ .../xcshareddata/swiftpm/Package.resolved | 124 +++++++++++++++++- .../UserInterfaceState.xcuserstate | Bin 22132 -> 38255 bytes .../xcschemes/FrostCompanion.xcscheme | 2 +- .../FrostCompanion/ContentView.swift | 35 +++-- .../FrostCompanion/FrostCompanionApp.swift | 8 +- .../FrostCompanion/MainScreenReducer.swift | 50 +++++++ .../FrostCompanion/Model/Model.swift | 17 +++ .../Participant/ParticipantImportView.swift | 72 ++++++++++ .../Participant/ParticipantReducer.swift | 54 ++++++++ 10 files changed, 400 insertions(+), 15 deletions(-) create mode 100644 Examples/FrostCompanion/FrostCompanion/MainScreenReducer.swift create mode 100644 Examples/FrostCompanion/FrostCompanion/Model/Model.swift create mode 100644 Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift create mode 100644 Examples/FrostCompanion/FrostCompanion/Participant/ParticipantReducer.swift diff --git a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj index a7e2456..28579bf 100644 --- a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj +++ b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj @@ -15,6 +15,11 @@ 0D50A35D2C0E6ED300B921E1 /* FrostCompanionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D50A35C2C0E6ED300B921E1 /* FrostCompanionUITests.swift */; }; 0D50A35F2C0E6ED300B921E1 /* FrostCompanionUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D50A35E2C0E6ED300B921E1 /* FrostCompanionUITestsLaunchTests.swift */; }; 0D50A36D2C0E701500B921E1 /* FrostSwiftFFI in Frameworks */ = {isa = PBXBuildFile; productRef = 0D50A36C2C0E701500B921E1 /* FrostSwiftFFI */; }; + 0DDAB7AF2C768320009FA521 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 0DDAB7AE2C768320009FA521 /* ComposableArchitecture */; }; + 0DDAB7B12C7683A6009FA521 /* MainScreenReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7B02C7683A6009FA521 /* MainScreenReducer.swift */; }; + 0DDAB7B42C7691D3009FA521 /* ParticipantReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7B32C7691D3009FA521 /* ParticipantReducer.swift */; }; + 0DDAB7B82C769345009FA521 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7B72C769345009FA521 /* Model.swift */; }; + 0DDAB7BB2C76A5A4009FA521 /* ParticipantImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7BA2C76A5A4009FA521 /* ParticipantImportView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -46,6 +51,10 @@ 0D50A3582C0E6ED300B921E1 /* FrostCompanionUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FrostCompanionUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 0D50A35C2C0E6ED300B921E1 /* FrostCompanionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrostCompanionUITests.swift; sourceTree = ""; }; 0D50A35E2C0E6ED300B921E1 /* FrostCompanionUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrostCompanionUITestsLaunchTests.swift; sourceTree = ""; }; + 0DDAB7B02C7683A6009FA521 /* MainScreenReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenReducer.swift; sourceTree = ""; }; + 0DDAB7B32C7691D3009FA521 /* ParticipantReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantReducer.swift; sourceTree = ""; }; + 0DDAB7B72C769345009FA521 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; + 0DDAB7BA2C76A5A4009FA521 /* ParticipantImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantImportView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -53,6 +62,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0DDAB7AF2C768320009FA521 /* ComposableArchitecture in Frameworks */, 0D50A36D2C0E701500B921E1 /* FrostSwiftFFI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -97,11 +107,14 @@ 0D50A33F2C0E6ED200B921E1 /* FrostCompanion */ = { isa = PBXGroup; children = ( + 0DDAB7B62C76931F009FA521 /* Model */, + 0DDAB7B22C7689E8009FA521 /* Participant */, 0D50A3402C0E6ED200B921E1 /* FrostCompanionApp.swift */, 0D50A3422C0E6ED200B921E1 /* ContentView.swift */, 0D50A3442C0E6ED300B921E1 /* Assets.xcassets */, 0D50A3462C0E6ED300B921E1 /* FrostCompanion.entitlements */, 0D50A3472C0E6ED300B921E1 /* Preview Content */, + 0DDAB7B02C7683A6009FA521 /* MainScreenReducer.swift */, ); path = FrostCompanion; sourceTree = ""; @@ -131,6 +144,23 @@ path = FrostCompanionUITests; sourceTree = ""; }; + 0DDAB7B22C7689E8009FA521 /* Participant */ = { + isa = PBXGroup; + children = ( + 0DDAB7B32C7691D3009FA521 /* ParticipantReducer.swift */, + 0DDAB7BA2C76A5A4009FA521 /* ParticipantImportView.swift */, + ); + path = Participant; + sourceTree = ""; + }; + 0DDAB7B62C76931F009FA521 /* Model */ = { + isa = PBXGroup; + children = ( + 0DDAB7B72C769345009FA521 /* Model.swift */, + ); + path = Model; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -149,6 +179,7 @@ name = FrostCompanion; packageProductDependencies = ( 0D50A36C2C0E701500B921E1 /* FrostSwiftFFI */, + 0DDAB7AE2C768320009FA521 /* ComposableArchitecture */, ); productName = FrostCompanion; productReference = 0D50A33D2C0E6ED200B921E1 /* FrostCompanion.app */; @@ -224,6 +255,7 @@ mainGroup = 0D50A3342C0E6ED200B921E1; packageReferences = ( 0D50A36B2C0E701500B921E1 /* XCRemoteSwiftPackageReference "frost-uniffi-sdk" */, + 0DDAB7AD2C768320009FA521 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */, ); productRefGroup = 0D50A33E2C0E6ED200B921E1 /* Products */; projectDirPath = ""; @@ -267,8 +299,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0DDAB7B82C769345009FA521 /* Model.swift in Sources */, + 0DDAB7B42C7691D3009FA521 /* ParticipantReducer.swift in Sources */, 0D50A3432C0E6ED200B921E1 /* ContentView.swift in Sources */, 0D50A3412C0E6ED200B921E1 /* FrostCompanionApp.swift in Sources */, + 0DDAB7B12C7683A6009FA521 /* MainScreenReducer.swift in Sources */, + 0DDAB7BB2C76A5A4009FA521 /* ParticipantImportView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -357,7 +393,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -412,7 +450,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SWIFT_COMPILATION_MODE = wholemodule; @@ -631,6 +671,14 @@ minimumVersion = 0.0.1; }; }; + 0DDAB7AD2C768320009FA521 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.13.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -639,6 +687,11 @@ package = 0D50A36B2C0E701500B921E1 /* XCRemoteSwiftPackageReference "frost-uniffi-sdk" */; productName = FrostSwiftFFI; }; + 0DDAB7AE2C768320009FA521 /* ComposableArchitecture */ = { + isa = XCSwiftPackageProductDependency; + package = 0DDAB7AD2C768320009FA521 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; + productName = ComposableArchitecture; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 0D50A3352C0E6ED200B921E1 /* Project object */; diff --git a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5b84138..4839d4d 100644 --- a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,15 +1,131 @@ { - "originHash" : "b992748e59d7d23f8a3fbf43833b6989ef58a466ab5e6d3e62d70703033fb93e", "pins" : [ + { + "identity" : "combine-schedulers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/combine-schedulers", + "state" : { + "revision" : "9fa31f4403da54855f1e2aeaeff478f4f0e40b13", + "version" : "1.0.2" + } + }, { "identity" : "frost-uniffi-sdk", "kind" : "remoteSourceControl", "location" : "https://github.com/pacu/frost-uniffi-sdk", "state" : { - "revision" : "f76b06af4dc5d06fb83153697242a3da4f0b9eb0", - "version" : "0.0.1" + "revision" : "95bdaf849a001beda121bc266c63d5ea870eb4cb", + "version" : "0.0.6" + } + }, + { + "identity" : "swift-case-paths", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "state" : { + "revision" : "71344dd930fde41e8f3adafe260adcbb2fc2a3dc", + "version" : "1.5.4" + } + }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks", + "state" : { + "revision" : "b9b24b69e2adda099a1fa381cda1eeec272d5b53", + "version" : "1.0.5" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d", + "version" : "1.1.2" + } + }, + { + "identity" : "swift-composable-architecture", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-composable-architecture", + "state" : { + "revision" : "b432441cbc11a38c678312bad1a01138a7cd64e0", + "version" : "1.13.0" + } + }, + { + "identity" : "swift-concurrency-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "state" : { + "revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-custom-dump", + "state" : { + "revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1", + "version" : "1.3.3" + } + }, + { + "identity" : "swift-dependencies", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-dependencies", + "state" : { + "revision" : "21660b042cd8fd0bdd45cc39050cacd4e91a63a4", + "version" : "1.3.8" + } + }, + { + "identity" : "swift-identified-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-identified-collections", + "state" : { + "revision" : "2f5ab6e091dd032b63dacbda052405756010dc3b", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-navigation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-navigation", + "state" : { + "revision" : "70321c441d51b0b893c3abbf79687f550a027fde", + "version" : "2.0.6" + } + }, + { + "identity" : "swift-perception", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-perception", + "state" : { + "revision" : "1552c8f722ac256cc0b8daaf1a7073217d4fcdfb", + "version" : "1.3.4" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax", + "state" : { + "revision" : "515f79b522918f83483068d99c68daeb5116342d", + "version" : "600.0.0-prerelease-2024-08-20" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "96beb108a57f24c8476ae1f309239270772b2940", + "version" : "1.2.5" } } ], - "version" : 3 + "version" : 2 } diff --git a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcuserdata/pacu.xcuserdatad/UserInterfaceState.xcuserstate b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcuserdata/pacu.xcuserdatad/UserInterfaceState.xcuserstate index d2b23d5b01a2a5fd2a112e274c0d11c16370fae1..3c07ea70eb3bb8d5cd2f4d48c1b0030ec03023aa 100644 GIT binary patch literal 38255 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 literal 22132 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 diff --git a/Examples/FrostCompanion/FrostCompanion.xcodeproj/xcshareddata/xcschemes/FrostCompanion.xcscheme b/Examples/FrostCompanion/FrostCompanion.xcodeproj/xcshareddata/xcschemes/FrostCompanion.xcscheme index 153fc9b..a132af6 100644 --- a/Examples/FrostCompanion/FrostCompanion.xcodeproj/xcshareddata/xcschemes/FrostCompanion.xcscheme +++ b/Examples/FrostCompanion/FrostCompanion.xcodeproj/xcshareddata/xcschemes/FrostCompanion.xcscheme @@ -1,7 +1,7 @@ + version = "1.8"> var body: some View { - VStack { - Image(systemName: "snow") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, FROST!") + NavigationStack { + VStack { + Image(systemName: "snow") + .imageScale(.large) + .foregroundStyle(.tint) + + Text("Who are you?") + VStack { + Button("Participant") { + store.send(.participantTapped) + } + + Button("Coordinator") { + store.send(.coordinatorTapped) + } + } + } + .padding() } - .padding() + .navigationTitle("Hello, FROST! ❄️") } + } #Preview { - ContentView() + ContentView( + store: Store(initialState: MainScreenFeature.State()){ + MainScreenFeature() + } + ) } diff --git a/Examples/FrostCompanion/FrostCompanion/FrostCompanionApp.swift b/Examples/FrostCompanion/FrostCompanion/FrostCompanionApp.swift index 96d6614..cffc707 100644 --- a/Examples/FrostCompanion/FrostCompanion/FrostCompanionApp.swift +++ b/Examples/FrostCompanion/FrostCompanion/FrostCompanionApp.swift @@ -6,12 +6,16 @@ // import SwiftUI - +import ComposableArchitecture @main struct FrostCompanionApp: App { var body: some Scene { WindowGroup { - ContentView() + ContentView( + store: Store(initialState: MainScreenFeature.State()){ + MainScreenFeature() + } + ) } } } diff --git a/Examples/FrostCompanion/FrostCompanion/MainScreenReducer.swift b/Examples/FrostCompanion/FrostCompanion/MainScreenReducer.swift new file mode 100644 index 0000000..4931111 --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/MainScreenReducer.swift @@ -0,0 +1,50 @@ +// +// MainScreenReducer.swift +// FrostCompanion +// +// Created by pacu on 21/08/2024. +// + +import Foundation +import ComposableArchitecture + + +@Reducer +struct MainScreenFeature { + @ObservableState + struct State: Equatable { + @Presents var participant: ParticipantReducer.State? + } + + enum Action { + case coordinatorTapped + case participantTapped + case importKeyShare(PresentationAction) + } + + var body: some ReducerOf { + Reduce { state, action in + switch action { + case .coordinatorTapped: + return .none + + case .participantTapped: + state.participant = ParticipantReducer.State( + keyShare: JSONKeyShare.empty + ) + return .none + + case .importKeyShare(.presented(.delegate(.keyShareImported(let keyShare)))): + debugPrint(keyShare) + return .none + + case .importKeyShare: + return .none + + } + } + .ifLet(\.$participant, action: \.importKeyShare) { + ParticipantReducer() + } + } +} diff --git a/Examples/FrostCompanion/FrostCompanion/Model/Model.swift b/Examples/FrostCompanion/FrostCompanion/Model/Model.swift new file mode 100644 index 0000000..9b2556e --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/Model/Model.swift @@ -0,0 +1,17 @@ +// +// Model.swift +// FrostCompanion +// +// Created by pacu on 2024-08-21. +// + +import Foundation + + +struct JSONKeyShare: Equatable { + let raw: String +} + +extension JSONKeyShare { + static let empty = JSONKeyShare(raw: "") +} diff --git a/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift new file mode 100644 index 0000000..1e233c9 --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift @@ -0,0 +1,72 @@ +// +// ParticipantImportView.swift +// FrostCompanion +// +// Created by pacu on 2024-08-21 +// + +import SwiftUI +import ComposableArchitecture + +struct ParticipantImportView: View { + @Bindable var store: StoreOf + var body: some View { + Form { + Text("Paste your key-package.json contents") + TextEditor(text: $store.keyShare.raw.sending( + \.setKeyShare + )) + + + Button( + "Import" + ) { + store.send( + .importButtonTapped + ) + } + } + .toolbar { + ToolbarItem { + Button( + "Cancel" + ) { + store.send( + .cancelButtonTapped + ) + } + } + } + .navigationTitle( + "Import your Key-package JSON" + ) + } +} + +#Preview { + ParticipantImportView(store: Store( + initialState: ParticipantReducer.State( + keyShare: JSONKeyShare( + raw: + """ + { + "header": { + "version": 0, + "ciphersuite": "FROST(Pallas, BLAKE2b-512)" + }, + "identifier": "0100000000000000000000000000000000000000000000000000000000000000", + "signing_share": "b02e5a1e43a7f305177682574ac63c1a5f7f57db644c992635d09f699e56f41e", + "commitment": [ + "4141ac3d66ff87c4d14eb14f4262b69de15f7093dfd1f411a02ea70644f0d41f", + "2eb4cd3ace283ba6bb9058ff08d0561ff6d87057ecc87b0701123979291fb082" + ] + } + """ + ) + ) + ) { + ParticipantReducer() + } + ) + .padding() +} diff --git a/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantReducer.swift b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantReducer.swift new file mode 100644 index 0000000..a8c1f51 --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantReducer.swift @@ -0,0 +1,54 @@ +// +// ParticipantReducer.swift +// FrostCompanion +// +// Created by pacu on 2024-08-21. +// + +import Foundation +import ComposableArchitecture + +@Reducer +struct ParticipantReducer { + @ObservableState + struct State: Equatable { + var keyShare: JSONKeyShare = .empty + } + + enum Action { + case cancelButtonTapped + case delegate(Delegate) + case importButtonTapped + case setKeyShare(String) + enum Delegate: Equatable { + case cancel + case keyShareImported(JSONKeyShare) + } + } + + @Dependency(\.dismiss) var dismiss + + var body: some ReducerOf { + Reduce { state, action in + switch action { + case .cancelButtonTapped: + return .run { _ in await self.dismiss() } + case .delegate: + return .none + case .importButtonTapped: + return .run { [share = state.keyShare] send in + await send(.delegate(.keyShareImported(share))) + await self.dismiss() + } + case .setKeyShare(let keyShare): + state.keyShare = JSONKeyShare(raw: keyShare) + return .none +// case .importSuccess: +// +// case .importFailure: +// return .none + } + + } + } +} From c1b5dcc0b57bde262beeee0e387be82e23292214 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Wed, 28 Aug 2024 09:10:29 -0300 Subject: [PATCH 2/5] Navigation to participant import --- .gitignore | 20 +++++++++++ .../FrostCompanion.xcodeproj/project.pbxproj | 20 ++++++----- .../IDEFindNavigatorScopes.plist | 5 --- .../UserInterfaceState.xcuserstate | Bin 38255 -> 0 bytes .../xcschemes/FrostCompanion.xcscheme | 2 +- .../FrostCompanion/FrostCompanionApp.swift | 2 +- .../FrostCompanion/MainScreenReducer.swift | 34 +++++++++++++----- ...ContentView.swift => MainScreenView.swift} | 21 +++++++---- ...r.swift => ParticipantImportFeature.swift} | 2 +- .../Participant/ParticipantImportView.swift | 6 ++-- .../FrostCompanionTests.swift | 31 ++++++---------- .../ParticipanFeatureTests.swift | 24 +++++++++++++ Package.swift | 2 +- 13 files changed, 113 insertions(+), 56 deletions(-) delete mode 100644 Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcuserdata/pacu.xcuserdatad/IDEFindNavigatorScopes.plist delete mode 100644 Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcuserdata/pacu.xcuserdatad/UserInterfaceState.xcuserstate rename Examples/FrostCompanion/FrostCompanion/{ContentView.swift => MainScreenView.swift} (59%) rename Examples/FrostCompanion/FrostCompanion/Participant/{ParticipantReducer.swift => ParticipantImportFeature.swift} (97%) create mode 100644 Examples/FrostCompanion/FrostCompanionTests/ParticipanFeatureTests.swift diff --git a/.gitignore b/.gitignore index d41a04e..458024a 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,23 @@ FrostSwiftFFI/Package.swift ### Xcodebuild gitignore Examples/FrostCompanion/build/ + + +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +*.xcuserdatad +xcuserdata/ +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +*/project.xcworkspace/xcuserdata/ diff --git a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj index 28579bf..25fccb7 100644 --- a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj +++ b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj @@ -8,16 +8,17 @@ /* Begin PBXBuildFile section */ 0D50A3412C0E6ED200B921E1 /* FrostCompanionApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D50A3402C0E6ED200B921E1 /* FrostCompanionApp.swift */; }; - 0D50A3432C0E6ED200B921E1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D50A3422C0E6ED200B921E1 /* ContentView.swift */; }; + 0D50A3432C0E6ED200B921E1 /* MainScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D50A3422C0E6ED200B921E1 /* MainScreenView.swift */; }; 0D50A3452C0E6ED300B921E1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0D50A3442C0E6ED300B921E1 /* Assets.xcassets */; }; 0D50A3492C0E6ED300B921E1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0D50A3482C0E6ED300B921E1 /* Preview Assets.xcassets */; }; 0D50A3532C0E6ED300B921E1 /* FrostCompanionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D50A3522C0E6ED300B921E1 /* FrostCompanionTests.swift */; }; 0D50A35D2C0E6ED300B921E1 /* FrostCompanionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D50A35C2C0E6ED300B921E1 /* FrostCompanionUITests.swift */; }; 0D50A35F2C0E6ED300B921E1 /* FrostCompanionUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D50A35E2C0E6ED300B921E1 /* FrostCompanionUITestsLaunchTests.swift */; }; 0D50A36D2C0E701500B921E1 /* FrostSwiftFFI in Frameworks */ = {isa = PBXBuildFile; productRef = 0D50A36C2C0E701500B921E1 /* FrostSwiftFFI */; }; + 0DB77E062C78F9ED0011DDCB /* ParticipanFeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB77E052C78F9ED0011DDCB /* ParticipanFeatureTests.swift */; }; 0DDAB7AF2C768320009FA521 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 0DDAB7AE2C768320009FA521 /* ComposableArchitecture */; }; 0DDAB7B12C7683A6009FA521 /* MainScreenReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7B02C7683A6009FA521 /* MainScreenReducer.swift */; }; - 0DDAB7B42C7691D3009FA521 /* ParticipantReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7B32C7691D3009FA521 /* ParticipantReducer.swift */; }; + 0DDAB7B42C7691D3009FA521 /* ParticipantImportFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7B32C7691D3009FA521 /* ParticipantImportFeature.swift */; }; 0DDAB7B82C769345009FA521 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7B72C769345009FA521 /* Model.swift */; }; 0DDAB7BB2C76A5A4009FA521 /* ParticipantImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7BA2C76A5A4009FA521 /* ParticipantImportView.swift */; }; /* End PBXBuildFile section */ @@ -42,7 +43,7 @@ /* Begin PBXFileReference section */ 0D50A33D2C0E6ED200B921E1 /* FrostCompanion.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FrostCompanion.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0D50A3402C0E6ED200B921E1 /* FrostCompanionApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrostCompanionApp.swift; sourceTree = ""; }; - 0D50A3422C0E6ED200B921E1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 0D50A3422C0E6ED200B921E1 /* MainScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenView.swift; sourceTree = ""; }; 0D50A3442C0E6ED300B921E1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0D50A3462C0E6ED300B921E1 /* FrostCompanion.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FrostCompanion.entitlements; sourceTree = ""; }; 0D50A3482C0E6ED300B921E1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; @@ -51,8 +52,9 @@ 0D50A3582C0E6ED300B921E1 /* FrostCompanionUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FrostCompanionUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 0D50A35C2C0E6ED300B921E1 /* FrostCompanionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrostCompanionUITests.swift; sourceTree = ""; }; 0D50A35E2C0E6ED300B921E1 /* FrostCompanionUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrostCompanionUITestsLaunchTests.swift; sourceTree = ""; }; + 0DB77E052C78F9ED0011DDCB /* ParticipanFeatureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipanFeatureTests.swift; sourceTree = ""; }; 0DDAB7B02C7683A6009FA521 /* MainScreenReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenReducer.swift; sourceTree = ""; }; - 0DDAB7B32C7691D3009FA521 /* ParticipantReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantReducer.swift; sourceTree = ""; }; + 0DDAB7B32C7691D3009FA521 /* ParticipantImportFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantImportFeature.swift; sourceTree = ""; }; 0DDAB7B72C769345009FA521 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; 0DDAB7BA2C76A5A4009FA521 /* ParticipantImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantImportView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -110,7 +112,7 @@ 0DDAB7B62C76931F009FA521 /* Model */, 0DDAB7B22C7689E8009FA521 /* Participant */, 0D50A3402C0E6ED200B921E1 /* FrostCompanionApp.swift */, - 0D50A3422C0E6ED200B921E1 /* ContentView.swift */, + 0D50A3422C0E6ED200B921E1 /* MainScreenView.swift */, 0D50A3442C0E6ED300B921E1 /* Assets.xcassets */, 0D50A3462C0E6ED300B921E1 /* FrostCompanion.entitlements */, 0D50A3472C0E6ED300B921E1 /* Preview Content */, @@ -131,6 +133,7 @@ isa = PBXGroup; children = ( 0D50A3522C0E6ED300B921E1 /* FrostCompanionTests.swift */, + 0DB77E052C78F9ED0011DDCB /* ParticipanFeatureTests.swift */, ); path = FrostCompanionTests; sourceTree = ""; @@ -147,7 +150,7 @@ 0DDAB7B22C7689E8009FA521 /* Participant */ = { isa = PBXGroup; children = ( - 0DDAB7B32C7691D3009FA521 /* ParticipantReducer.swift */, + 0DDAB7B32C7691D3009FA521 /* ParticipantImportFeature.swift */, 0DDAB7BA2C76A5A4009FA521 /* ParticipantImportView.swift */, ); path = Participant; @@ -300,8 +303,8 @@ buildActionMask = 2147483647; files = ( 0DDAB7B82C769345009FA521 /* Model.swift in Sources */, - 0DDAB7B42C7691D3009FA521 /* ParticipantReducer.swift in Sources */, - 0D50A3432C0E6ED200B921E1 /* ContentView.swift in Sources */, + 0DDAB7B42C7691D3009FA521 /* ParticipantImportFeature.swift in Sources */, + 0D50A3432C0E6ED200B921E1 /* MainScreenView.swift in Sources */, 0D50A3412C0E6ED200B921E1 /* FrostCompanionApp.swift in Sources */, 0DDAB7B12C7683A6009FA521 /* MainScreenReducer.swift in Sources */, 0DDAB7BB2C76A5A4009FA521 /* ParticipantImportView.swift in Sources */, @@ -313,6 +316,7 @@ buildActionMask = 2147483647; files = ( 0D50A3532C0E6ED300B921E1 /* FrostCompanionTests.swift in Sources */, + 0DB77E062C78F9ED0011DDCB /* ParticipanFeatureTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcuserdata/pacu.xcuserdatad/IDEFindNavigatorScopes.plist b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcuserdata/pacu.xcuserdatad/IDEFindNavigatorScopes.plist deleted file mode 100644 index 5dd5da8..0000000 --- a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcuserdata/pacu.xcuserdatad/IDEFindNavigatorScopes.plist +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcuserdata/pacu.xcuserdatad/UserInterfaceState.xcuserstate b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.xcworkspace/xcuserdata/pacu.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 3c07ea70eb3bb8d5cd2f4d48c1b0030ec03023aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38255 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 diff --git a/Examples/FrostCompanion/FrostCompanion.xcodeproj/xcshareddata/xcschemes/FrostCompanion.xcscheme b/Examples/FrostCompanion/FrostCompanion.xcodeproj/xcshareddata/xcschemes/FrostCompanion.xcscheme index a132af6..153fc9b 100644 --- a/Examples/FrostCompanion/FrostCompanion.xcodeproj/xcshareddata/xcschemes/FrostCompanion.xcscheme +++ b/Examples/FrostCompanion/FrostCompanion.xcodeproj/xcshareddata/xcschemes/FrostCompanion.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> () } enum Action { case coordinatorTapped case participantTapped - case importKeyShare(PresentationAction) + case destination(PresentationAction) + case path(StackAction) } var body: some ReducerOf { @@ -29,22 +31,36 @@ struct MainScreenFeature { return .none case .participantTapped: - state.participant = ParticipantReducer.State( + state.destination = .participant(ParticipantImportFeature.State( keyShare: JSONKeyShare.empty - ) + )) return .none - case .importKeyShare(.presented(.delegate(.keyShareImported(let keyShare)))): + case .destination(.presented(.participant(.delegate(.keyShareImported(let keyShare))))): debugPrint(keyShare) return .none - case .importKeyShare: + case .destination: return .none - + case .path: + return .none + } } - .ifLet(\.$participant, action: \.importKeyShare) { - ParticipantReducer() +// .ifLet(\.$destination, action: \.destination) { +// Destination.participant(ParticipantReducer()) +// } + .forEach(\.path, action: \.path) { + ParticipantImportFeature() } } } + + +extension MainScreenFeature { + + @Reducer(state: .equatable) + enum Destination { + case participant(ParticipantImportFeature) + } +} diff --git a/Examples/FrostCompanion/FrostCompanion/ContentView.swift b/Examples/FrostCompanion/FrostCompanion/MainScreenView.swift similarity index 59% rename from Examples/FrostCompanion/FrostCompanion/ContentView.swift rename to Examples/FrostCompanion/FrostCompanion/MainScreenView.swift index 46fa0b4..d9b3da1 100644 --- a/Examples/FrostCompanion/FrostCompanion/ContentView.swift +++ b/Examples/FrostCompanion/FrostCompanion/MainScreenView.swift @@ -7,10 +7,10 @@ import SwiftUI import ComposableArchitecture -struct ContentView: View { - let store: StoreOf +struct MainScreenView: View { + @Bindable var store: StoreOf var body: some View { - NavigationStack { + NavigationStack(path: $store.scope(state: \.path, action:\.path)) { VStack { Image(systemName: "snow") .imageScale(.large) @@ -18,24 +18,31 @@ struct ContentView: View { Text("Who are you?") VStack { - Button("Participant") { - store.send(.participantTapped) + NavigationLink(state: ParticipantImportFeature.State(keyShare: .empty)){ + Text("Participant") +// Button("Participant") { +// store.send(.participantTapped) +// } } + Button("Coordinator") { store.send(.coordinatorTapped) } } } .padding() + } destination: { store in + ParticipantImportView(store: store) } + .navigationTitle("Hello, FROST! ❄️") } - + } #Preview { - ContentView( + MainScreenView( store: Store(initialState: MainScreenFeature.State()){ MainScreenFeature() } diff --git a/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantReducer.swift b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportFeature.swift similarity index 97% rename from Examples/FrostCompanion/FrostCompanion/Participant/ParticipantReducer.swift rename to Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportFeature.swift index a8c1f51..b93717e 100644 --- a/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantReducer.swift +++ b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportFeature.swift @@ -9,7 +9,7 @@ import Foundation import ComposableArchitecture @Reducer -struct ParticipantReducer { +struct ParticipantImportFeature { @ObservableState struct State: Equatable { var keyShare: JSONKeyShare = .empty diff --git a/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift index 1e233c9..d5f9bad 100644 --- a/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift +++ b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift @@ -9,7 +9,7 @@ import SwiftUI import ComposableArchitecture struct ParticipantImportView: View { - @Bindable var store: StoreOf + @Bindable var store: StoreOf var body: some View { Form { Text("Paste your key-package.json contents") @@ -45,7 +45,7 @@ struct ParticipantImportView: View { #Preview { ParticipantImportView(store: Store( - initialState: ParticipantReducer.State( + initialState: ParticipantImportFeature.State( keyShare: JSONKeyShare( raw: """ @@ -65,7 +65,7 @@ struct ParticipantImportView: View { ) ) ) { - ParticipantReducer() + ParticipantImportFeature() } ) .padding() diff --git a/Examples/FrostCompanion/FrostCompanionTests/FrostCompanionTests.swift b/Examples/FrostCompanion/FrostCompanionTests/FrostCompanionTests.swift index c4e7dc6..9bb45cf 100644 --- a/Examples/FrostCompanion/FrostCompanionTests/FrostCompanionTests.swift +++ b/Examples/FrostCompanion/FrostCompanionTests/FrostCompanionTests.swift @@ -6,28 +6,19 @@ // import XCTest - +import ComposableArchitecture +@testable import FrostCompanion final class FrostCompanionTests: XCTestCase { - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } + @MainActor + func testParticipantModifiesMainState() async { + let store = TestStore(initialState: MainScreenFeature.State()) { + MainScreenFeature() + } - func testPerformanceExample() throws { - // This is an example of a performance test case. - measure { - // Put the code you want to measure the time of here. + await store.send(.participantTapped) { + $0.destination = .participant(ParticipantImportFeature.State( + keyShare: JSONKeyShare.empty + )) } } } diff --git a/Examples/FrostCompanion/FrostCompanionTests/ParticipanFeatureTests.swift b/Examples/FrostCompanion/FrostCompanionTests/ParticipanFeatureTests.swift new file mode 100644 index 0000000..130f39f --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanionTests/ParticipanFeatureTests.swift @@ -0,0 +1,24 @@ +// +// ParticipanFeatureTests.swift +// FrostCompanionTests +// +// Created by Pacu in 2024. +// + + +import XCTest +import ComposableArchitecture +@testable import FrostCompanion + +final class ParticipanFeatureTests: XCTestCase { + @MainActor + func testTextFieldInputInvalidInputDoesNotChangeState() async { + let store = TestStore(initialState: ParticipantImportFeature.State()) { + ParticipantImportFeature() + } + + await store.send(.setKeyShare("Hello test")) { + $0.keyShare = JSONKeyShare.empty + } + } +} diff --git a/Package.swift b/Package.swift index cc9c365..6a852b9 100644 --- a/Package.swift +++ b/Package.swift @@ -26,7 +26,7 @@ let package = Package( .target(name: "FrostSwiftFFI") ], path: "FrostSwift/Sources/FrostSwift" ), - .binaryTarget(name: "RustFramework", url: "https://github.com/pacu/frost-uniffi-sdk/releases/download/0.0.6/RustFramework.xcframework.zip", checksum: "b84677041219487bca551d563ff2993164454e6d70fd74076ed1f6309a7c9d85"), + .binaryTarget(name: "RustFramework", path: "FrostSwift/RustFramework.xcframework.zip"), .target( name: "FrostSwiftFFI", dependencies: [ From 31b01a87e42e7ae255d2a6d6e053a6d2b1f78258 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Wed, 28 Aug 2024 09:36:00 -0300 Subject: [PATCH 3/5] Participant Detail --- .../FrostCompanion.xcodeproj/project.pbxproj | 8 ++++ .../FrostCompanion/Model/Model.swift | 17 +++++++++ .../ParticipantDetailFeature.swift | 29 ++++++++++++++ .../Participant/ParticipantDetailView.swift | 38 +++++++++++++++++++ .../Participant/ParticipantImportView.swift | 18 +-------- 5 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 Examples/FrostCompanion/FrostCompanion/Participant/ParticipantDetailFeature.swift create mode 100644 Examples/FrostCompanion/FrostCompanion/Participant/ParticipantDetailView.swift diff --git a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj index 25fccb7..05cc27c 100644 --- a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj +++ b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ 0DDAB7B42C7691D3009FA521 /* ParticipantImportFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7B32C7691D3009FA521 /* ParticipantImportFeature.swift */; }; 0DDAB7B82C769345009FA521 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7B72C769345009FA521 /* Model.swift */; }; 0DDAB7BB2C76A5A4009FA521 /* ParticipantImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7BA2C76A5A4009FA521 /* ParticipantImportView.swift */; }; + 0DFAD1382C7F4B8B00072EF7 /* ParticipantDetailFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD1372C7F4B8B00072EF7 /* ParticipantDetailFeature.swift */; }; + 0DFAD13A2C7F4CDB00072EF7 /* ParticipantDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD1392C7F4CDB00072EF7 /* ParticipantDetailView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -57,6 +59,8 @@ 0DDAB7B32C7691D3009FA521 /* ParticipantImportFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantImportFeature.swift; sourceTree = ""; }; 0DDAB7B72C769345009FA521 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; 0DDAB7BA2C76A5A4009FA521 /* ParticipantImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantImportView.swift; sourceTree = ""; }; + 0DFAD1372C7F4B8B00072EF7 /* ParticipantDetailFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantDetailFeature.swift; sourceTree = ""; }; + 0DFAD1392C7F4CDB00072EF7 /* ParticipantDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantDetailView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -152,6 +156,8 @@ children = ( 0DDAB7B32C7691D3009FA521 /* ParticipantImportFeature.swift */, 0DDAB7BA2C76A5A4009FA521 /* ParticipantImportView.swift */, + 0DFAD1372C7F4B8B00072EF7 /* ParticipantDetailFeature.swift */, + 0DFAD1392C7F4CDB00072EF7 /* ParticipantDetailView.swift */, ); path = Participant; sourceTree = ""; @@ -305,7 +311,9 @@ 0DDAB7B82C769345009FA521 /* Model.swift in Sources */, 0DDAB7B42C7691D3009FA521 /* ParticipantImportFeature.swift in Sources */, 0D50A3432C0E6ED200B921E1 /* MainScreenView.swift in Sources */, + 0DFAD13A2C7F4CDB00072EF7 /* ParticipantDetailView.swift in Sources */, 0D50A3412C0E6ED200B921E1 /* FrostCompanionApp.swift in Sources */, + 0DFAD1382C7F4B8B00072EF7 /* ParticipantDetailFeature.swift in Sources */, 0DDAB7B12C7683A6009FA521 /* MainScreenReducer.swift in Sources */, 0DDAB7BB2C76A5A4009FA521 /* ParticipantImportView.swift in Sources */, ); diff --git a/Examples/FrostCompanion/FrostCompanion/Model/Model.swift b/Examples/FrostCompanion/FrostCompanion/Model/Model.swift index 9b2556e..31ce15a 100644 --- a/Examples/FrostCompanion/FrostCompanion/Model/Model.swift +++ b/Examples/FrostCompanion/FrostCompanion/Model/Model.swift @@ -14,4 +14,21 @@ struct JSONKeyShare: Equatable { extension JSONKeyShare { static let empty = JSONKeyShare(raw: "") + static let mock = JSONKeyShare( + raw: + """ + { + "header": { + "version": 0, + "ciphersuite": "FROST(Pallas, BLAKE2b-512)" + }, + "identifier": "0100000000000000000000000000000000000000000000000000000000000000", + "signing_share": "b02e5a1e43a7f305177682574ac63c1a5f7f57db644c992635d09f699e56f41e", + "commitment": [ + "4141ac3d66ff87c4d14eb14f4262b69de15f7093dfd1f411a02ea70644f0d41f", + "2eb4cd3ace283ba6bb9058ff08d0561ff6d87057ecc87b0701123979291fb082" + ] + } + """ + ) } diff --git a/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantDetailFeature.swift b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantDetailFeature.swift new file mode 100644 index 0000000..c516724 --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantDetailFeature.swift @@ -0,0 +1,29 @@ +// +// ParticipantDetail.swift +// FrostCompanion +// +// Created by Pacu in 2024. +// + + +import Foundation +import ComposableArchitecture + +@Reducer +struct ParticipantDetailFeature { + @ObservableState + struct State: Equatable { + let keyShare: JSONKeyShare + } + enum Action { + + } + + var body: some ReducerOf { + Reduce { state, action in + switch action { + + } + } + } +} diff --git a/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantDetailView.swift b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantDetailView.swift new file mode 100644 index 0000000..e9b1297 --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantDetailView.swift @@ -0,0 +1,38 @@ +// +// ParticipantDetailView.swift +// FrostCompanion +// +// Created by Pacu in 2024. +// + + +import SwiftUI +import ComposableArchitecture +struct ParticipantDetailView: View { + + @Bindable var store: StoreOf + var body: some View { + Text(verbatim: store.keyShare.raw) + .navigationTitle("Participant Detail") + .toolbar { + ToolbarItem { + Button(action: /*@START_MENU_TOKEN@*/{}/*@END_MENU_TOKEN@*/, label: { + Image(systemName: "trash") + .foregroundColor(.red) + }) + } + } + } +} + +#Preview { + NavigationStack { + ParticipantDetailView(store: Store( + initialState: ParticipantDetailFeature.State( + keyShare: JSONKeyShare.mock + ) + ) { + ParticipantDetailFeature() + }) + } +} diff --git a/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift index d5f9bad..f93e436 100644 --- a/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift +++ b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift @@ -46,23 +46,7 @@ struct ParticipantImportView: View { #Preview { ParticipantImportView(store: Store( initialState: ParticipantImportFeature.State( - keyShare: JSONKeyShare( - raw: - """ - { - "header": { - "version": 0, - "ciphersuite": "FROST(Pallas, BLAKE2b-512)" - }, - "identifier": "0100000000000000000000000000000000000000000000000000000000000000", - "signing_share": "b02e5a1e43a7f305177682574ac63c1a5f7f57db644c992635d09f699e56f41e", - "commitment": [ - "4141ac3d66ff87c4d14eb14f4262b69de15f7093dfd1f411a02ea70644f0d41f", - "2eb4cd3ace283ba6bb9058ff08d0561ff6d87057ecc87b0701123979291fb082" - ] - } - """ - ) + keyShare: JSONKeyShare.mock ) ) { ParticipantImportFeature() From 1162f4166898ad546039a127d5e732267aa40093 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Wed, 28 Aug 2024 09:47:03 -0300 Subject: [PATCH 4/5] WIP - trusted dealer flow --- .../FrostCompanion.xcodeproj/project.pbxproj | 16 ++++++++++++ .../NewTrustedDealerSchemeFeature.swift | 18 +++++++++++++ .../Coordinator/TrustedDealerFeature.swift | 26 +++++++++++++++++++ .../FrostCompanion/Model/Model.swift | 7 +++++ 4 files changed, 67 insertions(+) create mode 100644 Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedDealerSchemeFeature.swift create mode 100644 Examples/FrostCompanion/FrostCompanion/Coordinator/TrustedDealerFeature.swift diff --git a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj index 05cc27c..bdd550a 100644 --- a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj +++ b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj @@ -23,6 +23,8 @@ 0DDAB7BB2C76A5A4009FA521 /* ParticipantImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7BA2C76A5A4009FA521 /* ParticipantImportView.swift */; }; 0DFAD1382C7F4B8B00072EF7 /* ParticipantDetailFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD1372C7F4B8B00072EF7 /* ParticipantDetailFeature.swift */; }; 0DFAD13A2C7F4CDB00072EF7 /* ParticipantDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD1392C7F4CDB00072EF7 /* ParticipantDetailView.swift */; }; + 0DFAD13D2C7F517200072EF7 /* TrustedDealerFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD13C2C7F517200072EF7 /* TrustedDealerFeature.swift */; }; + 0DFAD13F2C7F523900072EF7 /* NewTrustedDealerSchemeFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD13E2C7F523900072EF7 /* NewTrustedDealerSchemeFeature.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -61,6 +63,8 @@ 0DDAB7BA2C76A5A4009FA521 /* ParticipantImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantImportView.swift; sourceTree = ""; }; 0DFAD1372C7F4B8B00072EF7 /* ParticipantDetailFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantDetailFeature.swift; sourceTree = ""; }; 0DFAD1392C7F4CDB00072EF7 /* ParticipantDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantDetailView.swift; sourceTree = ""; }; + 0DFAD13C2C7F517200072EF7 /* TrustedDealerFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedDealerFeature.swift; sourceTree = ""; }; + 0DFAD13E2C7F523900072EF7 /* NewTrustedDealerSchemeFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTrustedDealerSchemeFeature.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -113,6 +117,7 @@ 0D50A33F2C0E6ED200B921E1 /* FrostCompanion */ = { isa = PBXGroup; children = ( + 0DFAD13B2C7F514300072EF7 /* Coordinator */, 0DDAB7B62C76931F009FA521 /* Model */, 0DDAB7B22C7689E8009FA521 /* Participant */, 0D50A3402C0E6ED200B921E1 /* FrostCompanionApp.swift */, @@ -170,6 +175,15 @@ path = Model; sourceTree = ""; }; + 0DFAD13B2C7F514300072EF7 /* Coordinator */ = { + isa = PBXGroup; + children = ( + 0DFAD13C2C7F517200072EF7 /* TrustedDealerFeature.swift */, + 0DFAD13E2C7F523900072EF7 /* NewTrustedDealerSchemeFeature.swift */, + ); + path = Coordinator; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -315,7 +329,9 @@ 0D50A3412C0E6ED200B921E1 /* FrostCompanionApp.swift in Sources */, 0DFAD1382C7F4B8B00072EF7 /* ParticipantDetailFeature.swift in Sources */, 0DDAB7B12C7683A6009FA521 /* MainScreenReducer.swift in Sources */, + 0DFAD13D2C7F517200072EF7 /* TrustedDealerFeature.swift in Sources */, 0DDAB7BB2C76A5A4009FA521 /* ParticipantImportView.swift in Sources */, + 0DFAD13F2C7F523900072EF7 /* NewTrustedDealerSchemeFeature.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedDealerSchemeFeature.swift b/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedDealerSchemeFeature.swift new file mode 100644 index 0000000..6ee8a2d --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedDealerSchemeFeature.swift @@ -0,0 +1,18 @@ +// +// NewTrustedDealerSchemeFeature.swift +// FrostCompanion +// +// Created by Pacu in 2024. +// + + +import Foundation +import ComposableArchitecture + +@Reducer +struct NewTrustedDealerSchemeFeature { + @ObservableState + struct State { + + } +} diff --git a/Examples/FrostCompanion/FrostCompanion/Coordinator/TrustedDealerFeature.swift b/Examples/FrostCompanion/FrostCompanion/Coordinator/TrustedDealerFeature.swift new file mode 100644 index 0000000..0bc42e5 --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/Coordinator/TrustedDealerFeature.swift @@ -0,0 +1,26 @@ +// +// TrustedDealerFeature.swift +// FrostCompanion +// +// Created by Pacu in 2024. +// + + +import Foundation +import ComposableArchitecture + +@Reducer +struct TrustedDealerFeature { + @ObservableState + struct State { + var maxParticipants: UInt = 3 + var minParticipants: UInt = 2 + var path: StackState + } + + enum Action { + case createScheme + case setMaxParticipants(Int) + case setMinParticipants(Int) + } +} diff --git a/Examples/FrostCompanion/FrostCompanion/Model/Model.swift b/Examples/FrostCompanion/FrostCompanion/Model/Model.swift index 31ce15a..907c88d 100644 --- a/Examples/FrostCompanion/FrostCompanion/Model/Model.swift +++ b/Examples/FrostCompanion/FrostCompanion/Model/Model.swift @@ -32,3 +32,10 @@ extension JSONKeyShare { """ ) } + + +struct FROSTScheme { + let maxParticipants: Int + let minParticipants: Int + let keyShares: [String : JSONKeyShare] +} From 96a3ce2c633c87e099915b1b29d7baada4615874 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Wed, 4 Sep 2024 15:50:11 -0300 Subject: [PATCH 5/5] WIP - coordinator screens --- .../FrostCompanion.xcodeproj/project.pbxproj | 36 ++++++ .../FrostCompanion/AppFeature.swift | 47 +++++++ .../FrostCompanion/AppView.swift | 44 +++++++ .../NewTrustedDealerSchemeFeature.swift | 10 +- .../NewTrustedDealerSchemeView.swift | 44 +++++++ .../Coordinator/NewTrustedScheme.swift | 48 +++++++ .../Coordinator/TrustedDealerFeature.swift | 33 +++-- .../FrostCompanion/FrostCompanionApp.swift | 22 +++- .../FrostCompanion/MainScreenReducer.swift | 39 +----- .../FrostCompanion/MainScreenView.swift | 14 +- .../FrostCompanion/Model/Errors.swift | 13 ++ .../FrostCompanion/Model/Model.swift | 120 +++++++++++++++++- .../Participant/ParticipantImportView.swift | 11 -- .../Public/PublicKeyPackageDetail.swift | 37 ++++++ .../Public/PublicKeyPackageFeature.swift | 20 +++ 15 files changed, 467 insertions(+), 71 deletions(-) create mode 100644 Examples/FrostCompanion/FrostCompanion/AppFeature.swift create mode 100644 Examples/FrostCompanion/FrostCompanion/AppView.swift create mode 100644 Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedDealerSchemeView.swift create mode 100644 Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedScheme.swift create mode 100644 Examples/FrostCompanion/FrostCompanion/Model/Errors.swift create mode 100644 Examples/FrostCompanion/FrostCompanion/Public/PublicKeyPackageDetail.swift create mode 100644 Examples/FrostCompanion/FrostCompanion/Public/PublicKeyPackageFeature.swift diff --git a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj index bdd550a..212bc61 100644 --- a/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj +++ b/Examples/FrostCompanion/FrostCompanion.xcodeproj/project.pbxproj @@ -15,6 +15,9 @@ 0D50A35D2C0E6ED300B921E1 /* FrostCompanionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D50A35C2C0E6ED300B921E1 /* FrostCompanionUITests.swift */; }; 0D50A35F2C0E6ED300B921E1 /* FrostCompanionUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D50A35E2C0E6ED300B921E1 /* FrostCompanionUITestsLaunchTests.swift */; }; 0D50A36D2C0E701500B921E1 /* FrostSwiftFFI in Frameworks */ = {isa = PBXBuildFile; productRef = 0D50A36C2C0E701500B921E1 /* FrostSwiftFFI */; }; + 0D5110762C80B3AA006E55DE /* AppFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D5110752C80B3AA006E55DE /* AppFeature.swift */; }; + 0D5110782C80B3E3006E55DE /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D5110772C80B3E3006E55DE /* AppView.swift */; }; + 0D51107A2C810907006E55DE /* NewTrustedDealerSchemeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D5110792C810907006E55DE /* NewTrustedDealerSchemeView.swift */; }; 0DB77E062C78F9ED0011DDCB /* ParticipanFeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB77E052C78F9ED0011DDCB /* ParticipanFeatureTests.swift */; }; 0DDAB7AF2C768320009FA521 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 0DDAB7AE2C768320009FA521 /* ComposableArchitecture */; }; 0DDAB7B12C7683A6009FA521 /* MainScreenReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAB7B02C7683A6009FA521 /* MainScreenReducer.swift */; }; @@ -25,6 +28,10 @@ 0DFAD13A2C7F4CDB00072EF7 /* ParticipantDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD1392C7F4CDB00072EF7 /* ParticipantDetailView.swift */; }; 0DFAD13D2C7F517200072EF7 /* TrustedDealerFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD13C2C7F517200072EF7 /* TrustedDealerFeature.swift */; }; 0DFAD13F2C7F523900072EF7 /* NewTrustedDealerSchemeFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD13E2C7F523900072EF7 /* NewTrustedDealerSchemeFeature.swift */; }; + 0DFAD1412C7FB74900072EF7 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD1402C7FB74900072EF7 /* Errors.swift */; }; + 0DFAD1432C7FBA7700072EF7 /* NewTrustedScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD1422C7FBA7700072EF7 /* NewTrustedScheme.swift */; }; + 0DFAD1462C7FD72600072EF7 /* PublicKeyPackageFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD1452C7FD72600072EF7 /* PublicKeyPackageFeature.swift */; }; + 0DFAD1482C7FD78800072EF7 /* PublicKeyPackageDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFAD1472C7FD78800072EF7 /* PublicKeyPackageDetail.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -56,6 +63,9 @@ 0D50A3582C0E6ED300B921E1 /* FrostCompanionUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FrostCompanionUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 0D50A35C2C0E6ED300B921E1 /* FrostCompanionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrostCompanionUITests.swift; sourceTree = ""; }; 0D50A35E2C0E6ED300B921E1 /* FrostCompanionUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrostCompanionUITestsLaunchTests.swift; sourceTree = ""; }; + 0D5110752C80B3AA006E55DE /* AppFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFeature.swift; sourceTree = ""; }; + 0D5110772C80B3E3006E55DE /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; }; + 0D5110792C810907006E55DE /* NewTrustedDealerSchemeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTrustedDealerSchemeView.swift; sourceTree = ""; }; 0DB77E052C78F9ED0011DDCB /* ParticipanFeatureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipanFeatureTests.swift; sourceTree = ""; }; 0DDAB7B02C7683A6009FA521 /* MainScreenReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenReducer.swift; sourceTree = ""; }; 0DDAB7B32C7691D3009FA521 /* ParticipantImportFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantImportFeature.swift; sourceTree = ""; }; @@ -65,6 +75,10 @@ 0DFAD1392C7F4CDB00072EF7 /* ParticipantDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantDetailView.swift; sourceTree = ""; }; 0DFAD13C2C7F517200072EF7 /* TrustedDealerFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedDealerFeature.swift; sourceTree = ""; }; 0DFAD13E2C7F523900072EF7 /* NewTrustedDealerSchemeFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTrustedDealerSchemeFeature.swift; sourceTree = ""; }; + 0DFAD1402C7FB74900072EF7 /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 0DFAD1422C7FBA7700072EF7 /* NewTrustedScheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTrustedScheme.swift; sourceTree = ""; }; + 0DFAD1452C7FD72600072EF7 /* PublicKeyPackageFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicKeyPackageFeature.swift; sourceTree = ""; }; + 0DFAD1472C7FD78800072EF7 /* PublicKeyPackageDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicKeyPackageDetail.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -117,6 +131,7 @@ 0D50A33F2C0E6ED200B921E1 /* FrostCompanion */ = { isa = PBXGroup; children = ( + 0DFAD1442C7FD70900072EF7 /* Public */, 0DFAD13B2C7F514300072EF7 /* Coordinator */, 0DDAB7B62C76931F009FA521 /* Model */, 0DDAB7B22C7689E8009FA521 /* Participant */, @@ -126,6 +141,8 @@ 0D50A3462C0E6ED300B921E1 /* FrostCompanion.entitlements */, 0D50A3472C0E6ED300B921E1 /* Preview Content */, 0DDAB7B02C7683A6009FA521 /* MainScreenReducer.swift */, + 0D5110752C80B3AA006E55DE /* AppFeature.swift */, + 0D5110772C80B3E3006E55DE /* AppView.swift */, ); path = FrostCompanion; sourceTree = ""; @@ -171,6 +188,7 @@ isa = PBXGroup; children = ( 0DDAB7B72C769345009FA521 /* Model.swift */, + 0DFAD1402C7FB74900072EF7 /* Errors.swift */, ); path = Model; sourceTree = ""; @@ -180,10 +198,21 @@ children = ( 0DFAD13C2C7F517200072EF7 /* TrustedDealerFeature.swift */, 0DFAD13E2C7F523900072EF7 /* NewTrustedDealerSchemeFeature.swift */, + 0DFAD1422C7FBA7700072EF7 /* NewTrustedScheme.swift */, + 0D5110792C810907006E55DE /* NewTrustedDealerSchemeView.swift */, ); path = Coordinator; sourceTree = ""; }; + 0DFAD1442C7FD70900072EF7 /* Public */ = { + isa = PBXGroup; + children = ( + 0DFAD1452C7FD72600072EF7 /* PublicKeyPackageFeature.swift */, + 0DFAD1472C7FD78800072EF7 /* PublicKeyPackageDetail.swift */, + ); + path = Public; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -322,13 +351,20 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0DFAD1482C7FD78800072EF7 /* PublicKeyPackageDetail.swift in Sources */, 0DDAB7B82C769345009FA521 /* Model.swift in Sources */, 0DDAB7B42C7691D3009FA521 /* ParticipantImportFeature.swift in Sources */, 0D50A3432C0E6ED200B921E1 /* MainScreenView.swift in Sources */, + 0D5110782C80B3E3006E55DE /* AppView.swift in Sources */, + 0D51107A2C810907006E55DE /* NewTrustedDealerSchemeView.swift in Sources */, 0DFAD13A2C7F4CDB00072EF7 /* ParticipantDetailView.swift in Sources */, 0D50A3412C0E6ED200B921E1 /* FrostCompanionApp.swift in Sources */, + 0D5110762C80B3AA006E55DE /* AppFeature.swift in Sources */, 0DFAD1382C7F4B8B00072EF7 /* ParticipantDetailFeature.swift in Sources */, + 0DFAD1462C7FD72600072EF7 /* PublicKeyPackageFeature.swift in Sources */, 0DDAB7B12C7683A6009FA521 /* MainScreenReducer.swift in Sources */, + 0DFAD1432C7FBA7700072EF7 /* NewTrustedScheme.swift in Sources */, + 0DFAD1412C7FB74900072EF7 /* Errors.swift in Sources */, 0DFAD13D2C7F517200072EF7 /* TrustedDealerFeature.swift in Sources */, 0DDAB7BB2C76A5A4009FA521 /* ParticipantImportView.swift in Sources */, 0DFAD13F2C7F523900072EF7 /* NewTrustedDealerSchemeFeature.swift in Sources */, diff --git a/Examples/FrostCompanion/FrostCompanion/AppFeature.swift b/Examples/FrostCompanion/FrostCompanion/AppFeature.swift new file mode 100644 index 0000000..ad44744 --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/AppFeature.swift @@ -0,0 +1,47 @@ +// +// AppFeature.swift +// FrostCompanion +// +// Created by Pacu in 2024. +// + + +import Foundation +import ComposableArchitecture +@Reducer +struct AppFeature { + @ObservableState + struct State: Equatable { + var path = StackState() + var mainScreen = MainScreenFeature.State() + } + + enum Action { + case path(StackActionOf) + case mainScreen(MainScreenFeature.Action) + } + + @Reducer(state: .equatable) + enum Path { + case importParticipant(ParticipantImportFeature) + case coordinator(TrustedDealerFeature) + case newTrustedDealerScheme(NewTrustedScheme) + } + + var body: some ReducerOf { + Scope(state: \.mainScreen, action: \.mainScreen) { + MainScreenFeature() + } + Reduce { state, action in + switch action { +// case .path(.element(_, .coordinator(.))) + case .path: + return .none + + case .mainScreen: + return .none + } + } + .forEach(\.path, action: \.path) + } +} diff --git a/Examples/FrostCompanion/FrostCompanion/AppView.swift b/Examples/FrostCompanion/FrostCompanion/AppView.swift new file mode 100644 index 0000000..b5c8c18 --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/AppView.swift @@ -0,0 +1,44 @@ +// +// AppView.swift +// FrostCompanion +// +// Created by Pacu in 2024. +// + + +import SwiftUI +import ComposableArchitecture +struct AppView: View { + @Bindable var store: StoreOf + var body: some View { + NavigationStack( + path: $store.scope(state: \.path, action: \.path) + ) { + MainScreenView( + store: + store.scope( + state: \.mainScreen, + action: \.mainScreen + ) + ) + } destination: { store in + switch store.case { + case let .importParticipant(importStore): + ParticipantImportView(store: importStore) + case let .coordinator(trustedDealerStore): + NewTrustedDealerSchemeView(store: trustedDealerStore) + case let .newTrustedDealerScheme(newTrustedDealerScheme): + NewTrustedDealerSchemeFeature() + } + } + } +} + +#Preview { + AppView(store: Store( + initialState: AppFeature.State() + ){ + AppFeature() + } + ) +} diff --git a/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedDealerSchemeFeature.swift b/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedDealerSchemeFeature.swift index 6ee8a2d..4982cf4 100644 --- a/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedDealerSchemeFeature.swift +++ b/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedDealerSchemeFeature.swift @@ -13,6 +13,14 @@ import ComposableArchitecture struct NewTrustedDealerSchemeFeature { @ObservableState struct State { - + let schemeConfig: FROSTSchemeConfig + var scheme: TrustedDealerScheme? +// var path: StackState< + } + + enum Action { + case dealerSucceeded(TrustedDealerScheme) + case dealerFailed(AppErrors) } } + diff --git a/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedDealerSchemeView.swift b/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedDealerSchemeView.swift new file mode 100644 index 0000000..a058b6b --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedDealerSchemeView.swift @@ -0,0 +1,44 @@ +// +// NewTrustedDealerSchemeView.swift +// FrostCompanion +// +// Created by Pacu in 2024. +// + + +import SwiftUI +import ComposableArchitecture + +struct NewTrustedDealerSchemeView: View { + static let intFormater: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.allowsFloats = false + formatter.maximumFractionDigits = 0 + return formatter + }() + + @Bindable var store: StoreOf + @FocusState var focus: TrustedDealerFeature.State.Field? + var body: some View { + Form { + Section { + TextField("Min Participants", value: $store.minParticipants, format: .number) + .focused($focus, equals: .minParticipants) + TextField("Max Participants", value: $store.maxParticipants, formatter: Self.intFormater) + .focused($focus, equals: .minParticipants) + Button("create scheme") { + store.send(.createSchemePressed) + } + } + .bind($store.focus, to: $focus) + } + .padding() + } +} + +#Preview { + NewTrustedDealerSchemeView(store: Store(initialState: TrustedDealerFeature.State(), reducer: { + TrustedDealerFeature() + })) +} diff --git a/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedScheme.swift b/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedScheme.swift new file mode 100644 index 0000000..7985fb0 --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/Coordinator/NewTrustedScheme.swift @@ -0,0 +1,48 @@ +// +// NewTrustedScheme.swift +// FrostCompanion +// +// Created by Pacu in 2024. +// + + +import SwiftUI +import ComposableArchitecture +struct NewTrustedScheme: View { + @Bindable var store: StoreOf + var body: some View { + + if let scheme = store.scheme { + List { + ForEach(scheme.shares.keys.sorted(), id: \.self) { identifier in + NavigationLink(state: ParticipantDetailFeature.State(keyShare: scheme.shares[identifier]!)){ + Text(identifier) + } + + } + NavigationLink(state: PublicKeyPackageFeature.State(package: scheme.publicKeyPackage)) { + + Text("Public Key Package") + } + + } + .navigationTitle("Participants") + } else { + Text("❄️ FROSTING... ❄️") + } + + + } +} + +#Preview { + NewTrustedScheme( + store: Store(initialState: NewTrustedDealerSchemeFeature.State( + schemeConfig: FROSTSchemeConfig.twoOfThree, + scheme: TrustedDealerScheme.mock + ), + reducer: { + NewTrustedDealerSchemeFeature() + }) + ) +} diff --git a/Examples/FrostCompanion/FrostCompanion/Coordinator/TrustedDealerFeature.swift b/Examples/FrostCompanion/FrostCompanion/Coordinator/TrustedDealerFeature.swift index 0bc42e5..93f5f3d 100644 --- a/Examples/FrostCompanion/FrostCompanion/Coordinator/TrustedDealerFeature.swift +++ b/Examples/FrostCompanion/FrostCompanion/Coordinator/TrustedDealerFeature.swift @@ -12,15 +12,32 @@ import ComposableArchitecture @Reducer struct TrustedDealerFeature { @ObservableState - struct State { - var maxParticipants: UInt = 3 - var minParticipants: UInt = 2 - var path: StackState + struct State: Equatable { + var maxParticipants: Int = 3 + var minParticipants: Int = 2 + + var focus: Field? = .minParticipants + + enum Field: Hashable { + case minParticipants + case maxParticipants + } + } + + enum Action: BindableAction, Equatable { + case binding(BindingAction) + case createSchemePressed } - enum Action { - case createScheme - case setMaxParticipants(Int) - case setMinParticipants(Int) + var body: some ReducerOf { + BindingReducer() + Reduce { state, action in + switch action { + case .binding: + return .none + case .createSchemePressed: + return .none + } + } } } diff --git a/Examples/FrostCompanion/FrostCompanion/FrostCompanionApp.swift b/Examples/FrostCompanion/FrostCompanion/FrostCompanionApp.swift index cdc99f1..60f05a5 100644 --- a/Examples/FrostCompanion/FrostCompanion/FrostCompanionApp.swift +++ b/Examples/FrostCompanion/FrostCompanion/FrostCompanionApp.swift @@ -9,13 +9,25 @@ import SwiftUI import ComposableArchitecture @main struct FrostCompanionApp: App { + // NB: This is static to avoid interference with Xcode previews, which create this entry + // point each time they are run. + @MainActor + static let store = Store(initialState: AppFeature.State()) { + AppFeature() + ._printChanges() + } withDependencies: { + if ProcessInfo.processInfo.environment["UITesting"] == "true" { + $0.defaultFileStorage = .inMemory + } + } var body: some Scene { WindowGroup { - MainScreenView( - store: Store(initialState: MainScreenFeature.State()){ - MainScreenFeature() - } - ) + if _XCTIsTesting { + // NB: Don't run application in tests to avoid interference between the app and the test. + EmptyView() + } else { + AppView(store: Self.store) + } } } } diff --git a/Examples/FrostCompanion/FrostCompanion/MainScreenReducer.swift b/Examples/FrostCompanion/FrostCompanion/MainScreenReducer.swift index 2b246db..67fc7d2 100644 --- a/Examples/FrostCompanion/FrostCompanion/MainScreenReducer.swift +++ b/Examples/FrostCompanion/FrostCompanion/MainScreenReducer.swift @@ -11,49 +11,24 @@ import ComposableArchitecture @Reducer struct MainScreenFeature { + + @ObservableState struct State: Equatable { @Presents var destination: Destination.State? - var path = StackState() + } enum Action { - case coordinatorTapped - case participantTapped - case destination(PresentationAction) - case path(StackAction) } - var body: some ReducerOf { - Reduce { state, action in - switch action { - case .coordinatorTapped: - return .none - - case .participantTapped: - state.destination = .participant(ParticipantImportFeature.State( - keyShare: JSONKeyShare.empty - )) - return .none - - case .destination(.presented(.participant(.delegate(.keyShareImported(let keyShare))))): - debugPrint(keyShare) - return .none - - case .destination: - return .none - case .path: - return .none - - } - } // .ifLet(\.$destination, action: \.destination) { // Destination.participant(ParticipantReducer()) // } - .forEach(\.path, action: \.path) { - ParticipantImportFeature() - } - } +// .forEach(\.path, action: \.path) { +// Path() +// } + } diff --git a/Examples/FrostCompanion/FrostCompanion/MainScreenView.swift b/Examples/FrostCompanion/FrostCompanion/MainScreenView.swift index d9b3da1..31c7851 100644 --- a/Examples/FrostCompanion/FrostCompanion/MainScreenView.swift +++ b/Examples/FrostCompanion/FrostCompanion/MainScreenView.swift @@ -10,7 +10,6 @@ import ComposableArchitecture struct MainScreenView: View { @Bindable var store: StoreOf var body: some View { - NavigationStack(path: $store.scope(state: \.path, action:\.path)) { VStack { Image(systemName: "snow") .imageScale(.large) @@ -18,23 +17,16 @@ struct MainScreenView: View { Text("Who are you?") VStack { - NavigationLink(state: ParticipantImportFeature.State(keyShare: .empty)){ + NavigationLink(state: AppFeature.Path.State.importParticipant(.init())){ Text("Participant") -// Button("Participant") { -// store.send(.participantTapped) -// } } - - Button("Coordinator") { - store.send(.coordinatorTapped) + NavigationLink(state: AppFeature.Path.State.coordinator(.init())) { + Text("Coordinator") } } } .padding() - } destination: { store in - ParticipantImportView(store: store) - } .navigationTitle("Hello, FROST! ❄️") } diff --git a/Examples/FrostCompanion/FrostCompanion/Model/Errors.swift b/Examples/FrostCompanion/FrostCompanion/Model/Errors.swift new file mode 100644 index 0000000..73a29bf --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/Model/Errors.swift @@ -0,0 +1,13 @@ +// +// Errors.swift +// FrostCompanion +// +// Created by Pacu in 2024. +// + + +import Foundation + +enum AppErrors: Error { + case invalidConfiguration +} diff --git a/Examples/FrostCompanion/FrostCompanion/Model/Model.swift b/Examples/FrostCompanion/FrostCompanion/Model/Model.swift index 907c88d..ebe36f3 100644 --- a/Examples/FrostCompanion/FrostCompanion/Model/Model.swift +++ b/Examples/FrostCompanion/FrostCompanion/Model/Model.swift @@ -7,13 +7,24 @@ import Foundation +struct TrustedDealerScheme { + let config: FROSTSchemeConfig + let shares: [String : JSONKeyShare] + let publicKeyPackage: JSONPublicKeyPackage +} + +struct JSONPublicKeyPackage: Equatable { + let raw: String +} struct JSONKeyShare: Equatable { let raw: String } extension JSONKeyShare { - static let empty = JSONKeyShare(raw: "") + static let empty = JSONKeyShare( + raw: "" + ) static let mock = JSONKeyShare( raw: """ @@ -34,8 +45,111 @@ extension JSONKeyShare { } -struct FROSTScheme { +struct FROSTSchemeConfig { let maxParticipants: Int let minParticipants: Int - let keyShares: [String : JSONKeyShare] + + fileprivate init( + uncheckedMax: Int, + uncheckedMin: Int + ) { + self.maxParticipants = uncheckedMax + self.minParticipants = uncheckedMin + } + + init( + maxParticipants: Int, + minParticipants: Int + ) throws { + guard maxParticipants > minParticipants, minParticipants > 2 else { + throw AppErrors.invalidConfiguration + } + self.maxParticipants = maxParticipants + self.minParticipants = minParticipants + } +} + + +extension FROSTSchemeConfig { + static let twoOfThree = FROSTSchemeConfig( + uncheckedMax: 3, + uncheckedMin: 2 + ) +} + + +extension JSONPublicKeyPackage { + static let mock = JSONPublicKeyPackage(raw: """ + { + "header": { + "version": 0, + "ciphersuite": "FROST(Pallas, BLAKE2b-512)" + }, + "verifying_shares": { + "0100000000000000000000000000000000000000000000000000000000000000": "35a83b6ce26fd8812e7e100ff0f9557dd873080478272770745d35c2c1698fad", + "0200000000000000000000000000000000000000000000000000000000000000": "a2e91b0636456d78b4830f4c926d07638dcfa41083071ab94b87dd8d9ea22f26", + "0300000000000000000000000000000000000000000000000000000000000000": "cdca48566dd4dc57a9cd546b1ad64212eb3d53eba9c852c1a1d6f661d08d67b2" + }, + "verifying_key": "4141ac3d66ff87c4d14eb14f4262b69de15f7093dfd1f411a02ea70644f0d41f" + } + """) +} + +extension TrustedDealerScheme { + static let mock = TrustedDealerScheme( + config: .twoOfThree, + shares: Self.mockShares, + publicKeyPackage: JSONPublicKeyPackage.mock + ) + + static let mockShares = [ + "0100000000000000000000000000000000000000000000000000000000000000" : JSONKeyShare( + raw: """ + { + "header": { + "version": 0, + "ciphersuite": "FROST(Pallas, BLAKE2b-512)" + }, + "identifier": "0100000000000000000000000000000000000000000000000000000000000000", + "signing_share": "b02e5a1e43a7f305177682574ac63c1a5f7f57db644c992635d09f699e56f41e", + "commitment": [ + "4141ac3d66ff87c4d14eb14f4262b69de15f7093dfd1f411a02ea70644f0d41f", + "2eb4cd3ace283ba6bb9058ff08d0561ff6d87057ecc87b0701123979291fb082" + ] + } + """ + ), + "0200000000000000000000000000000000000000000000000000000000000000" : JSONKeyShare( + raw: """ + { + "header": { + "version": 0, + "ciphersuite": "FROST(Pallas, BLAKE2b-512)" + }, + "identifier": "0200000000000000000000000000000000000000000000000000000000000000", + "signing_share": "850da1fd0b6f609b60a0f81d0aa79986e46cee490f837a5ccf349ac9b904790b", + "commitment": [ + "4141ac3d66ff87c4d14eb14f4262b69de15f7093dfd1f411a02ea70644f0d41f", + "2eb4cd3ace283ba6bb9058ff08d0561ff6d87057ecc87b0701123979291fb082" + ] + } + """ + ), + "0300000000000000000000000000000000000000000000000000000000000000" : JSONKeyShare( + raw: """ + { + "header": { + "version": 0, + "ciphersuite": "FROST(Pallas, BLAKE2b-512)" + }, + "identifier": "", + "signing_share": "5bece7dcf52114bd877303eec5203d156a5a85b8b9b95b9269999429d5b2fd37", + "commitment": [ + "4141ac3d66ff87c4d14eb14f4262b69de15f7093dfd1f411a02ea70644f0d41f", + "2eb4cd3ace283ba6bb9058ff08d0561ff6d87057ecc87b0701123979291fb082" + ] + } + """ + ) + ] } diff --git a/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift index f93e436..bf65b21 100644 --- a/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift +++ b/Examples/FrostCompanion/FrostCompanion/Participant/ParticipantImportView.swift @@ -26,17 +26,6 @@ struct ParticipantImportView: View { ) } } - .toolbar { - ToolbarItem { - Button( - "Cancel" - ) { - store.send( - .cancelButtonTapped - ) - } - } - } .navigationTitle( "Import your Key-package JSON" ) diff --git a/Examples/FrostCompanion/FrostCompanion/Public/PublicKeyPackageDetail.swift b/Examples/FrostCompanion/FrostCompanion/Public/PublicKeyPackageDetail.swift new file mode 100644 index 0000000..6ab49ed --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/Public/PublicKeyPackageDetail.swift @@ -0,0 +1,37 @@ +// +// PublicKeyPackageDetail.swift +// FrostCompanion +// +// Created by Pacu in 2024. +// + + +import SwiftUI +import ComposableArchitecture +struct PublicKeyPackageDetail: View { + let store: StoreOf + var body: some View { + Text(store.package.raw) + .multilineTextAlignment(/*@START_MENU_TOKEN@*/.leading/*@END_MENU_TOKEN@*/) + .padding() + .navigationTitle("Public Key Package") + .toolbar { + Button(action: /*@START_MENU_TOKEN@*/{}/*@END_MENU_TOKEN@*/, label: { + Image(systemName: "square.and.arrow.up") + }) + } + } +} + +#Preview { + NavigationStack { + PublicKeyPackageDetail( + store: Store(initialState: PublicKeyPackageFeature.State( + package: .mock + ), + reducer: { + PublicKeyPackageFeature() + }) + ) + } +} diff --git a/Examples/FrostCompanion/FrostCompanion/Public/PublicKeyPackageFeature.swift b/Examples/FrostCompanion/FrostCompanion/Public/PublicKeyPackageFeature.swift new file mode 100644 index 0000000..c5e372e --- /dev/null +++ b/Examples/FrostCompanion/FrostCompanion/Public/PublicKeyPackageFeature.swift @@ -0,0 +1,20 @@ +// +// PublicKeyPackageFeature.swift +// FrostCompanion +// +// Created by Pacu in 2024. +// + + +import Foundation +import ComposableArchitecture + +@Reducer +struct PublicKeyPackageFeature { + @ObservableState + struct State { + let package: JSONPublicKeyPackage + } + +} +