Skip to content

Instantly share code, notes, and snippets.

@ruslankuksa
Last active May 27, 2021 11:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ruslankuksa/69dc127093ec0ae5b709a629519cfa72 to your computer and use it in GitHub Desktop.
Save ruslankuksa/69dc127093ec0ae5b709a629519cfa72 to your computer and use it in GitHub Desktop.
Composable Architecture Crash
import SwiftUI
import ComposableArchitecture
struct AdminScheduleView: View {
let store: Store<UserState, UserAction>
init(store: Store<UserState, UserAction>) {
self.store = store
}
var body: some View {
IfLetStore(store.scope(state: \.adminState, action: UserAction.adminAction)) { adminStore in
WithViewStore(adminStore) { viewStore in
ZStack {
VStack(alignment: .leading, spacing: 20) {
//...
HStack {
FilterButton {
viewStore.send(.filterButtonTapped)
}
.sheet(isPresented: viewStore.binding(get: \.showFiltersSheet, send: .dismissFiltersSheet).removeDuplicates()) {
IfLetStore(adminStore.scope(state: \.filterState, action: AdminAction.filtersAction)) { filterStore in
FilterView(store: filterStore)
}
}
Spacer()
//...
}
.padding(.horizontal, 20)
}
}
}
}
}
}
struct AdminState: Equatable {
var showFiltersSheet: Bool = false
var filterState: FiltersState?
}
let adminReducer = Reducer<AdminState, AdminAction, AdminEnvironment>.combine(
filterReducer.optional().pullback(state: \.filterState,
action: /AdminAction.filtersAction,
environment: { _ in FiltersEnvironment(client: .live,
mainQueue: DispatchQueue.main.eraseToAnyScheduler()) }
),
Reducer { state, action, environment in
switch action {
case .filterButtonTapped:
state.filterState = FiltersState()
state.showFiltersSheet = true
return .none
case .dismissFiltersSheet:
state.showFiltersSheet = false
state.filterState = nil
debugPrint("Dismiss filters")
return .none
//...
}
}
)
import Foundation
import SwiftUI
import QGrid
import ComposableArchitecture
struct DashboardView: View {
let store: Store<UserState, UserAction>
init(store: Store<UserState, UserAction>) {
self.store = store
}
var body: some View {
WithViewStore(store) { viewStore in
ZStack {
VStack(alignment: .leading) {
Text("Choose your company")
.font(.system(size: 28, weight: .heavy))
.foregroundColor(.scPrimaryGray)
QGrid(viewStore.user.companies, columns: 2, vSpacing: 15, hSpacing: 15, vPadding: 0, hPadding: 0, isScrollable: true, showScrollIndicators: false) { company in
DashboardButton(title: company.role.capitalizeFirst, subtitle: company.name) {
viewStore.send(.setCompany(company))
}
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.padding(.horizontal, 20)
.padding(.vertical, 16)
ZStack {
//Workaround for navigation bug in iOS 14.5
NavigationLink(destination: EmptyView()) {
EmptyView()
}
NavigationLink(
destination: DriverScheduleView(store: store),
tag: .driver,
selection: viewStore.binding(get: \.user.role,
send: UserAction.scheduleDismissed)
.removeDuplicates()
) {
EmptyView()
}
NavigationLink(
destination: AdminScheduleView(store: store),
tag: .admin,
selection: viewStore.binding(get: \.user.role,
send: UserAction.scheduleDismissed)
.removeDuplicates()
) {
EmptyView()
}
}
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
.overlay(
viewStore.requestInProgress ? OverlayView() : nil
)
.onAppear() {
viewStore.send(.sendDeviceToken)
}
}
}
}
import Foundation
import ComposableArchitecture
enum FilterType: String, CaseIterable, Equatable {
case all = "All"
case quick = "Quick"
}
enum FiltersAction: Equatable {
case setTicketsFilter(FilterType)
case setDriverFilter(Driver)
case showSearchSheet
case fetchDrivers(_ search: String)
case applyFilters(FilterType, Driver?)
case changeDriverName(String)
case fetchDriverResult(Result<[Driver], ApiError>)
}
struct FiltersEnvironment {
var client: TicketCreationClient
var mainQueue: AnySchedulerOf<DispatchQueue>
}
private struct DriverId: Hashable {}
struct FiltersState: Equatable, Identifiable {
var id: String {
UUID().uuidString
}
var driverName: String = ""
var ticketFilter: FilterType = .all
var selectedDriver: Driver?
var drivers: [Driver] = []
var presentSearchSheet: Bool = false
var requestInProgress: Bool = false
}
let filterReducer = Reducer<FiltersState, FiltersAction, FiltersEnvironment> { state, action, environment in
switch action {
case .setTicketsFilter(let filter):
state.ticketFilter = filter
return .none
case .setDriverFilter(let driver):
let driverName = "\(driver.firstName) \(driver.lastName)"
state.selectedDriver = driver
state.presentSearchSheet = false
return Effect(value: .changeDriverName(driverName))
case .showSearchSheet:
state.presentSearchSheet.toggle()
return .none
case .fetchDrivers(let name):
state.requestInProgress = true
return environment.client.fetchDrivers(name)
.map(\.drivers)
.receive(on: environment.mainQueue)
.catchToEffect()
.debounce(id: DriverId(), for: 0.3, scheduler: environment.mainQueue)
.map(FiltersAction.fetchDriverResult)
case .fetchDriverResult(.success(let drivers)):
state.requestInProgress = false
state.drivers = drivers
return .none
case .fetchDriverResult(.failure(let error)):
state.requestInProgress = false
debugPrint(error)
return .none
case .applyFilters(let filter, let driver):
return .none
case .changeDriverName(let name):
state.driverName = name
return .none
}
}
import Foundation
import ComposableArchitecture
enum UserAction: Equatable {
case getCompanies
case setCompany(UserCompany)
case setCompanyResponse(Result<DefaultResponse, ApiError>)
case scheduleDismissed
case signOut
case driverAction(DriverAction)
case adminAction(AdminAction)
case sendDeviceToken
case sendDeviceTokenResult(Result<DefaultResponse, ApiError>)
}
struct UserEnvironment {
let userServicesClient: UserServicesClient = .liveClient
let mainQueue: AnySchedulerOf<DispatchQueue> = DispatchQueue.main.eraseToAnyScheduler()
}
struct UserState: Equatable {
var user: User
var driverState: DriverState?
var adminState: AdminState?
var requestInProgress: Bool = false
}
let userReducer = Reducer<UserState, UserAction, UserEnvironment>.combine(
driverReducer.optional().pullback(state: \.driverState,
action: /UserAction.driverAction,
environment: { _ in SharedEnvironment.live(environment: DriverEnvironment())}
),
adminReducer.optional().pullback(state: \.adminState,
action: /UserAction.adminAction,
environment: { _ in AdminEnvironment(ticketsOperationClient: .live,
mainQueue: DispatchQueue.main.eraseToAnyScheduler())}
),
Reducer { state, action, environment in
switch action {
case .getCompanies:
return .none
case .setCompany(let company):
state.user.currentCompany = company
state.requestInProgress = true
return environment.userServicesClient.setUserCompany(company.id, company.role)
.receive(on: environment.mainQueue)
.catchToEffect()
.map(UserAction.setCompanyResponse)
case .setCompanyResponse(.success(let response)):
state.requestInProgress = false
guard let company = state.user.currentCompany else {
debugPrint("User doesn't have an active company")
return .none
}
state.user.role = UserRole(rawValue: company.role)
switch state.user.role {
case .driver:
state.driverState = DriverState()
case .admin:
state.adminState = AdminState()
case .scheduler, .user, .none:
return .none
}
return .none
case .setCompanyResponse(.failure(let error)):
print(error)
state.requestInProgress = false
return .none
case .sendDeviceToken:
let value = UserDefaults.standard.bool(forKey: "tokenIsSent")
if value { return .none }
return environment.userServicesClient.sendDeviceToken()
.catchToEffect()
.map(UserAction.sendDeviceTokenResult)
case .sendDeviceTokenResult(.success(_)):
UserDefaults.standard.set(true, forKey: "tokenIsSent")
debugPrint("Token has been sent")
return .none
case .sendDeviceTokenResult(.failure(let error)):
debugPrint("Failed sending user device token: \(error)")
return .none
case .scheduleDismissed:
state.user.role = .user
state.driverState = nil
state.adminState = nil
print("Schedule dismissed")
return .none
case .signOut:
return .none
//MARK: - Driver actions
case .driverAction(_):
return .none
//MARK: - Admin actions
case .adminAction(_):
return .none
}
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment