Files
bundesmessenger-ios/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/MatrixSDK/SpaceCreationPostProcessService.swift
Gil Eluard 272d778779 [iOS] Create public space #143
- Initial space creation flow
2021-11-23 09:35:32 +01:00

331 lines
13 KiB
Swift

// File created from SimpleUserProfileExample
// $ createScreen.sh Spaces/SpaceCreation/SpaceCreationPostProcess SpaceCreationPostProcess
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import Combine
import MatrixSDK
@available(iOS 14.0, *)
class SpaceCreationPostProcessService: SpaceCreationPostProcessServiceProtocol {
// MARK: - Properties
// MARK: Private
private let session: MXSession
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(set) var createdSpaceId: String?
private var createdRoomsByName: [String: MXRoom] = [:]
private var currentSubTaskIndex = 0
private var processingQueue = DispatchQueue(label: "org.matrix.sdk.MXSpace.processingQueue", attributes: .concurrent)
private lazy var stateEventBuilder: MXRoomInitialStateEventBuilder = {
return MXRoomInitialStateEventBuilder()
}()
private lazy var mediaUploader: MXMediaLoader = {
return MXMediaManager.prepareUploader(withMatrixSession: session, initialRange: 0, andRange: 1.0)
}()
// MARK: Public
private(set) var tasksSubject: CurrentValueSubject<[SpaceCreationPostProcessTask], Never>
// MARK: - Setup
init(session: MXSession, creationParams: SpaceCreationParameters) {
self.session = session
self.creationParams = creationParams
self.tasks = Self.tasks(with: creationParams)
self.tasksSubject = CurrentValueSubject(tasks)
}
deinit {
}
// MARK: - Public
func run() {
self.isRetry = self.currentTaskIndex > 0
self.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 creationParams.addedRoomIds.isEmpty {
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)
}))
} else {
let subTasks = creationParams.addedRoomIds.map { roomId in
SpaceCreationPostProcessTask(type: .addRooms, title: roomId, state: .none)
}
tasks.append(SpaceCreationPostProcessTask(type: .addRooms, title: VectorL10n.spacesCreationPostProcessAddingRooms("\(creationParams.addedRoomIds.count)"), state: .none, subTasks: subTasks))
}
if creationParams.userIdInvites.isEmpty {
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
}
// fakeTaskExecution(task: task)
// 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) {
let parameters = MXSpaceCreationParameters()
parameters.name = creationParams.name
parameters.topic = creationParams.topic
parameters.preset = creationParams.isPublic ? kMXRoomPresetPublicChat : kMXRoomPresetPrivateChat
parameters.visibility = creationParams.isPublic ? kMXRoomDirectoryVisibilityPublic : kMXRoomDirectoryVisibilityPrivate
if creationParams.isPublic {
var alias = creationParams.address
if let userDefinedAlias = creationParams.userDefinedAddress {
alias = userDefinedAlias
}
parameters.roomAlias = alias?.fullLocalAlias(with: session)
let guestAccessStateEvent = self.stateEventBuilder.buildGuestAccessEvent(withAccess: .canJoin)
parameters.addOrUpdateInitialStateEvent(guestAccessStateEvent)
let historyVisibilityStateEvent = self.stateEventBuilder.buildHistoryVisibilityEvent(withVisibility: .worldReadable)
parameters.addOrUpdateInitialStateEvent(historyVisibilityStateEvent)
}
parameters.inviteArray = creationParams.userIdInvites
updateCurrentTask(with: .started)
session.spaceService.createSpace(with: parameters) { [weak self] response in
guard let self = self else { return }
if response.isFailure {
self.updateCurrentTask(with: .failure)
} else {
self.updateCurrentTask(with: .success)
self.createdSpace = response.value
self.runNextTask()
}
}
}
private func uploadAvatar(andUpdate task: SpaceCreationPostProcessTask) {
self.updateCurrentTask(with: .started)
guard let avatar = creationParams.userSelectedAvatar, let spaceRoom = self.createdSpace?.room else {
self.updateCurrentTask(with: .success)
self.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] (error) 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) {
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) {
let parameters = MXRoomCreationParameters()
parameters.name = roomName
parameters.visibility = creationParams.isPublic ? kMXRoomDirectoryVisibilityPublic : kMXRoomDirectoryVisibilityPrivate
parameters.preset = creationParams.isPublic ? kMXRoomPresetPublicChat : kMXRoomPresetPrivateChat
updateCurrentTask(with: .started)
session.createRoom(parameters: parameters) { [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) {
self.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 = self.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 = self.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
}
createdSpace.addChild(roomId: creationParams.addedRoomIds[currentSubTaskIndex], 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
self.tasksSubject.send(tasks)
}
}