Skip to content

Commit

Permalink
added the initial loading state
Browse files Browse the repository at this point in the history
improved code and the tests
  • Loading branch information
Velin92 committed Dec 12, 2024
1 parent 7294108 commit 6b4ee5b
Show file tree
Hide file tree
Showing 14 changed files with 208 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@
"common_creating_room" = "Creating room…";
"common_current_user_left_room" = "Left room";
"common_dark" = "Dark";
"common_date_separator_this_month" = "This month";
"common_date_date_at_time" = "%1$@ at %2$@";
"common_date_this_month" = "This month";
"common_decryption_error" = "Decryption error";
"common_developer_options" = "Developer options";
"common_device_id" = "Device ID";
Expand Down Expand Up @@ -392,6 +393,7 @@
"screen_knock_requests_list_decline_loading_title" = "Declining request to join";
"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_initial_loading_title" = "Loading requests to join…";
"screen_knock_requests_list_title" = "Requests to join";
"screen_media_details_file_format" = "File format";
"screen_media_details_filename" = "File name";
Expand Down
8 changes: 7 additions & 1 deletion ElementX/Sources/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,12 @@ internal enum L10n {
internal static var commonCurrentUserLeftRoom: String { return L10n.tr("Localizable", "common_current_user_left_room") }
/// Dark
internal static var commonDark: String { return L10n.tr("Localizable", "common_dark") }
/// %1$@ at %2$@
internal static func commonDateDateAtTime(_ p1: Any, _ p2: Any) -> String {
return L10n.tr("Localizable", "common_date_date_at_time", String(describing: p1), String(describing: p2))
}
/// This month
internal static var commonDateSeparatorThisMonth: String { return L10n.tr("Localizable", "common_date_separator_this_month") }
internal static var commonDateThisMonth: String { return L10n.tr("Localizable", "common_date_this_month") }
/// Decryption error
internal static var commonDecryptionError: String { return L10n.tr("Localizable", "common_decryption_error") }
/// Developer options
Expand Down Expand Up @@ -1346,6 +1350,8 @@ internal enum L10n {
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") }
/// Loading requests to join…
internal static var screenKnockRequestsListInitialLoadingTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_initial_loading_title") }
/// Requests to join
internal static var screenKnockRequestsListTitle: String { return L10n.tr("Localizable", "screen_knock_requests_list_title") }
/// This account has been deactivated.
Expand Down
4 changes: 2 additions & 2 deletions ElementX/Sources/Mocks/Generated/GeneratedMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6252,11 +6252,11 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol {
set(value) { underlyingIdentityStatusChangesPublisher = value }
}
var underlyingIdentityStatusChangesPublisher: CurrentValuePublisher<[IdentityStatusChange], Never>!
var joinRequestsPublisher: CurrentValuePublisher<[JoinRequestProxyProtocol], Never> {
var joinRequestsStatePublisher: CurrentValuePublisher<JoinRequestsState, Never> {
get { return underlyingJoinRequestsPublisher }
set(value) { underlyingJoinRequestsPublisher = value }
}
var underlyingJoinRequestsPublisher: CurrentValuePublisher<[JoinRequestProxyProtocol], Never>!
var underlyingJoinRequestsPublisher: CurrentValuePublisher<JoinRequestsState, Never>!
var timeline: TimelineProxyProtocol {
get { return underlyingTimeline }
set(value) { underlyingTimeline = value }
Expand Down
8 changes: 4 additions & 4 deletions ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14231,8 +14231,8 @@ open class RoomSDKMock: MatrixRustSDK.Room {
open var subscribeToJoinRequestsListenerCalled: Bool {
return subscribeToJoinRequestsListenerCallsCount > 0
}
open var subscribeToJoinRequestsListenerReceivedListener: RequestsToJoinListener?
open var subscribeToJoinRequestsListenerReceivedInvocations: [RequestsToJoinListener] = []
open var subscribeToJoinRequestsListenerReceivedListener: JoinRequestsListener?
open var subscribeToJoinRequestsListenerReceivedInvocations: [JoinRequestsListener] = []

var subscribeToJoinRequestsListenerUnderlyingReturnValue: TaskHandle!
open var subscribeToJoinRequestsListenerReturnValue: TaskHandle! {
Expand All @@ -14258,9 +14258,9 @@ open class RoomSDKMock: MatrixRustSDK.Room {
}
}
}
open var subscribeToJoinRequestsListenerClosure: ((RequestsToJoinListener) async throws -> TaskHandle)?
open var subscribeToJoinRequestsListenerClosure: ((JoinRequestsListener) async throws -> TaskHandle)?

open override func subscribeToJoinRequests(listener: RequestsToJoinListener) async throws -> TaskHandle {
open override func subscribeToJoinRequests(listener: JoinRequestsListener) async throws -> TaskHandle {
if let error = subscribeToJoinRequestsListenerThrowableError {
throw error
}
Expand Down
31 changes: 31 additions & 0 deletions ElementX/Sources/Mocks/JoinRequestProxyMock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//

import Foundation

struct JoinRequestProxyMockConfiguration {
let eventID: String
let userID: String
var displayName: String?
var avatarURL: URL?
var timestamp: String?
var reason: String?
var isSeen = true
}

extension JoinRequestProxyMock {
convenience init(_ configuration: JoinRequestProxyMockConfiguration) {
self.init()
eventID = configuration.eventID
userID = configuration.userID
displayName = configuration.displayName
avatarURL = configuration.avatarURL
reason = configuration.reason
formattedTimestamp = configuration.timestamp
isSeen = configuration.isSeen
}
}
4 changes: 2 additions & 2 deletions ElementX/Sources/Mocks/JoinedRoomProxyMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct JoinedRoomProxyMockConfiguration {
var timelineStartReached = false

var members: [RoomMemberProxyMock] = .allMembers
var knockRequests: [JoinRequestProxyMock] = []
var joinRequestsState: JoinRequestsState = .loaded([])
var ownUserID = RoomMemberProxyMock.mockMe.userID
var inviter: RoomMemberProxyProtocol?

Expand Down Expand Up @@ -58,7 +58,7 @@ extension JoinedRoomProxyMock {

infoPublisher = CurrentValueSubject(.init(roomInfo: .init(configuration))).asCurrentValuePublisher()
membersPublisher = CurrentValueSubject(configuration.members).asCurrentValuePublisher()
joinRequestsPublisher = CurrentValueSubject(configuration.knockRequests).asCurrentValuePublisher()
joinRequestsStatePublisher = CurrentValueSubject(configuration.joinRequestsState).asCurrentValuePublisher()
typingMembersPublisher = CurrentValueSubject([]).asCurrentValuePublisher()
identityStatusChangesPublisher = CurrentValueSubject([]).asCurrentValuePublisher()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,22 @@ import Foundation
enum KnockRequestsListScreenViewModelAction { }

struct KnockRequestsListScreenViewState: BindableState {
var requests: [KnockRequestCellInfo] = []
var requestsState: KnockRequestsState = .loading

var displayedRequests: [KnockRequestCellInfo] {
requests.filter { !handledEventIDs.contains($0.id) }
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
Expand All @@ -28,6 +41,14 @@ struct KnockRequestsListScreenViewState: BindableState {
!displayedRequests.isEmpty && isKnockableRoom && (canAccept || canDecline || canBan)
}

var shouldDisplayAcceptAllButton: Bool {
!isLoading && shouldDisplayRequests && displayedRequests.count > 1
}

var shouldDisplayEmptyView: Bool {
!isLoading && !shouldDisplayRequests
}

var bindings = KnockRequestsListStateBindings()
}

Expand All @@ -47,3 +68,28 @@ enum KnockRequestsListScreenViewAction {
case declineRequest(eventID: String)
case ban(eventID: String)
}

enum KnockRequestsState: Equatable {
case loading
case loaded([KnockRequestCellInfo])

init(from state: JoinRequestsState) {
switch state {
case .loading:
self = .loading
case .loaded(let requests):
self = .loaded(requests.map(KnockRequestCellInfo.init))
}
}
}

private extension KnockRequestCellInfo {
init(from proxy: JoinRequestProxyProtocol) {
self.init(eventID: proxy.eventID,
userID: proxy.userID,
displayName: proxy.displayName,
avatarURL: proxy.avatarURL,
timestamp: proxy.formattedTimestamp,
reason: proxy.reason)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
case .acceptRequest(let eventID):
acceptRequest(eventID: eventID)
case .declineRequest(let eventID):
guard let request = roomProxy.joinRequestsPublisher.value.first(where: { $0.eventID == eventID }) else {
guard let request = getRequest(eventID: eventID) else {
return
}

state.bindings.alertInfo = .init(id: .declineRequest,
title: L10n.screenKnockRequestsListDeclineAlertTitle,
message: L10n.screenKnockRequestsListDeclineAlertDescription(request.userID),
Expand All @@ -59,9 +60,10 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
action: { [weak self] in self?.decline(request: request) }),
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
case .ban(let eventID):
guard let request = roomProxy.joinRequestsPublisher.value.first(where: { $0.eventID == eventID }) else {
guard let request = getRequest(eventID: eventID) else {
return
}

state.bindings.alertInfo = .init(id: .declineAndBan,
title: L10n.screenKnockRequestsListBanAlertTitle,
primaryButton: .init(title: L10n.screenKnockRequestsListBanAlertConfirmButtonTitle,
Expand All @@ -73,8 +75,16 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn

// MARK: - Private

private func getRequest(eventID: String) -> JoinRequestProxyProtocol? {
guard case let .loaded(requests) = roomProxy.joinRequestsStatePublisher.value,
let request = requests.first(where: { $0.eventID == eventID }) else {
return nil
}
return request
}

private func acceptRequest(eventID: String) {
guard let request = roomProxy.joinRequestsPublisher.value.first(where: { $0.eventID == eventID }) else {
guard let request = getRequest(eventID: eventID) else {
return
}

Expand Down Expand Up @@ -134,8 +144,11 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
showLoadingIndicator(title: L10n.screenKnockRequestsListAcceptAllLoadingTitle)
defer { hideLoadingIndicator() }

let requests = roomProxy.joinRequestsPublisher.value
guard case let .loaded(requests) = roomProxy.joinRequestsStatePublisher.value else {
return
}
state.handledEventIDs.formUnion(Set(requests.map(\.eventID)))

Task {
let failedIDs = await withTaskGroup(of: (String, Result<Void, JoinRequestProxyError>).self) { group in
for request in requests {
Expand Down Expand Up @@ -164,11 +177,24 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
}
.store(in: &cancellables)

roomProxy.joinRequestsPublisher
.map { $0.map(KnockRequestCellInfo.init) }
roomProxy.joinRequestsStatePublisher
.map(KnockRequestsState.init)
.removeDuplicates()
.throttle(for: .milliseconds(100), scheduler: DispatchQueue.main, latest: true)
.weakAssign(to: \.state.requests, on: self)
.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)
}

Expand All @@ -189,6 +215,15 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn

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))
}

private func showLoadingIndicator(title: String) {
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
type: .modal(progress: .indeterminate,
Expand All @@ -202,28 +237,4 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
private func hideLoadingIndicator() {
userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
}

// For testing purposes
private init(initialViewState: KnockRequestsListScreenViewState) {
roomProxy = JoinedRoomProxyMock(.init())
userIndicatorController = UserIndicatorControllerMock()
super.init(initialViewState: initialViewState)
}
}

extension KnockRequestsListScreenViewModel {
static func mockWithInitialState(_ initialViewState: KnockRequestsListScreenViewState) -> KnockRequestsListScreenViewModel {
.init(initialViewState: initialViewState)
}
}

extension KnockRequestCellInfo {
init(from proxy: JoinRequestProxyProtocol) {
self.init(eventID: proxy.eventID,
userID: proxy.userID,
displayName: proxy.displayName,
avatarURL: proxy.avatarURL,
timestamp: proxy.formattedTimestamp,
reason: proxy.reason)
}
}
Loading

0 comments on commit 6b4ee5b

Please sign in to comment.