Skip to content

Commit

Permalink
4850 bring leaving space experience in line with web (#6062)
Browse files Browse the repository at this point in the history
* Bring leaving space experience in line with Web #4850

- Done
  • Loading branch information
gileluard authored Apr 27, 2022
1 parent 291bcc3 commit 1a3effb
Show file tree
Hide file tree
Showing 23 changed files with 537 additions and 60 deletions.
3 changes: 2 additions & 1 deletion Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand Down
7 changes: 7 additions & 0 deletions Riot/Assets/en.lproj/Untranslated.strings
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,10 @@
"image_picker_action_files" = "Choose from files";

"spaces_feature_not_available" = "This feature isn't available here. For now, you can do this with %@ on your computer.";

"leave_space_action" = "Leave space";
"leave_space_and_one_room" = "Leave space and 1 room";
"leave_space_and_more_rooms" = "Leave space and %@ rooms";
"leave_space_selection_title" = "SELECT ROOMS";
"leave_space_selection_all_rooms" = "Select all rooms";
"leave_space_selection_no_rooms" = "Select no rooms";
24 changes: 24 additions & 0 deletions Riot/Generated/UntranslatedStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@ public extension VectorL10n {
static var imagePickerActionFiles: String {
return VectorL10n.tr("Untranslated", "image_picker_action_files")
}
/// Leave space
static var leaveSpaceAction: String {
return VectorL10n.tr("Untranslated", "leave_space_action")
}
/// Leave space and %@ rooms
static func leaveSpaceAndMoreRooms(_ p1: String) -> String {
return VectorL10n.tr("Untranslated", "leave_space_and_more_rooms", p1)
}
/// Leave space and 1 room
static var leaveSpaceAndOneRoom: String {
return VectorL10n.tr("Untranslated", "leave_space_and_one_room")
}
/// Select all rooms
static var leaveSpaceSelectionAllRooms: String {
return VectorL10n.tr("Untranslated", "leave_space_selection_all_rooms")
}
/// Select no rooms
static var leaveSpaceSelectionNoRooms: String {
return VectorL10n.tr("Untranslated", "leave_space_selection_no_rooms")
}
/// SELECT ROOMS
static var leaveSpaceSelectionTitle: String {
return VectorL10n.tr("Untranslated", "leave_space_selection_title")
}
/// This feature isn't available here. For now, you can do this with %@ on your computer.
static func spacesFeatureNotAvailable(_ p1: String) -> String {
return VectorL10n.tr("Untranslated", "spaces_feature_not_available", p1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ enum SpaceMenuListItemAction {
case addSpace
case settings
case leaveSpace
case leaveSpaceAndChooseRooms
case invite
}

Expand Down
32 changes: 31 additions & 1 deletion Riot/Modules/Spaces/SpaceMenu/SpaceMenuPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ class SpaceMenuPresenter: NSObject {
private weak var selectedSpace: MXSpace?
private var session: MXSession!
private var spaceId: String!

private weak var spaceMenuViewController: SpaceMenuViewController?
private var leaveSpaceCoordinator: Coordinator?

// MARK: - Public

func present(forSpaceWithId spaceId: String,
Expand Down Expand Up @@ -74,6 +76,7 @@ class SpaceMenuPresenter: NSObject {
private func showMenu(for spaceId: String, session: MXSession) {
let menuViewController = SpaceMenuViewController.instantiate(forSpaceWithId: spaceId, matrixSession: session, viewModel: self.viewModel)
self.present(menuViewController, animated: true)
self.spaceMenuViewController = menuViewController
}

private func present(_ viewController: SpaceMenuViewController, animated: Bool) {
Expand All @@ -96,6 +99,29 @@ class SpaceMenuPresenter: NSObject {
self.presentingViewController?.present(viewController, animated: animated, completion: nil)
}
}

@available(iOS 14.0, *)
private func showLeaveSpace() {
let name = session.spaceService.getSpace(withId: spaceId)?.summary?.displayname ?? VectorL10n.spaceTag

let selectionHeader = MatrixItemChooserSelectionHeader(title: VectorL10n.leaveSpaceSelectionTitle,
selectAllTitle: VectorL10n.leaveSpaceSelectionAllRooms,
selectNoneTitle: VectorL10n.leaveSpaceSelectionNoRooms)
let paramaters = MatrixItemChooserCoordinatorParameters(session: session,
title: VectorL10n.leaveSpaceTitle(name),
detail: VectorL10n.leaveSpaceMessage(name),
selectionHeader: selectionHeader,
viewProvider: LeaveSpaceViewProvider(navTitle: nil),
itemsProcessor: LeaveSpaceItemsProcessor(spaceId: spaceId, session: session))
let coordinator = MatrixItemChooserCoordinator(parameters: paramaters)
coordinator.start()
self.leaveSpaceCoordinator = coordinator
coordinator.completion = { [weak self] result in
self?.spaceMenuViewController?.dismiss(animated: true)
self?.leaveSpaceCoordinator = nil
}
self.spaceMenuViewController?.present(coordinator.toPresentable(), animated: true)
}
}

// MARK: - SpaceMenuModelViewModelCoordinatorDelegate
Expand All @@ -120,6 +146,10 @@ extension SpaceMenuPresenter: SpaceMenuModelViewModelCoordinatorDelegate {
self.delegate?.spaceMenuPresenter(self, didCompleteWith: .settings, forSpaceWithId: self.spaceId, with: self.session)
case .invite:
self.delegate?.spaceMenuPresenter(self, didCompleteWith: .invite, forSpaceWithId: self.spaceId, with: self.session)
case .leaveSpaceAndChooseRooms:
if #available(iOS 14.0, *) {
self.showLeaveSpace()
}
default:
MXLog.error("[SpaceMenuPresenter] spaceListViewModel didSelectItem: invalid action \(action)")
}
Expand Down
26 changes: 16 additions & 10 deletions Riot/Modules/Spaces/SpaceMenu/SpaceMenuViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,24 @@ class SpaceMenuViewModel: SpaceMenuViewModelType {
}

private func leaveSpace() {
guard let room = self.session.room(withRoomId: self.spaceId), let displayName = room.summary?.displayname else {
return
}
guard #available(iOS 14, *) else {
guard let room = self.session.room(withRoomId: self.spaceId), let displayName = room.summary?.displayname else {
return
}

var isAdmin = false
if let roomState = room.dangerousSyncState, let powerLevels = roomState.powerLevels {
let powerLevel = powerLevels.powerLevelOfUser(withUserID: self.session.myUserId)
let roomPowerLevel = RoomPowerLevelHelper.roomPowerLevel(from: powerLevel)
isAdmin = roomPowerLevel == .admin
}
var isAdmin = false
if let roomState = room.dangerousSyncState, let powerLevels = roomState.powerLevels {
let powerLevel = powerLevels.powerLevelOfUser(withUserID: self.session.myUserId)
let roomPowerLevel = RoomPowerLevelHelper.roomPowerLevel(from: powerLevel)
isAdmin = roomPowerLevel == .admin
}

self.viewDelegate?.spaceMenuViewModel(self, didUpdateViewState: .leaveOptions(displayName, isAdmin))
self.viewDelegate?.spaceMenuViewModel(self, didUpdateViewState: .leaveOptions(displayName, isAdmin))
return
}

self.viewDelegate?.spaceMenuViewModel(self, didUpdateViewState: .deselect)
self.coordinatorDelegate?.spaceMenuViewModel(self, didSelectItemWith: .leaveSpaceAndChooseRooms)
}

private func leaveSpaceAndKeepRooms() {
Expand Down
70 changes: 70 additions & 0 deletions RiotSwiftUI/Modules/Common/Util/RadioButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// Copyright 2022 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 SwiftUI

@available(iOS 14.0, *)
struct RadioButton: View {

// MARK: - Properties

var title: String
var selected: Bool
let action: () -> Void

// MARK: Private

@Environment(\.theme) private var theme: ThemeSwiftUI

// MARK: Public

var body: some View {
Button(action: action, label: {
HStack {
Image(systemName: selected ? "largecircle.fill.circle" : "circle")
.renderingMode(.template)
.resizable().frame(width: 20, height: 20)
.foregroundColor(selected ? theme.colors.accent : theme.colors.tertiaryContent)
Text(title)
.font(theme.fonts.callout)
.foregroundColor(theme.colors.primaryContent)
Spacer()
}
.padding(EdgeInsets(top: 3, leading: 3, bottom: 3, trailing: 3))
.background(Color.clear)
})
}
}

// MARK: - Previews

@available(iOS 14.0, *)
struct RadioButton_Previews: PreviewProvider {
static var previews: some View {
Group {
buttonGroup.theme(.light)
buttonGroup.theme(.dark).preferredColorScheme(.dark)
}
.padding()
}

static var buttonGroup: some View {
VStack {
RadioButton(title: "A title", selected: false, action: {})
RadioButton(title: "A title", selected: true, action: {})
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// 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 SwiftUI

class LeaveSpaceViewProvider: MatrixItemChooserCoordinatorViewProvider {

private let navTitle: String?

init(navTitle: String?) {
self.navTitle = navTitle
}

@available(iOS 14, *)
func view(with viewModel: MatrixItemChooserViewModelType.Context) -> AnyView {
return AnyView(LeaveSpace(viewModel: viewModel, navTitle: navTitle))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//
// 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 MatrixSDK

class LeaveSpaceItemsProcessor: MatrixItemChooserProcessorProtocol {

// MARK: Private

private let spaceId: String
private let session: MXSession

// MARK: Setup

init(spaceId: String, session: MXSession) {
self.spaceId = spaceId
self.session = session
self.dataSource = MatrixItemChooserDirectChildrenDataSource(parentId: spaceId)
}

// MARK: MatrixItemChooserSelectionProcessorProtocol

private(set) var dataSource: MatrixItemChooserDataSource

var loadingText: String? {
VectorL10n.roomAccessSettingsScreenSettingRoomAccess
}

func computeSelection(withIds itemsIds: [String], completion: @escaping (Result<Void, Error>) -> Void) {
guard let space = self.session.spaceService.getSpace(withId: self.spaceId) else {
return
}

self.leaveAllRooms(from: itemsIds, at: 0) { [weak self] result in
guard let self = self else { return }

switch result {
case .success:
self.leaveSpace(space, completion: completion)
case .failure(let error):
completion(.failure(error))
}
}
}

func isItemIncluded(_ item: (MatrixListItemData)) -> Bool {
return true
}

// MARK: Private

/// Leave room with room ID from `roomIds` at `index`.
/// Recurse to the next index once done.
private func leaveAllRooms(from roomIds: [String], at index: Int, completion: @escaping (Result<Void, Error>) -> Void) {
guard index < roomIds.count else {
completion(.success(()))
return
}

guard let room = self.session.room(withRoomId: roomIds[index]), !room.isDirect else {
self.leaveAllRooms(from: roomIds, at: index+1, completion: completion)
return
}

MXLog.debug("[LeaveSpaceItemsProcessor] leaving room \(room.displayName ?? room.roomId)")
room.leave { [weak self] response in
guard let self = self else { return }

switch response {
case .success:
self.leaveAllRooms(from: roomIds, at: index+1, completion: completion)
case .failure(let error):
MXLog.error("[LeaveSpaceItemsProcessor] failed to leave room with error: \(error)")
completion(.failure(error))
}
}
}

private func leaveSpace(_ space: MXSpace, completion: @escaping (Result<Void, Error>) -> Void) {
MXLog.debug("[LeaveSpaceItemsProcessor] leaving space")
space.room?.leave(completion: { response in
switch response {
case .success:
completion(.success(()))
case .failure(let error):
MXLog.error("[LeaveSpaceItemsProcessor] failed to leave space with error: \(error)")
completion(.failure(error))
}
})
}
}
Loading

0 comments on commit 1a3effb

Please sign in to comment.