增加换肤功能
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// ConferenceInvitationActions.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by jeremiawang on 2024/8/12.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RTCRoomEngine
|
||||
|
||||
enum ConferenceInvitationActions {
|
||||
static let key = "action.conferenceInvitation"
|
||||
|
||||
static let inviteUsers = ActionTemplate(id: key.appending(".inviteUsers"),
|
||||
payloadType: (String, [String]).self)
|
||||
static let accept = ActionTemplate(id: key.appending(".accept"), payloadType: String.self)
|
||||
static let reject = ActionTemplate(id: key.appending(".reject"), payloadType: (String, TUIInvitationRejectedReason).self)
|
||||
static let getInvitationList = ActionTemplate(id: key.appending(".getInvitationList"), payloadType: (String, String, [TUIInvitation]).self)
|
||||
static let fetchAttendees = ActionTemplate(id: key.appending(".fetchAttendees"), payloadType: (String, String, [UserInfo]).self)
|
||||
static let clearInvitationList = ActionTemplate(id: key.appending(".fetchAttendees"))
|
||||
|
||||
// MARK: callback
|
||||
static let updateInvitationList = ActionTemplate(id: key.appending(".setInvitationList"), payloadType: [TUIInvitation].self)
|
||||
static let addInvitation = ActionTemplate(id: key.appending(".addInvitation"), payloadType: TUIInvitation.self)
|
||||
static let removeInvitation = ActionTemplate(id: key.appending(".addInvitation"), payloadType: String.self)
|
||||
static let changeInvitationStatus = ActionTemplate(id: key.appending(".addInvitation"), payloadType: TUIInvitation.self)
|
||||
static let onInviteSuccess = ActionTemplate(id: key.appending("onInviteSuccess"))
|
||||
static let onAcceptSuccess = ActionTemplate(id: key.appending("onAcceptSuccess"), payloadType: String.self)
|
||||
static let onRejectSuccess = ActionTemplate(id: key.appending("onRejectSuccess"))
|
||||
static let onReceiveInvitation = ActionTemplate(id: key.appending("onAcceptSuccess"), payloadType: (TUIRoomInfo, TUIInvitation).self)
|
||||
static let onGetInvitationSuccess = ActionTemplate(id: key.appending("onGetInvitationSuccess"), payloadType: (String, [TUIInvitation]).self)
|
||||
static let onFetchAttendeesSuccess = ActionTemplate(id: key.appending("onFetchAttendeesSuccess"), payloadType: [UserInfo].self)
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
//
|
||||
// ConferenceInvitationEffects.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by jeremiawang on 2024/8/12.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RTCRoomEngine
|
||||
import Combine
|
||||
import Factory
|
||||
|
||||
class ConferenceInvitationEffects: Effects {
|
||||
typealias Environment = ServiceCenter
|
||||
|
||||
let inviteUsers = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ConferenceInvitationActions.inviteUsers)
|
||||
.flatMap { action in
|
||||
environment.conferenceInvitationService.inviteUsers(roomId: action.payload.0, userIdList: action.payload.1)
|
||||
.map { _ in
|
||||
ConferenceInvitationActions.onInviteSuccess()
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let accept = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ConferenceInvitationActions.accept)
|
||||
.flatMap { action in
|
||||
environment.conferenceInvitationService.accept(roomId: action.payload)
|
||||
.map { roomId in
|
||||
ConferenceInvitationActions.onAcceptSuccess(payload: roomId)
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
environment.store?.dispatch(action: InvitationViewActions.dismissInvitationView())
|
||||
return Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let reject = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ConferenceInvitationActions.reject)
|
||||
.flatMap { action in
|
||||
environment.conferenceInvitationService.reject(roomId: action.payload.0, reason: action.payload.1)
|
||||
.map { _ in
|
||||
ConferenceInvitationActions.onRejectSuccess()
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let getInvitationList = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ConferenceInvitationActions.getInvitationList)
|
||||
.flatMap { action in
|
||||
environment.conferenceInvitationService.getInvitationList(roomId: action.payload.0, cursor: action.payload.1)
|
||||
.map { invitations, cursor in
|
||||
var invitationList = action.payload.2
|
||||
let newList = invitationList + invitations
|
||||
if cursor.isEmpty {
|
||||
return ConferenceInvitationActions.onGetInvitationSuccess(payload: (action.payload.0, newList))
|
||||
} else {
|
||||
return ConferenceInvitationActions.getInvitationList(payload: (action.payload.0, cursor, newList))
|
||||
}
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let fetchAttendees = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ConferenceInvitationActions.fetchAttendees)
|
||||
.flatMap { action in
|
||||
environment.conferenceListService.fetchAttendeeList(conferenceId: action.payload.0, cursor: action.payload.1)
|
||||
.map { userInfoList, cursor, totalCount in
|
||||
var attendeesList = action.payload.2
|
||||
attendeesList.append(contentsOf: userInfoList)
|
||||
if cursor.isEmpty {
|
||||
return ConferenceInvitationActions.onFetchAttendeesSuccess(payload: attendeesList)
|
||||
} else {
|
||||
return ConferenceInvitationActions.fetchAttendees(payload:(action.payload.0, cursor, attendeesList))
|
||||
}
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let onAcceptSuccess = Effect<Environment>.nonDispatching { actions, environment in
|
||||
actions
|
||||
.wasCreated(from: ConferenceInvitationActions.onAcceptSuccess)
|
||||
.sink { action in
|
||||
let roomId = action.payload
|
||||
InvitationObserverService.shared.dismissInvitationWindow()
|
||||
let joinParams = JoinConferenceParams(roomId: roomId)
|
||||
joinParams.isOpenMicrophone = true
|
||||
joinParams.isOpenCamera = false
|
||||
joinParams.isOpenSpeaker = true
|
||||
let vc = ConferenceMainViewController()
|
||||
vc.setJoinConferenceParams(params: joinParams)
|
||||
DispatchQueue.main.async {
|
||||
RoomRouter.shared.push(viewController: vc)
|
||||
}
|
||||
environment.store?.dispatch(action: InvitationViewActions.dismissInvitationView())
|
||||
}
|
||||
}
|
||||
|
||||
let onReceiveInvitation = Effect<Environment>.nonDispatching { actions, environment in
|
||||
actions
|
||||
.wasCreated(from: ConferenceInvitationActions.onReceiveInvitation)
|
||||
.sink { action in
|
||||
let roomInfo = action.payload.0
|
||||
let invitation = action.payload.1
|
||||
let isEnteredRoom = environment.store?.selectCurrent(RoomSelectors.getIsEnteredRoom)
|
||||
let isNotBeingInviting = environment.store?.selectCurrent(ViewSelectors.getDismissInvitationFlag)
|
||||
if isEnteredRoom == true {
|
||||
environment.store?.dispatch(action: ConferenceInvitationActions.reject(payload: (roomInfo.roomId, .inOtherConference)))
|
||||
} else if isNotBeingInviting == false {
|
||||
environment.store?.dispatch(action: ConferenceInvitationActions.reject(payload: (roomInfo.roomId, .rejectToEnter)))
|
||||
} else {
|
||||
InvitationObserverService.shared.showInvitationWindow(roomInfo: roomInfo, invitation: invitation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let onGetInvitationSuccess = Effect<Environment>.nonDispatching { actions, environment in
|
||||
actions
|
||||
.wasCreated(from: ConferenceInvitationActions.onGetInvitationSuccess)
|
||||
.sink { action in
|
||||
let roomId = action.payload.0
|
||||
let invitations = action.payload.1
|
||||
environment.store?.dispatch(action: ConferenceInvitationActions.updateInvitationList(payload: invitations))
|
||||
environment.store?.dispatch(action: ConferenceInvitationActions.fetchAttendees(payload: (roomId, "", [])))
|
||||
}
|
||||
}
|
||||
|
||||
let onFetchAttendeesSuccess = Effect<Environment>.nonDispatching { actions, environment in
|
||||
actions
|
||||
.wasCreated(from: ConferenceInvitationActions.onFetchAttendeesSuccess)
|
||||
.sink { action in
|
||||
let attendeeList = action.payload
|
||||
var filteredList: [UserInfo] = []
|
||||
if let allUsers = environment.store?.selectCurrent(UserSelectors.getAllUsers) {
|
||||
filteredList = attendeeList.filter { user in
|
||||
!allUsers.contains { existedUser in
|
||||
user.userId == existedUser.userId
|
||||
}
|
||||
}
|
||||
}
|
||||
let resultList = filteredList.map{ TUIInvitation(userInfo: $0) }
|
||||
environment.store?.dispatch(action: ConferenceInvitationActions.updateInvitationList(payload: resultList))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// ConferenceInvitationReducer.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by jeremiawang on 2024/8/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RTCRoomEngine
|
||||
|
||||
let ConferenceInvitationReducer = Reducer<ConferenceInvitationState>(
|
||||
ReduceOn(ConferenceInvitationActions.updateInvitationList, reduce: { state, action in
|
||||
let newInvitations: [TUIInvitation] = action.payload
|
||||
let previousInvitations = state.invitationList
|
||||
var existingIds = Set(previousInvitations.map { $0.invitee.userId })
|
||||
var combinedInvitations = previousInvitations
|
||||
for invitation in newInvitations {
|
||||
if !existingIds.contains(invitation.invitee.userId) {
|
||||
combinedInvitations.append(invitation)
|
||||
existingIds.insert(invitation.invitee.userId)
|
||||
}
|
||||
}
|
||||
state.invitationList = combinedInvitations
|
||||
}),
|
||||
ReduceOn(ConferenceInvitationActions.addInvitation, reduce: { state, action in
|
||||
let userIdToAdd = action.payload.invitee.userId
|
||||
if let index = state.invitationList.firstIndex(where: { $0.invitee.userId == userIdToAdd }) {
|
||||
state.invitationList[index] = action.payload
|
||||
} else {
|
||||
state.invitationList.insert(action.payload, at: 0)
|
||||
}
|
||||
}),
|
||||
ReduceOn(ConferenceInvitationActions.removeInvitation, reduce: { state, action in
|
||||
let userIdToRemove = action.payload
|
||||
if let index = state.invitationList.firstIndex(where: { $0.invitee.userId == userIdToRemove }) {
|
||||
state.invitationList.remove(at: index)
|
||||
}
|
||||
}),
|
||||
ReduceOn(ConferenceInvitationActions.changeInvitationStatus, reduce: { state, action in
|
||||
let userIdToChange = action.payload.invitee.userId
|
||||
if let index = state.invitationList.firstIndex(where: { $0.invitee.userId == userIdToChange }) {
|
||||
state.invitationList[index] = action.payload
|
||||
}
|
||||
}),
|
||||
ReduceOn(ConferenceInvitationActions.clearInvitationList, reduce: { state, action in
|
||||
state.invitationList.removeAll()
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// ConferenceInvitationSelector.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by jeremiawang on 2024/8/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ConferenceInvitationSelectors {
|
||||
static let getConferenceInvitationState = Selector(keyPath: \OperationState.conferenceInvitationState)
|
||||
|
||||
static let getInvitationList = Selector.with(getConferenceInvitationState, keyPath:\ConferenceInvitationState.invitationList)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// ConferenceListActions.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/6/12.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RTCRoomEngine
|
||||
|
||||
enum ConferenceListActions {
|
||||
static let key = "action.conferenceList"
|
||||
|
||||
static let fetchConferenceList = ActionTemplate(id: key.appending(".fetchConferenceList"),
|
||||
payloadType: (String, Int).self)
|
||||
static let scheduleConference = ActionTemplate(id: key.appending(".scheduleConference"),
|
||||
payloadType: TUIConferenceInfo.self)
|
||||
static let cancelConference = ActionTemplate(id: key.appending(".cancelConference"),
|
||||
payloadType: String.self)
|
||||
static let updateConferenceInfo = ActionTemplate(id: key.appending(".updateConferenceInfo"),
|
||||
payloadType: (TUIConferenceInfo, TUIConferenceModifyFlag).self)
|
||||
static let addAttendeesByAdmin = ActionTemplate(id: key.appending(".addAttendeesByAdmin"),
|
||||
payloadType: (String, [String]).self)
|
||||
static let removeAttendeesByAdmin = ActionTemplate(id: key.appending(".removeAttendeesByAdmin"),
|
||||
payloadType: (String, [String]).self)
|
||||
|
||||
static let resetConferenceList = ActionTemplate(id: key.appending(".resetConferenceList"))
|
||||
|
||||
// MARK: callback
|
||||
static let updateConferenceList = ActionTemplate(id: key.appending(".updateConferenceList"), payloadType: ([ConferenceInfo], String).self)
|
||||
static let insertConference = ActionTemplate(id: key.appending(".insertConference"), payloadType: ConferenceInfo.self)
|
||||
static let removeConference = ActionTemplate(id: key.appending(".removeConference"), payloadType: String.self)
|
||||
static let onConferenceUpdated = ActionTemplate(id: key.appending(".onConferenceUpdated"), payloadType: ConferenceInfo.self)
|
||||
static let onScheduleSuccess = ActionTemplate(id: key.appending("onScheduleSuccess"), payloadType: TUIConferenceInfo.self)
|
||||
static let onCancelSuccess = ActionTemplate(id: key.appending("onCancelSuccess"))
|
||||
|
||||
static let onUpdateInfoSuccess = ActionTemplate(id: key.appending("onUpdateInfoSuccess"))
|
||||
static let onAddAttendeesSuccess = ActionTemplate(id: key.appending("onAddAttendeesSuccess"))
|
||||
static let onRemoveAttendeesSuccess = ActionTemplate(id: key.appending("onRemoveAttendeesSuccess"))
|
||||
}
|
||||
|
||||
// MARK: - Subject action, only event, no reduce.
|
||||
enum ScheduleResponseActions {
|
||||
static let key = "action.schedule.response"
|
||||
static let onScheduleSuccess = ActionTemplate(id: key.appending("onScheduleSuccess"), payloadType: TUIConferenceInfo.self)
|
||||
static let onCancelSuccess = ActionTemplate(id: key.appending("onScheduleSuccess"))
|
||||
static let onUpdateInfoSuccess = ActionTemplate(id: key.appending("onUpdateInfoSuccess"))
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// ConferenceListEffects.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/6/12.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RTCRoomEngine
|
||||
import Combine
|
||||
|
||||
class ConferenceListEffects: Effects {
|
||||
typealias Environment = ServiceCenter
|
||||
|
||||
let scheduleConference = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ConferenceListActions.scheduleConference)
|
||||
.flatMap { action in
|
||||
environment.conferenceListService.scheduleConference(conferenceInfo: action.payload)
|
||||
.map { conferenceInfo in
|
||||
ConferenceListActions.onScheduleSuccess(payload: conferenceInfo)
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let cancelConference = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ConferenceListActions.cancelConference)
|
||||
.flatMap { action in
|
||||
environment.conferenceListService.cancelConference(conferenceId: action.payload)
|
||||
.map { _ in
|
||||
ConferenceListActions.onCancelSuccess()
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let updateConferenceInfo = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ConferenceListActions.updateConferenceInfo)
|
||||
.flatMap { action in
|
||||
environment.conferenceListService.updateConferenceInfo(conferenceInfo: action.payload.0,
|
||||
modifyFlag: action.payload.1)
|
||||
.map { _ in
|
||||
ConferenceListActions.onUpdateInfoSuccess()
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let fetchConferenceList = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ConferenceListActions.fetchConferenceList)
|
||||
.flatMap { action in
|
||||
environment.conferenceListService.fetchConferenceList(status: [.notStarted, .running],
|
||||
cursor: action.payload.0,
|
||||
count: action.payload.1)
|
||||
.map { (conferenceList, cursor) in
|
||||
ConferenceListActions.updateConferenceList(payload: (conferenceList, cursor))
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let addAttendeesByAdmin = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ConferenceListActions.addAttendeesByAdmin)
|
||||
.flatMap { action in
|
||||
environment.conferenceListService.addAttendeesByAdmin(conferenceId: action.payload.0, userIdList: action.payload.1)
|
||||
.map { _ in
|
||||
ConferenceListActions.onAddAttendeesSuccess()
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let removeAttendeesByAdmin = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ConferenceListActions.removeAttendeesByAdmin)
|
||||
.flatMap { action in
|
||||
environment.conferenceListService.removeAttendeesByAdmin(conferenceId: action.payload.0, userIdList: action.payload.1)
|
||||
.map { _ in
|
||||
ConferenceListActions.onRemoveAttendeesSuccess()
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let onScheduleSuccess = Effect<Environment>.nonDispatching { actions, environment in
|
||||
actions
|
||||
.wasCreated(from: ConferenceListActions.onScheduleSuccess)
|
||||
.sink { action in
|
||||
let conferenceInfo = action.payload
|
||||
environment.store?.dispatch(action: ScheduleResponseActions.onScheduleSuccess(payload: conferenceInfo))
|
||||
}
|
||||
}
|
||||
|
||||
let onCancelSuccess = Effect<Environment>.nonDispatching { actions, environment in
|
||||
actions
|
||||
.wasCreated(from: ConferenceListActions.onCancelSuccess)
|
||||
.sink { action in
|
||||
environment.store?.dispatch(action: ScheduleResponseActions.onCancelSuccess())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Conference.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/6/12.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
let ConferenceListReducer = Reducer<ConferenceListState>(
|
||||
ReduceOn(ConferenceListActions.updateConferenceList, reduce: { state, action in
|
||||
let incomingConferences = action.payload.0
|
||||
let previousConferences = state.scheduledConferences
|
||||
let combinedSet = Set(incomingConferences + previousConferences)
|
||||
state.scheduledConferences = Array(combinedSet)
|
||||
state.fetchScheduledConferencesCursor = action.payload.1
|
||||
}),
|
||||
ReduceOn(ConferenceListActions.insertConference, reduce: { state, action in
|
||||
if !state.scheduledConferences.contains(action.payload) {
|
||||
state.scheduledConferences.append(action.payload)
|
||||
}
|
||||
}),
|
||||
ReduceOn(ConferenceListActions.removeConference, reduce: { state, action in
|
||||
let conferenceToRemove = action.payload
|
||||
let conferences = state.scheduledConferences.map { $0.basicInfo.roomId }
|
||||
if let index = conferences.firstIndex(of: conferenceToRemove) {
|
||||
state.scheduledConferences.remove(at: index)
|
||||
}
|
||||
}),
|
||||
ReduceOn(ConferenceListActions.onConferenceUpdated, reduce: { state, action in
|
||||
let conference = action.payload
|
||||
if let index = state.scheduledConferences.firstIndex(where: { $0.basicInfo.roomId == conference.basicInfo.roomId }) {
|
||||
state.scheduledConferences[index] = conference
|
||||
}
|
||||
}),
|
||||
ReduceOn(ConferenceListActions.resetConferenceList, reduce: { state, action in
|
||||
state.scheduledConferences.removeAll()
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// ConferenceListSelectors.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/6/12.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ConferenceListSelectors {
|
||||
static let getConferenceListState = Selector(keyPath: \OperationState.conferenceListState)
|
||||
|
||||
static let getConferenceList = Selector.with(getConferenceListState, keyPath:\ConferenceListState.scheduledConferences)
|
||||
static let getConferenceListCursor = Selector.with(getConferenceListState, keyPath:\ConferenceListState.fetchScheduledConferencesCursor)
|
||||
}
|
||||
27
TUIKit/TUIRoomKit/Source/Store/ConferenceStore.swift
Normal file
27
TUIKit/TUIRoomKit/Source/Store/ConferenceStore.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// ConferenceStore.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/6/12.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
protocol ActionDispatcher {
|
||||
func dispatch(action: Action)
|
||||
}
|
||||
|
||||
protocol ConferenceStore: ActionDispatcher {
|
||||
var errorSubject: PassthroughSubject<RoomError, Never> { get }
|
||||
var toastSubject: PassthroughSubject<ToastInfo, Never> { get }
|
||||
var scheduleActionSubject: PassthroughSubject<IdentifiableAction, Never> { get }
|
||||
|
||||
func select<Value:Equatable>(_ selector: Selector<OperationState, Value>) -> AnyPublisher<Value, Never>
|
||||
|
||||
func selectCurrent<Value>(_ selector: Selector<OperationState, Value>) -> Value
|
||||
|
||||
func select<Value:Equatable>(_ selector: Selector<ViewState, Value>) -> AnyPublisher<Value, Never>
|
||||
|
||||
func selectCurrent<Value>(_ selector: Selector<ViewState, Value>) -> Value
|
||||
}
|
||||
138
TUIKit/TUIRoomKit/Source/Store/ConferenceStoreProvider.swift
Normal file
138
TUIKit/TUIRoomKit/Source/Store/ConferenceStoreProvider.swift
Normal file
@@ -0,0 +1,138 @@
|
||||
//
|
||||
// ConferenceStoreProvider.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/6/12.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
class ConferenceStoreProvider {
|
||||
let errorSubject = PassthroughSubject<RoomError, Never>()
|
||||
let toastSubject = PassthroughSubject<ToastInfo, Never>()
|
||||
let scheduleActionSubject = PassthroughSubject<any IdentifiableAction, Never>()
|
||||
|
||||
private(set) lazy var operation: Store<OperationState, ServiceCenter> = {
|
||||
return Store(initialState: OperationState(), environment: ServiceCenter())
|
||||
}()
|
||||
private(set) lazy var viewStore: Store<ViewState, Void> = Store(initialState: ViewState())
|
||||
private var cancellableSet: Set<AnyCancellable> = []
|
||||
|
||||
init() {
|
||||
initializeStore()
|
||||
}
|
||||
|
||||
private func initializeStore() {
|
||||
initializeUserStore()
|
||||
initializeConferenceListStore()
|
||||
initializeConferenceInvitationStore()
|
||||
initializeRoomStore()
|
||||
initializeErrorEffect()
|
||||
initializedViewStore()
|
||||
}
|
||||
|
||||
private func initializeUserStore() {
|
||||
operation.register(reducer: userReducer, for: \.userState)
|
||||
operation.register(effects: UserEffects())
|
||||
}
|
||||
|
||||
private func initializeConferenceListStore() {
|
||||
operation.register(reducer: ConferenceListReducer, for: \.conferenceListState)
|
||||
operation.register(effects: ConferenceListEffects())
|
||||
}
|
||||
|
||||
private func initializeConferenceInvitationStore() {
|
||||
operation.register(reducer: ConferenceInvitationReducer, for: \.conferenceInvitationState)
|
||||
operation.register(effects: ConferenceInvitationEffects())
|
||||
}
|
||||
|
||||
private func initializeRoomStore() {
|
||||
operation.register(reducer: roomReducer, for: \.roomState)
|
||||
operation.register(effects: RoomEffects())
|
||||
}
|
||||
|
||||
private func initializeErrorEffect() {
|
||||
operation.register(effects: ErrorEffects())
|
||||
errorSubject
|
||||
.sink { [weak self] error in
|
||||
guard let self = self else { return }
|
||||
self.handle(error: error)
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}
|
||||
|
||||
private func initializedViewStore() {
|
||||
viewStore.register(reducer: scheduleViewReducer,for: \ViewState.scheduleViewState)
|
||||
viewStore.register(reducer: invitationViewReducer, for: \ViewState.invitationViewState)
|
||||
}
|
||||
|
||||
deinit {
|
||||
operation.unregister(reducer: userReducer)
|
||||
operation.unregisterEffects(withId: UserEffects.id)
|
||||
|
||||
operation.unregister(reducer: ConferenceListReducer)
|
||||
operation.unregisterEffects(withId: ConferenceListEffects.id)
|
||||
|
||||
operation.unregister(reducer: ConferenceInvitationReducer)
|
||||
operation.unregisterEffects(withId: ConferenceInvitationEffects.id)
|
||||
|
||||
operation.unregister(reducer: roomReducer)
|
||||
operation.unregisterEffects(withId: RoomEffects.id)
|
||||
|
||||
operation.unregisterEffects(withId: ErrorEffects.id)
|
||||
|
||||
viewStore.unregister(reducer: scheduleViewReducer)
|
||||
viewStore.unregister(reducer: invitationViewReducer)
|
||||
}
|
||||
}
|
||||
|
||||
extension ConferenceStoreProvider: ConferenceStore {
|
||||
func dispatch(action: Action) {
|
||||
guard let action = action as? IdentifiableAction else { return }
|
||||
if action.id.hasPrefix(ScheduleResponseActions.key) {
|
||||
scheduleActionSubject.send(action)
|
||||
}
|
||||
|
||||
if action.id.hasPrefix(ViewActions.toastActionKey) {
|
||||
handleToast(action: action)
|
||||
} else if action.id.hasPrefix(ViewActions.key) {
|
||||
viewStore.dispatch(action: action)
|
||||
} else {
|
||||
operation.dispatch(action: action)
|
||||
}
|
||||
}
|
||||
|
||||
func select<Value: Equatable>(_ selector: Selector<OperationState, Value>) -> AnyPublisher<Value, Never> {
|
||||
return operation.select(selector)
|
||||
.removeDuplicates()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func selectCurrent<Value>(_ selector: Selector<OperationState, Value>) -> Value {
|
||||
return operation.selectCurrent(selector)
|
||||
}
|
||||
|
||||
func select<Value:Equatable>(_ selector: Selector<ViewState, Value>) -> AnyPublisher<Value, Never> {
|
||||
return viewStore.select(selector)
|
||||
}
|
||||
|
||||
func selectCurrent<Value>(_ selector: Selector<ViewState, Value>) -> Value {
|
||||
return viewStore.selectCurrent(selector)
|
||||
}
|
||||
}
|
||||
|
||||
extension ConferenceStoreProvider {
|
||||
private func handle(error: RoomError) {
|
||||
error.actions.forEach { action in
|
||||
guard let action = action as? IdentifiableAction else { return }
|
||||
dispatch(action: action)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleToast(action: Action) {
|
||||
if let viewAction = action as? AnonymousAction<ToastInfo> {
|
||||
toastSubject.send(viewAction.payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// ConferenceStoreResolverRegister.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/6/12.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Factory
|
||||
|
||||
extension Container {
|
||||
var conferenceStore: Factory<ConferenceStore> {
|
||||
Factory(self) {
|
||||
ConferenceStoreProvider()
|
||||
}
|
||||
.shared
|
||||
}
|
||||
}
|
||||
13
TUIKit/TUIRoomKit/Source/Store/Error/ErrorActions.swift
Normal file
13
TUIKit/TUIRoomKit/Source/Store/Error/ErrorActions.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// ErrorActions.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/6/13.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ErrorActions {
|
||||
static let key = "action.error"
|
||||
static let throwError = ActionTemplate(id: key.appending(".throwError"), payloadType: RoomError.self)
|
||||
}
|
||||
20
TUIKit/TUIRoomKit/Source/Store/Error/ErrorEffects.swift
Normal file
20
TUIKit/TUIRoomKit/Source/Store/Error/ErrorEffects.swift
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// ErrorEffects.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/7/8.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class ErrorEffects: Effects {
|
||||
typealias Environment = ServiceCenter
|
||||
|
||||
let throwError = Effect<Environment>.nonDispatching { actions, environment in
|
||||
actions
|
||||
.wasCreated(from: ErrorActions.throwError)
|
||||
.sink { action in
|
||||
environment.store?.errorSubject.send(action.payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
19
TUIKit/TUIRoomKit/Source/Store/Room/RoomActions.swift
Normal file
19
TUIKit/TUIRoomKit/Source/Store/Room/RoomActions.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// RoomActions.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/7/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RTCRoomEngine
|
||||
|
||||
enum RoomActions {
|
||||
static let key = "action.room"
|
||||
|
||||
static let joinConference = ActionTemplate(id: key.appending(".joinConference"), payloadType: (String).self)
|
||||
|
||||
static let updateRoomState = ActionTemplate(id: key.appending(".updateRoomState"), payloadType: RoomInfo.self)
|
||||
static let clearRoomState = ActionTemplate(id: key.appending(".clearRoomState"))
|
||||
static let onJoinSuccess = ActionTemplate(id: key.appending(".onJoinSuccess"))
|
||||
}
|
||||
26
TUIKit/TUIRoomKit/Source/Store/Room/RoomEffects.swift
Normal file
26
TUIKit/TUIRoomKit/Source/Store/Room/RoomEffects.swift
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// RoomEffects.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/7/2.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import RTCRoomEngine
|
||||
|
||||
class RoomEffects: Effects {
|
||||
typealias Environment = ServiceCenter
|
||||
|
||||
let joinConference = Effect<Environment>.nonDispatching { actions, environment in
|
||||
actions.wasCreated(from: RoomActions.joinConference)
|
||||
.sink { action in
|
||||
let joinParams = JoinConferenceParams(roomId: action.payload)
|
||||
joinParams.isOpenMicrophone = true
|
||||
joinParams.isOpenCamera = false
|
||||
joinParams.isOpenSpeaker = true
|
||||
environment.navigator?.pushTo(route: .main(conferenceParams: ConferenceParamType(joinParams: joinParams)))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
18
TUIKit/TUIRoomKit/Source/Store/Room/RoomReducer.swift
Normal file
18
TUIKit/TUIRoomKit/Source/Store/Room/RoomReducer.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// RoomReducer.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by jeremiawang on 2024/8/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
let roomReducer = Reducer<RoomInfo>(
|
||||
ReduceOn(RoomActions.updateRoomState, reduce: { state, action in
|
||||
state = action.payload
|
||||
state.isEnteredRoom = true
|
||||
}),
|
||||
ReduceOn(RoomActions.clearRoomState, reduce: { state, action in
|
||||
state = RoomInfo()
|
||||
})
|
||||
)
|
||||
15
TUIKit/TUIRoomKit/Source/Store/Room/RoomSelector.swift
Normal file
15
TUIKit/TUIRoomKit/Source/Store/Room/RoomSelector.swift
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// RoomSelector.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by jeremiawang on 2024/8/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum RoomSelectors {
|
||||
static let getRoomState = Selector(keyPath: \OperationState.roomState)
|
||||
|
||||
static let getRoomId = Selector.with(getRoomState, keyPath:\RoomInfo.roomId)
|
||||
static let getIsEnteredRoom = Selector.with(getRoomState, keyPath:\RoomInfo.isEnteredRoom)
|
||||
}
|
||||
16
TUIKit/TUIRoomKit/Source/Store/User/UserActions.swift
Normal file
16
TUIKit/TUIRoomKit/Source/Store/User/UserActions.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// UserActions.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/6/5.
|
||||
//
|
||||
|
||||
import RTCRoomEngine
|
||||
|
||||
enum UserActions {
|
||||
static let key = "action.user"
|
||||
static let getSelfInfo = ActionTemplate(id: key.appending(".getSelfInfo"))
|
||||
static let updateSelfInfo = ActionTemplate(id: key.appending(".updateSelfInfo"), payloadType: UserInfo.self)
|
||||
static let fetchUserInfo = ActionTemplate(id: key.appending(".fetchUserInfo"), payloadType: String.self)
|
||||
static let updateAllUsers = ActionTemplate(id: key.appending(".updateAllUsers"), payloadType: [UserInfo].self)
|
||||
}
|
||||
32
TUIKit/TUIRoomKit/Source/Store/User/UserEffects.swift
Normal file
32
TUIKit/TUIRoomKit/Source/Store/User/UserEffects.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// UserEffects.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/6/5.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import ImSDK_Plus
|
||||
import RTCRoomEngine
|
||||
|
||||
class UserEffects: Effects {
|
||||
typealias Environment = ServiceCenter
|
||||
|
||||
let getSelfInfo = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions
|
||||
.wasCreated(from: UserActions.getSelfInfo)
|
||||
.flatMap { action -> AnyPublisher<Action, Never> in
|
||||
let selfId = environment.store?.selectCurrent(UserSelectors.getSelfId) ?? ""
|
||||
return environment.userService.fetchUserInfo(selfId)
|
||||
.map { userInfo in
|
||||
return UserActions.updateSelfInfo(payload: userInfo)
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
return Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
25
TUIKit/TUIRoomKit/Source/Store/User/UserReducer.swift
Normal file
25
TUIKit/TUIRoomKit/Source/Store/User/UserReducer.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// UserReducer.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/6/5.
|
||||
//
|
||||
|
||||
import RTCRoomEngine
|
||||
import TUICore
|
||||
|
||||
let userReducer = Reducer<UserState>(
|
||||
ReduceOn(UserActions.getSelfInfo, reduce: { state, action in
|
||||
var selfInfo = UserInfo()
|
||||
selfInfo.userId = TUILogin.getUserID() ?? ""
|
||||
selfInfo.userName = TUILogin.getNickName() ?? ""
|
||||
selfInfo.avatarUrl = TUILogin.getFaceUrl() ?? ""
|
||||
state.selfInfo = selfInfo
|
||||
}),
|
||||
ReduceOn(UserActions.updateSelfInfo, reduce: { state, action in
|
||||
state.selfInfo = action.payload
|
||||
}),
|
||||
ReduceOn(UserActions.updateAllUsers, reduce: { state, action in
|
||||
state.allUsers = action.payload
|
||||
})
|
||||
)
|
||||
19
TUIKit/TUIRoomKit/Source/Store/User/UserSelectors.swift
Normal file
19
TUIKit/TUIRoomKit/Source/Store/User/UserSelectors.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// UserSelectors.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/6/5.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum UserSelectors {
|
||||
static let getUserState = Selector(keyPath: \OperationState.userState)
|
||||
|
||||
static let getSelfInfo = Selector.with(getUserState, keyPath:\UserState.selfInfo)
|
||||
static let getSelfId = Selector.with(getSelfInfo, keyPath:\UserInfo.userId)
|
||||
static let getSelfAvatarURL = Selector.with(getSelfInfo, keyPath: \UserInfo.avatarUrl)
|
||||
static let getSelfUserName = Selector.with(getSelfInfo, keyPath: \UserInfo.userName)
|
||||
static let getAllUsers = Selector.with(getUserState, keyPath: \UserState.allUsers)
|
||||
}
|
||||
|
||||
58
TUIKit/TUIRoomKit/Source/Store/View/ViewActions.swift
Normal file
58
TUIKit/TUIRoomKit/Source/Store/View/ViewActions.swift
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// ViewActions.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/7/9.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ViewActions {
|
||||
static let key = "action.view"
|
||||
static let toastActionKey = key + ".showToast"
|
||||
|
||||
static let showToast = ActionTemplate(id: toastActionKey, payloadType: ToastInfo.self)
|
||||
}
|
||||
|
||||
enum ScheduleViewActions {
|
||||
static let key = ViewActions.key + ".scheduleView"
|
||||
|
||||
static let refreshConferenceList = ActionTemplate(id: key.appending(".refreshConferenceList"))
|
||||
static let stopRefreshList = ActionTemplate(id: key.appending(".stopRefreshList"))
|
||||
|
||||
static let popDetailView = ActionTemplate(id: key.appending(".popDetailView"))
|
||||
static let resetPopDetailFlag = ActionTemplate(id: key.appending(".resetPopDetailFlag"))
|
||||
}
|
||||
|
||||
enum InvitationViewActions {
|
||||
static let key = ViewActions.key + ".invitationView"
|
||||
|
||||
static let dismissInvitationView = ActionTemplate(id: key.appending(".dismissInvitationView"))
|
||||
static let resetInvitationFlag = ActionTemplate(id: key.appending(".resetInvitationFlag"))
|
||||
static let showInvitationPopupView = ActionTemplate(id: key.appending(".showInvitationPopupView"))
|
||||
static let resetPopupViewFlag = ActionTemplate(id: key.appending(".resetPopupViewFlag"))
|
||||
}
|
||||
|
||||
struct ToastInfo: Identifiable {
|
||||
enum Position {
|
||||
case center
|
||||
case bottom
|
||||
}
|
||||
let id: UUID
|
||||
let duration: TimeInterval
|
||||
let position: Position
|
||||
let message: String
|
||||
|
||||
init(message: String, position: Position = .center, duration: TimeInterval = 1.5) {
|
||||
id = UUID()
|
||||
self.message = message
|
||||
self.position = position
|
||||
self.duration = duration
|
||||
}
|
||||
}
|
||||
|
||||
extension ToastInfo: Equatable {
|
||||
static func ==(lhs: ToastInfo, rhs: ToastInfo) -> Bool{
|
||||
return lhs.id == rhs.id || lhs.message == rhs.message
|
||||
}
|
||||
}
|
||||
38
TUIKit/TUIRoomKit/Source/Store/View/ViewReducers.swift
Normal file
38
TUIKit/TUIRoomKit/Source/Store/View/ViewReducers.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// ViewReducers.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/7/15.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
let scheduleViewReducer = Reducer<ScheduleViewState>(
|
||||
ReduceOn(ScheduleViewActions.refreshConferenceList, reduce: { state, action in
|
||||
state.shouldRefreshList = true
|
||||
}),
|
||||
ReduceOn(ScheduleViewActions.stopRefreshList, reduce: { state, action in
|
||||
state.shouldRefreshList = false
|
||||
}),
|
||||
ReduceOn(ScheduleViewActions.popDetailView, reduce: { state, action in
|
||||
state.detailViewPopFlag = true
|
||||
}),
|
||||
ReduceOn(ScheduleViewActions.resetPopDetailFlag, reduce: { state, action in
|
||||
state.detailViewPopFlag = false
|
||||
})
|
||||
)
|
||||
|
||||
let invitationViewReducer = Reducer<InvitationViewState> (
|
||||
ReduceOn(InvitationViewActions.dismissInvitationView, reduce: { state, action in
|
||||
state.invitationViewDismissFlag = true
|
||||
}),
|
||||
ReduceOn(InvitationViewActions.resetInvitationFlag, reduce: { state, action in
|
||||
state.invitationViewDismissFlag = false
|
||||
}),
|
||||
ReduceOn(InvitationViewActions.showInvitationPopupView, reduce: { state, action in
|
||||
state.showInvitationPopupView = true
|
||||
}),
|
||||
ReduceOn(InvitationViewActions.resetPopupViewFlag, reduce: { state, action in
|
||||
state.showInvitationPopupView = false
|
||||
})
|
||||
)
|
||||
18
TUIKit/TUIRoomKit/Source/Store/View/ViewSelectors.swift
Normal file
18
TUIKit/TUIRoomKit/Source/Store/View/ViewSelectors.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// ViewSelectors.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by CY zhao on 2024/7/15.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ViewSelectors {
|
||||
private static let getScheduleStatus = Selector(keyPath: \ViewState.scheduleViewState)
|
||||
static let getRefreshListFlag = Selector.with(getScheduleStatus, projector: \ScheduleViewState.shouldRefreshList)
|
||||
static let getPopDetailFlag = Selector.with(getScheduleStatus, projector: \ScheduleViewState.detailViewPopFlag)
|
||||
|
||||
private static let getInvitationStatus = Selector(keyPath: \ViewState.invitationViewState)
|
||||
static let getDismissInvitationFlag = Selector.with(getInvitationStatus, projector: \InvitationViewState.invitationViewDismissFlag)
|
||||
static let getShowinvitationPopupView = Selector.with(getInvitationStatus, projector: \InvitationViewState.showInvitationPopupView)
|
||||
}
|
||||
Reference in New Issue
Block a user