From 29551920fdb58e5133e76675128393be9bcf13dd Mon Sep 17 00:00:00 2001 From: Alessandro Martin Date: Mon, 26 Mar 2018 23:56:13 +0100 Subject: [PATCH 1/6] Many possible modifications --- .gitignore | 3 + ReduxExample/AppDelegate.swift | 3 + ReduxExample/Redux.swift | 175 +++++++++++++++++++++--------- ReduxExample/ViewController.swift | 25 +++-- 4 files changed, 143 insertions(+), 63 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c811eb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/ReduxExample.xcworkspace/xcuserdata/alessandromartin.xcuserdatad/UserInterfaceState.xcuserstate +/ReduxExample.xcodeproj/xcuserdata/alessandromartin.xcuserdatad/xcschemes/xcschememanagement.plist +/Pods/Pods.xcodeproj/xcuserdata/alessandromartin.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/ReduxExample/AppDelegate.swift b/ReduxExample/AppDelegate.swift index a774566..68973ec 100644 --- a/ReduxExample/AppDelegate.swift +++ b/ReduxExample/AppDelegate.swift @@ -8,6 +8,9 @@ import UIKit +// It's a var so it can be replaced and the app state fully reset +var store = Store(reducer: appReducer, state: .initial) + @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { diff --git a/ReduxExample/Redux.swift b/ReduxExample/Redux.swift index 23f4acf..7851eae 100644 --- a/ReduxExample/Redux.swift +++ b/ReduxExample/Redux.swift @@ -1,82 +1,155 @@ // -// GenericRedux.swift +// Redux.swift // ReduxExample // // Created by Jonathan Cravotta on 3/25/18. // Copyright © 2018 Jonathan Cravotta. All rights reserved. // -import Foundation import ReactiveSwift -protocol Store { - associatedtype State - associatedtype Action - - var state: MutableProperty { get } - func reducer(_ action: Action, _ state: State) -> State -} +protocol StateType {} +protocol Action {} + +typealias Reducer = (Action, ReducerStateType) -> ReducerStateType + +final class Store { + + let reducer: Reducer + let state: MutableProperty + + init(reducer: @escaping Reducer, state: State) { + self.reducer = reducer + self.state = MutableProperty(state) + } -extension Store { func dispatch(action: Action) { state.value = reducer(action, state.value) } + + @discardableResult + public func observeSignal(keyPath: KeyPath, action: @escaping (Part) -> Void) -> Disposable? { + return state.signal.combinePrevious().observeValues { previous, current in + let previousPart = previous[keyPath: keyPath] + let currentPart = current[keyPath: keyPath] + + if previousPart != currentPart { + action(currentPart) + } + } + } + + @discardableResult + public func observeProducer(keyPath: KeyPath, action: @escaping (Part) -> Void) -> Disposable { + return state.producer.combinePrevious().startWithValues { previous, current in + let previousPart = previous[keyPath: keyPath] + let currentPart = current[keyPath: keyPath] + + if previousPart != currentPart { + action(currentPart) + } + } + } } // Example: -struct User { + +struct AppState: StateType { + let user: User + let appConfiguration: AppConfiguration + + static let initial = AppState(user: .anonymous, appConfiguration: .default) +} + +// Note: equatable conformance for types like this should be automatic in Swift 4.1 +struct AppConfiguration: Equatable { + var arePushNotificationsEnabled: Bool + var isDarkThemed: Bool + + static let `default` = AppConfiguration(arePushNotificationsEnabled: false, isDarkThemed: false) + + static func ==(lhs: AppConfiguration, rhs: AppConfiguration) -> Bool { + return lhs.arePushNotificationsEnabled == rhs.arePushNotificationsEnabled + && lhs.isDarkThemed == rhs.isDarkThemed + } +} + +struct TogglePushNotifications: Action { + let enable: Bool +} + +struct ToggleDarkTheme: Action { + let enable: Bool +} + +// Note: equatable conformance for types like this should be automatic in Swift 4.1 +struct User: Equatable { var name: String var zipcode: Int var sizes: [Int] + + static let anonymous = User(name: "Anonymous", zipcode: 90210, sizes: []) + + static func ==(lhs: User, rhs: User) -> Bool { + return lhs.name == rhs.name + && lhs.zipcode == rhs.zipcode + && lhs.sizes == rhs.sizes + } } -enum UserAction { - case updateName(String) - case updateZip(Int) - case updateSizes([Int]) - case updateZipAndSizes(zip: Int, sizes: [Int]) +struct UserName: Action { + let name: String } -class UserStore: Store { - - typealias Action = UserAction - typealias State = User - - var state: MutableProperty +struct UserZipCode: Action { + let zipcode: Int +} - init(user: User) { - self.state = MutableProperty(user) - } - - func reducer(_ action: Action, _ state: User) -> User { - var newState = state - - switch action { - case .updateName(let name): newState.name = name - case .updateZip(let zip): newState.zipcode = zip - case .updateSizes(let sizes): newState.sizes = sizes - - case let .updateZipAndSizes(zip, sizes): - newState.zipcode = zip - newState.sizes = sizes - } - - return newState - } +struct UserSizes: Action { + let sizes: [Int] } +struct UserZipCodeAndSizes: Action{ + let zipcode: Int + let sizes: [Int] +} -//:D -extension MutableProperty { - @discardableResult - public func observeProducer(_ value: @escaping (MutableProperty.Value) -> Void) -> Disposable { - return producer.startWithValues(value) - } - - @discardableResult - public func observeSignal(_ value: @escaping (MutableProperty.Value) -> Void) -> Disposable? { - return signal.observeValues(value) +func appReducer(_ action: Action, _ state: AppState) -> AppState { + return AppState(user: userReducer(action, state.user), + appConfiguration: appConfigurationReducer(action, state.appConfiguration)) +} + +func userReducer(_ action: Action, _ state: User) -> User { + var newState = state + + switch action { + case let action as UserName: + newState.name = action.name + case let action as UserZipCode: + newState.zipcode = action.zipcode + case let action as UserSizes: + newState.sizes = action.sizes + case let action as UserZipCodeAndSizes: + newState.zipcode = action.zipcode + newState.sizes = action.sizes + default: + break } + + return newState } +func appConfigurationReducer(_ action: Action, _ state: AppConfiguration) -> AppConfiguration { + var newState = state + switch action { + case let action as TogglePushNotifications: + newState.arePushNotificationsEnabled = action.enable + case let action as ToggleDarkTheme: + newState.isDarkThemed = action.enable + default: + break + } + + return newState +} diff --git a/ReduxExample/ViewController.swift b/ReduxExample/ViewController.swift index f9a93b7..642d29a 100644 --- a/ReduxExample/ViewController.swift +++ b/ReduxExample/ViewController.swift @@ -8,36 +8,37 @@ import UIKit -class ViewController: UIViewController { +final class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. + let t = UserExample() t.test() } } -class UserExample { - - let userStore: UserStore +final class UserExample { init() { - let user = User(name: "Foo", zipcode: 10021, sizes: [4, 5]) - self.userStore = UserStore(user: user) observe() } func test() { - userStore.dispatch(action: .updateName("Baz")) - userStore.dispatch(action: .updateSizes([0, 2])) - userStore.dispatch(action: .updateZip(10014)) - userStore.dispatch(action: .updateZipAndSizes(zip: 10013, sizes: [4,5,6,8])) + store.dispatch(action: UserName(name: "Baz")) + store.dispatch(action: UserSizes(sizes: [0, 2])) + store.dispatch(action: UserZipCode(zipcode: 10014)) + store.dispatch(action: UserZipCodeAndSizes(zipcode: 10013, sizes: [4,5,6,8])) + store.dispatch(action: ToggleDarkTheme(enable: true)) } func observe() { - userStore.state.observeProducer { (user) in + store.observeProducer(keyPath: \.user) { user in print("Name: \(user.name), Zip: \(user.zipcode), Sizes: \(user.sizes.map { $0 })") } + + store.observeProducer(keyPath: \.appConfiguration) { appConfiguration in + print("Push Notifications enabled: \(appConfiguration.arePushNotificationsEnabled), Dark Theme: \(appConfiguration.isDarkThemed)") + } } } From 1061111620a1ad6e41399a3d207e2a6e3bf46714 Mon Sep 17 00:00:00 2001 From: Alessandro Martin Date: Tue, 27 Mar 2018 00:18:46 +0100 Subject: [PATCH 2/6] Code structure improvements --- ReduxExample.xcodeproj/project.pbxproj | 12 +++ ReduxExample/AppConfiguration.swift | 45 +++++++++++ ReduxExample/AppState.swift | 20 +++++ ReduxExample/Redux.swift | 102 ------------------------- ReduxExample/User.swift | 63 +++++++++++++++ 5 files changed, 140 insertions(+), 102 deletions(-) create mode 100644 ReduxExample/AppConfiguration.swift create mode 100644 ReduxExample/AppState.swift create mode 100644 ReduxExample/User.swift diff --git a/ReduxExample.xcodeproj/project.pbxproj b/ReduxExample.xcodeproj/project.pbxproj index cfa8808..f596b45 100644 --- a/ReduxExample.xcodeproj/project.pbxproj +++ b/ReduxExample.xcodeproj/project.pbxproj @@ -16,6 +16,9 @@ 1CF6803220692DA30022C9E7 /* ReduxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF6803120692DA30022C9E7 /* ReduxTests.swift */; }; 1CF6803920692DC30022C9E7 /* Redux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF6802720687A000022C9E7 /* Redux.swift */; }; 895901BBDECC2146CC7DEE8E /* Pods_ReduxTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36947F3EFFA34BEFF75C6EDA /* Pods_ReduxTests.framework */; }; + BF6A98DA2069B5D700B4D05E /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6A98D92069B5D700B4D05E /* AppState.swift */; }; + BF6A98DC2069B5FB00B4D05E /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6A98DB2069B5FB00B4D05E /* User.swift */; }; + BF6A98DE2069B65A00B4D05E /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6A98DD2069B65A00B4D05E /* AppConfiguration.swift */; }; FDDE9FCDBEA4910EB4969E60 /* Pods_ReduxExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0F7A592F2BBBDB070D9E1D6 /* Pods_ReduxExample.framework */; }; /* End PBXBuildFile section */ @@ -44,6 +47,9 @@ 36947F3EFFA34BEFF75C6EDA /* Pods_ReduxTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReduxTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 63E6A6F89869742E0F68463B /* Pods-ReduxExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReduxExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-ReduxExample/Pods-ReduxExample.release.xcconfig"; sourceTree = ""; }; 8C3A44712CC3EA2BA6F64619 /* Pods-ReduxTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReduxTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ReduxTests/Pods-ReduxTests.release.xcconfig"; sourceTree = ""; }; + BF6A98D92069B5D700B4D05E /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; + BF6A98DB2069B5FB00B4D05E /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + BF6A98DD2069B65A00B4D05E /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = ""; }; D12C02EA05B7841D17CB7E14 /* Pods-ReduxTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReduxTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ReduxTests/Pods-ReduxTests.debug.xcconfig"; sourceTree = ""; }; DC904AC289E2A89F33F26855 /* Pods-ReduxExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReduxExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ReduxExample/Pods-ReduxExample.debug.xcconfig"; sourceTree = ""; }; F0F7A592F2BBBDB070D9E1D6 /* Pods_ReduxExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReduxExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -99,6 +105,9 @@ 1CF6801C2066AF840022C9E7 /* LaunchScreen.storyboard */, 1CF6801F2066AF840022C9E7 /* Info.plist */, 1CF6802720687A000022C9E7 /* Redux.swift */, + BF6A98DD2069B65A00B4D05E /* AppConfiguration.swift */, + BF6A98D92069B5D700B4D05E /* AppState.swift */, + BF6A98DB2069B5FB00B4D05E /* User.swift */, ); path = ReduxExample; sourceTree = ""; @@ -354,9 +363,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BF6A98DE2069B65A00B4D05E /* AppConfiguration.swift in Sources */, 1CF680162066AF840022C9E7 /* ViewController.swift in Sources */, + BF6A98DC2069B5FB00B4D05E /* User.swift in Sources */, 1CF680142066AF840022C9E7 /* AppDelegate.swift in Sources */, 1CF6802820687A000022C9E7 /* Redux.swift in Sources */, + BF6A98DA2069B5D700B4D05E /* AppState.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ReduxExample/AppConfiguration.swift b/ReduxExample/AppConfiguration.swift new file mode 100644 index 0000000..f61b21e --- /dev/null +++ b/ReduxExample/AppConfiguration.swift @@ -0,0 +1,45 @@ +// +// AppConfiguration.swift +// ReduxExample +// +// Created by Alessandro Martin on 03/27/2018. +// Copyright © 2018 Jonathan Cravotta. All rights reserved. +// + +// Note: equatable conformance for types like this should be automatic in Swift 4.1 +struct AppConfiguration: Equatable { + var arePushNotificationsEnabled: Bool + var isDarkThemed: Bool + + static let `default` = AppConfiguration(arePushNotificationsEnabled: false, isDarkThemed: false) + + static func ==(lhs: AppConfiguration, rhs: AppConfiguration) -> Bool { + return lhs.arePushNotificationsEnabled == rhs.arePushNotificationsEnabled + && lhs.isDarkThemed == rhs.isDarkThemed + } +} + +//MARK: - Reducer +func appConfigurationReducer(_ action: Action, _ state: AppConfiguration) -> AppConfiguration { + var newState = state + + switch action { + case let action as TogglePushNotifications: + newState.arePushNotificationsEnabled = action.enable + case let action as ToggleDarkTheme: + newState.isDarkThemed = action.enable + default: + break + } + + return newState +} + +//MARK: - Actions +struct TogglePushNotifications: Action { + let enable: Bool +} + +struct ToggleDarkTheme: Action { + let enable: Bool +} diff --git a/ReduxExample/AppState.swift b/ReduxExample/AppState.swift new file mode 100644 index 0000000..1c0090f --- /dev/null +++ b/ReduxExample/AppState.swift @@ -0,0 +1,20 @@ +// +// AppState.swift +// ReduxExample +// +// Created by Alessandro Martin on 03/27/2018. +// Copyright © 2018 Jonathan Cravotta. All rights reserved. +// + +struct AppState: StateType { + let user: User + let appConfiguration: AppConfiguration + + static let initial = AppState(user: .anonymous, appConfiguration: .default) +} + +//MARK: - Reducer +func appReducer(_ action: Action, _ state: AppState) -> AppState { + return AppState(user: userReducer(action, state.user), + appConfiguration: appConfigurationReducer(action, state.appConfiguration)) +} diff --git a/ReduxExample/Redux.swift b/ReduxExample/Redux.swift index 7851eae..56d7fbf 100644 --- a/ReduxExample/Redux.swift +++ b/ReduxExample/Redux.swift @@ -51,105 +51,3 @@ final class Store { } } } - -// Example: - -struct AppState: StateType { - let user: User - let appConfiguration: AppConfiguration - - static let initial = AppState(user: .anonymous, appConfiguration: .default) -} - -// Note: equatable conformance for types like this should be automatic in Swift 4.1 -struct AppConfiguration: Equatable { - var arePushNotificationsEnabled: Bool - var isDarkThemed: Bool - - static let `default` = AppConfiguration(arePushNotificationsEnabled: false, isDarkThemed: false) - - static func ==(lhs: AppConfiguration, rhs: AppConfiguration) -> Bool { - return lhs.arePushNotificationsEnabled == rhs.arePushNotificationsEnabled - && lhs.isDarkThemed == rhs.isDarkThemed - } -} - -struct TogglePushNotifications: Action { - let enable: Bool -} - -struct ToggleDarkTheme: Action { - let enable: Bool -} - -// Note: equatable conformance for types like this should be automatic in Swift 4.1 -struct User: Equatable { - var name: String - var zipcode: Int - var sizes: [Int] - - static let anonymous = User(name: "Anonymous", zipcode: 90210, sizes: []) - - static func ==(lhs: User, rhs: User) -> Bool { - return lhs.name == rhs.name - && lhs.zipcode == rhs.zipcode - && lhs.sizes == rhs.sizes - } -} - -struct UserName: Action { - let name: String -} - -struct UserZipCode: Action { - let zipcode: Int -} - -struct UserSizes: Action { - let sizes: [Int] -} - -struct UserZipCodeAndSizes: Action{ - let zipcode: Int - let sizes: [Int] -} - -func appReducer(_ action: Action, _ state: AppState) -> AppState { - return AppState(user: userReducer(action, state.user), - appConfiguration: appConfigurationReducer(action, state.appConfiguration)) -} - -func userReducer(_ action: Action, _ state: User) -> User { - var newState = state - - switch action { - case let action as UserName: - newState.name = action.name - case let action as UserZipCode: - newState.zipcode = action.zipcode - case let action as UserSizes: - newState.sizes = action.sizes - case let action as UserZipCodeAndSizes: - newState.zipcode = action.zipcode - newState.sizes = action.sizes - default: - break - } - - return newState -} - -func appConfigurationReducer(_ action: Action, _ state: AppConfiguration) -> AppConfiguration { - var newState = state - - switch action { - case let action as TogglePushNotifications: - newState.arePushNotificationsEnabled = action.enable - case let action as ToggleDarkTheme: - newState.isDarkThemed = action.enable - default: - break - } - - return newState -} diff --git a/ReduxExample/User.swift b/ReduxExample/User.swift new file mode 100644 index 0000000..25cd89e --- /dev/null +++ b/ReduxExample/User.swift @@ -0,0 +1,63 @@ +// +// User.swift +// ReduxExample +// +// Created by Alessandro Martin on 03/27/2018. +// Copyright © 2018 Jonathan Cravotta. All rights reserved. +// + +struct User { + var name: String + var zipcode: Int + var sizes: [Int] + + static let anonymous = User(name: "Anonymous", zipcode: 90210, sizes: []) +} + +// Note: equatable conformance for types like this should be automatic in Swift 4.1 +extension User: Equatable { + static func ==(lhs: User, rhs: User) -> Bool { + return lhs.name == rhs.name + && lhs.zipcode == rhs.zipcode + && lhs.sizes == rhs.sizes + } +} + +//MARK: - Reducer +func userReducer(_ action: Action, _ state: User) -> User { + var newState = state + + switch action { + case let action as UserName: + newState.name = action.name + case let action as UserZipCode: + newState.zipcode = action.zipcode + case let action as UserSizes: + newState.sizes = action.sizes + case let action as UserZipCodeAndSizes: + newState.zipcode = action.zipcode + newState.sizes = action.sizes + default: + break + } + + return newState +} + +//MARK: - Actions +struct UserName: Action { + let name: String +} + +struct UserZipCode: Action { + let zipcode: Int +} + +struct UserSizes: Action { + let sizes: [Int] +} + +struct UserZipCodeAndSizes: Action{ + let zipcode: Int + let sizes: [Int] +} From 215297bda6677fd5fe766ea8187d517491014a6b Mon Sep 17 00:00:00 2001 From: Alessandro Martin Date: Tue, 27 Mar 2018 00:20:55 +0100 Subject: [PATCH 3/6] Cleanup --- ReduxExample/Redux.swift | 2 +- ReduxExample/ViewController.swift | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ReduxExample/Redux.swift b/ReduxExample/Redux.swift index 56d7fbf..83a2994 100644 --- a/ReduxExample/Redux.swift +++ b/ReduxExample/Redux.swift @@ -23,7 +23,7 @@ final class Store { self.state = MutableProperty(state) } - func dispatch(action: Action) { + func dispatch(_ action: Action) { state.value = reducer(action, state.value) } diff --git a/ReduxExample/ViewController.swift b/ReduxExample/ViewController.swift index 642d29a..5a990ff 100644 --- a/ReduxExample/ViewController.swift +++ b/ReduxExample/ViewController.swift @@ -25,11 +25,11 @@ final class UserExample { } func test() { - store.dispatch(action: UserName(name: "Baz")) - store.dispatch(action: UserSizes(sizes: [0, 2])) - store.dispatch(action: UserZipCode(zipcode: 10014)) - store.dispatch(action: UserZipCodeAndSizes(zipcode: 10013, sizes: [4,5,6,8])) - store.dispatch(action: ToggleDarkTheme(enable: true)) + store.dispatch(UserName(name: "Baz")) + store.dispatch(UserSizes(sizes: [0, 2])) + store.dispatch(UserZipCode(zipcode: 10014)) + store.dispatch(UserZipCodeAndSizes(zipcode: 10013, sizes: [4,5,6,8])) + store.dispatch(ToggleDarkTheme(enable: true)) } func observe() { From d885eee68c68e01679f32623be64dc039c28e8bc Mon Sep 17 00:00:00 2001 From: Alessandro Martin Date: Tue, 27 Mar 2018 00:52:22 +0100 Subject: [PATCH 4/6] Gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4c811eb..5e14c6c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /ReduxExample.xcworkspace/xcuserdata/alessandromartin.xcuserdatad/UserInterfaceState.xcuserstate /ReduxExample.xcodeproj/xcuserdata/alessandromartin.xcuserdatad/xcschemes/xcschememanagement.plist /Pods/Pods.xcodeproj/xcuserdata/alessandromartin.xcuserdatad/xcschemes/xcschememanagement.plist +/ReduxExample.xcworkspace/xcuserdata/alessandromartin.xcuserdatad From 723bde91545fa404600b55935ed02ee40a19955c Mon Sep 17 00:00:00 2001 From: Alessandro Martin Date: Tue, 27 Mar 2018 00:54:06 +0100 Subject: [PATCH 5/6] Playing around with user defaults and side effects --- ReduxExample/AppConfiguration.swift | 28 ++++++++++++++++++++++++++++ ReduxExample/AppDelegate.swift | 11 ++++++++--- ReduxExample/ViewController.swift | 4 +++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/ReduxExample/AppConfiguration.swift b/ReduxExample/AppConfiguration.swift index f61b21e..b473cdc 100644 --- a/ReduxExample/AppConfiguration.swift +++ b/ReduxExample/AppConfiguration.swift @@ -6,6 +6,8 @@ // Copyright © 2018 Jonathan Cravotta. All rights reserved. // +import Foundation + // Note: equatable conformance for types like this should be automatic in Swift 4.1 struct AppConfiguration: Equatable { var arePushNotificationsEnabled: Bool @@ -28,6 +30,10 @@ func appConfigurationReducer(_ action: Action, _ state: AppConfiguration) -> App newState.arePushNotificationsEnabled = action.enable case let action as ToggleDarkTheme: newState.isDarkThemed = action.enable + case is LoadAppConfiguration: + newState = loadAppConfiguration() + case is SaveAppConfiguration: + saveAppConfiguration(newState) default: break } @@ -43,3 +49,25 @@ struct TogglePushNotifications: Action { struct ToggleDarkTheme: Action { let enable: Bool } + +struct LoadAppConfiguration: Action {} +private func loadAppConfiguration(from defaults: UserDefaults = .standard) -> AppConfiguration { + let pushNotificationsEnabled = defaults.bool(forKey: .pushNotificationsKey) + let darkThemeEnabled = defaults.bool(forKey: .darkThemeKey) + + return AppConfiguration(arePushNotificationsEnabled: pushNotificationsEnabled, + isDarkThemed: darkThemeEnabled) +} + +struct SaveAppConfiguration: Action {} +private func saveAppConfiguration(_ appConfiguration: AppConfiguration, to defaults: UserDefaults = .standard) { + defaults.set(appConfiguration.arePushNotificationsEnabled, forKey: .pushNotificationsKey) + defaults.set(appConfiguration.isDarkThemed, forKey: .darkThemeKey) + defaults.synchronize() + print("Configuration saved!") +} + +private extension String { + static let pushNotificationsKey = "PushNotificationsEnabled" + static let darkThemeKey = "DarkThemeEnabled" +} diff --git a/ReduxExample/AppDelegate.swift b/ReduxExample/AppDelegate.swift index 68973ec..8ee4d8d 100644 --- a/ReduxExample/AppDelegate.swift +++ b/ReduxExample/AppDelegate.swift @@ -11,6 +11,10 @@ import UIKit // It's a var so it can be replaced and the app state fully reset var store = Store(reducer: appReducer, state: .initial) +func state() -> AppState { + return store.state.value +} + @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -18,7 +22,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + + store.dispatch(LoadAppConfiguration()) + return true } @@ -30,6 +36,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + store.dispatch(SaveAppConfiguration()) } func applicationWillEnterForeground(_ application: UIApplication) { @@ -43,7 +50,5 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - - } diff --git a/ReduxExample/ViewController.swift b/ReduxExample/ViewController.swift index 5a990ff..d8d5bd8 100644 --- a/ReduxExample/ViewController.swift +++ b/ReduxExample/ViewController.swift @@ -29,7 +29,9 @@ final class UserExample { store.dispatch(UserSizes(sizes: [0, 2])) store.dispatch(UserZipCode(zipcode: 10014)) store.dispatch(UserZipCodeAndSizes(zipcode: 10013, sizes: [4,5,6,8])) - store.dispatch(ToggleDarkTheme(enable: true)) + let isDarkThemed = state().appConfiguration.isDarkThemed + print("Before toggling dark theme: \(isDarkThemed)") + store.dispatch(ToggleDarkTheme(enable: !isDarkThemed)) } func observe() { From a1b5ad7bbb68d9beb4c2901dee3dabedae5a0d3d Mon Sep 17 00:00:00 2001 From: Alessandro Martin Date: Tue, 27 Mar 2018 00:55:53 +0100 Subject: [PATCH 6/6] Matching print --- ReduxExample/AppConfiguration.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ReduxExample/AppConfiguration.swift b/ReduxExample/AppConfiguration.swift index b473cdc..a05f075 100644 --- a/ReduxExample/AppConfiguration.swift +++ b/ReduxExample/AppConfiguration.swift @@ -54,6 +54,7 @@ struct LoadAppConfiguration: Action {} private func loadAppConfiguration(from defaults: UserDefaults = .standard) -> AppConfiguration { let pushNotificationsEnabled = defaults.bool(forKey: .pushNotificationsKey) let darkThemeEnabled = defaults.bool(forKey: .darkThemeKey) + print("Configuration loaded!") return AppConfiguration(arePushNotificationsEnabled: pushNotificationsEnabled, isDarkThemed: darkThemeEnabled)