增加换肤功能

This commit is contained in:
启星
2025-08-14 10:07:49 +08:00
parent f6964c1e89
commit 4f9318d98e
8789 changed files with 978530 additions and 2 deletions

View File

@@ -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)
}

View File

@@ -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))
}
}
}

View File

@@ -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()
})
)

View File

@@ -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)
}

View File

@@ -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"))
}

View File

@@ -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())
}
}
}

View File

@@ -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()
})
)

View File

@@ -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)
}

View 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
}

View 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)
}
}
}

View File

@@ -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
}
}

View 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)
}

View 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)
}
}
}

View 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"))
}

View 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)))
}
}
}

View 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()
})
)

View 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)
}

View 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)
}

View 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()
}
}

View 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
})
)

View 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)
}

View 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
}
}

View 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
})
)

View 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)
}