344 lines
13 KiB
Swift
344 lines
13 KiB
Swift
// File created from SimpleUserProfileExample
|
|
// $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess
|
|
//
|
|
// Copyright 2021-2024 New Vector Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
// Please see LICENSE in the repository root for full details.
|
|
//
|
|
|
|
import Combine
|
|
import Foundation
|
|
import MatrixSDK
|
|
|
|
class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol {
|
|
// MARK: - Properties
|
|
|
|
// MARK: Private
|
|
|
|
private let session: MXSession
|
|
private let parentSpaceId: String?
|
|
private let creationParams: SpaceCreationParameters
|
|
|
|
private var tasks: [SpaceCreationPostProcessTask] = []
|
|
private var currentTaskIndex = 0
|
|
private var isRetry = false
|
|
|
|
private(set) var createdSpace: MXSpace? {
|
|
didSet {
|
|
createdSpaceId = createdSpace?.spaceId
|
|
}
|
|
}
|
|
|
|
private var createdRoomsByName: [String: MXRoom] = [:]
|
|
|
|
private var currentSubTaskIndex = 0
|
|
|
|
private var processingQueue = DispatchQueue(label: "io.element.MXSpace.processingQueue", attributes: .concurrent)
|
|
|
|
private lazy var stateEventBuilder = MXRoomInitialStateEventBuilder()
|
|
|
|
private lazy var mediaUploader: MXMediaLoader = MXMediaManager.prepareUploader(withMatrixSession: session, initialRange: 0, andRange: 1.0)
|
|
|
|
// MARK: Public
|
|
|
|
private(set) var tasksSubject: CurrentValueSubject<[SpaceCreationPostProcessTask], Never>
|
|
private(set) var createdSpaceId: String?
|
|
var avatar: AvatarInput {
|
|
let alias = creationParams.userDefinedAddress.isEmptyOrNil ? creationParams.address : creationParams.userDefinedAddress
|
|
return AvatarInput(mxContentUri: alias, matrixItemId: "", displayName: creationParams.name)
|
|
}
|
|
|
|
var avatarImage: UIImage? {
|
|
creationParams.userSelectedAvatar
|
|
}
|
|
|
|
// MARK: - Setup
|
|
|
|
init(session: MXSession, parentSpaceId: String?, creationParams: SpaceCreationParameters) {
|
|
self.session = session
|
|
self.parentSpaceId = parentSpaceId
|
|
self.creationParams = creationParams
|
|
tasks = Self.tasks(with: creationParams)
|
|
tasksSubject = CurrentValueSubject(tasks)
|
|
}
|
|
|
|
deinit { }
|
|
|
|
// MARK: - Public
|
|
|
|
func run() {
|
|
isRetry = currentTaskIndex > 0
|
|
currentTaskIndex = -1
|
|
runNextTask()
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private static func tasks(with creationParams: SpaceCreationParameters) -> [SpaceCreationPostProcessTask] {
|
|
guard let spaceName = creationParams.name else {
|
|
MXLog.error("[SpaceCreationPostProcessService] setupTasks: space name shouldn't be nil")
|
|
return []
|
|
}
|
|
|
|
var tasks = [SpaceCreationPostProcessTask(type: .createSpace, title: VectorL10n.spacesCreationPostProcessCreatingSpaceTask(spaceName), state: .none)]
|
|
if creationParams.userSelectedAvatar != nil {
|
|
tasks.append(SpaceCreationPostProcessTask(type: .uploadAvatar, title: VectorL10n.spacesCreationPostProcessUploadingAvatar, state: .none))
|
|
}
|
|
if let addedRoomIds = creationParams.addedRoomIds {
|
|
if !addedRoomIds.isEmpty {
|
|
let subTasks = addedRoomIds.map { roomId in
|
|
SpaceCreationPostProcessTask(type: .addRooms, title: roomId, state: .none)
|
|
}
|
|
tasks.append(SpaceCreationPostProcessTask(type: .addRooms, title: VectorL10n.spacesCreationPostProcessAddingRooms("\(addedRoomIds.count)"), state: .none, subTasks: subTasks))
|
|
}
|
|
} else {
|
|
tasks.append(contentsOf: creationParams.newRooms.compactMap { room in
|
|
guard !room.name.isEmpty else {
|
|
return nil
|
|
}
|
|
|
|
return SpaceCreationPostProcessTask(type: .createRoom(room.name), title: VectorL10n.spacesCreationPostProcessCreatingRoom(room.name), state: .none)
|
|
})
|
|
}
|
|
|
|
if creationParams.inviteType == .email {
|
|
let emailInviteCount = creationParams.userDefinedEmailInvites.count
|
|
if emailInviteCount > 0 {
|
|
let subTasks = creationParams.userDefinedEmailInvites.map { emailAddress in
|
|
SpaceCreationPostProcessTask(type: .inviteUsersByEmail, title: emailAddress, state: .none)
|
|
}
|
|
|
|
tasks.append(SpaceCreationPostProcessTask(type: .inviteUsersByEmail, title: VectorL10n.spacesCreationPostProcessInvitingUsers("\(creationParams.userDefinedEmailInvites.count)"), state: .none, subTasks: subTasks))
|
|
}
|
|
}
|
|
|
|
return tasks
|
|
}
|
|
|
|
private func runNextTask() {
|
|
currentTaskIndex += 1
|
|
guard currentTaskIndex < tasks.count else {
|
|
return
|
|
}
|
|
|
|
let task = tasks[currentTaskIndex]
|
|
|
|
guard !task.isFinished || task.state == .failure else {
|
|
runNextTask()
|
|
return
|
|
}
|
|
|
|
switch task.type {
|
|
case .createSpace:
|
|
createSpace(andUpdate: task)
|
|
case .uploadAvatar:
|
|
uploadAvatar(andUpdate: task)
|
|
case .addRooms:
|
|
addRooms(andUpdate: task)
|
|
case .createRoom(let roomName):
|
|
if let room = createdRoomsByName[roomName] {
|
|
addToSpace(room: room)
|
|
} else {
|
|
createRoom(withName: roomName, andUpdate: task)
|
|
}
|
|
case .inviteUsersByEmail:
|
|
inviteUsersByEmail(andUpdate: task)
|
|
}
|
|
}
|
|
|
|
private func createSpace(andUpdate task: SpaceCreationPostProcessTask) {
|
|
updateCurrentTask(with: .started)
|
|
|
|
var alias = creationParams.address
|
|
if let userDefinedAlias = creationParams.userDefinedAddress, !userDefinedAlias.isEmpty {
|
|
alias = userDefinedAlias
|
|
}
|
|
let userIdInvites = creationParams.inviteType == .userId ? creationParams.userIdInvites : []
|
|
session.spaceService.createSpace(withName: creationParams.name, topic: creationParams.topic, isPublic: creationParams.isPublic, aliasLocalPart: alias, inviteArray: userIdInvites) { [weak self] response in
|
|
guard let self = self else { return }
|
|
|
|
if response.isFailure {
|
|
self.updateCurrentTask(with: .failure)
|
|
} else {
|
|
self.creationParams.isModified = false
|
|
self.createdSpace = response.value
|
|
|
|
guard let createdSpaceId = self.createdSpace?.spaceId, let parentSpaceId = self.parentSpaceId, let parentSpace = self.session.spaceService.getSpace(withId: parentSpaceId) else {
|
|
self.updateCurrentTask(with: .success)
|
|
self.runNextTask()
|
|
return
|
|
}
|
|
|
|
parentSpace.addChild(roomId: createdSpaceId) { [weak self] _ in
|
|
guard let self = self else { return }
|
|
|
|
self.updateCurrentTask(with: .success)
|
|
self.runNextTask()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func uploadAvatar(andUpdate task: SpaceCreationPostProcessTask) {
|
|
updateCurrentTask(with: .started)
|
|
|
|
guard let avatar = creationParams.userSelectedAvatar, let spaceRoom = createdSpace?.room else {
|
|
updateCurrentTask(with: .success)
|
|
runNextTask()
|
|
return
|
|
}
|
|
|
|
let avatarUp = MXKTools.forceImageOrientationUp(avatar)
|
|
|
|
mediaUploader.uploadData(avatarUp?.jpegData(compressionQuality: 0.5), filename: nil, mimeType: "image/jpeg",
|
|
success: { [weak self] urlString in
|
|
guard let self = self else { return }
|
|
guard let urlString = urlString else { return }
|
|
guard let url = URL(string: urlString) else { return }
|
|
|
|
self.setAvatar(ofRoom: spaceRoom, withURL: url, andUpdate: task)
|
|
},
|
|
failure: { [weak self] _ in
|
|
guard let self = self else { return }
|
|
|
|
self.updateCurrentTask(with: .failure)
|
|
self.runNextTask()
|
|
})
|
|
}
|
|
|
|
private func setAvatar(ofRoom room: MXRoom, withURL url: URL, andUpdate task: SpaceCreationPostProcessTask) {
|
|
updateCurrentTask(with: .started)
|
|
|
|
room.setAvatar(url: url) { [weak self] response in
|
|
guard let self = self else { return }
|
|
|
|
self.updateCurrentTask(with: response.isSuccess ? .success : .failure)
|
|
self.runNextTask()
|
|
}
|
|
}
|
|
|
|
private func createRoom(withName roomName: String, andUpdate task: SpaceCreationPostProcessTask) {
|
|
guard let createdSpace = createdSpace else {
|
|
updateCurrentTask(with: .failure)
|
|
runNextTask()
|
|
return
|
|
}
|
|
|
|
updateCurrentTask(with: .started)
|
|
|
|
let joinRule: MXRoomJoinRule = creationParams.isPublic ? .public : .restricted
|
|
let parentRoomId = creationParams.isPublic ? nil : createdSpace.spaceId
|
|
session.createRoom(withName: roomName, joinRule: joinRule, topic: nil, parentRoomId: parentRoomId, aliasLocalPart: nil) { [weak self] response in
|
|
guard let self = self else { return }
|
|
|
|
guard response.isSuccess, let createdRoom = response.value else {
|
|
self.updateCurrentTask(with: .failure)
|
|
self.runNextTask()
|
|
return
|
|
}
|
|
|
|
self.createdRoomsByName[roomName] = createdRoom
|
|
self.addToSpace(room: createdRoom)
|
|
}
|
|
}
|
|
|
|
private func addToSpace(room: MXRoom) {
|
|
guard let createdSpace = createdSpace else {
|
|
updateCurrentTask(with: .failure)
|
|
runNextTask()
|
|
return
|
|
}
|
|
|
|
createdSpace.addChild(roomId: room.matrixItemId, completion: { response in
|
|
self.updateCurrentTask(with: response.isFailure ? .failure : .success)
|
|
self.runNextTask()
|
|
})
|
|
}
|
|
|
|
private func addRooms(andUpdate task: SpaceCreationPostProcessTask) {
|
|
updateCurrentTask(with: .started)
|
|
currentSubTaskIndex = -1
|
|
addNextExistingRoom()
|
|
}
|
|
|
|
private func inviteUsersByEmail(andUpdate task: SpaceCreationPostProcessTask) {
|
|
updateCurrentTask(with: .started)
|
|
currentSubTaskIndex = -1
|
|
inviteNextUserByEmail()
|
|
}
|
|
|
|
private func inviteNextUserByEmail() {
|
|
guard let createdSpace = createdSpace, let room = createdSpace.room else {
|
|
updateCurrentTask(with: .failure)
|
|
runNextTask()
|
|
return
|
|
}
|
|
|
|
currentSubTaskIndex += 1
|
|
|
|
guard currentSubTaskIndex < tasks[currentTaskIndex].subTasks.count else {
|
|
let isSuccess = tasks[currentTaskIndex].subTasks.reduce(true) { $0 && $1.state == .success }
|
|
updateCurrentTask(with: isSuccess ? .success : .failure)
|
|
runNextTask()
|
|
return
|
|
}
|
|
|
|
room.invite(.email(creationParams.emailInvites[currentSubTaskIndex])) { [weak self] response in
|
|
guard let self = self else { return }
|
|
|
|
self.tasks[self.currentTaskIndex].subTasks[self.currentSubTaskIndex].state = response.isSuccess ? .success : .failure
|
|
self.inviteNextUserByEmail()
|
|
}
|
|
}
|
|
|
|
private func addNextExistingRoom() {
|
|
guard let createdSpace = createdSpace else {
|
|
updateCurrentTask(with: .failure)
|
|
runNextTask()
|
|
return
|
|
}
|
|
|
|
currentSubTaskIndex += 1
|
|
|
|
guard currentSubTaskIndex < tasks[currentTaskIndex].subTasks.count else {
|
|
let isSuccess = tasks[currentTaskIndex].subTasks.reduce(true) { $0 && $1.state == .success }
|
|
updateCurrentTask(with: isSuccess ? .success : .failure)
|
|
runNextTask()
|
|
return
|
|
}
|
|
|
|
guard let roomId = creationParams.addedRoomIds?[currentSubTaskIndex] else {
|
|
updateCurrentTask(with: .failure)
|
|
runNextTask()
|
|
return
|
|
}
|
|
|
|
createdSpace.addChild(roomId: roomId, completion: { [weak self] response in
|
|
guard let self = self else { return }
|
|
|
|
self.tasks[self.currentTaskIndex].subTasks[self.currentSubTaskIndex].state = response.isSuccess ? .success : .failure
|
|
self.addNextExistingRoom()
|
|
})
|
|
}
|
|
|
|
private func fakeTaskExecution(task: SpaceCreationPostProcessTask) {
|
|
updateCurrentTask(with: .started)
|
|
processingQueue.async {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
|
self.updateCurrentTask(with: .success)
|
|
self.runNextTask()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func updateCurrentTask(with state: SpaceCreationPostProcessTaskState) {
|
|
guard currentTaskIndex < tasks.count else {
|
|
return
|
|
}
|
|
|
|
tasks[currentTaskIndex].state = state
|
|
tasksSubject.send(tasks)
|
|
}
|
|
}
|