From 00c903bc3a13b8ac8fd52d3f0d920fce7dace274 Mon Sep 17 00:00:00 2001 From: dankinsoid <30962149+dankinsoid@users.noreply.github.com> Date: Sun, 17 Mar 2024 15:16:49 +0300 Subject: [PATCH] 0.28.0 --- Examples/SyncUps/SyncUps/AppFeature.swift | 8 +- .../SyncUps/Dependencies/OpenSettings.swift | 4 +- .../Dependencies/SpeechRecognizer.swift | 2 +- Examples/SyncUps/SyncUps/RecordMeeting.swift | 20 ++-- Examples/SyncUps/SyncUps/SyncUpDetail.swift | 6 +- Examples/SyncUps/SyncUps/SyncUpForm.swift | 6 +- Examples/TicTacToe/App/RootView.swift | 52 ++++----- .../UserInterfaceState.xcuserstate | Bin 72451 -> 73296 bytes Examples/TicTacToe/tic-tac-toe/Package.swift | 18 ++-- .../tic-tac-toe/Sources/AppCore/AppCore.swift | 20 ++-- .../Sources/AppSwiftUI/AppView.swift | 26 ++--- .../Sources/AppUIKit/AppViewController.swift | 34 +++--- .../AuthenticationClient.swift | 69 ++++++------ .../Sources/GameCore/GameCore.swift | 82 +++++++-------- .../Sources/GameSwiftUI/GameView.swift | 4 +- .../GameUIKit/GameViewController.swift | 38 +++---- .../Sources/LoginCore/LoginCore.swift | 64 +++++------ .../Sources/LoginSwiftUI/LoginView.swift | 24 ++--- .../LoginUIKit/LoginViewController.swift | 99 +++++++++--------- .../Sources/NewGameCore/NewGameCore.swift | 55 +++++----- .../Sources/NewGameSwiftUI/NewGameView.swift | 26 ++--- .../NewGameUIKit/NewGameViewController.swift | 50 ++++----- .../Sources/TwoFactorCore/TwoFactorCore.swift | 80 +++++++------- .../TwoFactorSwiftUI/TwoFactorView.swift | 36 +++---- .../TwoFactorViewController.swift | 54 +++++----- .../Tests/AppCoreTests/AppCoreTests.swift | 2 +- .../Tests/GameCoreTests/GameCoreTests.swift | 2 +- .../Tests/LoginCoreTests/LoginCoreTests.swift | 2 +- .../NewGameCoreTests/NewGameCoreTests.swift | 2 +- .../TwoFactorCoreTests.swift | 2 +- README.md | 2 +- Sources/VDStore/Dependencies/Clock.swift | 42 ++++---- Sources/VDStore/Dependencies/Date.swift | 74 ++++++------- Sources/VDStore/Dependencies/Dismiss.swift | 90 ++++++++-------- Sources/VDStore/Store.swift | 4 +- Sources/VDStore/Utils/DIPublisher.swift | 2 +- Sources/VDStore/ViewStore.swift | 4 +- 37 files changed, 552 insertions(+), 553 deletions(-) diff --git a/Examples/SyncUps/SyncUps/AppFeature.swift b/Examples/SyncUps/SyncUps/AppFeature.swift index e555370..58e48ba 100644 --- a/Examples/SyncUps/SyncUps/AppFeature.swift +++ b/Examples/SyncUps/SyncUps/AppFeature.swift @@ -51,7 +51,7 @@ extension Store: RecordMeetingDelegate { func debounceSave(syncUps: [SyncUp]) async throws { cancel(Self.debounceSave) - try await di.continuousClock.sleep(for: .seconds(1)) + try await di.continuousClock.sleep(for: .seconds(1)) try await di.dataManager.save(JSONEncoder().encode(syncUps), .syncUps) } } @@ -81,8 +81,8 @@ struct AppView: View { var body: some View { NavigationSteps( - selection: $state.binding.path.selected - ) { + selection: $state.binding.path.selected + ) { listView detailView @@ -93,7 +93,7 @@ struct AppView: View { meetingView } } - .stepEnvironment($state.binding.path) + .stepEnvironment($state.binding.path) } private var listView: some View { diff --git a/Examples/SyncUps/SyncUps/Dependencies/OpenSettings.swift b/Examples/SyncUps/SyncUps/Dependencies/OpenSettings.swift index 6c7c691..93077e2 100644 --- a/Examples/SyncUps/SyncUps/Dependencies/OpenSettings.swift +++ b/Examples/SyncUps/SyncUps/Dependencies/OpenSettings.swift @@ -3,8 +3,8 @@ import VDStore extension StoreDIValues { - @StoreDIValue - var openSettings: @Sendable () async -> Void = Self.openSettings + @StoreDIValue + var openSettings: @Sendable () async -> Void = Self.openSettings private static let openSettings: @Sendable () async -> Void = { await MainActor.run { diff --git a/Examples/SyncUps/SyncUps/Dependencies/SpeechRecognizer.swift b/Examples/SyncUps/SyncUps/Dependencies/SpeechRecognizer.swift index d53f8c6..8659af6 100644 --- a/Examples/SyncUps/SyncUps/Dependencies/SpeechRecognizer.swift +++ b/Examples/SyncUps/SyncUps/Dependencies/SpeechRecognizer.swift @@ -152,7 +152,7 @@ private actor Speech { request: SFSpeechAudioBufferRecognitionRequest ) -> AsyncThrowingStream { AsyncThrowingStream { continuation in - return; + return; self.recognitionContinuation = continuation let audioSession = AVAudioSession.sharedInstance() do { diff --git a/Examples/SyncUps/SyncUps/RecordMeeting.swift b/Examples/SyncUps/SyncUps/RecordMeeting.swift index 8a6aafd..8fd0759 100644 --- a/Examples/SyncUps/SyncUps/RecordMeeting.swift +++ b/Examples/SyncUps/SyncUps/RecordMeeting.swift @@ -38,22 +38,22 @@ extension StoreDIValues { extension Store { func confirmDiscard() { - di.pop() - cancel(Self.onTask) + di.pop() + cancel(Self.onTask) } func save() { state.syncUp.meetings.insert( Meeting( id: di.uuid(), - date: di.date.now, + date: di.date.now, transcript: state.transcript ), at: 0 ) di.recordMeetingDelegate?.savePath(transcript: state.transcript) - di.pop() - cancel(Self.onTask) + di.pop() + cancel(Self.onTask) } func endMeetingButtonTapped() { @@ -85,10 +85,10 @@ extension Store { } group.addTask { - while !Task.isCancelled { - await timerTick() - try? await di.continuousClock.sleep(for: .seconds(1)) - } + while !Task.isCancelled { + await timerTick() + try? await di.continuousClock.sleep(for: .seconds(1)) + } } } } @@ -231,7 +231,7 @@ struct MeetingHeaderView: View { var body: some View { VStack { - ProgressView(value: max(0, min(1, progress))) + ProgressView(value: max(0, min(1, progress))) .progressViewStyle(MeetingProgressViewStyle(theme: theme)) HStack { VStack(alignment: .leading) { diff --git a/Examples/SyncUps/SyncUps/SyncUpDetail.swift b/Examples/SyncUps/SyncUps/SyncUpDetail.swift index 03334a6..b141144 100644 --- a/Examples/SyncUps/SyncUps/SyncUpDetail.swift +++ b/Examples/SyncUps/SyncUps/SyncUpDetail.swift @@ -10,7 +10,7 @@ struct SyncUpDetail: Equatable { @Steps struct Destination: Equatable { var alert = Alert() - var edit = SyncUpForm(syncUp: SyncUp(id: StoreDIValues.current.uuid())) + var edit = SyncUpForm(syncUp: SyncUp(id: StoreDIValues.current.uuid())) @Steps struct Alert: Equatable { @@ -52,7 +52,7 @@ extension Store { withAnimation { di.syncUpDetailDelegate?.deleteSyncUp(syncUp: state.syncUp) } - di.pop() + di.pop() } func continueWithoutRecording() { @@ -210,7 +210,7 @@ extension View { isPresented: store.binding.destination.alert.isSelected(.confirmDeletion) ) { Button("Yes", role: .destructive) { - store.confirmDeletion() + store.confirmDeletion() } Button("Nevermind", role: .cancel) {} } message: { diff --git a/Examples/SyncUps/SyncUps/SyncUpForm.swift b/Examples/SyncUps/SyncUps/SyncUpForm.swift index ca92d40..275a56f 100644 --- a/Examples/SyncUps/SyncUps/SyncUpForm.swift +++ b/Examples/SyncUps/SyncUps/SyncUpForm.swift @@ -12,9 +12,9 @@ struct SyncUpForm: Equatable { ) { self.focus = focus self.syncUp = syncUp - if self.syncUp.attendees.isEmpty { - self.syncUp.attendees.append(Attendee(id: StoreDIValues.current.uuid())) - } + if self.syncUp.attendees.isEmpty { + self.syncUp.attendees.append(Attendee(id: StoreDIValues.current.uuid())) + } } enum Field: Hashable { diff --git a/Examples/TicTacToe/App/RootView.swift b/Examples/TicTacToe/App/RootView.swift index 48a8594..abaff6c 100644 --- a/Examples/TicTacToe/App/RootView.swift +++ b/Examples/TicTacToe/App/RootView.swift @@ -1,8 +1,8 @@ import AppCore import AppSwiftUI import AppUIKit -import VDStore import SwiftUI +import VDStore private let readMe = """ This application demonstrates how to build a moderately complex application in the VDStore. @@ -27,35 +27,35 @@ enum GameType: Identifiable { struct RootView: View { - @ViewStore( - Store(TicTacToe.login()).transformDI { - $0.logoutButtonDelegate = $0.store(for: TicTacToe.self) - $0.loginDelegate = $0.store(for: TicTacToe.self) - } - ) - private var state + @ViewStore( + Store(TicTacToe.login()).transformDI { + $0.logoutButtonDelegate = $0.store(for: TicTacToe.self) + $0.loginDelegate = $0.store(for: TicTacToe.self) + } + ) + private var state @State var showGame: GameType? var body: some View { - NavigationStack { - Form { - Text(readMe) - - Section { - Button("SwiftUI version") { showGame = .swiftui } - Button("UIKit version") { showGame = .uikit } - } - } - .sheet(item: $showGame) { gameType in - if gameType == .swiftui { - AppView(store: $state) - } else { - UIKitAppView(store: $state) - } - } - .navigationTitle("Tic-Tac-Toe") - } + NavigationStack { + Form { + Text(readMe) + + Section { + Button("SwiftUI version") { showGame = .swiftui } + Button("UIKit version") { showGame = .uikit } + } + } + .sheet(item: $showGame) { gameType in + if gameType == .swiftui { + AppView(store: $state) + } else { + UIKitAppView(store: $state) + } + } + .navigationTitle("Tic-Tac-Toe") + } } } diff --git a/Examples/TicTacToe/TicTacToe.xcodeproj/project.xcworkspace/xcuserdata/danil.xcuserdatad/UserInterfaceState.xcuserstate b/Examples/TicTacToe/TicTacToe.xcodeproj/project.xcworkspace/xcuserdata/danil.xcuserdatad/UserInterfaceState.xcuserstate index 4b50676830acd584ae6faa0561fb79d253dd399a..694a372b6b886843b5d184031708f396742ec901 100644 GIT binary patch delta 29417 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 WzUzO7L0!81&%<>8^Z&Db&;J2@VYtNr diff --git a/Examples/TicTacToe/tic-tac-toe/Package.swift b/Examples/TicTacToe/tic-tac-toe/Package.swift index 1566953..21c2c73 100644 --- a/Examples/TicTacToe/tic-tac-toe/Package.swift +++ b/Examples/TicTacToe/tic-tac-toe/Package.swift @@ -27,7 +27,7 @@ let package = Package( ], dependencies: [ .package(name: "VDStore", path: "../../.."), - .package(url: "https://github.com/dankinsoid/VDFlow.git", from: "4.21.0") + .package(url: "https://github.com/dankinsoid/VDFlow.git", from: "4.21.0"), ], targets: [ .target( @@ -37,7 +37,7 @@ let package = Package( "LoginCore", "NewGameCore", "VDStore", - "VDFlow" + "VDFlow", ] ), .testTarget( @@ -67,7 +67,7 @@ let package = Package( ), .target( name: "GameCore", - dependencies: ["VDStore", "VDFlow"] + dependencies: ["VDStore", "VDFlow"] ), .testTarget( name: "GameCoreTests", @@ -87,8 +87,8 @@ let package = Package( dependencies: [ "AuthenticationClient", "TwoFactorCore", - "VDStore", - "VDFlow" + "VDStore", + "VDFlow", ] ), .testTarget( @@ -114,8 +114,8 @@ let package = Package( name: "NewGameCore", dependencies: [ "GameCore", - "VDStore", - "VDFlow" + "VDStore", + "VDFlow", ] ), .testTarget( @@ -141,8 +141,8 @@ let package = Package( name: "TwoFactorCore", dependencies: [ "AuthenticationClient", - "VDStore", - "VDFlow" + "VDStore", + "VDFlow", ] ), .testTarget( diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/AppCore/AppCore.swift b/Examples/TicTacToe/tic-tac-toe/Sources/AppCore/AppCore.swift index 9ffc51f..6f70d5c 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/AppCore/AppCore.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/AppCore/AppCore.swift @@ -1,26 +1,26 @@ -import VDStore -import VDFlow import LoginCore import NewGameCore import TwoFactorCore +import VDFlow +import VDStore @Steps public struct TicTacToe: Equatable { - public var login: Login = Login() - public var newGame: NewGame = NewGame() + public var login = Login() + public var newGame = NewGame() } extension Store: LogoutButtonDelegate { - public func logoutButtonTapped() { - state = .login() - } + public func logoutButtonTapped() { + state = .login() + } } extension Store: LoginDelegate { - public func didSucceedLogin() { - state = .newGame() - } + public func didSucceedLogin() { + state = .newGame() + } } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/AppSwiftUI/AppView.swift b/Examples/TicTacToe/tic-tac-toe/Sources/AppSwiftUI/AppView.swift index bcef31d..0b0ae3f 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/AppSwiftUI/AppView.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/AppSwiftUI/AppView.swift @@ -1,8 +1,8 @@ import AppCore -import VDStore import LoginSwiftUI import NewGameSwiftUI import SwiftUI +import VDStore public struct AppView: View { @ViewStore private var state: TicTacToe @@ -11,16 +11,16 @@ public struct AppView: View { _state = ViewStore(store) } - public var body: some View { - switch state.selected { - case .login: - NavigationStack { - LoginView(store: $state.login) - } - case .newGame: - NavigationStack { - NewGameView(store: $state.newGame) - } - } - } + public var body: some View { + switch state.selected { + case .login: + NavigationStack { + LoginView(store: $state.login) + } + case .newGame: + NavigationStack { + NewGameView(store: $state.newGame) + } + } + } } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/AppUIKit/AppViewController.swift b/Examples/TicTacToe/tic-tac-toe/Sources/AppUIKit/AppViewController.swift index f153c7f..62e42c7 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/AppUIKit/AppViewController.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/AppUIKit/AppViewController.swift @@ -1,10 +1,10 @@ import AppCore -import VDStore import Combine import LoginUIKit import NewGameUIKit import SwiftUI import UIKit +import VDStore public struct UIKitAppView: UIViewControllerRepresentable { @Store private var state: TicTacToe @@ -26,11 +26,11 @@ public struct UIKitAppView: UIViewControllerRepresentable { } class AppViewController: UINavigationController { - @Store private var state: TicTacToe - private var cancellableSet: Set = [] + @Store private var state: TicTacToe + private var cancellableSet: Set = [] init(store: Store) { - _state = store + _state = store super.init(nibName: nil, bundle: nil) } @@ -42,18 +42,18 @@ class AppViewController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() - $state.publisher - .map(\.selected) - .removeDuplicates() - .sink { [weak self] selected in - guard let self else { return } - switch selected { - case .login: - setViewControllers([LoginViewController(store: $state.login)], animated: false) - case .newGame: - setViewControllers([NewGameViewController(store: $state.newGame)], animated: false) - } - } - .store(in: &cancellableSet) + $state.publisher + .map(\.selected) + .removeDuplicates() + .sink { [weak self] selected in + guard let self else { return } + switch selected { + case .login: + setViewControllers([LoginViewController(store: $state.login)], animated: false) + case .newGame: + setViewControllers([NewGameViewController(store: $state.newGame)], animated: false) + } + } + .store(in: &cancellableSet) } } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/AuthenticationClient/AuthenticationClient.swift b/Examples/TicTacToe/tic-tac-toe/Sources/AuthenticationClient/AuthenticationClient.swift index ce0a614..efa87bf 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/AuthenticationClient/AuthenticationClient.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/AuthenticationClient/AuthenticationClient.swift @@ -18,7 +18,7 @@ public enum AuthenticationError: Equatable, LocalizedError, Sendable { case invalidUserPassword case invalidTwoFactor case invalidIntermediateToken - case unimplemented + case unimplemented public var errorDescription: String? { switch self { @@ -28,8 +28,8 @@ public enum AuthenticationError: Equatable, LocalizedError, Sendable { return "Invalid second factor (try 1234)" case .invalidIntermediateToken: return "404!! What happened to your token there bud?!?!" - case .unimplemented: - return "This feature is not yet implemented." + case .unimplemented: + return "This feature is not yet implemented." } } } @@ -37,42 +37,43 @@ public enum AuthenticationError: Equatable, LocalizedError, Sendable { public struct AuthenticationClient: Sendable { public var login: - @Sendable (_ email: String, _ password: String) async throws -> AuthenticationResponse = { _, _ in - throw AuthenticationError.unimplemented - } + @Sendable (_ email: String, _ password: String) async throws -> AuthenticationResponse = { _, _ in + throw AuthenticationError.unimplemented + } public var twoFactor: - @Sendable (_ code: String, _ token: String) async throws -> AuthenticationResponse = { _, _ in - throw AuthenticationError.unimplemented - } + @Sendable (_ code: String, _ token: String) async throws -> AuthenticationResponse = { _, _ in + throw AuthenticationError.unimplemented + } } -extension AuthenticationClient { - public static let liveValue = Self( - login: { email, password in - guard email.contains("@"), password == "password" - else { throw AuthenticationError.invalidUserPassword } - - try await Task.sleep(for: .seconds(1)) - return AuthenticationResponse( - token: "deadbeef", twoFactorRequired: email.contains("2fa") - ) - }, - twoFactor: { code, token in - guard token == "deadbeef" - else { throw AuthenticationError.invalidIntermediateToken } - - guard code == "1234" - else { throw AuthenticationError.invalidTwoFactor } - - try await Task.sleep(for: .seconds(1)) - return AuthenticationResponse(token: "deadbeefdeadbeef", twoFactorRequired: false) - } - ) +public extension AuthenticationClient { + + static let liveValue = Self( + login: { email, password in + guard email.contains("@"), password == "password" + else { throw AuthenticationError.invalidUserPassword } + + try await Task.sleep(for: .seconds(1)) + return AuthenticationResponse( + token: "deadbeef", twoFactorRequired: email.contains("2fa") + ) + }, + twoFactor: { code, token in + guard token == "deadbeef" + else { throw AuthenticationError.invalidIntermediateToken } + + guard code == "1234" + else { throw AuthenticationError.invalidTwoFactor } + + try await Task.sleep(for: .seconds(1)) + return AuthenticationResponse(token: "deadbeefdeadbeef", twoFactorRequired: false) + } + ) } public extension StoreDIValues { - - @StoreDIValue - var authenticationClient = valueFor(live: AuthenticationClient.liveValue, test: AuthenticationClient()) + + @StoreDIValue + var authenticationClient = valueFor(live: AuthenticationClient.liveValue, test: AuthenticationClient()) } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/GameCore/GameCore.swift b/Examples/TicTacToe/tic-tac-toe/Sources/GameCore/GameCore.swift index a3d47ce..d79e08f 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/GameCore/GameCore.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/GameCore/GameCore.swift @@ -1,50 +1,50 @@ -import VDStore -import VDFlow import SwiftUI +import VDFlow +import VDStore public struct Game: Sendable, Equatable { - - public var board: Three> = .empty - public var currentPlayer: Player = .x - public let oPlayerName: String - public let xPlayerName: String - - public init(oPlayerName: String, xPlayerName: String) { - self.oPlayerName = oPlayerName - self.xPlayerName = xPlayerName - } - - public var currentPlayerName: String { - switch currentPlayer { - case .o: return oPlayerName - case .x: return xPlayerName - } - } + + public var board: Three> = .empty + public var currentPlayer: Player = .x + public let oPlayerName: String + public let xPlayerName: String + + public init(oPlayerName: String, xPlayerName: String) { + self.oPlayerName = oPlayerName + self.xPlayerName = xPlayerName + } + + public var currentPlayerName: String { + switch currentPlayer { + case .o: return oPlayerName + case .x: return xPlayerName + } + } } @Actions -extension Store { - - public func cellTapped(row: Int, column: Int) { - guard - state.board[row][column] == nil, - !state.board.hasWinner - else { return } - - state.board[row][column] = state.currentPlayer - - if !state.board.hasWinner { - state.currentPlayer.toggle() - } - } - - public func playAgainButtonTapped() { - state = Game(oPlayerName: state.oPlayerName, xPlayerName: state.xPlayerName) - } - - public func quitButtonTapped() { - di.dismiss() - } +public extension Store { + + func cellTapped(row: Int, column: Int) { + guard + state.board[row][column] == nil, + !state.board.hasWinner + else { return } + + state.board[row][column] = state.currentPlayer + + if !state.board.hasWinner { + state.currentPlayer.toggle() + } + } + + func playAgainButtonTapped() { + state = Game(oPlayerName: state.oPlayerName, xPlayerName: state.xPlayerName) + } + + func quitButtonTapped() { + di.dismiss() + } } public enum Player: Equatable, Sendable { diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/GameSwiftUI/GameView.swift b/Examples/TicTacToe/tic-tac-toe/Sources/GameSwiftUI/GameView.swift index b8b8e2b..0f92be7 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/GameSwiftUI/GameView.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/GameSwiftUI/GameView.swift @@ -1,7 +1,7 @@ -import VDStore -import VDFlow import GameCore import SwiftUI +import VDFlow +import VDStore public struct GameView: View { diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/GameUIKit/GameViewController.swift b/Examples/TicTacToe/tic-tac-toe/Sources/GameUIKit/GameViewController.swift index b5d246f..c961c3a 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/GameUIKit/GameViewController.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/GameUIKit/GameViewController.swift @@ -1,11 +1,11 @@ -import VDStore import Combine import GameCore import UIKit +import VDStore public final class GameViewController: UIViewController { @Store private var state: Game - private var cancellableSet: Set = [] + private var cancellableSet: Set = [] public init(store: Store) { _state = store @@ -109,21 +109,21 @@ public final class GameViewController: UIViewController { ]) } - $state.publisher - .removeDuplicates() - .sink { state in - titleLabel.text = state.title - playAgainButton.isHidden = state.isPlayAgainButtonHidden - - for (rowIdx, row) in state.rows.enumerated() { - for (colIdx, label) in row.enumerated() { - let button = cells[rowIdx][colIdx] - button.setTitle(label, for: .normal) - button.isEnabled = state.isGameEnabled - } - } - } - .store(in: &cancellableSet) + $state.publisher + .removeDuplicates() + .sink { state in + titleLabel.text = state.title + playAgainButton.isHidden = state.isPlayAgainButtonHidden + + for (rowIdx, row) in state.rows.enumerated() { + for (colIdx, label) in row.enumerated() { + let button = cells[rowIdx][colIdx] + button.setTitle(label, for: .normal) + button.isEnabled = state.isGameEnabled + } + } + } + .store(in: &cancellableSet) } @objc private func gridCell11Tapped() { $state.cellTapped(row: 0, column: 0) } @@ -137,11 +137,11 @@ public final class GameViewController: UIViewController { @objc private func gridCell33Tapped() { $state.cellTapped(row: 2, column: 2) } @objc private func quitButtonTapped() { - $state.quitButtonTapped() + $state.quitButtonTapped() } @objc private func playAgainButtonTapped() { - $state.playAgainButtonTapped() + $state.playAgainButtonTapped() } } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/LoginCore/LoginCore.swift b/Examples/TicTacToe/tic-tac-toe/Sources/LoginCore/LoginCore.swift index 552f31d..4b1bebb 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/LoginCore/LoginCore.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/LoginCore/LoginCore.swift @@ -1,47 +1,47 @@ import AuthenticationClient -import VDStore -import VDFlow import Dispatch import TwoFactorCore +import VDFlow +import VDStore public struct Login: Sendable, Equatable { - public var flow: Flow = .none - public var email = "" - public var isLoginRequestInFlight = false - public var password = "" + public var flow: Flow = .none + public var email = "" + public var isLoginRequestInFlight = false + public var password = "" - public init() {} + public init() {} - public var isFormValid: Bool { - !email.isEmpty && !password.isEmpty - } + public var isFormValid: Bool { + !email.isEmpty && !password.isEmpty + } - @Steps - public struct Flow: Equatable, Sendable { - public var twoFactor: TwoFactor = TwoFactor(token: "") - public var alert = "" - public var none - } + @Steps + public struct Flow: Equatable, Sendable { + public var twoFactor = TwoFactor(token: "") + public var alert = "" + public var none + } } @Actions public extension Store { - func loginButtonTapped() async { - state.isLoginRequestInFlight = true - defer { - state.isLoginRequestInFlight = false - } - do { - let response = try await di.authenticationClient.login(state.email, state.password) - if response.twoFactorRequired { - state.flow.$twoFactor.select(with: TwoFactor(token: response.token)) - } else { - di.loginDelegate?.didSucceedLogin() - } - } catch { - state.flow.$alert.select(with: error.localizedDescription) - } - } + func loginButtonTapped() async { + state.isLoginRequestInFlight = true + defer { + state.isLoginRequestInFlight = false + } + do { + let response = try await di.authenticationClient.login(state.email, state.password) + if response.twoFactorRequired { + state.flow.$twoFactor.select(with: TwoFactor(token: response.token)) + } else { + di.loginDelegate?.didSucceedLogin() + } + } catch { + state.flow.$alert.select(with: error.localizedDescription) + } + } } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/LoginSwiftUI/LoginView.swift b/Examples/TicTacToe/tic-tac-toe/Sources/LoginSwiftUI/LoginView.swift index 8f633ad..7850e4d 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/LoginSwiftUI/LoginView.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/LoginSwiftUI/LoginView.swift @@ -1,10 +1,10 @@ import AuthenticationClient -import VDStore -import VDFlow import LoginCore import SwiftUI import TwoFactorCore import TwoFactorSwiftUI +import VDFlow +import VDStore public struct LoginView: View { @@ -30,7 +30,7 @@ public struct LoginView: View { .keyboardType(.emailAddress) .textContentType(.emailAddress) - SecureField("••••••••", text: $state.binding.password) + SecureField("••••••••", text: $state.binding.password) } Button { @@ -41,9 +41,9 @@ public struct LoginView: View { _ = UIApplication.shared.sendAction( #selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil ) - Task { - await $state.loginButtonTapped() - } + Task { + await $state.loginButtonTapped() + } } label: { HStack { Text("Log in") @@ -56,11 +56,11 @@ public struct LoginView: View { .disabled(state.isLoginButtonDisabled) } .disabled(state.isFormDisabled) - .alert(state.flow.alert, isPresented: $state.binding.flow.isSelected(.alert)) { - Button("Ok") {} - } - .navigationDestination(isPresented: $state.binding.flow.isSelected(.twoFactor)) { - TwoFactorView(store: $state.flow.twoFactor) + .alert(state.flow.alert, isPresented: $state.binding.flow.isSelected(.alert)) { + Button("Ok") {} + } + .navigationDestination(isPresented: $state.binding.flow.isSelected(.twoFactor)) { + TwoFactorView(store: $state.flow.twoFactor) } .navigationTitle("Login") } @@ -75,7 +75,7 @@ private extension Login { #Preview { NavigationStack { LoginView( - store: Store(Login()).transformDI { + store: Store(Login()).transformDI { $0.authenticationClient.login = { @Sendable _, _ in AuthenticationResponse(token: "deadbeef", twoFactorRequired: false) } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/LoginUIKit/LoginViewController.swift b/Examples/TicTacToe/tic-tac-toe/Sources/LoginUIKit/LoginViewController.swift index 74f8d89..58b959b 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/LoginUIKit/LoginViewController.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/LoginUIKit/LoginViewController.swift @@ -1,13 +1,13 @@ -import VDStore -import VDFlow import Combine import LoginCore import TwoFactorUIKit import UIKit +import VDFlow +import VDStore public class LoginViewController: UIViewController { public let store: Store - private var cancellableSet: Set = [] + private var cancellableSet: Set = [] public init(store: Store) { self.store = store @@ -92,46 +92,46 @@ public class LoginViewController: UIViewController { var alertController: UIAlertController? var twoFactorController: TwoFactorViewController? - store.publisher - .removeDuplicates() - .sink { [weak self] state in - guard let self else { return } - if state.email != emailTextField.text { - emailTextField.text = state.email - } - emailTextField.isEnabled = state.isEmailTextFieldEnabled - if passwordTextField.text != state.password { - passwordTextField.text = state.password - } - passwordTextField.isEnabled = state.isPasswordTextFieldEnabled - loginButton.isEnabled = state.isLoginButtonEnabled - activityIndicator.isHidden = state.isActivityIndicatorHidden - - if store.state.flow.selected == .alert, - alertController == nil - { - alertController = UIAlertController(title: store.state.flow.alert, message: nil, preferredStyle: .alert) - alertController?.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) - present(alertController!, animated: true, completion: nil) - } else if store.state.flow.selected != .alert, alertController != nil { - alertController?.dismiss(animated: true) - alertController = nil - } - - if store.state.flow.selected == .twoFactor, - twoFactorController == nil - { - twoFactorController = TwoFactorViewController(store: store.flow.twoFactor) - navigationController?.pushViewController( - twoFactorController!, - animated: true - ) - } else if store.state.flow.selected != .twoFactor, twoFactorController != nil { - navigationController?.popToViewController(self, animated: true) - twoFactorController = nil - } - } - .store(in: &cancellableSet) + store.publisher + .removeDuplicates() + .sink { [weak self] state in + guard let self else { return } + if state.email != emailTextField.text { + emailTextField.text = state.email + } + emailTextField.isEnabled = state.isEmailTextFieldEnabled + if passwordTextField.text != state.password { + passwordTextField.text = state.password + } + passwordTextField.isEnabled = state.isPasswordTextFieldEnabled + loginButton.isEnabled = state.isLoginButtonEnabled + activityIndicator.isHidden = state.isActivityIndicatorHidden + + if store.state.flow.selected == .alert, + alertController == nil + { + alertController = UIAlertController(title: store.state.flow.alert, message: nil, preferredStyle: .alert) + alertController?.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) + present(alertController!, animated: true, completion: nil) + } else if store.state.flow.selected != .alert, alertController != nil { + alertController?.dismiss(animated: true) + alertController = nil + } + + if store.state.flow.selected == .twoFactor, + twoFactorController == nil + { + twoFactorController = TwoFactorViewController(store: store.flow.twoFactor) + navigationController?.pushViewController( + twoFactorController!, + animated: true + ) + } else if store.state.flow.selected != .twoFactor, twoFactorController != nil { + navigationController?.popToViewController(self, animated: true) + twoFactorController = nil + } + } + .store(in: &cancellableSet) } override public func viewDidAppear(_ animated: Bool) { @@ -143,17 +143,17 @@ public class LoginViewController: UIViewController { } @objc private func loginButtonTapped(sender: UIButton) { - Task { - await store.loginButtonTapped() - } + Task { + await store.loginButtonTapped() + } } @objc private func emailTextFieldChanged(sender: UITextField) { - store.state.email = sender.text ?? "" + store.state.email = sender.text ?? "" } @objc private func passwordTextFieldChanged(sender: UITextField) { - store.state.password = sender.text ?? "" + store.state.password = sender.text ?? "" } } @@ -167,6 +167,5 @@ private extension Login { @Actions private extension Store { - func twoFactorDismissed() { - } + func twoFactorDismissed() {} } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/NewGameCore/NewGameCore.swift b/Examples/TicTacToe/tic-tac-toe/Sources/NewGameCore/NewGameCore.swift index ffbb18f..66b77b9 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/NewGameCore/NewGameCore.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/NewGameCore/NewGameCore.swift @@ -1,42 +1,41 @@ -import VDStore -import VDFlow import GameCore +import VDFlow +import VDStore public struct NewGame: Equatable { - public var flow: Flow = .none - public var oPlayerName = "" - public var xPlayerName = "" - - @Steps - public struct Flow: Equatable { - public var game: Game = Game(oPlayerName: "", xPlayerName: "") - public var none - } - - public init() { - } + public var flow: Flow = .none + public var oPlayerName = "" + public var xPlayerName = "" + + @Steps + public struct Flow: Equatable { + public var game = Game(oPlayerName: "", xPlayerName: "") + public var none + } + + public init() {} } @MainActor public protocol LogoutButtonDelegate { - func logoutButtonTapped() + func logoutButtonTapped() } -extension StoreDIValues { - @StoreDIValue - public var logoutButtonDelegate: LogoutButtonDelegate? +public extension StoreDIValues { + @StoreDIValue + var logoutButtonDelegate: LogoutButtonDelegate? } @Actions -extension Store { - - public func letsPlayButtonTapped() { - state.flow.$game.select( - with: Game( - oPlayerName: state.oPlayerName, - xPlayerName: state.xPlayerName - ) - ) - } +public extension Store { + + func letsPlayButtonTapped() { + state.flow.$game.select( + with: Game( + oPlayerName: state.oPlayerName, + xPlayerName: state.xPlayerName + ) + ) + } } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/NewGameSwiftUI/NewGameView.swift b/Examples/TicTacToe/tic-tac-toe/Sources/NewGameSwiftUI/NewGameView.swift index ab7b0a1..1a18ad0 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/NewGameSwiftUI/NewGameView.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/NewGameSwiftUI/NewGameView.swift @@ -1,13 +1,13 @@ -import VDStore -import VDFlow import GameCore import GameSwiftUI import NewGameCore import SwiftUI +import VDFlow +import VDStore public struct NewGameView: View { - - @ViewStore var state: NewGame + + @ViewStore var state: NewGame public init(store: Store) { _state = ViewStore(store) @@ -16,7 +16,7 @@ public struct NewGameView: View { public var body: some View { Form { Section { - TextField("Blob Sr.", text: $state.binding.xPlayerName) + TextField("Blob Sr.", text: $state.binding.xPlayerName) .autocapitalization(.words) .disableAutocorrection(true) .textContentType(.name) @@ -34,18 +34,18 @@ public struct NewGameView: View { } Button("Let's play!") { - $state.letsPlayButtonTapped() + $state.letsPlayButtonTapped() } .disabled(state.isLetsPlayButtonDisabled) } .navigationTitle("New Game") - .navigationBarItems( - trailing: Button("Logout") { - $state.di.logoutButtonDelegate?.logoutButtonTapped() - } - ) - .navigationDestination(isPresented: $state.binding.flow.isSelected(.game)) { - GameView(store: $state.flow.game) + .navigationBarItems( + trailing: Button("Logout") { + $state.di.logoutButtonDelegate?.logoutButtonTapped() + } + ) + .navigationDestination(isPresented: $state.binding.flow.isSelected(.game)) { + GameView(store: $state.flow.game) } } } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/NewGameUIKit/NewGameViewController.swift b/Examples/TicTacToe/tic-tac-toe/Sources/NewGameUIKit/NewGameViewController.swift index d870dd1..5bb12b4 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/NewGameUIKit/NewGameViewController.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/NewGameUIKit/NewGameViewController.swift @@ -1,12 +1,12 @@ -import VDStore import Combine import GameUIKit import NewGameCore import UIKit +import VDStore public class NewGameViewController: UIViewController { @Store var state: NewGame - private var cancellableBag: Set = [] + private var cancellableBag: Set = [] public init(store: Store) { _state = store @@ -91,41 +91,41 @@ public class NewGameViewController: UIViewController { var gameController: GameViewController? - $state.publisher - .removeDuplicates() - .sink { [weak self] state in - guard let self else { return } - playerOTextField.text = state.oPlayerName - playerXTextField.text = state.xPlayerName - letsPlayButton.isEnabled = state.isLetsPlayButtonEnabled - - if state.flow.selected == .game, - gameController == nil - { - gameController = GameViewController(store: $state.flow.game) - navigationController?.pushViewController(gameController!, animated: true) - } else if state.flow.selected != .game, gameController != nil { - navigationController?.popToViewController(self, animated: true) - gameController = nil - } - } - .store(in: &cancellableBag) + $state.publisher + .removeDuplicates() + .sink { [weak self] state in + guard let self else { return } + playerOTextField.text = state.oPlayerName + playerXTextField.text = state.xPlayerName + letsPlayButton.isEnabled = state.isLetsPlayButtonEnabled + + if state.flow.selected == .game, + gameController == nil + { + gameController = GameViewController(store: $state.flow.game) + navigationController?.pushViewController(gameController!, animated: true) + } else if state.flow.selected != .game, gameController != nil { + navigationController?.popToViewController(self, animated: true) + gameController = nil + } + } + .store(in: &cancellableBag) } @objc private func logoutButtonTapped() { - $state.di.logoutButtonDelegate?.logoutButtonTapped() + $state.di.logoutButtonDelegate?.logoutButtonTapped() } @objc private func playerXTextChanged(sender: UITextField) { - state.xPlayerName = sender.text ?? "" + state.xPlayerName = sender.text ?? "" } @objc private func playerOTextChanged(sender: UITextField) { - state.oPlayerName = sender.text ?? "" + state.oPlayerName = sender.text ?? "" } @objc private func letsPlayTapped() { - $state.letsPlayButtonTapped() + $state.letsPlayButtonTapped() } } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorCore/TwoFactorCore.swift b/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorCore/TwoFactorCore.swift index 7befcf5..408db40 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorCore/TwoFactorCore.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorCore/TwoFactorCore.swift @@ -1,56 +1,56 @@ import AuthenticationClient import Combine -import VDStore import Dispatch import VDFlow +import VDStore public struct TwoFactor: Sendable, Equatable { - - public var flow: Flow = .none - public var code = "" - public var isTwoFactorRequestInFlight = false - public let token: String - - public init(token: String) { - self.token = token - } - - public var isFormValid: Bool { - code.count >= 4 - } - - @Steps - public struct Flow: Sendable, Equatable { - public var alert = "" - public var none - } + + public var flow: Flow = .none + public var code = "" + public var isTwoFactorRequestInFlight = false + public let token: String + + public init(token: String) { + self.token = token + } + + public var isFormValid: Bool { + code.count >= 4 + } + + @Steps + public struct Flow: Sendable, Equatable { + public var alert = "" + public var none + } } @Actions -extension Store { - - public func submitButtonTapped() async { - state.isTwoFactorRequestInFlight = true - defer { - state.isTwoFactorRequestInFlight = false - } - do { - _ = try await di.authenticationClient.twoFactor(state.code, state.token) - di.loginDelegate?.didSucceedLogin() - } catch { - state.flow.$alert.select(with: error.localizedDescription) - } - } +public extension Store { + + func submitButtonTapped() async { + state.isTwoFactorRequestInFlight = true + defer { + state.isTwoFactorRequestInFlight = false + } + do { + _ = try await di.authenticationClient.twoFactor(state.code, state.token) + di.loginDelegate?.didSucceedLogin() + } catch { + state.flow.$alert.select(with: error.localizedDescription) + } + } } @MainActor public protocol LoginDelegate { - - func didSucceedLogin() + + func didSucceedLogin() } -extension StoreDIValues { - - @StoreDIValue - public var loginDelegate: LoginDelegate? +public extension StoreDIValues { + + @StoreDIValue + var loginDelegate: LoginDelegate? } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorSwiftUI/TwoFactorView.swift b/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorSwiftUI/TwoFactorView.swift index e1590b8..5d52cd6 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorSwiftUI/TwoFactorView.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorSwiftUI/TwoFactorView.swift @@ -1,15 +1,15 @@ import AuthenticationClient -import VDStore -import VDFlow import SwiftUI import TwoFactorCore +import VDFlow +import VDStore public struct TwoFactorView: View { @ViewStore public var state: TwoFactor public init(store: Store) { - _state = ViewStore(store) + _state = ViewStore(store) } public var body: some View { @@ -17,7 +17,7 @@ public struct TwoFactorView: View { Text(#"To confirm the second factor enter "1234" into the form."#) Section { - TextField("1234", text: $state.binding.code) + TextField("1234", text: $state.binding.code) .keyboardType(.numberPad) } @@ -30,9 +30,9 @@ public struct TwoFactorView: View { UIApplication.shared.sendAction( #selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil ) - Task { - await $state.submitButtonTapped() - } + Task { + await $state.submitButtonTapped() + } } .disabled(state.isSubmitButtonDisabled) @@ -42,9 +42,9 @@ public struct TwoFactorView: View { } } } - .alert(state.flow.alert, isPresented: $state.binding.flow.isSelected(.alert)) { - Button("Ok") {} - } + .alert(state.flow.alert, isPresented: $state.binding.flow.isSelected(.alert)) { + Button("Ok") {} + } .disabled(state.isFormDisabled) .navigationTitle("Confirmation Code") } @@ -60,14 +60,14 @@ private extension TwoFactor { NavigationStack { TwoFactorView( store: Store(TwoFactor(token: "deadbeef")) - .transformDI { - $0.authenticationClient.login = { @Sendable _, _ in - AuthenticationResponse(token: "deadbeef", twoFactorRequired: false) - } - $0.authenticationClient.twoFactor = { @Sendable _, _ in - AuthenticationResponse(token: "deadbeef", twoFactorRequired: false) - } - } + .transformDI { + $0.authenticationClient.login = { @Sendable _, _ in + AuthenticationResponse(token: "deadbeef", twoFactorRequired: false) + } + $0.authenticationClient.twoFactor = { @Sendable _, _ in + AuthenticationResponse(token: "deadbeef", twoFactorRequired: false) + } + } ) } } diff --git a/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorUIKit/TwoFactorViewController.swift b/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorUIKit/TwoFactorViewController.swift index 199318b..92f16ac 100644 --- a/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorUIKit/TwoFactorViewController.swift +++ b/Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorUIKit/TwoFactorViewController.swift @@ -1,12 +1,12 @@ -import VDStore +import Combine import TwoFactorCore import UIKit -import Combine +import VDStore public final class TwoFactorViewController: UIViewController { public let store: Store - private var cancellableSet: Set = [] + private var cancellableSet: Set = [] public init(store: Store) { self.store = store @@ -63,36 +63,36 @@ public final class TwoFactorViewController: UIViewController { var alertController: UIAlertController? - store.publisher - .removeDuplicates() - .sink { [weak self] state in - guard let self else { return } - activityIndicator.isHidden = state.isActivityIndicatorHidden - codeTextField.text = state.code - loginButton.isEnabled = state.isLoginButtonEnabled - - if state.flow.selected == .alert, - alertController == nil - { - alertController = UIAlertController(title: state.flow.alert, message: nil, preferredStyle: .alert) - alertController?.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) - present(alertController!, animated: true, completion: nil) - } else if state.flow.selected != .alert, alertController != nil { - alertController?.dismiss(animated: true) - alertController = nil - } - } - .store(in: &cancellableSet) + store.publisher + .removeDuplicates() + .sink { [weak self] state in + guard let self else { return } + activityIndicator.isHidden = state.isActivityIndicatorHidden + codeTextField.text = state.code + loginButton.isEnabled = state.isLoginButtonEnabled + + if state.flow.selected == .alert, + alertController == nil + { + alertController = UIAlertController(title: state.flow.alert, message: nil, preferredStyle: .alert) + alertController?.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) + present(alertController!, animated: true, completion: nil) + } else if state.flow.selected != .alert, alertController != nil { + alertController?.dismiss(animated: true) + alertController = nil + } + } + .store(in: &cancellableSet) } @objc private func codeTextFieldChanged(sender: UITextField) { - store.state.code = sender.text ?? "" + store.state.code = sender.text ?? "" } @objc private func loginButtonTapped() { - Task { - await store.submitButtonTapped() - } + Task { + await store.submitButtonTapped() + } } } diff --git a/Examples/TicTacToe/tic-tac-toe/Tests/AppCoreTests/AppCoreTests.swift b/Examples/TicTacToe/tic-tac-toe/Tests/AppCoreTests/AppCoreTests.swift index dd43d2d..656ee79 100644 --- a/Examples/TicTacToe/tic-tac-toe/Tests/AppCoreTests/AppCoreTests.swift +++ b/Examples/TicTacToe/tic-tac-toe/Tests/AppCoreTests/AppCoreTests.swift @@ -1,9 +1,9 @@ import AppCore import AuthenticationClient -import VDStore import LoginCore import NewGameCore import TwoFactorCore +import VDStore import XCTest final class AppCoreTests: XCTestCase { diff --git a/Examples/TicTacToe/tic-tac-toe/Tests/GameCoreTests/GameCoreTests.swift b/Examples/TicTacToe/tic-tac-toe/Tests/GameCoreTests/GameCoreTests.swift index 164dec5..b3934c8 100644 --- a/Examples/TicTacToe/tic-tac-toe/Tests/GameCoreTests/GameCoreTests.swift +++ b/Examples/TicTacToe/tic-tac-toe/Tests/GameCoreTests/GameCoreTests.swift @@ -1,5 +1,5 @@ -import VDStore import GameCore +import VDStore import XCTest final class GameCoreTests: XCTestCase { diff --git a/Examples/TicTacToe/tic-tac-toe/Tests/LoginCoreTests/LoginCoreTests.swift b/Examples/TicTacToe/tic-tac-toe/Tests/LoginCoreTests/LoginCoreTests.swift index 9ab506d..4f8ab8b 100644 --- a/Examples/TicTacToe/tic-tac-toe/Tests/LoginCoreTests/LoginCoreTests.swift +++ b/Examples/TicTacToe/tic-tac-toe/Tests/LoginCoreTests/LoginCoreTests.swift @@ -1,7 +1,7 @@ import AuthenticationClient -import VDStore import LoginCore import TwoFactorCore +import VDStore import XCTest final class LoginCoreTests: XCTestCase { diff --git a/Examples/TicTacToe/tic-tac-toe/Tests/NewGameCoreTests/NewGameCoreTests.swift b/Examples/TicTacToe/tic-tac-toe/Tests/NewGameCoreTests/NewGameCoreTests.swift index a43a218..ac4256e 100644 --- a/Examples/TicTacToe/tic-tac-toe/Tests/NewGameCoreTests/NewGameCoreTests.swift +++ b/Examples/TicTacToe/tic-tac-toe/Tests/NewGameCoreTests/NewGameCoreTests.swift @@ -1,6 +1,6 @@ -import VDStore import GameCore import NewGameCore +import VDStore import XCTest final class NewGameCoreTests: XCTestCase { diff --git a/Examples/TicTacToe/tic-tac-toe/Tests/TwoFactorCoreTests/TwoFactorCoreTests.swift b/Examples/TicTacToe/tic-tac-toe/Tests/TwoFactorCoreTests/TwoFactorCoreTests.swift index 8f918d6..a84a183 100644 --- a/Examples/TicTacToe/tic-tac-toe/Tests/TwoFactorCoreTests/TwoFactorCoreTests.swift +++ b/Examples/TicTacToe/tic-tac-toe/Tests/TwoFactorCoreTests/TwoFactorCoreTests.swift @@ -1,6 +1,6 @@ import AuthenticationClient -import VDStore import TwoFactorCore +import VDStore import XCTest final class TwoFactorCoreTests: XCTestCase { diff --git a/README.md b/README.md index f2bec2a..b092476 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ import PackageDescription let package = Package( name: "SomeProject", dependencies: [ - .package(url: "https://github.com/dankinsoid/VDStore.git", from: "0.27.0") + .package(url: "https://github.com/dankinsoid/VDStore.git", from: "0.28.0") ], targets: [ .target(name: "SomeProject", dependencies: ["VDStore"]) diff --git a/Sources/VDStore/Dependencies/Clock.swift b/Sources/VDStore/Dependencies/Clock.swift index 47c9cb3..98b184e 100644 --- a/Sources/VDStore/Dependencies/Clock.swift +++ b/Sources/VDStore/Dependencies/Clock.swift @@ -1,25 +1,25 @@ -#if (canImport(RegexBuilder) || !os(macOS) && !targetEnvironment(macCatalyst)) +#if canImport(RegexBuilder) || !os(macOS) && !targetEnvironment(macCatalyst) @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) -extension StoreDIValues { +public extension StoreDIValues { - /// The current clock that features should use when a `ContinuousClock` would be appropriate. - /// - /// By default, a live `ContinuousClock` is supplied. - /// - /// See ``suspendingClock`` to override a feature's `SuspendingClock`, instead. - public var continuousClock: any Clock { - get { get(\.continuousClock, or: ContinuousClock()) } - set { set(\.continuousClock, newValue) } - } - - /// The current clock that features should use when a `SuspendingClock` would be appropriate. - /// - /// By default, a live `SuspendingClock` is supplied. - /// - /// See ``continuousClock`` to override a feature's `ContinuousClock`, instead. - public var suspendingClock: any Clock { - get { get(\.suspendingClock, or: SuspendingClock()) } - set { set(\.suspendingClock, newValue) } - } + /// The current clock that features should use when a `ContinuousClock` would be appropriate. + /// + /// By default, a live `ContinuousClock` is supplied. + /// + /// See ``suspendingClock`` to override a feature's `SuspendingClock`, instead. + var continuousClock: any Clock { + get { get(\.continuousClock, or: ContinuousClock()) } + set { set(\.continuousClock, newValue) } + } + + /// The current clock that features should use when a `SuspendingClock` would be appropriate. + /// + /// By default, a live `SuspendingClock` is supplied. + /// + /// See ``continuousClock`` to override a feature's `ContinuousClock`, instead. + var suspendingClock: any Clock { + get { get(\.suspendingClock, or: SuspendingClock()) } + set { set(\.suspendingClock, newValue) } + } } #endif diff --git a/Sources/VDStore/Dependencies/Date.swift b/Sources/VDStore/Dependencies/Date.swift index 9b79448..5ef54f7 100644 --- a/Sources/VDStore/Dependencies/Date.swift +++ b/Sources/VDStore/Dependencies/Date.swift @@ -1,16 +1,16 @@ import Foundation -extension StoreDIValues { - - /// A dependency that returns the current date. - /// - /// By default, a "live" generator is supplied, which returns the current system date when called - /// by invoking `Date.init` under the hood. When used in tests, an "unimplemented" generator that - /// additionally reports test failures is supplied, unless explicitly overridden. - public var date: DateGenerator { - get { get(\.date, or: DateGenerator { Date() }) } - set { set(\.date, newValue) } - } +public extension StoreDIValues { + + /// A dependency that returns the current date. + /// + /// By default, a "live" generator is supplied, which returns the current system date when called + /// by invoking `Date.init` under the hood. When used in tests, an "unimplemented" generator that + /// additionally reports test failures is supplied, unless explicitly overridden. + var date: DateGenerator { + get { get(\.date, or: DateGenerator { Date() }) } + set { set(\.date, newValue) } + } } /// A dependency that generates a date. @@ -18,30 +18,30 @@ extension StoreDIValues { /// See ``StoreDIValues/date`` for more information. public struct DateGenerator: Sendable { - private var generate: @Sendable () -> Date - - /// A generator that returns a constant date. - /// - /// - Parameter now: A date to return. - /// - Returns: A generator that always returns the given date. - public static func constant(_ now: Date) -> Self { - Self { now } - } - - /// The current date. - public var now: Date { - get { self.generate() } - set { self.generate = { newValue } } - } - - /// Initializes a date generator that generates a date from a closure. - /// - /// - Parameter generate: A closure that returns the current date when called. - public init(_ generate: @escaping @Sendable () -> Date) { - self.generate = generate - } - - public func callAsFunction() -> Date { - self.generate() - } + private var generate: @Sendable () -> Date + + /// A generator that returns a constant date. + /// + /// - Parameter now: A date to return. + /// - Returns: A generator that always returns the given date. + public static func constant(_ now: Date) -> Self { + Self { now } + } + + /// The current date. + public var now: Date { + get { generate() } + set { generate = { newValue } } + } + + /// Initializes a date generator that generates a date from a closure. + /// + /// - Parameter generate: A closure that returns the current date when called. + public init(_ generate: @escaping @Sendable () -> Date) { + self.generate = generate + } + + public func callAsFunction() -> Date { + generate() + } } diff --git a/Sources/VDStore/Dependencies/Dismiss.swift b/Sources/VDStore/Dependencies/Dismiss.swift index b663403..e1ecaf1 100644 --- a/Sources/VDStore/Dependencies/Dismiss.swift +++ b/Sources/VDStore/Dependencies/Dismiss.swift @@ -1,60 +1,60 @@ import SwiftUI -extension StoreDIValues { - - public var dismiss: () -> Void { - get { get(\.dismiss, or: _dismiss) } - set { set(\.dismiss, newValue) } - } - - public var pop: () -> Void { - get { get(\.pop, or: _pop) } - set { set(\.pop, newValue) } - } +public extension StoreDIValues { + + var dismiss: () -> Void { + get { get(\.dismiss, or: _dismiss) } + set { set(\.dismiss, newValue) } + } + + var pop: () -> Void { + get { get(\.pop, or: _pop) } + set { set(\.pop, newValue) } + } } private func _dismiss() { - guard let root = UIApplication.shared.windows.first(where: \.isKeyWindow)?.rootViewController else { - return - } - let topController = root.topPresented - if topController.presentingViewController != nil { - topController.dismiss(animated: true) - } + guard let root = UIApplication.shared.windows.first(where: \.isKeyWindow)?.rootViewController else { + return + } + let topController = root.topPresented + if topController.presentingViewController != nil { + topController.dismiss(animated: true) + } } private func _pop() { - guard let root = UIApplication.shared.windows.first(where: \.isKeyWindow)?.rootViewController else { - return - } - let topController = root.topPresented - if let navController = topController.navController, navController.viewControllers.count > 1 { - navController.popViewController(animated: true) - } + guard let root = UIApplication.shared.windows.first(where: \.isKeyWindow)?.rootViewController else { + return + } + let topController = root.topPresented + if let navController = topController.navController, navController.viewControllers.count > 1 { + navController.popViewController(animated: true) + } } private extension UIViewController { - - var topPresented: UIViewController { - presentedViewController?.topPresented ?? self - } - - var navController: UINavigationController? { - (self as? UINavigationController) ?? navigationController ?? children.navController - } + + var topPresented: UIViewController { + presentedViewController?.topPresented ?? self + } + + var navController: UINavigationController? { + (self as? UINavigationController) ?? navigationController ?? children.navController + } } private extension [UIViewController] { - - var navController: UINavigationController? { - for viewController in self { - if let navController = viewController as? UINavigationController { - return navController - } - if let navController = viewController.children.navController { - return navController - } - } - return nil - } + + var navController: UINavigationController? { + for viewController in self { + if let navController = viewController as? UINavigationController { + return navController + } + if let navController = viewController.children.navController { + return navController + } + } + return nil + } } diff --git a/Sources/VDStore/Store.swift b/Sources/VDStore/Store.swift index 39cd964..baefdb6 100644 --- a/Sources/VDStore/Store.swift +++ b/Sources/VDStore/Store.swift @@ -319,11 +319,11 @@ public struct Store: Sendable { } public nonisolated func withDIValues(operation: () throws -> T) rethrows -> T { - try StoreDIValues.$current.withValue(diModifier, operation: operation) + try StoreDIValues.$current.withValue(diModifier, operation: operation) } public nonisolated func withDIValues(operation: () async throws -> T) async rethrows -> T { - try await StoreDIValues.$current.withValue(diModifier, operation: operation) + try await StoreDIValues.$current.withValue(diModifier, operation: operation) } func forceUpdateIfNeeded() { diff --git a/Sources/VDStore/Utils/DIPublisher.swift b/Sources/VDStore/Utils/DIPublisher.swift index 8255665..c6cabf3 100644 --- a/Sources/VDStore/Utils/DIPublisher.swift +++ b/Sources/VDStore/Utils/DIPublisher.swift @@ -49,7 +49,7 @@ struct DISubscriber: Subscriber { func execute(_ operation: () -> T) -> T { // StoreDIValues.$current.withValue(values) { - operation() + operation() // } } } diff --git a/Sources/VDStore/ViewStore.swift b/Sources/VDStore/ViewStore.swift index 74ff1f9..66707fc 100644 --- a/Sources/VDStore/ViewStore.swift +++ b/Sources/VDStore/ViewStore.swift @@ -34,8 +34,8 @@ public struct ViewStore: DynamicProperty { projectedValue.binding } - public init(_ store: Store) { - if store.di.isViewStore { + public init(_ store: Store) { + if store.di.isViewStore { property = .store(store) } else { property = .stateObject(