mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 03:48:27 +00:00
Implement Knock Logic (#3573)
* WIP RequestToJoin struct * implemented the logic to display the cells * knock request banner accept flow * mark all knocks as seen implemented * details logic * implemented accept, decline and ban in the list * added a loader and modified the stacked view of the banner * pr suggestions * updated naming and loading strings * added the initial loading state improved code and the tests * updated a string that has changed * code improvement * tests for the room screen view model * room details tests and improved the knock requests tests for the room screen * knock requests list tests * added error state alerts with retry * struct has been renamed on the sdk so I renamed it also on the app side * update SDK
This commit is contained in:
@@ -195,7 +195,6 @@ struct CreateRoomScreen: View {
|
||||
Text("#")
|
||||
.font(.compound.bodyLG)
|
||||
.foregroundStyle(.compound.textSecondary)
|
||||
|
||||
TextField("", text: aliasBinding)
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
|
||||
@@ -219,13 +219,13 @@ extension HomeScreenRoom {
|
||||
|
||||
let hasUnreadMessages = hideUnreadMessagesBadge ? false : summary.hasUnreadMessages
|
||||
|
||||
let isDotShown = hasUnreadMessages || summary.hasUnreadMentions || summary.hasUnreadNotifications || summary.isMarkedUnread || summary.joinRequestType?.isKnock == true
|
||||
let isDotShown = hasUnreadMessages || summary.hasUnreadMentions || summary.hasUnreadNotifications || summary.isMarkedUnread || summary.knockRequestType?.isKnock == true
|
||||
let isMentionShown = summary.hasUnreadMentions && !summary.isMuted
|
||||
let isMuteShown = summary.isMuted
|
||||
let isCallShown = summary.hasOngoingCall
|
||||
let isHighlighted = summary.isMarkedUnread || (!summary.isMuted && (summary.hasUnreadNotifications || summary.hasUnreadMentions)) || summary.joinRequestType?.isKnock == true
|
||||
let isHighlighted = summary.isMarkedUnread || (!summary.isMuted && (summary.hasUnreadNotifications || summary.hasUnreadMentions)) || summary.knockRequestType?.isKnock == true
|
||||
|
||||
let type: HomeScreenRoom.RoomType = switch summary.joinRequestType {
|
||||
let type: HomeScreenRoom.RoomType = switch summary.knockRequestType {
|
||||
case .invite(let inviter): .invite(inviterDetails: inviter.map(RoomInviterDetails.init))
|
||||
case .knock: .knock
|
||||
case .none: .room
|
||||
|
||||
@@ -178,7 +178,7 @@ private extension HomeScreenRoom {
|
||||
|
||||
let summary = RoomSummary(roomListItem: RoomListItemSDKMock(),
|
||||
id: "@someone:somewhere.com",
|
||||
joinRequestType: .invite(inviter: inviter),
|
||||
knockRequestType: .invite(inviter: inviter),
|
||||
name: "Some Guy",
|
||||
isDirect: true,
|
||||
avatarURL: nil,
|
||||
@@ -205,7 +205,7 @@ private extension HomeScreenRoom {
|
||||
|
||||
let summary = RoomSummary(roomListItem: RoomListItemSDKMock(),
|
||||
id: "@someone:somewhere.com",
|
||||
joinRequestType: .invite(inviter: inviter),
|
||||
knockRequestType: .invite(inviter: inviter),
|
||||
name: "Awesome Room",
|
||||
isDirect: false,
|
||||
avatarURL: avatarURL,
|
||||
|
||||
@@ -152,7 +152,7 @@ private extension HomeScreenRoom {
|
||||
|
||||
let summary = RoomSummary(roomListItem: RoomListItemSDKMock(),
|
||||
id: "@someone:somewhere.com",
|
||||
joinRequestType: .invite(inviter: inviter),
|
||||
knockRequestType: .invite(inviter: inviter),
|
||||
name: "Some Guy",
|
||||
isDirect: true,
|
||||
avatarURL: nil,
|
||||
@@ -179,7 +179,7 @@ private extension HomeScreenRoom {
|
||||
|
||||
let summary = RoomSummary(roomListItem: RoomListItemSDKMock(),
|
||||
id: "@someone:somewhere.com",
|
||||
joinRequestType: .invite(inviter: inviter),
|
||||
knockRequestType: .invite(inviter: inviter),
|
||||
name: "Awesome Room",
|
||||
isDirect: false,
|
||||
avatarURL: avatarURL,
|
||||
|
||||
+3
-1
@@ -13,6 +13,7 @@ import SwiftUI
|
||||
struct KnockRequestsListScreenCoordinatorParameters {
|
||||
let roomProxy: JoinedRoomProxyProtocol
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
}
|
||||
|
||||
enum KnockRequestsListScreenCoordinatorAction { }
|
||||
@@ -29,7 +30,8 @@ final class KnockRequestsListScreenCoordinator: CoordinatorProtocol {
|
||||
|
||||
init(parameters: KnockRequestsListScreenCoordinatorParameters) {
|
||||
viewModel = KnockRequestsListScreenViewModel(roomProxy: parameters.roomProxy,
|
||||
mediaProvider: parameters.mediaProvider)
|
||||
mediaProvider: parameters.mediaProvider,
|
||||
userIndicatorController: parameters.userIndicatorController)
|
||||
}
|
||||
|
||||
func start() { }
|
||||
|
||||
+59
-6
@@ -10,18 +10,43 @@ import Foundation
|
||||
enum KnockRequestsListScreenViewModelAction { }
|
||||
|
||||
struct KnockRequestsListScreenViewState: BindableState {
|
||||
// TODO: Not sure yet how we will fetch this, this is just for testing purposes
|
||||
var requests: [KnockRequestCellInfo] = [.init(id: "@alice:matrix.org", displayName: "Alice", avatarURL: nil, timestamp: "Now", reason: "Hello")]
|
||||
var requestsState: KnockRequestsListState = .loading
|
||||
|
||||
var displayedRequests: [KnockRequestCellInfo] {
|
||||
guard case let .loaded(requests) = requestsState else {
|
||||
return []
|
||||
}
|
||||
return requests.filter { !handledEventIDs.contains($0.id) }
|
||||
}
|
||||
|
||||
var isLoading: Bool {
|
||||
switch requestsState {
|
||||
case .loading:
|
||||
true
|
||||
default:
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// If you are in this view one of these must have been true so by default we assume all of them to be true
|
||||
var canAccept = true
|
||||
var canDecline = true
|
||||
var canBan = true
|
||||
var isKnockableRoom = true
|
||||
var handledEventIDs: Set<String> = []
|
||||
|
||||
// If all the permissions are denied or the join rule changes while we are in the view
|
||||
// we want to stop displaying any request
|
||||
var shouldDisplayRequests: Bool {
|
||||
!requests.isEmpty && isKnockableRoom && (canAccept || canDecline || canBan)
|
||||
!displayedRequests.isEmpty && isKnockableRoom && (canAccept || canDecline || canBan)
|
||||
}
|
||||
|
||||
var shouldDisplayAcceptAllButton: Bool {
|
||||
!isLoading && shouldDisplayRequests && displayedRequests.count > 1
|
||||
}
|
||||
|
||||
var shouldDisplayEmptyView: Bool {
|
||||
!isLoading && !shouldDisplayRequests
|
||||
}
|
||||
|
||||
var bindings = KnockRequestsListStateBindings()
|
||||
@@ -35,11 +60,39 @@ enum KnockRequestsListAlertType {
|
||||
case acceptAllRequests
|
||||
case declineRequest
|
||||
case declineAndBan
|
||||
case acceptAllFailed
|
||||
case acceptFailed
|
||||
case declineFailed
|
||||
}
|
||||
|
||||
enum KnockRequestsListScreenViewAction {
|
||||
case acceptAllRequests
|
||||
case acceptRequest(userID: String)
|
||||
case declineRequest(userID: String)
|
||||
case ban(userID: String)
|
||||
case acceptRequest(eventID: String)
|
||||
case declineRequest(eventID: String)
|
||||
case ban(eventID: String)
|
||||
}
|
||||
|
||||
enum KnockRequestsListState: Equatable {
|
||||
case loading
|
||||
case loaded([KnockRequestCellInfo])
|
||||
|
||||
init(from state: KnockRequestsState) {
|
||||
switch state {
|
||||
case .loading:
|
||||
self = .loading
|
||||
case .loaded(let requests):
|
||||
self = .loaded(requests.map(KnockRequestCellInfo.init))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension KnockRequestCellInfo {
|
||||
init(from proxy: KnockRequestProxyProtocol) {
|
||||
self.init(eventID: proxy.eventID,
|
||||
userID: proxy.userID,
|
||||
displayName: proxy.displayName,
|
||||
avatarURL: proxy.avatarURL,
|
||||
timestamp: proxy.formattedTimestamp,
|
||||
reason: proxy.reason)
|
||||
}
|
||||
}
|
||||
|
||||
+176
-26
@@ -12,14 +12,18 @@ typealias KnockRequestsListScreenViewModelType = StateStoreViewModel<KnockReques
|
||||
|
||||
class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, KnockRequestsListScreenViewModelProtocol {
|
||||
private let roomProxy: JoinedRoomProxyProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
|
||||
private let actionsSubject: PassthroughSubject<KnockRequestsListScreenViewModelAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<KnockRequestsListScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(roomProxy: JoinedRoomProxyProtocol, mediaProvider: MediaProviderProtocol) {
|
||||
init(roomProxy: JoinedRoomProxyProtocol,
|
||||
mediaProvider: MediaProviderProtocol,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
self.roomProxy = roomProxy
|
||||
self.userIndicatorController = userIndicatorController
|
||||
super.init(initialViewState: KnockRequestsListScreenViewState(), mediaProvider: mediaProvider)
|
||||
|
||||
updateRoomInfo(roomInfo: roomProxy.infoPublisher.value)
|
||||
@@ -39,35 +43,147 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
|
||||
title: L10n.screenKnockRequestsListAcceptAllAlertTitle,
|
||||
message: L10n.screenKnockRequestsListAcceptAllAlertDescription,
|
||||
primaryButton: .init(title: L10n.screenKnockRequestsListAcceptAllAlertConfirmButtonTitle,
|
||||
// TODO: Implement action
|
||||
action: nil),
|
||||
action: acceptAll),
|
||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
||||
case .acceptRequest(let userID):
|
||||
// TODO: Implement
|
||||
break
|
||||
case .declineRequest(let userID):
|
||||
case .acceptRequest(let eventID):
|
||||
guard let request = getRequest(eventID: eventID) else {
|
||||
return
|
||||
}
|
||||
accept(request: request)
|
||||
case .declineRequest(let eventID):
|
||||
guard let request = getRequest(eventID: eventID) else {
|
||||
return
|
||||
}
|
||||
|
||||
state.bindings.alertInfo = .init(id: .declineRequest,
|
||||
title: L10n.screenKnockRequestsListDeclineAlertTitle,
|
||||
message: L10n.screenKnockRequestsListDeclineAlertDescription(userID),
|
||||
message: L10n.screenKnockRequestsListDeclineAlertDescription(request.userID),
|
||||
primaryButton: .init(title: L10n.screenKnockRequestsListDeclineAlertConfirmButtonTitle,
|
||||
role: .destructive,
|
||||
// TODO: Implement action
|
||||
action: nil),
|
||||
role: .destructive) { [weak self] in self?.decline(request: request) },
|
||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
||||
case .ban(let userID):
|
||||
case .ban(let eventID):
|
||||
guard let request = getRequest(eventID: eventID) else {
|
||||
return
|
||||
}
|
||||
|
||||
state.bindings.alertInfo = .init(id: .declineAndBan,
|
||||
title: L10n.screenKnockRequestsListBanAlertTitle,
|
||||
message: L10n.screenKnockRequestsListBanAlertDescription(userID),
|
||||
// TODO: Implement action
|
||||
primaryButton: .init(title: L10n.screenKnockRequestsListBanAlertConfirmButtonTitle,
|
||||
role: .destructive,
|
||||
action: nil),
|
||||
role: .destructive) { [weak self] in self?.declineAndBan(request: request) },
|
||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func getRequest(eventID: String) -> KnockRequestProxyProtocol? {
|
||||
guard case let .loaded(requests) = roomProxy.knockRequestsStatePublisher.value,
|
||||
let request = requests.first(where: { $0.eventID == eventID }) else {
|
||||
return nil
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
private func accept(request: KnockRequestProxyProtocol) {
|
||||
showLoadingIndicator(title: L10n.screenKnockRequestsListAcceptLoadingTitle)
|
||||
|
||||
let eventID = request.eventID
|
||||
state.handledEventIDs.insert(eventID)
|
||||
|
||||
Task {
|
||||
switch await request.accept() {
|
||||
case .success:
|
||||
hideLoadingIndicator()
|
||||
case .failure:
|
||||
hideLoadingIndicator()
|
||||
state.handledEventIDs.remove(eventID)
|
||||
state.bindings.alertInfo = .init(id: .acceptFailed,
|
||||
title: L10n.screenKnockRequestsListAcceptFailedAlertTitle,
|
||||
message: L10n.screenKnockRequestsListAcceptFailedAlertDescription,
|
||||
primaryButton: .init(title: L10n.actionYesTryAgain) { [weak self] in self?.accept(request: request) },
|
||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func decline(request: KnockRequestProxyProtocol) {
|
||||
showLoadingIndicator(title: L10n.screenKnockRequestsListDeclineLoadingTitle)
|
||||
|
||||
let eventID = request.eventID
|
||||
state.handledEventIDs.insert(eventID)
|
||||
|
||||
Task {
|
||||
switch await request.decline() {
|
||||
case .success:
|
||||
hideLoadingIndicator()
|
||||
case .failure:
|
||||
hideLoadingIndicator()
|
||||
state.handledEventIDs.remove(eventID)
|
||||
state.bindings.alertInfo = .init(id: .declineFailed,
|
||||
title: L10n.screenKnockRequestsListDeclineFailedAlertTitle,
|
||||
message: L10n.screenKnockRequestsListDeclineFailedAlertDescription,
|
||||
primaryButton: .init(title: L10n.actionYesTryAgain) { [weak self] in self?.decline(request: request) },
|
||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func declineAndBan(request: KnockRequestProxyProtocol) {
|
||||
showLoadingIndicator(title: L10n.screenKnockRequestsListBanLoadingTitle)
|
||||
|
||||
let eventID = request.eventID
|
||||
state.handledEventIDs.insert(eventID)
|
||||
|
||||
Task {
|
||||
switch await request.ban() {
|
||||
case .success:
|
||||
hideLoadingIndicator()
|
||||
case .failure:
|
||||
hideLoadingIndicator()
|
||||
state.handledEventIDs.remove(eventID)
|
||||
state.bindings.alertInfo = .init(id: .declineFailed,
|
||||
title: L10n.screenKnockRequestsListDeclineFailedAlertTitle,
|
||||
message: L10n.screenKnockRequestsListDeclineFailedAlertDescription,
|
||||
primaryButton: .init(title: L10n.actionYesTryAgain) { [weak self] in self?.declineAndBan(request: request) },
|
||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func acceptAll() {
|
||||
guard case let .loaded(requests) = roomProxy.knockRequestsStatePublisher.value else {
|
||||
return
|
||||
}
|
||||
showLoadingIndicator(title: L10n.screenKnockRequestsListAcceptAllLoadingTitle)
|
||||
state.handledEventIDs.formUnion(Set(requests.map(\.eventID)))
|
||||
|
||||
Task {
|
||||
let failedIDs = await withTaskGroup(of: (String, Result<Void, KnockRequestProxyError>).self) { group in
|
||||
for request in requests {
|
||||
group.addTask {
|
||||
await (request.eventID, request.accept())
|
||||
}
|
||||
}
|
||||
|
||||
var failedIDs = [String]()
|
||||
for await result in group where result.1.isFailure {
|
||||
failedIDs.append(result.0)
|
||||
}
|
||||
return failedIDs
|
||||
}
|
||||
hideLoadingIndicator()
|
||||
|
||||
if !failedIDs.isEmpty {
|
||||
state.handledEventIDs.subtract(failedIDs)
|
||||
state.bindings.alertInfo = .init(id: .acceptAllFailed,
|
||||
title: L10n.screenKnockRequestsListAcceptAllFailedAlertTitle,
|
||||
message: L10n.screenKnockRequestsListAcceptAllFailedAlertDescription,
|
||||
primaryButton: .init(title: L10n.actionYesTryAgain) { [weak self] in self?.acceptAll() },
|
||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setupSubscriptions() {
|
||||
roomProxy.infoPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
@@ -76,6 +192,26 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
|
||||
Task { await self?.updatePermissions() }
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
roomProxy.knockRequestsStatePublisher
|
||||
.map(KnockRequestsListState.init)
|
||||
.removeDuplicates()
|
||||
.throttle(for: .milliseconds(100), scheduler: DispatchQueue.main, latest: true)
|
||||
.weakAssign(to: \.state.requestsState, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
context.$viewState
|
||||
.map(\.isLoading)
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] isLoading in
|
||||
guard let self else { return }
|
||||
if isLoading {
|
||||
showInitialLoadingIndicator()
|
||||
} else {
|
||||
hideLoadingIndicator()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func updateRoomInfo(roomInfo: RoomInfoProxy) {
|
||||
@@ -93,15 +229,29 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
|
||||
state.canBan = await (try? roomProxy.canUserBan(userID: roomProxy.ownUserID).get()) == true
|
||||
}
|
||||
|
||||
// For testing purposes
|
||||
private init(initialViewState: KnockRequestsListScreenViewState) {
|
||||
roomProxy = JoinedRoomProxyMock(.init())
|
||||
super.init(initialViewState: initialViewState)
|
||||
}
|
||||
}
|
||||
|
||||
extension KnockRequestsListScreenViewModel {
|
||||
static func mockWithInitialState(_ initialViewState: KnockRequestsListScreenViewState) -> KnockRequestsListScreenViewModel {
|
||||
.init(initialViewState: initialViewState)
|
||||
private static let loadingIndicatorIdentifier = "\(KnockRequestsListScreenViewModel.self)-Loading"
|
||||
|
||||
private func showInitialLoadingIndicator() {
|
||||
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
|
||||
type: .modal(progress: .indeterminate,
|
||||
interactiveDismissDisabled: false,
|
||||
allowsInteraction: true),
|
||||
title: L10n.screenKnockRequestsListInitialLoadingTitle,
|
||||
persistent: true),
|
||||
delay: .milliseconds(100))
|
||||
}
|
||||
|
||||
private func showLoadingIndicator(title: String) {
|
||||
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
|
||||
type: .modal(progress: .indeterminate,
|
||||
interactiveDismissDisabled: false,
|
||||
allowsInteraction: false),
|
||||
title: title,
|
||||
persistent: true),
|
||||
delay: .milliseconds(200))
|
||||
}
|
||||
|
||||
private func hideLoadingIndicator() {
|
||||
userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct KnockRequestCellInfo: Identifiable {
|
||||
/// user identifier of the usee that sent the request
|
||||
let id: String
|
||||
struct KnockRequestCellInfo: Equatable {
|
||||
let eventID: String
|
||||
let userID: String
|
||||
let displayName: String?
|
||||
let avatarURL: URL?
|
||||
let timestamp: String?
|
||||
@@ -35,7 +35,7 @@ struct KnockRequestCell: View {
|
||||
HStack(alignment: .top, spacing: 16) {
|
||||
LoadableAvatarImage(url: cellInfo.avatarURL,
|
||||
name: cellInfo.displayName,
|
||||
contentID: cellInfo.id,
|
||||
contentID: cellInfo.userID,
|
||||
avatarSize: .user(on: .knockingUserList),
|
||||
mediaProvider: mediaProvider)
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
@@ -60,7 +60,7 @@ struct KnockRequestCell: View {
|
||||
private var header: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text(cellInfo.displayName ?? cellInfo.id)
|
||||
Text(cellInfo.displayName ?? cellInfo.userID)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.foregroundStyle(.compound.textPrimary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -71,7 +71,7 @@ struct KnockRequestCell: View {
|
||||
}
|
||||
}
|
||||
if cellInfo.displayName != nil {
|
||||
Text(cellInfo.id)
|
||||
Text(cellInfo.userID)
|
||||
.font(.compound.bodyMD)
|
||||
.foregroundStyle(.compound.textSecondary)
|
||||
}
|
||||
@@ -85,14 +85,14 @@ struct KnockRequestCell: View {
|
||||
HStack(spacing: 16) {
|
||||
if let onDecline {
|
||||
Button(L10n.actionDecline) {
|
||||
onDecline(cellInfo.id)
|
||||
onDecline(cellInfo.eventID)
|
||||
}
|
||||
.buttonStyle(.compound(.secondary, size: .medium))
|
||||
}
|
||||
|
||||
if let onAccept {
|
||||
Button(L10n.actionAccept) {
|
||||
onAccept(cellInfo.id)
|
||||
onAccept(cellInfo.eventID)
|
||||
}
|
||||
.buttonStyle(.compound(.primary, size: .medium))
|
||||
}
|
||||
@@ -101,7 +101,7 @@ struct KnockRequestCell: View {
|
||||
|
||||
if let onDeclineAndBan {
|
||||
Button(role: .destructive) {
|
||||
onDeclineAndBan(cellInfo.id)
|
||||
onDeclineAndBan(cellInfo.eventID)
|
||||
} label: {
|
||||
Text(L10n.screenKnockRequestsListDeclineAndBanActionTitle)
|
||||
.padding(.top, 8)
|
||||
@@ -166,15 +166,19 @@ private struct DisclosableText: View {
|
||||
}
|
||||
}
|
||||
|
||||
extension KnockRequestCellInfo: Identifiable {
|
||||
var id: String { eventID }
|
||||
}
|
||||
|
||||
struct KnockRequestCell_Previews: PreviewProvider, TestablePreview {
|
||||
// swiftlint:disable:next line_length
|
||||
static let aliceWithLongReason = KnockRequestCellInfo(id: "@alice:matrix.org", displayName: "Alice", avatarURL: nil, timestamp: "20 Nov 2024", reason: "Hello would like to join this room, also this is a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long reason")
|
||||
static let aliceWithLongReason = KnockRequestCellInfo(eventID: "1", userID: "@alice:matrix.org", displayName: "Alice", avatarURL: nil, timestamp: "20 Nov 2024", reason: "Hello would like to join this room, also this is a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long reason")
|
||||
|
||||
static let aliceWithShortReason = KnockRequestCellInfo(id: "@alice:matrix.org", displayName: "Alice", avatarURL: nil, timestamp: "20 Nov 2024", reason: "Hello, I am Alice and would like to join this room, please")
|
||||
static let aliceWithShortReason = KnockRequestCellInfo(eventID: "1", userID: "@alice:matrix.org", displayName: "Alice", avatarURL: nil, timestamp: "20 Nov 2024", reason: "Hello, I am Alice and would like to join this room, please")
|
||||
|
||||
static let aliceWithNoReason = KnockRequestCellInfo(id: "@alice:matrix.org", displayName: "Alice", avatarURL: nil, timestamp: "20 Nov 2024", reason: nil)
|
||||
static let aliceWithNoReason = KnockRequestCellInfo(eventID: "1", userID: "@alice:matrix.org", displayName: "Alice", avatarURL: nil, timestamp: "20 Nov 2024", reason: nil)
|
||||
|
||||
static let aliceWithNoName = KnockRequestCellInfo(id: "@alice:matrix.org", displayName: nil, avatarURL: nil, timestamp: "20 Nov 2024", reason: nil)
|
||||
static let aliceWithNoName = KnockRequestCellInfo(eventID: "1", userID: "@alice:matrix.org", displayName: nil, avatarURL: nil, timestamp: "20 Nov 2024", reason: nil)
|
||||
|
||||
static var previews: some View {
|
||||
KnockRequestCell(cellInfo: aliceWithLongReason) { _ in } onDecline: { _ in } onDeclineAndBan: { _ in }
|
||||
|
||||
+53
-16
@@ -17,12 +17,12 @@ struct KnockRequestsListScreen: View {
|
||||
.navigationTitle(L10n.screenKnockRequestsListTitle)
|
||||
.background(.compound.bgCanvasDefault)
|
||||
.overlay {
|
||||
if !context.viewState.shouldDisplayRequests {
|
||||
if context.viewState.shouldDisplayEmptyView {
|
||||
KnockRequestsListEmptyStateView()
|
||||
}
|
||||
}
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
if context.viewState.shouldDisplayRequests {
|
||||
if context.viewState.shouldDisplayAcceptAllButton {
|
||||
acceptAllButton
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,18 @@ struct KnockRequestsListScreen: View {
|
||||
|
||||
@ViewBuilder
|
||||
private var mainContent: some View {
|
||||
if context.viewState.isLoading {
|
||||
EmptyView()
|
||||
} else {
|
||||
list
|
||||
}
|
||||
}
|
||||
|
||||
private var list: some View {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 0) {
|
||||
if context.viewState.shouldDisplayRequests {
|
||||
ForEach(context.viewState.requests) { requestInfo in
|
||||
ForEach(context.viewState.displayedRequests) { requestInfo in
|
||||
ListRow(kind: .custom {
|
||||
KnockRequestCell(cellInfo: requestInfo,
|
||||
mediaProvider: context.mediaProvider,
|
||||
@@ -60,37 +68,66 @@ struct KnockRequestsListScreen: View {
|
||||
.background(.compound.bgCanvasDefault)
|
||||
}
|
||||
|
||||
private func onAccept(userID: String) {
|
||||
context.send(viewAction: .acceptRequest(userID: userID))
|
||||
private func onAccept(eventID: String) {
|
||||
context.send(viewAction: .acceptRequest(eventID: eventID))
|
||||
}
|
||||
|
||||
private func onDecline(userID: String) {
|
||||
context.send(viewAction: .declineRequest(userID: userID))
|
||||
private func onDecline(eventID: String) {
|
||||
context.send(viewAction: .declineRequest(eventID: eventID))
|
||||
}
|
||||
|
||||
private func onDeclineAndBan(userID: String) {
|
||||
context.send(viewAction: .ban(userID: userID))
|
||||
private func onDeclineAndBan(eventID: String) {
|
||||
context.send(viewAction: .ban(eventID: eventID))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct KnockRequestsListScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let emptyViewModel = KnockRequestsListScreenViewModel.mockWithInitialState(.init(requests: []))
|
||||
static let loadingViewModel = KnockRequestsListScreenViewModel.mockWithRequestsState(.loading)
|
||||
|
||||
static let emptyViewModel = KnockRequestsListScreenViewModel.mockWithRequestsState(.loaded([]))
|
||||
|
||||
static let singleRequestViewModel = KnockRequestsListScreenViewModel.mockWithRequestsState(.loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org", displayName: "Alice", avatarURL: nil, timestamp: "Now", reason: "Hello"))]))
|
||||
|
||||
static let viewModel = KnockRequestsListScreenViewModel.mockWithRequestsState(.loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org", displayName: "Alice", avatarURL: nil, timestamp: "Now", reason: "Hello")),
|
||||
// swiftlint:disable:next line_length
|
||||
KnockRequestProxyMock(.init(eventID: "2", userID: "@bob:matrix.org", displayName: "Bob", avatarURL: nil, timestamp: "Now", reason: "Hello this one is a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long reason")),
|
||||
KnockRequestProxyMock(.init(eventID: "3", userID: "@charlie:matrix.org", displayName: "Charlie", avatarURL: nil, timestamp: "Now", reason: nil)),
|
||||
KnockRequestProxyMock(.init(eventID: "4", userID: "@dan:matrix.org", displayName: "Dan", avatarURL: nil, timestamp: "Now", reason: "Hello! It's a me! Dan!"))]))
|
||||
|
||||
static let viewModel = KnockRequestsListScreenViewModel.mockWithInitialState(.init(requests: [.init(id: "@alice:matrix.org", displayName: "Alice", avatarURL: nil, timestamp: "Now", reason: "Hello"),
|
||||
// swiftlint:disable:next line_length
|
||||
.init(id: "@bob:matrix.org", displayName: "Bob", avatarURL: nil, timestamp: "Now", reason: "Hello this one is a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long reason"),
|
||||
.init(id: "@charlie:matrix.org", displayName: "Charlie", avatarURL: nil, timestamp: "Now", reason: nil),
|
||||
.init(id: "@dan:matrix.org", displayName: "Dan", avatarURL: nil, timestamp: "Now", reason: "Hello! It's a me! Dan!")]))
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
KnockRequestsListScreen(context: viewModel.context)
|
||||
}
|
||||
.snapshotPreferences(delay: 0.2)
|
||||
|
||||
NavigationStack {
|
||||
KnockRequestsListScreen(context: singleRequestViewModel.context)
|
||||
}
|
||||
.previewDisplayName("Single Request")
|
||||
.snapshotPreferences(delay: 0.2)
|
||||
|
||||
NavigationStack {
|
||||
KnockRequestsListScreen(context: emptyViewModel.context)
|
||||
}
|
||||
.previewDisplayName("Empty state")
|
||||
.snapshotPreferences(delay: 0.2)
|
||||
|
||||
NavigationStack {
|
||||
KnockRequestsListScreen(context: loadingViewModel.context)
|
||||
}
|
||||
.previewDisplayName("Loading state")
|
||||
}
|
||||
}
|
||||
|
||||
extension KnockRequestsListScreenViewModel {
|
||||
static func mockWithRequestsState(_ requestsState: KnockRequestsState) -> KnockRequestsListScreenViewModel {
|
||||
.init(roomProxy: JoinedRoomProxyMock(.init(members: [.mockAdmin],
|
||||
knockRequestsState: requestsState,
|
||||
ownUserID: RoomMemberProxyMock.mockAdmin.userID,
|
||||
joinRule: .knock)),
|
||||
mediaProvider: MediaProviderMock(),
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ struct RoomDetailsScreenViewState: BindableState {
|
||||
|
||||
var knockingEnabled = false
|
||||
var isKnockableRoom = false
|
||||
var knockRequestsCount = 0
|
||||
|
||||
var canSeeKnockingRequests: Bool {
|
||||
knockingEnabled && dmRecipient == nil && isKnockableRoom && (canInviteUsers || canKickUsers || canBanUsers)
|
||||
}
|
||||
|
||||
@@ -187,6 +187,18 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
Task { await self?.updatePowerLevelPermissions() }
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
roomProxy.knockRequestsStatePublisher
|
||||
.map { requestsState in
|
||||
guard case let .loaded(requests) = requestsState else {
|
||||
return 0
|
||||
}
|
||||
return requests.count
|
||||
}
|
||||
.removeDuplicates()
|
||||
.throttle(for: .milliseconds(100), scheduler: DispatchQueue.main, latest: true)
|
||||
.weakAssign(to: \.state.knockRequestsCount, on: self)
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func updateRoomInfo(_ roomInfo: RoomInfoProxy) {
|
||||
|
||||
@@ -166,8 +166,7 @@ struct RoomDetailsScreen: View {
|
||||
if context.viewState.canSeeKnockingRequests {
|
||||
ListRow(label: .default(title: L10n.screenRoomDetailsRequestsToJoinTitle,
|
||||
icon: \.askToJoin),
|
||||
// TODO: Display count if requests > 0 when an API for them is available
|
||||
details: .counter(1),
|
||||
details: context.viewState.knockRequestsCount > 0 ? .counter(context.viewState.knockRequestsCount) : nil,
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .processTapRequestsToJoin)
|
||||
})
|
||||
@@ -324,6 +323,7 @@ struct RoomDetailsScreen: View {
|
||||
struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let genericRoomViewModel = {
|
||||
ServiceLocator.shared.settings.knockingEnabled = true
|
||||
let knockRequests: [KnockRequestProxyMock] = [.init()]
|
||||
let members: [RoomMemberProxyMock] = [
|
||||
.mockMeAdmin,
|
||||
.mockAlice,
|
||||
@@ -344,6 +344,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
isEncrypted: true,
|
||||
canonicalAlias: "#alias:domain.com",
|
||||
members: members,
|
||||
knockRequestsState: .loaded(knockRequests),
|
||||
joinRule: .knock))
|
||||
|
||||
var notificationSettingsProxyMockConfiguration = NotificationSettingsProxyMockConfiguration()
|
||||
@@ -388,6 +389,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
}()
|
||||
|
||||
static let simpleRoomViewModel = {
|
||||
let knockRequests: [KnockRequestProxyMock] = [.init()]
|
||||
ServiceLocator.shared.settings.knockingEnabled = true
|
||||
let members: [RoomMemberProxyMock] = [
|
||||
.mockMeAdmin,
|
||||
@@ -400,6 +402,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
isDirect: false,
|
||||
isEncrypted: false,
|
||||
members: members,
|
||||
knockRequestsState: .loaded(knockRequests),
|
||||
joinRule: .knock))
|
||||
let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init())
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import Foundation
|
||||
import OrderedCollections
|
||||
|
||||
enum RoomScreenViewModelAction {
|
||||
enum RoomScreenViewModelAction: Equatable {
|
||||
case focusEvent(eventID: String)
|
||||
case displayPinnedEventsTimeline
|
||||
case displayRoomDetails
|
||||
@@ -23,7 +23,7 @@ enum RoomScreenViewAction {
|
||||
case displayRoomDetails
|
||||
case displayCall
|
||||
case footerViewAction(RoomScreenFooterViewAction)
|
||||
case acceptKnock(userID: String)
|
||||
case acceptKnock(eventID: String)
|
||||
case dismissKnockRequests
|
||||
case viewKnockRequests
|
||||
}
|
||||
@@ -48,11 +48,18 @@ struct RoomScreenViewState: BindableState {
|
||||
var canAcceptKnocks = false
|
||||
var canDeclineKnocks = false
|
||||
var canBan = false
|
||||
// TODO: We still don't know how to get these, but these will be the non already seen knock requests of the room, for now we are using this as a mock for testing purposes
|
||||
var unseenKnockRequests: [KnockRequestInfo] = [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: "Helloooo")]
|
||||
var unseenKnockRequests: [KnockRequestInfo] = []
|
||||
var handledEventIDs: Set<String> = []
|
||||
|
||||
var displayedKnockRequests: [KnockRequestInfo] {
|
||||
unseenKnockRequests.filter { !handledEventIDs.contains($0.eventID) }
|
||||
}
|
||||
|
||||
var shouldSeeKnockRequests: Bool {
|
||||
isKnockingEnabled && isKnockableRoom && !unseenKnockRequests.isEmpty && (canAcceptKnocks || canDeclineKnocks || canBan)
|
||||
isKnockingEnabled &&
|
||||
isKnockableRoom &&
|
||||
!displayedKnockRequests.isEmpty &&
|
||||
(canAcceptKnocks || canDeclineKnocks || canBan)
|
||||
}
|
||||
|
||||
var footerDetails: RoomScreenFooterViewDetails?
|
||||
|
||||
@@ -103,12 +103,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
case .resolvePinViolation(let userID):
|
||||
Task { await resolveIdentityPinningViolation(userID) }
|
||||
}
|
||||
case .acceptKnock(userID: let userID):
|
||||
// TODO: API to accept a knock required
|
||||
break
|
||||
case .acceptKnock(let eventID):
|
||||
Task { await acceptKnock(eventID: eventID) }
|
||||
case .dismissKnockRequests:
|
||||
// TODO: API to mark knocks as seen required
|
||||
break
|
||||
Task { await markAllKnocksAsSeen() }
|
||||
case .viewKnockRequests:
|
||||
actionsSubject.send(.displayKnockRequests)
|
||||
}
|
||||
@@ -181,6 +179,23 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
state.shouldShowCallButton = ongoingCallRoomID != roomProxy.id
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
roomProxy.knockRequestsStatePublisher
|
||||
// We only care about unseen requests
|
||||
.map { knockRequestsState in
|
||||
guard case let .loaded(requests) = knockRequestsState else {
|
||||
return []
|
||||
}
|
||||
|
||||
return requests
|
||||
.filter { !$0.isSeen }
|
||||
.map(KnockRequestInfo.init)
|
||||
}
|
||||
// If the requests have the same event ids we can discard the output
|
||||
.removeDuplicates { Set($0.map(\.eventID)) == Set($1.map(\.eventID)) }
|
||||
.throttle(for: .milliseconds(100), scheduler: DispatchQueue.main, latest: true)
|
||||
.weakAssign(to: \.state.unseenKnockRequests, on: self)
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func processIdentityStatusChanges(_ changes: [IdentityStatusChange]) async {
|
||||
@@ -277,10 +292,49 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func acceptKnock(eventID: String) async {
|
||||
guard case let .loaded(requests) = roomProxy.knockRequestsStatePublisher.value,
|
||||
let request = requests.first(where: { $0.eventID == eventID }) else {
|
||||
return
|
||||
}
|
||||
|
||||
state.handledEventIDs.insert(eventID)
|
||||
switch await request.accept() {
|
||||
case .success:
|
||||
break
|
||||
case .failure:
|
||||
userIndicatorController.submitIndicator(.init(id: Self.errorIndicatorIdentifier, type: .toast, title: L10n.errorUnknown))
|
||||
state.handledEventIDs.remove(eventID)
|
||||
}
|
||||
}
|
||||
|
||||
private func markAllKnocksAsSeen() async {
|
||||
guard case let .loaded(requests) = roomProxy.knockRequestsStatePublisher.value else {
|
||||
return
|
||||
}
|
||||
state.handledEventIDs.formUnion(Set(requests.map(\.eventID)))
|
||||
|
||||
let failedIDs = await withTaskGroup(of: (String, Result<Void, KnockRequestProxyError>).self) { group in
|
||||
for request in requests {
|
||||
group.addTask {
|
||||
await (request.eventID, request.markAsSeen())
|
||||
}
|
||||
}
|
||||
|
||||
var failedIDs = [String]()
|
||||
for await result in group where result.1.isFailure {
|
||||
failedIDs.append(result.0)
|
||||
}
|
||||
return failedIDs
|
||||
}
|
||||
state.handledEventIDs.subtract(failedIDs)
|
||||
}
|
||||
|
||||
// MARK: Loading indicators
|
||||
|
||||
private static let loadingIndicatorIdentifier = "\(RoomScreenViewModel.self)-Loading"
|
||||
private static let errorIndicatorIdentifier = "\(RoomScreenViewModel.self)-Error"
|
||||
|
||||
private func showLoadingIndicator() {
|
||||
userIndicatorController.submitIndicator(.init(id: Self.loadingIndicatorIdentifier, type: .toast, title: L10n.commonLoading))
|
||||
@@ -304,3 +358,13 @@ extension RoomScreenViewModel {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
}
|
||||
}
|
||||
|
||||
private extension KnockRequestInfo {
|
||||
init(from proxy: KnockRequestProxyProtocol) {
|
||||
self.init(displayName: proxy.displayName,
|
||||
avatarURL: proxy.avatarURL,
|
||||
userID: proxy.userID,
|
||||
reason: proxy.reason,
|
||||
eventID: proxy.eventID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct KnockRequestInfo {
|
||||
struct KnockRequestInfo: Equatable {
|
||||
let displayName: String?
|
||||
let avatarURL: URL?
|
||||
let userID: String
|
||||
let reason: String?
|
||||
let eventID: String
|
||||
}
|
||||
|
||||
struct KnockRequestsBannerView: View {
|
||||
@@ -102,10 +103,8 @@ private struct SingleKnockRequestBannerContent: View {
|
||||
Button(L10n.screenRoomSingleKnockRequestViewButtonTitle, action: onViewAll)
|
||||
.buttonStyle(.compound(.secondary, size: .medium))
|
||||
if let onAccept {
|
||||
Button(L10n.screenRoomSingleKnockRequestAcceptButtonTitle) {
|
||||
onAccept(request.userID)
|
||||
}
|
||||
.buttonStyle(.compound(.primary, size: .medium))
|
||||
Button(L10n.screenRoomSingleKnockRequestAcceptButtonTitle) { onAccept(request.eventID) }
|
||||
.buttonStyle(.compound(.primary, size: .medium))
|
||||
}
|
||||
}
|
||||
.padding(.top, request.reason == nil ? 0 : 2)
|
||||
@@ -123,7 +122,6 @@ private struct MultipleKnockRequestsBannerContent: View {
|
||||
requests
|
||||
.prefix(3)
|
||||
.map { .init(url: $0.avatarURL, name: $0.displayName, contentID: $0.userID) }
|
||||
.reversed()
|
||||
}
|
||||
|
||||
private var multipleKnockRequestsTitle: String {
|
||||
@@ -138,7 +136,7 @@ private struct MultipleKnockRequestsBannerContent: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 14) {
|
||||
HStack(spacing: 10) {
|
||||
StackedAvatarsView(overlap: 16, lineWidth: 2, shouldStackFromLast: true, avatars: avatars, avatarSize: .user(on: .knockingUsersBannerStack), mediaProvider: mediaProvider)
|
||||
StackedAvatarsView(overlap: 16, lineWidth: 2, avatars: avatars, avatarSize: .user(on: .knockingUsersBannerStack), mediaProvider: mediaProvider)
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text(multipleKnockRequestsTitle)
|
||||
.lineLimit(2)
|
||||
@@ -173,18 +171,22 @@ private struct KnockRequestsBannerDismissButton: View {
|
||||
}
|
||||
|
||||
struct KnockRequestsBannerView_Previews: PreviewProvider, TestablePreview {
|
||||
static let singleRequest: [KnockRequestInfo] = [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: nil)]
|
||||
static let singleRequest: [KnockRequestInfo] = [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: nil, eventID: "1")]
|
||||
|
||||
static let singleRequestWithReason: [KnockRequestInfo] = [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: "Hey, I’d like to join this room because of xyz topic and I’d like to participate in the room.")]
|
||||
static let singleRequestWithReason: [KnockRequestInfo] = [.init(displayName: "Alice",
|
||||
avatarURL: nil,
|
||||
userID: "@alice:matrix.org",
|
||||
reason: "Hey, I’d like to join this room because of xyz topic and I’d like to participate in the room.",
|
||||
eventID: "1")]
|
||||
|
||||
static let singleRequestNoDisplayName: [KnockRequestInfo] = [.init(displayName: nil, avatarURL: nil, userID: "@alice:matrix.org", reason: nil)]
|
||||
static let singleRequestNoDisplayName: [KnockRequestInfo] = [.init(displayName: nil, avatarURL: nil, userID: "@alice:matrix.org", reason: nil, eventID: "1")]
|
||||
|
||||
static let multipleRequests: [KnockRequestInfo] = [
|
||||
.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: nil),
|
||||
.init(displayName: "Bob", avatarURL: nil, userID: "@bob:matrix.org", reason: nil),
|
||||
.init(displayName: "Charlie", avatarURL: nil, userID: "@charlie:matrix.org", reason: nil),
|
||||
.init(displayName: "Dan", avatarURL: nil, userID: "@dan:matrix.org", reason: nil),
|
||||
.init(displayName: "Test", avatarURL: nil, userID: "@dan:matrix.org", reason: nil)
|
||||
.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: nil, eventID: "1"),
|
||||
.init(displayName: "Bob", avatarURL: nil, userID: "@bob:matrix.org", reason: nil, eventID: "2"),
|
||||
.init(displayName: "Charlie", avatarURL: nil, userID: "@charlie:matrix.org", reason: nil, eventID: "3"),
|
||||
.init(displayName: "Dan", avatarURL: nil, userID: "@dan:matrix.org", reason: nil, eventID: "4"),
|
||||
.init(displayName: "Test", avatarURL: nil, userID: "@dan:matrix.org", reason: nil, eventID: "5")
|
||||
]
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
@@ -137,7 +137,7 @@ struct RoomScreen: View {
|
||||
private var knockRequestsBanner: some View {
|
||||
Group {
|
||||
if roomContext.viewState.shouldSeeKnockRequests {
|
||||
KnockRequestsBannerView(requests: roomContext.viewState.unseenKnockRequests,
|
||||
KnockRequestsBannerView(requests: roomContext.viewState.displayedKnockRequests,
|
||||
onDismiss: dismissKnockRequestsBanner,
|
||||
onAccept: roomContext.viewState.canAcceptKnocks ? acceptKnockRequest : nil,
|
||||
onViewAll: onViewAllKnockRequests,
|
||||
@@ -153,8 +153,8 @@ struct RoomScreen: View {
|
||||
roomContext.send(viewAction: .dismissKnockRequests)
|
||||
}
|
||||
|
||||
private func acceptKnockRequest(userID: String) {
|
||||
roomContext.send(viewAction: .acceptKnock(userID: userID))
|
||||
private func acceptKnockRequest(eventID: String) {
|
||||
roomContext.send(viewAction: .acceptKnock(eventID: eventID))
|
||||
}
|
||||
|
||||
private func onViewAllKnockRequests() {
|
||||
|
||||
Reference in New Issue
Block a user