Skip to content

Commit

Permalink
Knock and knocked state for the join room screen (#3424)
Browse files Browse the repository at this point in the history
* JoinRoomScreen ui for knocking

* code improvement

* updated previews

* added knocked state with tests

* send knock request

* Apply suggestions from code review

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>

* pr comments

---------

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
  • Loading branch information
Velin92 and pixlwave authored Oct 17, 2024
1 parent 1723542 commit bd4ecdd
Show file tree
Hide file tree
Showing 24 changed files with 627 additions and 48 deletions.
48 changes: 24 additions & 24 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@
"common_username" = "Username";
"common_verification_cancelled" = "Verification cancelled";
"common_verification_complete" = "Verification complete";
"common_verified" = "Verified";
"common_verify_device" = "Verify device";
"common_video" = "Video";
"common_voice_message" = "Voice message";
Expand Down Expand Up @@ -341,6 +342,10 @@
"screen_create_room_access_section_header" = "Room Access";
"screen_create_room_access_section_knocking_option_description" = "Anyone can ask to join the room but an administrator or a moderator will have to accept the request";
"screen_create_room_access_section_knocking_option_title" = "Ask to join";
"screen_join_room_cancel_knock_action" = "Cancel request";
"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_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 Expand Up @@ -504,7 +509,7 @@
"screen_invites_empty_list" = "No Invites";
"screen_invites_invited_you" = "%1$@ (%2$@) invited you";
"screen_join_room_join_action" = "Join room";
"screen_join_room_knock_action" = "Knock to join";
"screen_join_room_knock_action" = "Send request to join";
"screen_join_room_space_not_supported_description" = "%1$@ does not support spaces yet. You can access spaces on web.";
"screen_join_room_space_not_supported_title" = "Spaces are not supported yet";
"screen_join_room_subtitle_knock" = "Click the button below and a room administrator will be notified. You’ll be able to join the conversation once approved.";
Expand Down
3 changes: 2 additions & 1 deletion ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
via: via,
clientProxy: userSession.clientProxy,
mediaProvider: userSession.mediaProvider,
userIndicatorController: userIndicatorController))
userIndicatorController: userIndicatorController,
appSettings: appSettings))

joinRoomScreenCoordinator = coordinator

Expand Down
12 changes: 11 additions & 1 deletion ElementX/Sources/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,8 @@ internal enum L10n {
internal static var commonVerificationCancelled: String { return L10n.tr("Localizable", "common_verification_cancelled") }
/// Verification complete
internal static var commonVerificationComplete: String { return L10n.tr("Localizable", "common_verification_complete") }
/// Verified
internal static var commonVerified: String { return L10n.tr("Localizable", "common_verified") }
/// Verify device
internal static var commonVerifyDevice: String { return L10n.tr("Localizable", "common_verify_device") }
/// Video
Expand Down Expand Up @@ -1187,10 +1189,18 @@ internal enum L10n {
internal static func screenInvitesInvitedYou(_ p1: Any, _ p2: Any) -> String {
return L10n.tr("Localizable", "screen_invites_invited_you", String(describing: p1), String(describing: p2))
}
/// Cancel request
internal static var screenJoinRoomCancelKnockAction: String { return L10n.tr("Localizable", "screen_join_room_cancel_knock_action") }
/// Join room
internal static var screenJoinRoomJoinAction: String { return L10n.tr("Localizable", "screen_join_room_join_action") }
/// Knock to join
/// Send request to join
internal static var screenJoinRoomKnockAction: String { return L10n.tr("Localizable", "screen_join_room_knock_action") }
/// Message (optional)
internal static var screenJoinRoomKnockMessageDescription: String { return L10n.tr("Localizable", "screen_join_room_knock_message_description") }
/// You will receive an invite to join the room if your request is accepted.
internal static var screenJoinRoomKnockSentDescription: String { return L10n.tr("Localizable", "screen_join_room_knock_sent_description") }
/// Request to join sent
internal static var screenJoinRoomKnockSentTitle: String { return L10n.tr("Localizable", "screen_join_room_knock_sent_title") }
/// %1$@ does not support spaces yet. You can access spaces on web.
internal static func screenJoinRoomSpaceNotSupportedDescription(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_join_room_space_not_supported_description", String(describing: p1))
Expand Down
251 changes: 251 additions & 0 deletions ElementX/Sources/Mocks/Generated/GeneratedMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2835,6 +2835,146 @@ class ClientProxyMock: ClientProxyProtocol {
return joinRoomAliasReturnValue
}
}
//MARK: - knockRoom

var knockRoomMessageUnderlyingCallsCount = 0
var knockRoomMessageCallsCount: Int {
get {
if Thread.isMainThread {
return knockRoomMessageUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = knockRoomMessageUnderlyingCallsCount
}

return returnValue!
}
}
set {
if Thread.isMainThread {
knockRoomMessageUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
knockRoomMessageUnderlyingCallsCount = newValue
}
}
}
}
var knockRoomMessageCalled: Bool {
return knockRoomMessageCallsCount > 0
}
var knockRoomMessageReceivedArguments: (roomID: String, message: String?)?
var knockRoomMessageReceivedInvocations: [(roomID: String, message: String?)] = []

