mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Ignore User UI (#737)
* generated files * Revert "generated files" This reverts commit f62c1dbcd9e505083ad4ff17532e2c054e253187. * renaming files to RoomMembersList * completed the renaming of the list files * added generated files * basic setup of the view and the mock * added a new mock with a avatar * share/copy link * copyUserLink implemented * removed unimplemented tests * block user UI * navigation to room member details added * implemented but we require a sync from the Rust side * adjusted some UI test screens * alert for unblocking * completed * some tests * changelog * ignore user ui enabled * loader inside the button when the request is fetching * removed unused code * blocking the button while loading * improved the code * changelog * UI tests * unit tests * added collection concurrency kit * Revert "added collection concurrency kit" This reverts commit 499fbe129f73a75e903d9f4952fe2ad672930f04. * replaced the asyncMap with a @MainActor builder function * pr comments * added localazy to setup * sdk bump to 1.0.49
This commit is contained in:
@@ -27,6 +27,7 @@ struct A11yIdentifiers {
|
||||
static let sessionVerificationScreen = SessionVerificationScreen()
|
||||
static let softLogoutScreen = SoftLogoutScreen()
|
||||
static let startChatScreen = StartChatScreen()
|
||||
static let roomMemberDetailsScreen = RoomMemberDetailsScreen()
|
||||
|
||||
struct BugReportScreen {
|
||||
let report = "bug_report-report"
|
||||
@@ -98,4 +99,9 @@ struct A11yIdentifiers {
|
||||
let closeStartChat = "start_chat-close"
|
||||
let inviteFriends = "start_chat-invite_friends"
|
||||
}
|
||||
|
||||
struct RoomMemberDetailsScreen {
|
||||
let ignore = "room_member_details-ignore"
|
||||
let unignore = "room_member_details-unignore"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
//
|
||||
// Copyright 2023 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
|
||||
|
||||
extension Sequence {
|
||||
func asyncMap<T>(_ transform: @escaping (Element) async -> T) async -> [T] {
|
||||
await withTaskGroup(of: T.self) { group in
|
||||
var transformedElements = [T]()
|
||||
|
||||
for element in self {
|
||||
group.addTask {
|
||||
await transform(element)
|
||||
}
|
||||
}
|
||||
|
||||
for await transformedElement in group {
|
||||
transformedElements.append(transformedElement)
|
||||
}
|
||||
|
||||
return transformedElements
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,16 @@ import SwiftUI
|
||||
/// A view to be added on the trailing edge of a form row.
|
||||
enum FormRowAccessory: View {
|
||||
case navigationLink
|
||||
case progressView
|
||||
|
||||
var body: some View {
|
||||
switch self {
|
||||
case .navigationLink:
|
||||
return Image(systemName: "chevron.forward")
|
||||
Image(systemName: "chevron.forward")
|
||||
.font(.element.subheadlineBold)
|
||||
.foregroundColor(.element.quaternaryContent)
|
||||
case .progressView:
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ struct RoomMemberDetailsViewState: BindableState {
|
||||
let isAccountOwner: Bool
|
||||
let permalink: URL?
|
||||
var isIgnored: Bool
|
||||
var isProcessingIgnoreRequest = false
|
||||
|
||||
var bindings: RoomMemberDetailsViewStateBindings
|
||||
}
|
||||
@@ -34,7 +35,7 @@ struct RoomMemberDetailsViewStateBindings {
|
||||
var errorAlert: ErrorAlertItem?
|
||||
}
|
||||
|
||||
struct IgnoreUserAlertItem: AlertItem {
|
||||
struct IgnoreUserAlertItem: AlertItem, Equatable {
|
||||
enum Action {
|
||||
case ignore
|
||||
case unignore
|
||||
@@ -73,8 +74,8 @@ struct IgnoreUserAlertItem: AlertItem {
|
||||
}
|
||||
|
||||
enum RoomMemberDetailsViewAction {
|
||||
case showUnblockAlert
|
||||
case showBlockAlert
|
||||
case showUnignoreAlert
|
||||
case showIgnoreAlert
|
||||
case ignoreConfirmed
|
||||
case unignoreConfirmed
|
||||
case copyUserLink
|
||||
|
||||
@@ -39,9 +39,9 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
|
||||
override func process(viewAction: RoomMemberDetailsViewAction) async {
|
||||
switch viewAction {
|
||||
case .showUnblockAlert:
|
||||
case .showUnignoreAlert:
|
||||
state.bindings.ignoreUserAlert = .init(action: .unignore)
|
||||
case .showBlockAlert:
|
||||
case .showIgnoreAlert:
|
||||
state.bindings.ignoreUserAlert = .init(action: .ignore)
|
||||
case .copyUserLink:
|
||||
copyUserLink()
|
||||
@@ -64,7 +64,10 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
}
|
||||
|
||||
private func ignoreUser() async {
|
||||
switch await roomMemberProxy.ignoreUser() {
|
||||
state.isProcessingIgnoreRequest = true
|
||||
let result = await roomMemberProxy.ignoreUser()
|
||||
state.isProcessingIgnoreRequest = false
|
||||
switch result {
|
||||
case .success:
|
||||
state.isIgnored = true
|
||||
case .failure:
|
||||
@@ -73,7 +76,10 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
}
|
||||
|
||||
private func unignoreUser() async {
|
||||
switch await roomMemberProxy.unignoreUser() {
|
||||
state.isProcessingIgnoreRequest = true
|
||||
let result = await roomMemberProxy.unignoreUser()
|
||||
state.isProcessingIgnoreRequest = false
|
||||
switch result {
|
||||
case .success:
|
||||
state.isIgnored = false
|
||||
case .failure:
|
||||
|
||||
@@ -23,10 +23,9 @@ struct RoomMemberDetailsScreen: View {
|
||||
Form {
|
||||
headerSection
|
||||
|
||||
// TODO: Uncomment when the feature is ready
|
||||
// if !context.viewState.isAccountOwner {
|
||||
// blockUserSection
|
||||
// }
|
||||
if !context.viewState.isAccountOwner {
|
||||
blockUserSection
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.element.formBackground.ignoresSafeArea())
|
||||
@@ -77,18 +76,22 @@ struct RoomMemberDetailsScreen: View {
|
||||
Section {
|
||||
if context.viewState.isIgnored {
|
||||
Button {
|
||||
context.send(viewAction: .showUnblockAlert)
|
||||
context.send(viewAction: .showUnignoreAlert)
|
||||
} label: {
|
||||
Label(L10n.screenRoomMemberDetailsUnblockUser, systemImage: "slash.circle")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: nil))
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomMemberDetailsScreen.unignore)
|
||||
.buttonStyle(FormButtonStyle(accessory: context.viewState.isProcessingIgnoreRequest ? .progressView : nil))
|
||||
.disabled(context.viewState.isProcessingIgnoreRequest)
|
||||
} else {
|
||||
Button(role: .destructive) {
|
||||
context.send(viewAction: .showBlockAlert)
|
||||
context.send(viewAction: .showIgnoreAlert)
|
||||
} label: {
|
||||
Label(L10n.screenRoomMemberDetailsBlockUser, systemImage: "slash.circle")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: nil))
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomMemberDetailsScreen.ignore)
|
||||
.buttonStyle(FormButtonStyle(accessory: context.viewState.isProcessingIgnoreRequest ? .progressView : nil))
|
||||
.disabled(context.viewState.isProcessingIgnoreRequest)
|
||||
}
|
||||
}
|
||||
.formSectionStyle()
|
||||
|
||||
@@ -278,12 +278,17 @@ class RoomProxy: RoomProxyProtocol {
|
||||
return members
|
||||
}
|
||||
|
||||
let proxiedMembers = await members.asyncMap { RoomMemberProxy(member: $0, backgroundTaskService: self.backgroundTaskService) }
|
||||
let proxiedMembers = buildRoomMemberProxies(members: members)
|
||||
return .success(proxiedMembers)
|
||||
} catch {
|
||||
return .failure(.failedRetrievingMembers)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func buildRoomMemberProxies(members: [RoomMember]) -> [RoomMemberProxy] {
|
||||
members.map { RoomMemberProxy(member: $0, backgroundTaskService: backgroundTaskService) }
|
||||
}
|
||||
|
||||
func retryDecryption(for sessionID: String) async {
|
||||
await Task.dispatch(on: .global()) { [weak self] in
|
||||
|
||||
@@ -318,6 +318,16 @@ class MockScreen: Identifiable {
|
||||
let coordinator = RoomMemberDetailsCoordinator(parameters: .init(roomMemberProxy: RoomMemberProxyMock.mockMe, mediaProvider: MockMediaProvider()))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomMemberDetails:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = RoomMemberDetailsCoordinator(parameters: .init(roomMemberProxy: RoomMemberProxyMock.mockAlice, mediaProvider: MockMediaProvider()))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomMemberDetailsIgnoredUser:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = RoomMemberDetailsCoordinator(parameters: .init(roomMemberProxy: RoomMemberProxyMock.mockIgnored, mediaProvider: MockMediaProvider()))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ enum UITestsScreenIdentifier: String {
|
||||
case roomDetailsScreenWithRoomAvatar
|
||||
case roomMembersListScreen
|
||||
case roomMemberDetailsAccountOwner
|
||||
case roomMemberDetails
|
||||
case roomMemberDetailsIgnoredUser
|
||||
case reportContent
|
||||
case startChat
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user