import AppCore import ChatFeature import CheckContactAuthFeature import ComposableArchitecture import ComposablePresentation import ConfirmRequestFeature import ContactLookupFeature import Foundation import ResetAuthFeature import SendRequestFeature import VerifyContactFeature import XCTestDynamicOverlay import XXClient import XXMessengerClient import XXModels public struct ContactState: Equatable { public init( id: Data, dbContact: XXModels.Contact? = nil, xxContact: XXClient.Contact? = nil, importUsername: Bool = true, importEmail: Bool = true, importPhone: Bool = true, lookup: ContactLookupState? = nil, sendRequest: SendRequestState? = nil, verifyContact: VerifyContactState? = nil, confirmRequest: ConfirmRequestState? = nil, checkAuth: CheckContactAuthState? = nil, resetAuth: ResetAuthState? = nil, chat: ChatState? = nil ) { self.id = id self.dbContact = dbContact self.xxContact = xxContact self.importUsername = importUsername self.importEmail = importEmail self.importPhone = importPhone self.lookup = lookup self.sendRequest = sendRequest self.verifyContact = verifyContact self.confirmRequest = confirmRequest self.checkAuth = checkAuth self.resetAuth = resetAuth self.chat = chat } public var id: Data public var dbContact: XXModels.Contact? public var xxContact: XXClient.Contact? @BindableState public var importUsername: Bool @BindableState public var importEmail: Bool @BindableState public var importPhone: Bool public var lookup: ContactLookupState? public var sendRequest: SendRequestState? public var verifyContact: VerifyContactState? public var confirmRequest: ConfirmRequestState? public var checkAuth: CheckContactAuthState? public var resetAuth: ResetAuthState? public var chat: ChatState? } public enum ContactAction: Equatable, BindableAction { case start case dbContactFetched(XXModels.Contact?) case importFactsTapped case lookupTapped case lookupDismissed case lookup(ContactLookupAction) case sendRequestTapped case sendRequestDismissed case sendRequest(SendRequestAction) case verifyContactTapped case verifyContactDismissed case verifyContact(VerifyContactAction) case checkAuthTapped case checkAuthDismissed case checkAuth(CheckContactAuthAction) case confirmRequestTapped case confirmRequestDismissed case confirmRequest(ConfirmRequestAction) case resetAuthTapped case resetAuthDismissed case resetAuth(ResetAuthAction) case chatTapped case chatDismissed case chat(ChatAction) case binding(BindingAction<ContactState>) } public struct ContactEnvironment { public init( messenger: Messenger, db: DBManagerGetDB, mainQueue: AnySchedulerOf<DispatchQueue>, bgQueue: AnySchedulerOf<DispatchQueue>, lookup: @escaping () -> ContactLookupEnvironment, sendRequest: @escaping () -> SendRequestEnvironment, verifyContact: @escaping () -> VerifyContactEnvironment, confirmRequest: @escaping () -> ConfirmRequestEnvironment, checkAuth: @escaping () -> CheckContactAuthEnvironment, resetAuth: @escaping () -> ResetAuthEnvironment, chat: @escaping () -> ChatEnvironment ) { self.messenger = messenger self.db = db self.mainQueue = mainQueue self.bgQueue = bgQueue self.lookup = lookup self.sendRequest = sendRequest self.verifyContact = verifyContact self.confirmRequest = confirmRequest self.checkAuth = checkAuth self.resetAuth = resetAuth self.chat = chat } public var messenger: Messenger public var db: DBManagerGetDB public var mainQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue> public var lookup: () -> ContactLookupEnvironment public var sendRequest: () -> SendRequestEnvironment public var verifyContact: () -> VerifyContactEnvironment public var confirmRequest: () -> ConfirmRequestEnvironment public var checkAuth: () -> CheckContactAuthEnvironment public var resetAuth: () -> ResetAuthEnvironment public var chat: () -> ChatEnvironment } #if DEBUG extension ContactEnvironment { public static let unimplemented = ContactEnvironment( messenger: .unimplemented, db: .unimplemented, mainQueue: .unimplemented, bgQueue: .unimplemented, lookup: { .unimplemented }, sendRequest: { .unimplemented }, verifyContact: { .unimplemented }, confirmRequest: { .unimplemented }, checkAuth: { .unimplemented }, resetAuth: { .unimplemented }, chat: { .unimplemented } ) } #endif public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironment> { state, action, env in enum DBFetchEffectID {} switch action { case .start: return try! env.db().fetchContactsPublisher(.init(id: [state.id])) .assertNoFailure() .map(\.first) .map(ContactAction.dbContactFetched) .subscribe(on: env.bgQueue) .receive(on: env.mainQueue) .eraseToEffect() .cancellable(id: DBFetchEffectID.self, cancelInFlight: true) case .dbContactFetched(let contact): state.dbContact = contact return .none case .importFactsTapped: guard let xxContact = state.xxContact else { return .none } return .fireAndForget { [state] in var dbContact = state.dbContact ?? XXModels.Contact(id: state.id) dbContact.marshaled = xxContact.data if state.importUsername { dbContact.username = try? xxContact.getFact(.username)?.value } if state.importEmail { dbContact.email = try? xxContact.getFact(.email)?.value } if state.importPhone { dbContact.phone = try? xxContact.getFact(.phone)?.value } _ = try! env.db().saveContact(dbContact) } .subscribe(on: env.bgQueue) .receive(on: env.mainQueue) .eraseToEffect() case .lookupTapped: state.lookup = ContactLookupState(id: state.id) return .none case .lookupDismissed: state.lookup = nil return .none case .lookup(.didLookup(let xxContact)): state.xxContact = xxContact state.lookup = nil return .none case .sendRequestTapped: if let xxContact = state.xxContact { state.sendRequest = SendRequestState(contact: xxContact) } else if let marshaled = state.dbContact?.marshaled { state.sendRequest = SendRequestState(contact: .live(marshaled)) } return .none case .sendRequestDismissed: state.sendRequest = nil return .none case .sendRequest(.sendSucceeded): state.sendRequest = nil return .none case .verifyContactTapped: if let marshaled = state.dbContact?.marshaled { state.verifyContact = VerifyContactState( contact: .live(marshaled) ) } return .none case .verifyContactDismissed: state.verifyContact = nil return .none case .checkAuthTapped: if let marshaled = state.dbContact?.marshaled { state.checkAuth = CheckContactAuthState( contact: .live(marshaled) ) } return .none case .checkAuthDismissed: state.checkAuth = nil return .none case .confirmRequestTapped: if let marshaled = state.dbContact?.marshaled { state.confirmRequest = ConfirmRequestState( contact: .live(marshaled) ) } return .none case .confirmRequestDismissed: state.confirmRequest = nil return .none case .chatTapped: state.chat = ChatState(id: .contact(state.id)) return .none case .chatDismissed: state.chat = nil return .none case .resetAuthTapped: if let marshaled = state.dbContact?.marshaled { state.resetAuth = ResetAuthState( partner: .live(marshaled) ) } return .none case .resetAuthDismissed: state.resetAuth = nil return .none case .binding(_), .lookup(_), .sendRequest(_), .verifyContact(_), .confirmRequest(_), .checkAuth(_), .resetAuth(_), .chat(_): return .none } } .binding() .presenting( contactLookupReducer, state: .keyPath(\.lookup), id: .notNil(), action: /ContactAction.lookup, environment: { $0.lookup() } ) .presenting( sendRequestReducer, state: .keyPath(\.sendRequest), id: .notNil(), action: /ContactAction.sendRequest, environment: { $0.sendRequest() } ) .presenting( verifyContactReducer, state: .keyPath(\.verifyContact), id: .notNil(), action: /ContactAction.verifyContact, environment: { $0.verifyContact() } ) .presenting( confirmRequestReducer, state: .keyPath(\.confirmRequest), id: .notNil(), action: /ContactAction.confirmRequest, environment: { $0.confirmRequest() } ) .presenting( checkContactAuthReducer, state: .keyPath(\.checkAuth), id: .notNil(), action: /ContactAction.checkAuth, environment: { $0.checkAuth() } ) .presenting( resetAuthReducer, state: .keyPath(\.resetAuth), id: .notNil(), action: /ContactAction.resetAuth, environment: { $0.resetAuth() } ) .presenting( chatReducer, state: .keyPath(\.chat), id: .keyPath(\.?.id), action: /ContactAction.chat, environment: { $0.chat() } )