Skip to content

Commit

Permalink
Knock Requests List Screen (#3533)
Browse files Browse the repository at this point in the history
* created the basic navigation and files

* updated the logic

so that the buttons that do not have permissions won't show

* added the empty state

* progress in making the list UI

* update tests

* UI improvements

* fixed an issue with media provider

* update button style

* fixed a navigation bug

* pr suggestions

* pr suggestions
  • Loading branch information
Velin92 authored Nov 21, 2024
1 parent 7e1476d commit e315451
Show file tree
Hide file tree
Showing 85 changed files with 701 additions and 145 deletions.
28 changes: 28 additions & 0 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions ElementX/Resources/Localizations/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,11 @@
"screen_join_room_knock_message_description" = "Message (optional)";
"screen_join_room_knock_sent_description" = "You will receive an invite to join the room if your request is accepted.";
"screen_join_room_knock_sent_title" = "Request to join sent";
"screen_knock_requests_list_accept_all_button_title" = "Accept all";
"screen_knock_requests_list_decline_and_ban_action_title" = "Decline and ban";
"screen_knock_requests_list_empty_state_description" = "When somebody will ask to join the room, you’ll be able to see their request here.";
"screen_knock_requests_list_empty_state_title" = "No pending request to join";
"screen_knock_requests_list_title" = "Requests to join";
"screen_pinned_timeline_empty_state_description" = "Press on a message and choose “%1$@” to include here.";
"screen_pinned_timeline_empty_state_headline" = "Pin important messages so that they can be easily discovered";
"screen_reset_encryption_password_error" = "An unknown error happened. Please check your account password is correct and try again.";
Expand Down
30 changes: 30 additions & 0 deletions ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
return .presentingChild(childRoomID: roomID, previousState: fromState)
case (.presentingChild(_, let previousState), .dismissChildFlow):
return previousState

case (.roomDetails, .presentKnockRequestsListScreen):
return .knockRequestsList
case (.knockRequestsList, .dismissKnockRequestsListScreen):
return .roomDetails(isRoot: false)

default:
return nil
Expand Down Expand Up @@ -565,6 +570,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
presentResolveSendFailure(failure: failure, sendHandle: sendHandle)
case (.resolveSendFailure, .dismissResolveSendFailure, .room):
break

case (.roomDetails, .presentKnockRequestsListScreen, .knockRequestsList):
presentKnockRequestsList()
case (.knockRequestsList, .dismissKnockRequestsListScreen, .roomDetails):
break

// Child flow
case (_, .startChildFlow(let roomID, let via, let entryPoint), .presentingChild):
Expand Down Expand Up @@ -837,6 +847,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .presentPinnedEventsTimeline:
stateMachine.tryEvent(.presentPinnedEventsTimeline)
case .presentKnockingRequestsListScreen:
stateMachine.tryEvent(.presentKnockRequestsListScreen)
}
}
.store(in: &cancellables)
Expand Down Expand Up @@ -883,6 +895,20 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
}
}

private func presentKnockRequestsList() {
let parameters = KnockRequestsListScreenCoordinatorParameters(roomProxy: roomProxy, mediaProvider: userSession.mediaProvider)
let coordinator = KnockRequestsListScreenCoordinator(parameters: parameters)

coordinator.actionsPublisher
.sink { [weak self] _ in
}
.store(in: &cancellables)

navigationStackCoordinator.push(coordinator) { [weak self] in
self?.stateMachine.tryEvent(.dismissKnockRequestsListScreen)
}
}

