diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Reactor.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Reactor.xcscheme new file mode 100644 index 0000000..c6dcf86 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Reactor.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/AsyncReactorExample.xcodeproj/xcshareddata/xcschemes/AsyncReactorExample.xcscheme b/Example/AsyncReactorExample.xcodeproj/xcshareddata/xcschemes/AsyncReactorExample.xcscheme new file mode 100644 index 0000000..119d051 --- /dev/null +++ b/Example/AsyncReactorExample.xcodeproj/xcshareddata/xcschemes/AsyncReactorExample.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/AsyncReactorExample/ContentView.swift b/Example/AsyncReactorExample/ContentView.swift index de85640..b437d48 100644 --- a/Example/AsyncReactorExample/ContentView.swift +++ b/Example/AsyncReactorExample/ContentView.swift @@ -9,10 +9,8 @@ struct ContentView: View { } } -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - NavigationStack { - ContentView() - } +#Preview { + NavigationStack { + ContentView() } } diff --git a/Example/AsyncReactorExample/Features/Repository/Detail/RepositoryDescriptionSheet.swift b/Example/AsyncReactorExample/Features/Repository/Detail/RepositoryDescriptionSheet.swift index 463c8a9..e8a8d44 100644 --- a/Example/AsyncReactorExample/Features/Repository/Detail/RepositoryDescriptionSheet.swift +++ b/Example/AsyncReactorExample/Features/Repository/Detail/RepositoryDescriptionSheet.swift @@ -16,8 +16,6 @@ struct RepositoryDescriptionSheet: View { } } -struct RepositoryDescriptionSheet_Previews: PreviewProvider { - static var previews: some View { - RepositoryDescriptionSheet(description: "Description") - } +#Preview { + RepositoryDescriptionSheet(description: "Description") } diff --git a/Example/AsyncReactorExample/Features/Repository/Detail/RepositoryDetailView.swift b/Example/AsyncReactorExample/Features/Repository/Detail/RepositoryDetailView.swift index 38c08c8..1f10304 100644 --- a/Example/AsyncReactorExample/Features/Repository/Detail/RepositoryDetailView.swift +++ b/Example/AsyncReactorExample/Features/Repository/Detail/RepositoryDetailView.swift @@ -77,11 +77,9 @@ struct RepositoryDetailView: View { } } -struct RepositoryDetailView_Previews: PreviewProvider { - static var previews: some View { - NavigationStack { - RepositoryDetailView(repository: Repository(id: 0, name: "Test Repo", fullName: "github/Test Repo", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ultricies nisi elit, non imperdiet nibh euismod in. Sed sit amet tincidunt arcu, nec ornare nisl. Pellentesque sollicitudin quam quis elit tempus, et interdum lorem tristique. Nunc rhoncus ornare efficitur. Ut tellus libero, pretium sit amet dolor a, maximus scelerisque sem. Phasellus posuere aliquam purus. Mauris justo tellus, molestie ut eros at, lobortis luctus nulla. Nullam libero leo, sagittis ac orci nec, viverra faucibus lacus. Phasellus faucibus ipsum nec velit mattis tincidunt. Phasellus nulla mauris, lobortis ac quam non, consectetur viverra odio. Praesent sed venenatis nulla. Praesent non maximus sem, quis ultricies ligula. Aliquam eleifend non velit eget venenatis. Vivamus aliquet, nisl vestibulum cursus aliquet, neque justo feugiat magna, eu suscipit turpis mi id orci.", htmlUrl: "google.com",watchersCount: 1, forks: 1, visibility: "public", owner: Repository.Owner(avatarUrl: "https://avatars.githubusercontent.com/u/60294?v=4"))) - } - .environmentObject(RepositoryDetailReactor()) +#Preview { + NavigationStack { + RepositoryDetailView(repository: Repository(id: 0, name: "Test Repo", fullName: "github/Test Repo", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ultricies nisi elit, non imperdiet nibh euismod in. Sed sit amet tincidunt arcu, nec ornare nisl. Pellentesque sollicitudin quam quis elit tempus, et interdum lorem tristique. Nunc rhoncus ornare efficitur. Ut tellus libero, pretium sit amet dolor a, maximus scelerisque sem. Phasellus posuere aliquam purus. Mauris justo tellus, molestie ut eros at, lobortis luctus nulla. Nullam libero leo, sagittis ac orci nec, viverra faucibus lacus. Phasellus faucibus ipsum nec velit mattis tincidunt. Phasellus nulla mauris, lobortis ac quam non, consectetur viverra odio. Praesent sed venenatis nulla. Praesent non maximus sem, quis ultricies ligula. Aliquam eleifend non velit eget venenatis. Vivamus aliquet, nisl vestibulum cursus aliquet, neque justo feugiat magna, eu suscipit turpis mi id orci.", htmlUrl: "google.com",watchersCount: 1, forks: 1, visibility: "public", owner: Repository.Owner(avatarUrl: "https://avatars.githubusercontent.com/u/60294?v=4"))) } + .environmentObject(RepositoryDetailReactor()) } diff --git a/Example/AsyncReactorExample/Features/Repository/Search/RepositoryItem.swift b/Example/AsyncReactorExample/Features/Repository/Search/RepositoryItem.swift index b249662..4507ae5 100644 --- a/Example/AsyncReactorExample/Features/Repository/Search/RepositoryItem.swift +++ b/Example/AsyncReactorExample/Features/Repository/Search/RepositoryItem.swift @@ -31,8 +31,6 @@ struct RepositoryItem: View { } } -struct RepositoryItem_Previews: PreviewProvider { - static var previews: some View { - RepositoryItem(repository: Repository(id: 0, name: "Test Repo", fullName: "github/Test Repo", description: "", htmlUrl: "google.com", watchersCount: 1, forks: 1, visibility: "public", owner: Repository.Owner(avatarUrl: "https://avatars.githubusercontent.com/u/60294?v=4"))) - } +#Preview { + RepositoryItem(repository: Repository(id: 0, name: "Test Repo", fullName: "github/Test Repo", description: "", htmlUrl: "google.com", watchersCount: 1, forks: 1, visibility: "public", owner: Repository.Owner(avatarUrl: "https://avatars.githubusercontent.com/u/60294?v=4"))) } diff --git a/Example/AsyncReactorExample/Features/Repository/Search/RepositoryList.swift b/Example/AsyncReactorExample/Features/Repository/Search/RepositoryList.swift index 485a9c0..31e891f 100644 --- a/Example/AsyncReactorExample/Features/Repository/Search/RepositoryList.swift +++ b/Example/AsyncReactorExample/Features/Repository/Search/RepositoryList.swift @@ -18,13 +18,11 @@ struct RepositoryList: View { } } -struct RepositoryList_Previews: PreviewProvider { - static var previews: some View { - RepositoryList(repositories: [ - Repository(id: 0, name: "Test Repo", fullName: "github/Test Repo", description: "",htmlUrl: "google.com",watchersCount: 1, forks: 1, visibility: "public", owner: Repository.Owner(avatarUrl: "https://avatars.githubusercontent.com/u/60294?v=4")), - Repository(id: 0, name: "Test Repo", fullName: "github/Test Repo", description: "",htmlUrl: "google.com",watchersCount: 1, forks: 1, visibility: "public", owner: Repository.Owner(avatarUrl: "https://avatars.githubusercontent.com/u/60294?v=4")), - Repository(id: 0, name: "Test Repo", fullName: "github/Test Repo", description: "",htmlUrl: "google.com",watchersCount: 1, forks: 1, visibility: "public", owner: Repository.Owner(avatarUrl: "https://avatars.githubusercontent.com/u/60294?v=4")), - Repository(id: 0, name: "Test Repo", fullName: "github/Test Repo", description: "", htmlUrl: "google.com",watchersCount: 1, forks: 1,visibility: "public", owner: Repository.Owner(avatarUrl: "https://avatars.githubusercontent.com/u/60294?v=4")) - ]) - } +#Preview { + RepositoryList(repositories: [ + Repository(id: 0, name: "Test Repo", fullName: "github/Test Repo", description: "",htmlUrl: "google.com",watchersCount: 1, forks: 1, visibility: "public", owner: Repository.Owner(avatarUrl: "https://avatars.githubusercontent.com/u/60294?v=4")), + Repository(id: 0, name: "Test Repo", fullName: "github/Test Repo", description: "",htmlUrl: "google.com",watchersCount: 1, forks: 1, visibility: "public", owner: Repository.Owner(avatarUrl: "https://avatars.githubusercontent.com/u/60294?v=4")), + Repository(id: 0, name: "Test Repo", fullName: "github/Test Repo", description: "",htmlUrl: "google.com",watchersCount: 1, forks: 1, visibility: "public", owner: Repository.Owner(avatarUrl: "https://avatars.githubusercontent.com/u/60294?v=4")), + Repository(id: 0, name: "Test Repo", fullName: "github/Test Repo", description: "", htmlUrl: "google.com",watchersCount: 1, forks: 1,visibility: "public", owner: Repository.Owner(avatarUrl: "https://avatars.githubusercontent.com/u/60294?v=4")) + ]) } diff --git a/Example/AsyncReactorExample/Features/Repository/Search/RepositorySearchReactor.swift b/Example/AsyncReactorExample/Features/Repository/Search/RepositorySearchReactor.swift index 7e7bb4f..82e3053 100644 --- a/Example/AsyncReactorExample/Features/Repository/Search/RepositorySearchReactor.swift +++ b/Example/AsyncReactorExample/Features/Repository/Search/RepositorySearchReactor.swift @@ -29,7 +29,7 @@ enum SortOptions: String, CaseIterable, Identifiable { } class RepositorySearchReactor: AsyncReactor { - enum Action { + enum AsyncAction { case onHidePrivateToggle case enterQuery(String) case load @@ -66,7 +66,7 @@ class RepositorySearchReactor: AsyncReactor { } } - func action(_ action: Action) async { + func action(_ action: AsyncAction) async { switch action { case .onHidePrivateToggle: state.hidePrivate.toggle() diff --git a/Example/AsyncReactorExample/Features/Repository/Search/RepositorySearchView.swift b/Example/AsyncReactorExample/Features/Repository/Search/RepositorySearchView.swift index e979d5e..462305c 100644 --- a/Example/AsyncReactorExample/Features/Repository/Search/RepositorySearchView.swift +++ b/Example/AsyncReactorExample/Features/Repository/Search/RepositorySearchView.swift @@ -13,13 +13,26 @@ struct RepositorySearchView: View { @EnvironmentObject private var reactor: RepositorySearchReactor - @ActionBinding(RepositorySearchReactor.self, keyPath: \.hidePrivate, action: RepositorySearchReactor.SyncAction.toggleHidePrivate) + @ActionBinding( + RepositorySearchReactor.self, + keyPath: \.hidePrivate, + action: RepositorySearchReactor.SyncAction.toggleHidePrivate + ) private var hidePrivate: Bool - @ActionBinding(RepositorySearchReactor.self, keyPath: \.query, cancelId: .init(id: "enterQuery", mode: .inFlight), action: RepositorySearchReactor.Action.enterQuery) + @ActionBinding( + RepositorySearchReactor.self, + keyPath: \.query, + cancelId: .init(id: "enterQuery", mode: .inFlight), + action: RepositorySearchReactor.AsyncAction.enterQuery + ) private var query: String - @ActionBinding(RepositorySearchReactor.self, keyPath: \.sortBy, action: RepositorySearchReactor.Action.onSortOptionSelected) + @ActionBinding( + RepositorySearchReactor.self, + keyPath: \.sortBy, + action: RepositorySearchReactor.AsyncAction.onSortOptionSelected + ) private var sortOption: SortOptions var body: some View { @@ -78,11 +91,8 @@ struct RepositorySearchView: View { } } -struct RepositorySearchView_Previews: PreviewProvider { - static var previews: some View { - ReactorView(RepositorySearchReactor()) { - RepositorySearchView() - } +#Preview { + ReactorView(RepositorySearchReactor()) { + RepositorySearchView() } } - diff --git a/Sources/AsyncReactor/AsyncReactor+SwiftUI.swift b/Sources/AsyncReactor/AsyncReactor+SwiftUI.swift index 19160d0..8587241 100644 --- a/Sources/AsyncReactor/AsyncReactor+SwiftUI.swift +++ b/Sources/AsyncReactor/AsyncReactor+SwiftUI.swift @@ -21,25 +21,48 @@ public struct ActionBinding: DynamicProper let cancelId: CancelId? - public init(_ reactorType: Reactor.Type, keyPath: KeyPath, cancelId: CancelId? = nil, action: @escaping (Value) -> Reactor.Action) where Action == Reactor.Action { + public init( + _ reactorType: Reactor.Type, + keyPath: KeyPath, + cancelId: CancelId? = nil, + action: @escaping (Value) -> Reactor.AsyncAction + ) where Action == Reactor.AsyncAction { target = EnvironmentObject() self.keyPath = keyPath self.action = action self.cancelId = cancelId } - public init(_ reactorType: Reactor.Type, keyPath: KeyPath, cancelId: CancelId? = nil, action: @escaping @autoclosure () -> Reactor.Action) where Action == Reactor.Action { - self.init(reactorType, keyPath: keyPath, cancelId: cancelId, action: { _ in action() }) + public init( + _ reactorType: Reactor.Type, + keyPath: KeyPath, + cancelId: CancelId? = nil, + action: @escaping @autoclosure () -> Reactor.AsyncAction + ) where Action == Reactor.AsyncAction { + self.init( + reactorType, + keyPath: keyPath, + cancelId: cancelId, + action: { _ in action() } + ) } - public init(_ reactorType: Reactor.Type, keyPath: KeyPath, action: @escaping (Value) -> Reactor.SyncAction) where Action == Reactor.SyncAction { + public init( + _ reactorType: Reactor.Type, + keyPath: KeyPath, + action: @escaping (Value) -> Reactor.SyncAction + ) where Action == Reactor.SyncAction { target = EnvironmentObject() self.keyPath = keyPath self.action = action cancelId = nil } - public init(_ reactorType: Reactor.Type, keyPath: KeyPath, action: @escaping @autoclosure () -> Reactor.SyncAction) where Action == Reactor.SyncAction { + public init( + _ reactorType: Reactor.Type, + keyPath: KeyPath, + action: @escaping @autoclosure () -> Reactor.SyncAction + ) where Action == Reactor.SyncAction { self.init(reactorType, keyPath: keyPath, action: { _ in action() }) } @@ -51,7 +74,7 @@ public struct ActionBinding: DynamicProper public var projectedValue: Binding { get { func bindAction() -> Binding { - target.wrappedValue.bind(keyPath, cancelId: cancelId, action: action as! (Value) -> Reactor.Action) + target.wrappedValue.bind(keyPath, cancelId: cancelId, action: action as! (Value) -> Reactor.AsyncAction) } func bindSyncAction() -> Binding { @@ -60,7 +83,7 @@ public struct ActionBinding: DynamicProper if Action.self == Reactor.SyncAction.self { return bindSyncAction() - } else if Action.self == Reactor.Action.self { + } else if Action.self == Reactor.AsyncAction.self { return bindAction() } else { fatalError("this should never happen :)") @@ -76,7 +99,11 @@ public struct ReactorView: View { @StateObject private var reactor: R - public init(_ reactor: @escaping @autoclosure () -> R, definesLifecycle: Bool = true, @ViewBuilder content: () -> Content) { + public init( + _ reactor: @escaping @autoclosure () -> R, + definesLifecycle: Bool = true, + @ViewBuilder content: () -> Content + ) { _reactor = StateObject(wrappedValue: reactor()) self.content = content() self.definesLifecycle = definesLifecycle diff --git a/Sources/Reactor/Reactor+SwiftUI.swift b/Sources/Reactor/Reactor+SwiftUI.swift index 1ea4a72..2f0490f 100644 --- a/Sources/Reactor/Reactor+SwiftUI.swift +++ b/Sources/Reactor/Reactor+SwiftUI.swift @@ -22,25 +22,43 @@ public struct ActionBinding: DynamicProperty { let cancelId: CancelId? - public init(_ reactorType: R.Type, keyPath: KeyPath, cancelId: CancelId? = nil, action: @escaping (Value) -> R.Action) where Action == R.Action { + public init( + _ reactorType: R.Type, + keyPath: KeyPath, + cancelId: CancelId? = nil, + action: @escaping (Value) -> R.AsyncAction + ) where Action == R.AsyncAction { target = Environment(R.self) self.keyPath = keyPath self.action = action self.cancelId = cancelId } - public init(_ reactorType: R.Type, keyPath: KeyPath, cancelId: CancelId? = nil, action: @escaping @autoclosure () -> R.Action) where Action == R.Action { + public init( + _ reactorType: R.Type, + keyPath: KeyPath, + cancelId: CancelId? = nil, + action: @escaping @autoclosure () -> R.AsyncAction + ) where Action == R.AsyncAction { self.init(reactorType, keyPath: keyPath, cancelId: cancelId, action: { _ in action() }) } - public init(_ reactorType: R.Type, keyPath: KeyPath, action: @escaping (Value) -> R.SyncAction) where Action == R.SyncAction { + public init( + _ reactorType: R.Type, + keyPath: KeyPath, + action: @escaping (Value) -> R.SyncAction + ) where Action == R.SyncAction { target = Environment(R.self) self.keyPath = keyPath self.action = action cancelId = nil } - public init(_ reactorType: R.Type, keyPath: KeyPath, action: @escaping @autoclosure () -> R.SyncAction) where Action == R.SyncAction { + public init( + _ reactorType: R.Type, + keyPath: KeyPath, + action: @escaping @autoclosure () -> R.SyncAction + ) where Action == R.SyncAction { self.init(reactorType, keyPath: keyPath, action: { _ in action() }) } @@ -52,7 +70,7 @@ public struct ActionBinding: DynamicProperty { public var projectedValue: Binding { get { func bindAction() -> Binding { - target.wrappedValue.bind(keyPath, cancelId: cancelId, action: action as! (Value) -> R.Action) + target.wrappedValue.bind(keyPath, cancelId: cancelId, action: action as! (Value) -> R.AsyncAction) } func bindSyncAction() -> Binding { @@ -61,7 +79,7 @@ public struct ActionBinding: DynamicProperty { if Action.self == R.SyncAction.self { return bindSyncAction() - } else if Action.self == R.Action.self { + } else if Action.self == R.AsyncAction.self { return bindAction() } else { fatalError("this should never happen :)") @@ -78,7 +96,11 @@ public struct ReactorView: View { @State private var reactor: R - public init(_ reactor: @escaping @autoclosure () -> R, definesLifecycle: Bool = true, @ViewBuilder content: () -> Content) { + public init( + _ reactor: @escaping @autoclosure () -> R, + definesLifecycle: Bool = true, + @ViewBuilder content: () -> Content + ) { _reactor = State(initialValue: reactor()) self.content = content() self.definesLifecycle = definesLifecycle diff --git a/Sources/ReactorBase/ReactorBase+SwiftUI.swift b/Sources/ReactorBase/ReactorBase+SwiftUI.swift index 3b03173..dd186f3 100644 --- a/Sources/ReactorBase/ReactorBase+SwiftUI.swift +++ b/Sources/ReactorBase/ReactorBase+SwiftUI.swift @@ -10,7 +10,11 @@ import SwiftUI extension ReactorBase { @MainActor - public func bind(_ keyPath: KeyPath, cancelId: CancelId? = nil, action: @escaping (T) -> Action) -> Binding { + public func bind( + _ keyPath: KeyPath, + cancelId: CancelId? = nil, + action: @escaping (T) -> AsyncAction + ) -> Binding { Binding { self.state[keyPath: keyPath] } set: { newValue in @@ -25,12 +29,19 @@ extension ReactorBase { } @MainActor - public func bind(_ keyPath: KeyPath, cancelId: CancelId? = nil, action: @escaping @autoclosure () -> Action) -> Binding { + public func bind( + _ keyPath: KeyPath + , cancelId: CancelId? = nil, + action: @escaping @autoclosure () -> AsyncAction + ) -> Binding { bind(keyPath, cancelId: cancelId) { _ in action() } } @MainActor - public func bind(_ keyPath: KeyPath, action: @escaping (T) -> SyncAction) -> Binding { + public func bind( + _ keyPath: KeyPath, + action: @escaping (T) -> SyncAction + ) -> Binding { Binding { self.state[keyPath: keyPath] } set: { newValue in @@ -39,7 +50,10 @@ extension ReactorBase { } @MainActor - public func bind(_ keyPath: KeyPath, action: @escaping @autoclosure () -> SyncAction) -> Binding { + public func bind( + _ keyPath: KeyPath, + action: @escaping @autoclosure () -> SyncAction + ) -> Binding { bind(keyPath) { _ in action() } } } diff --git a/Sources/ReactorBase/ReactorBase.swift b/Sources/ReactorBase/ReactorBase.swift index 48f38f7..eacd155 100644 --- a/Sources/ReactorBase/ReactorBase.swift +++ b/Sources/ReactorBase/ReactorBase.swift @@ -10,13 +10,13 @@ import Foundation @MainActor @dynamicMemberLookup public protocol ReactorBase: AnyObject { - associatedtype Action = Never + associatedtype AsyncAction = Never associatedtype SyncAction = Never associatedtype State var state: State { get } - func action(_ action: Action) async + func action(_ action: AsyncAction) async func action(_ action: SyncAction) @@ -25,13 +25,13 @@ public protocol ReactorBase: AnyObject { extension ReactorBase { @MainActor - public func send(_ action: Action) { + public func send(_ action: AsyncAction) { Task { await self.action(action) } } } -extension ReactorBase where Action == Never { - public func action(_ action: Action) async { +extension ReactorBase where AsyncAction == Never { + public func action(_ action: AsyncAction) async { } } @@ -91,7 +91,7 @@ struct TaskKey: Hashable { extension ReactorBase { @MainActor - public func action(_ action: Action, id: CancelId) async { + public func action(_ action: AsyncAction, id: CancelId) async { let key = TaskKey(reactor: self, id: id) if id.mode.contains(.inFlight) { @@ -111,7 +111,7 @@ extension ReactorBase { } } - public func send(_ action: Action, id: CancelId) { + public func send(_ action: AsyncAction, id: CancelId) { Task { await self.action(action, id: id) } }