mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Leave Room (#699)
* created the row in the view and the alert, and added the new function to the RoomProxy * fixed an issue with the alert function * handling the navigation * fixed a bug with the detail coordinators being dismissed incorrectly when inside a stack * implementation completed * replaced UI screenshots * added a test for the fixed bug of the coordinators * trying to increase the wait time for the expectation * improved the test * improved the buttons UI * uploading artifacts for unit tests * added result bundle true * improved the tests * added a new test * pr suggestions * updating mock * PR suggestions * improved tests * fixed UI tests * pr should be ready now * removed testing code * reduced complexity * fixed test * added a an assert to the new test case * more tests and messages cases * pr comments addressed * completed
This commit is contained in:
@@ -76,7 +76,7 @@ struct ReportContentScreen: View {
|
||||
// MARK: - Previews
|
||||
|
||||
struct ReportContent_Previews: PreviewProvider {
|
||||
static let viewModel = ReportContentViewModel(itemID: "", roomProxy: MockRoomProxy(displayName: nil))
|
||||
static let viewModel = ReportContentViewModel(itemID: "", roomProxy: RoomProxyMock(with: .init(displayName: nil)))
|
||||
|
||||
static var previews: some View {
|
||||
ReportContentScreen(context: viewModel.context)
|
||||
|
||||
@@ -24,6 +24,7 @@ struct RoomDetailsCoordinatorParameters {
|
||||
|
||||
enum RoomDetailsCoordinatorAction {
|
||||
case cancel
|
||||
case leftRoom
|
||||
}
|
||||
|
||||
final class RoomDetailsCoordinator: CoordinatorProtocol {
|
||||
@@ -51,6 +52,8 @@ final class RoomDetailsCoordinator: CoordinatorProtocol {
|
||||
self.presentRoomMemberDetails(members)
|
||||
case .cancel:
|
||||
self.callback?(.cancel)
|
||||
case .leftRoom:
|
||||
self.callback?(.leftRoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import UIKit
|
||||
|
||||
enum RoomDetailsViewModelAction {
|
||||
case requestMemberDetailsPresentation([RoomMemberProxy])
|
||||
case leftRoom
|
||||
case cancel
|
||||
}
|
||||
|
||||
@@ -49,15 +50,34 @@ struct RoomDetailsViewState: BindableState {
|
||||
struct RoomDetailsViewStateBindings {
|
||||
/// Information describing the currently displayed alert.
|
||||
var alertInfo: AlertInfo<RoomDetailsErrorType>?
|
||||
var leaveRoomAlertItem: LeaveRoomAlertItem?
|
||||
}
|
||||
|
||||
enum RoomDetailsErrorType: Hashable {
|
||||
/// A specific error message shown in an alert.
|
||||
case alert(String)
|
||||
struct LeaveRoomAlertItem: AlertItem {
|
||||
enum RoomState {
|
||||
case empty
|
||||
case `public`
|
||||
case `private`
|
||||
}
|
||||
|
||||
let state: RoomState
|
||||
let title = ElementL10n.roomProfileSectionMoreLeave
|
||||
let confirmationTitle = ElementL10n.actionLeave
|
||||
let cancelTitle = ElementL10n.actionCancel
|
||||
|
||||
var subtitle: String {
|
||||
switch state {
|
||||
case .empty: return ElementL10n.roomDetailsLeaveEmptyRoomAlertSubtitle
|
||||
case .private: return ElementL10n.roomDetailsLeavePrivateRoomAlertSubtitle
|
||||
case .public: return ElementL10n.roomDetailsLeaveRoomAlertSubtitle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum RoomDetailsViewAction {
|
||||
case processTapPeople
|
||||
case processTapLeave
|
||||
case confirmLeave
|
||||
case copyRoomLink
|
||||
}
|
||||
|
||||
@@ -72,3 +92,10 @@ struct RoomDetailsMember: Identifiable, Equatable {
|
||||
avatarURL = proxy.avatarURL
|
||||
}
|
||||
}
|
||||
|
||||
enum RoomDetailsErrorType: Hashable {
|
||||
/// A specific error message shown in an alert.
|
||||
case alert(String)
|
||||
/// Leaving room has failed..
|
||||
case unknown
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import SwiftUI
|
||||
typealias RoomDetailsViewModelType = StateStoreViewModel<RoomDetailsViewState, RoomDetailsViewAction>
|
||||
|
||||
class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtocol {
|
||||
private let roomProxy: RoomProxyProtocol
|
||||
private var members: [RoomMemberProxy] = [] {
|
||||
didSet {
|
||||
state.members = members.map { RoomDetailsMember(withProxy: $0) }
|
||||
@@ -29,6 +30,7 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
|
||||
init(roomProxy: RoomProxyProtocol,
|
||||
mediaProvider: MediaProviderProtocol) {
|
||||
self.roomProxy = roomProxy
|
||||
super.init(initialViewState: .init(roomId: roomProxy.id,
|
||||
canonicalAlias: roomProxy.canonicalAlias,
|
||||
isEncrypted: roomProxy.isEncrypted,
|
||||
@@ -60,10 +62,20 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
callback?(.requestMemberDetailsPresentation(members))
|
||||
case .copyRoomLink:
|
||||
copyRoomLink()
|
||||
case .processTapLeave:
|
||||
guard members.count > 1 else {
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: .empty)
|
||||
return
|
||||
}
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: roomProxy.isPublic ? .public : .private)
|
||||
case .confirmLeave:
|
||||
await leaveRoom()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private static let leaveRoomLoadingID = "LeaveRoomLoading"
|
||||
|
||||
private func copyRoomLink() {
|
||||
if let roomLink = state.permalink {
|
||||
@@ -73,4 +85,16 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: ElementL10n.unknownError))
|
||||
}
|
||||
}
|
||||
|
||||
private func leaveRoom() async {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.leaveRoomLoadingID, type: .modal, title: ElementL10n.loading, persistent: true))
|
||||
let result = await roomProxy.leaveRoom()
|
||||
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(Self.leaveRoomLoadingID)
|
||||
switch result {
|
||||
case .failure:
|
||||
state.bindings.alertInfo = AlertInfo(id: .unknown)
|
||||
case .success:
|
||||
callback?(.leftRoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,15 @@ struct RoomDetailsScreen: View {
|
||||
if context.viewState.isEncrypted {
|
||||
securitySection
|
||||
}
|
||||
|
||||
leaveRoomSection
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.element.formBackground.ignoresSafeArea())
|
||||
.alert(item: $context.alertInfo) { $0.alert }
|
||||
.alert(item: $context.leaveRoomAlertItem,
|
||||
actions: leaveRoomAlertActions,
|
||||
message: leaveRoomAlertMessage)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -147,6 +152,30 @@ struct RoomDetailsScreen: View {
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
private var leaveRoomSection: some View {
|
||||
Section {
|
||||
Button(role: .destructive) {
|
||||
context.send(viewAction: .processTapLeave)
|
||||
} label: {
|
||||
Label(ElementL10n.roomProfileSectionMoreLeave, systemImage: "door.right.hand.open")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: nil))
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func leaveRoomAlertActions(_ item: LeaveRoomAlertItem) -> some View {
|
||||
Button(item.cancelTitle, role: .cancel) { }
|
||||
Button(item.confirmationTitle, role: .destructive) {
|
||||
context.send(viewAction: .confirmLeave)
|
||||
}
|
||||
}
|
||||
|
||||
private func leaveRoomAlertMessage(_ item: LeaveRoomAlertItem) -> some View {
|
||||
Text(item.subtitle)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
@@ -158,12 +187,12 @@ struct RoomDetails_Previews: PreviewProvider {
|
||||
.mockBob,
|
||||
.mockCharlie
|
||||
]
|
||||
let roomProxy = MockRoomProxy(displayName: "Room A",
|
||||
topic: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
isDirect: false,
|
||||
isEncrypted: true,
|
||||
canonicalAlias: "#alias:domain.com",
|
||||
members: members)
|
||||
let roomProxy = RoomProxyMock(with: .init(displayName: "Room A",
|
||||
topic: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
isDirect: false,
|
||||
isEncrypted: true,
|
||||
canonicalAlias: "#alias:domain.com",
|
||||
members: members))
|
||||
|
||||
return RoomDetailsViewModel(roomProxy: roomProxy,
|
||||
mediaProvider: MockMediaProvider())
|
||||
|
||||
@@ -24,6 +24,10 @@ struct RoomScreenCoordinatorParameters {
|
||||
let emojiProvider: EmojiProviderProtocol
|
||||
}
|
||||
|
||||
enum RoomScreenCoordinatorAction {
|
||||
case leftRoom
|
||||
}
|
||||
|
||||
final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
private var parameters: RoomScreenCoordinatorParameters
|
||||
|
||||
@@ -31,6 +35,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
private var navigationStackCoordinator: NavigationStackCoordinator {
|
||||
parameters.navigationStackCoordinator
|
||||
}
|
||||
|
||||
var callback: ((RoomScreenCoordinatorAction) -> Void)?
|
||||
|
||||
init(parameters: RoomScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
@@ -111,8 +117,13 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
roomProxy: parameters.roomProxy,
|
||||
mediaProvider: parameters.mediaProvider)
|
||||
let coordinator = RoomDetailsCoordinator(parameters: params)
|
||||
coordinator.callback = { [weak self] _ in
|
||||
self?.navigationStackCoordinator.pop()
|
||||
coordinator.callback = { [weak self] action in
|
||||
switch action {
|
||||
case .cancel:
|
||||
self?.navigationStackCoordinator.pop()
|
||||
case .leftRoom:
|
||||
self?.callback?(.leftRoom)
|
||||
}
|
||||
}
|
||||
|
||||
navigationStackCoordinator.push(coordinator)
|
||||
|
||||
Reference in New Issue
Block a user