private func presentRoomDetailsEditScreen() {
let stackCoordinator = NavigationStackCoordinator()

Expand Down Expand Up @@ -1546,6 +1572,7 @@ private extension RoomFlowCoordinator {
case rolesAndPermissions
case pinnedEventsTimeline(previousState: PinnedEventsTimelineSource)
case resolveSendFailure
case knockRequestsList

/// A child flow is in progress.
case presentingChild(childRoomID: String, previousState: State)
Expand Down Expand Up @@ -1624,6 +1651,9 @@ private extension RoomFlowCoordinator {
// Child room flow events
case startChildFlow(roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint)
case dismissChildFlow

case presentKnockRequestsListScreen
case dismissKnockRequestsListScreen
}
}

Expand Down
8 changes: 8 additions & 0 deletions ElementX/Sources/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1298,8 +1298,16 @@ internal enum L10n {
}
/// Are you sure you want to turn off key storage and delete it?
internal static var screenKeyBackupDisableTitle: String { return L10n.tr("Localizable", "screen_key_backup_disable_title") }
/// Accept all
internal static var screenKnockRequestsListAcceptAllButtonTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_accept_all_button_title") }
/// Decline and ban
internal static var screenKnockRequestsListDeclineAndBanActionTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_decline_and_ban_action_title") }
/// When somebody will ask to join the room, you’ll be able to see their request here.
internal static var screenKnockRequestsListEmptyStateDescription: String { return L10n.tr("Localizable", "screen_knock_requests_list_empty_state_description") }
/// No pending request to join
internal static var screenKnockRequestsListEmptyStateTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_empty_state_title") }
/// Requests to join
internal static var screenKnockRequestsListTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_title") }
/// This account has been deactivated.
internal static var screenLoginErrorDeactivatedAccount: String { return L10n.tr("Localizable", "screen_login_error_deactivated_account") }
/// Incorrect username and/or password
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//

// periphery:ignore:all - this is just a knockRequestsList remove this comment once generating the final file

import Combine
import SwiftUI

struct KnockRequestsListScreenCoordinatorParameters {
let roomProxy: JoinedRoomProxyProtocol
let mediaProvider: MediaProviderProtocol
}

enum KnockRequestsListScreenCoordinatorAction { }

final class KnockRequestsListScreenCoordinator: CoordinatorProtocol {
private let viewModel: KnockRequestsListScreenViewModelProtocol

private var cancellables = Set<AnyCancellable>()

private let actionsSubject: PassthroughSubject<KnockRequestsListScreenCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<KnockRequestsListScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}

init(parameters: KnockRequestsListScreenCoordinatorParameters) {
viewModel = KnockRequestsListScreenViewModel(roomProxy: parameters.roomProxy,
mediaProvider: parameters.mediaProvider)
}

func start() {
viewModel.actionsPublisher.sink { [weak self] action in
MXLog.info("Coordinator: received view model action: \(action)")
}
.store(in: &cancellables)
}

func toPresentable() -> AnyView {
AnyView(KnockRequestsListScreen(context: viewModel.context))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//

import Foundation

enum KnockRequestsListScreenViewModelAction { }

struct KnockRequestsListScreenViewState: BindableState {
var requests: [KnockRequestCellInfo] = []
var canAccept = false
var canDecline = false
var canBan = false
}

enum KnockRequestsListScreenViewAction {
case acceptAllRequests
case acceptRequest(userID: String)
case declineRequest(userID: String)
case ban(userID: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//

import Combine
import SwiftUI

typealias KnockRequestsListScreenViewModelType = StateStoreViewModel<KnockRequestsListScreenViewState, KnockRequestsListScreenViewAction>

class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, KnockRequestsListScreenViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol

private let actionsSubject: PassthroughSubject<KnockRequestsListScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<KnockRequestsListScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}

init(roomProxy: JoinedRoomProxyProtocol, mediaProvider: MediaProviderProtocol) {
self.roomProxy = roomProxy
super.init(initialViewState: KnockRequestsListScreenViewState(), mediaProvider: mediaProvider)

Task {
await updatePermissions()
}

setupSubscriptions()
}

// MARK: - Public

override func process(viewAction: KnockRequestsListScreenViewAction) {
switch viewAction {
case .acceptAllRequests:
break
case .acceptRequest(let userID):
break
case .declineRequest(let userID):
break
case .ban(let userID):
break
}
}

// MARK: - Private

private func setupSubscriptions() {
roomProxy.infoPublisher
.throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true)
.sink { [weak self] _ in
Task { await self?.updatePermissions() }
}
.store(in: &cancellables)
}

private func updatePermissions() async {
state.canAccept = await (try? roomProxy.canUserInvite(userID: roomProxy.ownUserID).get()) == true
state.canDecline = await (try? roomProxy.canUserKick(userID: roomProxy.ownUserID).get()) == true
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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//

import Combine

@MainActor
protocol KnockRequestsListScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<KnockRequestsListScreenViewModelAction, Never> { get }
var context: KnockRequestsListScreenViewModelType.Context { get }
}
Loading

0 comments on commit e315451

Please sign in to comment.