var knockRoomMessageUnderlyingReturnValue: Result<Void, ClientProxyError>!
var knockRoomMessageReturnValue: Result<Void, ClientProxyError>! {
get {
if Thread.isMainThread {
return knockRoomMessageUnderlyingReturnValue
} else {
var returnValue: Result<Void, ClientProxyError>? = nil
DispatchQueue.main.sync {
returnValue = knockRoomMessageUnderlyingReturnValue
}

return returnValue!
}
}
set {
if Thread.isMainThread {
knockRoomMessageUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
knockRoomMessageUnderlyingReturnValue = newValue
}
}
}
}
var knockRoomMessageClosure: ((String, String?) async -> Result<Void, ClientProxyError>)?

func knockRoom(_ roomID: String, message: String?) async -> Result<Void, ClientProxyError> {
knockRoomMessageCallsCount += 1
knockRoomMessageReceivedArguments = (roomID: roomID, message: message)
DispatchQueue.main.async {
self.knockRoomMessageReceivedInvocations.append((roomID: roomID, message: message))
}
if let knockRoomMessageClosure = knockRoomMessageClosure {
return await knockRoomMessageClosure(roomID, message)
} else {
return knockRoomMessageReturnValue
}
}
//MARK: - knockRoomAlias

var knockRoomAliasMessageUnderlyingCallsCount = 0
var knockRoomAliasMessageCallsCount: Int {
get {
if Thread.isMainThread {
return knockRoomAliasMessageUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = knockRoomAliasMessageUnderlyingCallsCount
}

return returnValue!
}
}
set {
if Thread.isMainThread {
knockRoomAliasMessageUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
knockRoomAliasMessageUnderlyingCallsCount = newValue
}
}
}
}
var knockRoomAliasMessageCalled: Bool {
return knockRoomAliasMessageCallsCount > 0
}
var knockRoomAliasMessageReceivedArguments: (roomAlias: String, message: String?)?
var knockRoomAliasMessageReceivedInvocations: [(roomAlias: String, message: String?)] = []

var knockRoomAliasMessageUnderlyingReturnValue: Result<Void, ClientProxyError>!
var knockRoomAliasMessageReturnValue: Result<Void, ClientProxyError>! {
get {
if Thread.isMainThread {
return knockRoomAliasMessageUnderlyingReturnValue
} else {
var returnValue: Result<Void, ClientProxyError>? = nil
DispatchQueue.main.sync {
returnValue = knockRoomAliasMessageUnderlyingReturnValue
}

return returnValue!
}
}
set {
if Thread.isMainThread {
knockRoomAliasMessageUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
knockRoomAliasMessageUnderlyingReturnValue = newValue
}
}
}
}
var knockRoomAliasMessageClosure: ((String, String?) async -> Result<Void, ClientProxyError>)?

func knockRoomAlias(_ roomAlias: String, message: String?) async -> Result<Void, ClientProxyError> {
knockRoomAliasMessageCallsCount += 1
knockRoomAliasMessageReceivedArguments = (roomAlias: roomAlias, message: message)
DispatchQueue.main.async {
self.knockRoomAliasMessageReceivedInvocations.append((roomAlias: roomAlias, message: message))
}
if let knockRoomAliasMessageClosure = knockRoomAliasMessageClosure {
return await knockRoomAliasMessageClosure(roomAlias, message)
} else {
return knockRoomAliasMessageReturnValue
}
}
//MARK: - uploadMedia

var uploadMediaUnderlyingCallsCount = 0
Expand Down Expand Up @@ -9541,6 +9681,117 @@ class KeychainControllerMock: KeychainControllerProtocol {
removePINCodeBiometricStateClosure?()
}
}
class KnockedRoomProxyMock: KnockedRoomProxyProtocol {
var id: String {
get { return underlyingId }
set(value) { underlyingId = value }
}
var underlyingId: String!
var canonicalAlias: String?
var ownUserID: String {
get { return underlyingOwnUserID }
set(value) { underlyingOwnUserID = value }
}
var underlyingOwnUserID: String!
var name: String?
var topic: String?
var avatar: RoomAvatar {
get { return underlyingAvatar }
set(value) { underlyingAvatar = value }
}
var underlyingAvatar: RoomAvatar!
var avatarURL: URL?
var isPublic: Bool {
get { return underlyingIsPublic }
set(value) { underlyingIsPublic = value }
}
var underlyingIsPublic: Bool!
var isDirect: Bool {
get { return underlyingIsDirect }
set(value) { underlyingIsDirect = value }
}
var underlyingIsDirect: Bool!
var isSpace: Bool {
get { return underlyingIsSpace }
set(value) { underlyingIsSpace = value }
}
var underlyingIsSpace: Bool!
var joinedMembersCount: Int {
get { return underlyingJoinedMembersCount }
set(value) { underlyingJoinedMembersCount = value }
}
var underlyingJoinedMembersCount: Int!
var activeMembersCount: Int {
get { return underlyingActiveMembersCount }
set(value) { underlyingActiveMembersCount = value }
}
var underlyingActiveMembersCount: Int!

//MARK: - cancelKnock

var cancelKnockUnderlyingCallsCount = 0
var cancelKnockCallsCount: Int {
get {
if Thread.isMainThread {
return cancelKnockUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = cancelKnockUnderlyingCallsCount
}

return returnValue!
}
}
set {
if Thread.isMainThread {
cancelKnockUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
cancelKnockUnderlyingCallsCount = newValue
}
}
}
}
var cancelKnockCalled: Bool {
return cancelKnockCallsCount > 0
}

var cancelKnockUnderlyingReturnValue: Result<Void, RoomProxyError>!
var cancelKnockReturnValue: Result<Void, RoomProxyError>! {
get {
if Thread.isMainThread {
return cancelKnockUnderlyingReturnValue
} else {
var returnValue: Result<Void, RoomProxyError>? = nil
DispatchQueue.main.sync {
returnValue = cancelKnockUnderlyingReturnValue
}

return returnValue!
}
}
set {
if Thread.isMainThread {
cancelKnockUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
cancelKnockUnderlyingReturnValue = newValue
}
}
}
}
var cancelKnockClosure: (() async -> Result<Void, RoomProxyError>)?

func cancelKnock() async -> Result<Void, RoomProxyError> {
cancelKnockCallsCount += 1
if let cancelKnockClosure = cancelKnockClosure {
return await cancelKnockClosure()
} else {
return cancelKnockReturnValue
}
}
}
class MediaLoaderMock: MediaLoaderProtocol {

//MARK: - loadMediaContentForSource
Expand Down
29 changes: 29 additions & 0 deletions ElementX/Sources/Mocks/KnockedRoomProxyMock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//

import Combine
import Foundation

@MainActor
struct KnockedRoomProxyMockConfiguration {
var id = UUID().uuidString
var name: String?
var avatarURL: URL?
var members: [RoomMemberProxyMock] = .allMembers
}

extension KnockedRoomProxyMock {
@MainActor
convenience init(_ configuration: KnockedRoomProxyMockConfiguration) {
self.init()
id = configuration.id
name = configuration.name
avatarURL = configuration.avatarURL
avatar = .room(id: configuration.id, name: configuration.name, avatarURL: configuration.avatarURL) // Note: This doesn't replicate the real proxy logic.
activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count
}
}
7 changes: 7 additions & 0 deletions ElementX/Sources/Other/Extensions/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,10 @@ extension String {
return result
}
}

extension String {
/// detects if the string is empty or contains only whitespaces and newlines
var isBlank: Bool {
trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct JoinRoomScreenCoordinatorParameters {
let clientProxy: ClientProxyProtocol
let mediaProvider: MediaProviderProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let appSettings: AppSettings
}

enum JoinRoomScreenCoordinatorAction {
Expand All @@ -34,6 +35,7 @@ final class JoinRoomScreenCoordinator: CoordinatorProtocol {
init(parameters: JoinRoomScreenCoordinatorParameters) {
viewModel = JoinRoomScreenViewModel(roomID: parameters.roomID,
via: parameters.via,
appSettings: parameters.appSettings,
clientProxy: parameters.clientProxy,
mediaProvider: parameters.mediaProvider,
userIndicatorController: parameters.userIndicatorController)
Expand Down
Loading

0 comments on commit bd4ecdd

Please sign in to comment.