Skip to content

Instantly share code, notes, and snippets.

@saroar
Created October 8, 2021 07:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saroar/44a5dece186e4bd50e8dd2aa9cab79f6 to your computer and use it in GitHub Desktop.
Save saroar/44a5dece186e4bd50e8dd2aa9cab79f6 to your computer and use it in GitHub Desktop.
//
// AppView.swift
//
//
// Created by Saroar Khandoker on 05.05.2021.
//
import AuthClient
import AuthClientLive
import AuthenticationView
import ComposableArchitecture
import ConversationsView
import EventView
import ProfileView
import SwiftUI
import TabsView
import UserDefaultsClient
import KeychainService
import SharedModels
public enum AppState: Equatable {
case login(LoginState)
case tabs(TabsState)
public init() { self = .login(.init()) }
}
public enum AppAction: Equatable {
case onAppear
case login(LoginAction)
case tabs(TabsAction)
case logout
}
public struct AppEnvironment {
public var authenticationClient: AuthClient
public var userDefaults: UserDefaultsClient
public var mainQueue: AnySchedulerOf<DispatchQueue>
public init(
authenticationClient: AuthClient,
userDefaults: UserDefaultsClient,
mainQueue: AnySchedulerOf<DispatchQueue>
) {
self.authenticationClient = authenticationClient
self.userDefaults = userDefaults
self.mainQueue = mainQueue
}
}
public let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
loginReducer.pullback(
state: /AppState.login,
action: /AppAction.login,
environment: {
AuthenticationEnvironment(
authClient: $0.authenticationClient,
userDefaults: .live(),
mainQueue: $0.mainQueue
)
}
),
tabsReducer.pullback(
state: /AppState.tabs,
action: /AppAction.tabs,
environment: {
TabsEnvironment(
backgroundQueue: $0.mainQueue,
mainQueue: $0.mainQueue,
webSocketClient: .live
)
}
),
Reducer { state, action, environment in
switch action {
case .onAppear:
AppUserDefaults.save(false, forKey: .isAuthorized)
if environment.userDefaults.boolForKey(AppUserDefaults.Key.isAuthorized.rawValue) == true {
state = .tabs(
.init(
selectedTab: .event,
event: EventsState(),
conversations: ConversationsState(),
profile: ProfileState()
)
)
}
return .none
case let .login(.verificationResponse(.success(loginRes)))
where environment.userDefaults.boolForKey(AppUserDefaults.Key.isAuthorized.rawValue):
state = .tabs(
.init(
selectedTab: .event,
event: EventsState(),
conversations: ConversationsState(),
profile: ProfileState()
)
)
return .none
case .login:
return .none
case let .tabs(.profile(.settings(.isLogoutButton(tapped: tapped)))) where tapped:
KeychainService.save(codable: User?.none, for: .user)
KeychainService.save(codable: AuthResponse?.none, for: .token)
KeychainService.logout()
AppUserDefaults.erase()
state = .login(.init())
return environment
.userDefaults
.remove(AppUserDefaults.Key.isAuthorized.rawValue)
.fireAndForget()
case .tabs:
return .none
case .logout:
return .none
}
}
)
public struct AppView: View {
let store: Store<AppState, AppAction>
public init(store: Store<AppState, AppAction>) {
self.store = store
}
public var body: some View {
SwitchStore(self.store) {
CaseLet(state: /AppState.login, action: AppAction.login) { store in
AuthenticationView(store: store)
}
CaseLet(state: /AppState.tabs, action: AppAction.tabs) { store in
TabsView(store: store)
}
}
.onAppear {
ViewStore(store.stateless).send(.onAppear)
}
}
}
// Profile inside tabbars
.....
// Navigation from Profile to SettingsView
//
// UserSettings.swift
//
//
// Created by Saroar Khandoker on 05.05.2021.
//
import ComposableArchitecture
import UIApplicationClient
import UIKit
// import RemoteNotificationsClient
import UserDefaultsClient
import UserNotificationClient
import KeychainService
import SharedModels
import ComposablePresentation
import AuthenticationView
public struct UserSettings: Codable, Equatable {
public var colorScheme: ColorScheme
public enum ColorScheme: String, CaseIterable, Codable {
case dark
case light
case system
public var userInterfaceStyle: UIUserInterfaceStyle {
switch self {
case .dark:
return .dark
case .light:
return .light
case .system:
return .unspecified
}
}
}
public init(colorScheme: ColorScheme = .system) {
self.colorScheme = colorScheme
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
colorScheme = (try? container.decode(ColorScheme.self, forKey: .colorScheme)) ?? .system
}
}
public enum SettingsAction: Equatable {
case binding(BindingAction<SettingsState>)
case didBecomeActive
case leaveUsAReviewButtonTapped
case onAppear
case onDismiss
case userNotificationAuthorizationResponse(Result<Bool, NSError>)
case userNotificationSettingsResponse(UserNotificationClient.Notification.Settings)
case distanceView(DistanceAction)
case resetAuthData
case isLogoutButton(tapped: Bool)
case termsSheet(isPresented: Bool, url: String?)
case privacySheet(isPresented: Bool, url: String?)
case termsAndPrivacy(TermsAndPrivacyAction)
}
extension SettingsAction {
public static func view(_ localAction: SettingsView.ViewAction) -> Self {
switch localAction {
case .leaveUsAReviewButtonTapped:
return leaveUsAReviewButtonTapped
case .onAppear:
return onAppear
case .onDismiss:
return onDismiss
case let .distanceView(action):
return distanceView(action)
case .resetAuthData:
return resetAuthData
case let .isLogoutButton(tapped: tapped):
return .isLogoutButton(tapped: tapped)
case let .termsSheet(isPresented: isPresented, url: url):
return termsSheet(isPresented: isPresented, url: url)
case let .privacySheet(isPresented: isPresented, url: url):
return privacySheet(isPresented: isPresented, url: url)
}
}
}
extension SettingsState {
public var view: SettingsView.ViewState {
SettingsView.ViewState(
alert: alert,
userNotificationSettings: userNotificationSettings,
userSettings: userSettings,
distance: distance
)
}
}
public struct SettingsState: Equatable {
public init(
alert: AlertState<SettingsAction>? = nil,
userNotificationSettings: UserNotificationClient.Notification.Settings? = nil,
userSettings: UserSettings = UserSettings(),
distance: DistanceState = DistanceState(),
termsAndPrivacyState: TermsAndPrivacyState? = nil
) {
self.alert = alert
self.userNotificationSettings = userNotificationSettings
self.userSettings = userSettings
self.distance = distance
self.termsAndPrivacyState = termsAndPrivacyState
}
public var alert: AlertState<SettingsAction>?
public var userNotificationSettings: UserNotificationClient.Notification.Settings?
public var userSettings: UserSettings
public var distance: DistanceState
public var termsAndPrivacyState: TermsAndPrivacyState?
}
extension SettingsState {
public static let settingsSatate = Self(
userSettings: .init(colorScheme: .dark),
distance: .disState
)
}
public struct SettingsEnvironment {
public var applicationClient: UIApplicationClient
public var backgroundQueue: AnySchedulerOf<DispatchQueue>
public var mainQueue: AnySchedulerOf<DispatchQueue>
// public var remoteNotifications: RemoteNotificationsClient
public var userDefaults: UserDefaultsClient
public var userNotifications: UserNotificationClient
public init(
applicationClient: UIApplicationClient,
backgroundQueue: AnySchedulerOf<DispatchQueue>,
mainQueue: AnySchedulerOf<DispatchQueue>,
// remoteNotifications: RemoteNotificationsClient,
userDefaults: UserDefaultsClient,
userNotifications: UserNotificationClient
) {
self.applicationClient = applicationClient
self.backgroundQueue = backgroundQueue
self.mainQueue = mainQueue
// self.remoteNotifications = remoteNotifications
self.userDefaults = userDefaults
self.userNotifications = userNotifications
}
}
public let settingsReducer = Reducer<
SettingsState, SettingsAction, SettingsEnvironment
>.combine(
distanceReducer.pullback(
state: \.distance,
action: /SettingsAction.distanceView,
environment: {
DistanceEnvironment(
mainQueue: $0.mainQueue,
userDefaults: .live()
)
}
),
Reducer { state, action, _ in
switch action {
case let .binding(settingsState):
return .none
case .didBecomeActive:
return .none
case .leaveUsAReviewButtonTapped:
return .none
case .onAppear:
return .none
case .onDismiss:
return .none
case let .userNotificationAuthorizationResponse(value):
return .none
case let .userNotificationSettingsResponse(value):
return .none
case let .distanceView(action):
return .none
case .resetAuthData:
return .none
case let .isLogoutButton(tapped: tapped):
return .none
// swiftlint:disable pattern_matching_keywords
case .termsSheet(isPresented: let isPresented, url: let url):
state.termsAndPrivacyState = isPresented ? TermsAndPrivacyState(urlString: url) : nil
return .none
// swiftlint:disable pattern_matching_keywords
case .privacySheet(isPresented: let isPresented, url: let url):
return .none
case .termsAndPrivacy(_):
return .none
}
}
)
.presents(
termsAndPrivacyReducer,
state: \.termsAndPrivacyState,
action: /SettingsAction.termsAndPrivacy,
environment: { _ in
TermsAndPrivacyEnvironment()
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment