Skip to content

Commit a16e134

Browse files
Velin92pixlwave
andauthored
Create Room with knock rule (#3397)
* create knock room implementation (without SDK) * Apply suggestions from code review pr suggestions Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> * pr suggestions --------- Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
1 parent 25037a6 commit a16e134

17 files changed

+149
-37
lines changed

ElementX/Sources/Application/AppSettings.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ final class AppSettings {
4747
case pinningEnabled
4848
case enableOnlySignedDeviceIsolationMode
4949
case identityPinningViolationNotificationsEnabled
50+
case knockingEnabled
5051
}
5152

5253
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
@@ -282,6 +283,9 @@ final class AppSettings {
282283

283284
@UserPreference(key: UserDefaultsKeys.identityPinningViolationNotificationsEnabled, defaultValue: isDevelopmentBuild, storageType: .userDefaults(store))
284285
var identityPinningViolationNotificationsEnabled
286+
287+
@UserPreference(key: UserDefaultsKeys.knockingEnabled, defaultValue: false, storageType: .userDefaults(store))
288+
var knockingEnabled
285289

286290
#endif
287291

ElementX/Sources/Mocks/ClientProxyMock.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ extension ClientProxyMock {
5050
canDeactivateAccount = false
5151
directRoomForUserIDReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
5252
createDirectRoomWithExpectedRoomNameReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
53-
createRoomNameTopicIsRoomPrivateUserIDsAvatarURLReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
53+
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
5454
uploadMediaReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
5555
loadUserDisplayNameReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
5656
setUserDisplayNameReturnValue = .failure(.sdkError(ClientProxyMockError.generic))

ElementX/Sources/Mocks/Generated/GeneratedMocks.swift

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2627,72 +2627,72 @@ class ClientProxyMock: ClientProxyProtocol {
26272627
}
26282628
//MARK: - createRoom
26292629

2630-
var createRoomNameTopicIsRoomPrivateUserIDsAvatarURLUnderlyingCallsCount = 0
2631-
var createRoomNameTopicIsRoomPrivateUserIDsAvatarURLCallsCount: Int {
2630+
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLUnderlyingCallsCount = 0
2631+
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLCallsCount: Int {
26322632
get {
26332633
if Thread.isMainThread {
2634-
return createRoomNameTopicIsRoomPrivateUserIDsAvatarURLUnderlyingCallsCount
2634+
return createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLUnderlyingCallsCount
26352635
} else {
26362636
var returnValue: Int? = nil
26372637
DispatchQueue.main.sync {
2638-
returnValue = createRoomNameTopicIsRoomPrivateUserIDsAvatarURLUnderlyingCallsCount
2638+
returnValue = createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLUnderlyingCallsCount
26392639
}
26402640

26412641
return returnValue!
26422642
}
26432643
}
26442644
set {
26452645
if Thread.isMainThread {
2646-
createRoomNameTopicIsRoomPrivateUserIDsAvatarURLUnderlyingCallsCount = newValue
2646+
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLUnderlyingCallsCount = newValue
26472647
} else {
26482648
DispatchQueue.main.sync {
2649-
createRoomNameTopicIsRoomPrivateUserIDsAvatarURLUnderlyingCallsCount = newValue
2649+
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLUnderlyingCallsCount = newValue
26502650
}
26512651
}
26522652
}
26532653
}
2654-
var createRoomNameTopicIsRoomPrivateUserIDsAvatarURLCalled: Bool {
2655-
return createRoomNameTopicIsRoomPrivateUserIDsAvatarURLCallsCount > 0
2654+
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLCalled: Bool {
2655+
return createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLCallsCount > 0
26562656
}
2657-
var createRoomNameTopicIsRoomPrivateUserIDsAvatarURLReceivedArguments: (name: String, topic: String?, isRoomPrivate: Bool, userIDs: [String], avatarURL: URL?)?
2658-
var createRoomNameTopicIsRoomPrivateUserIDsAvatarURLReceivedInvocations: [(name: String, topic: String?, isRoomPrivate: Bool, userIDs: [String], avatarURL: URL?)] = []
2657+
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLReceivedArguments: (name: String, topic: String?, isRoomPrivate: Bool, isKnockingOnly: Bool, userIDs: [String], avatarURL: URL?)?
2658+
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLReceivedInvocations: [(name: String, topic: String?, isRoomPrivate: Bool, isKnockingOnly: Bool, userIDs: [String], avatarURL: URL?)] = []
26592659

2660-
var createRoomNameTopicIsRoomPrivateUserIDsAvatarURLUnderlyingReturnValue: Result<String, ClientProxyError>!
2661-
var createRoomNameTopicIsRoomPrivateUserIDsAvatarURLReturnValue: Result<String, ClientProxyError>! {
2660+
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLUnderlyingReturnValue: Result<String, ClientProxyError>!
2661+
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLReturnValue: Result<String, ClientProxyError>! {
26622662
get {
26632663
if Thread.isMainThread {
2664-
return createRoomNameTopicIsRoomPrivateUserIDsAvatarURLUnderlyingReturnValue
2664+
return createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLUnderlyingReturnValue
26652665
} else {
26662666
var returnValue: Result<String, ClientProxyError>? = nil
26672667
DispatchQueue.main.sync {
2668-
returnValue = createRoomNameTopicIsRoomPrivateUserIDsAvatarURLUnderlyingReturnValue
2668+
returnValue = createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLUnderlyingReturnValue
26692669
}
26702670

26712671
return returnValue!
26722672
}
26732673
}
26742674
set {
26752675
if Thread.isMainThread {
2676-
createRoomNameTopicIsRoomPrivateUserIDsAvatarURLUnderlyingReturnValue = newValue
2676+
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLUnderlyingReturnValue = newValue
26772677
} else {
26782678
DispatchQueue.main.sync {
2679-
createRoomNameTopicIsRoomPrivateUserIDsAvatarURLUnderlyingReturnValue = newValue
2679+
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLUnderlyingReturnValue = newValue
26802680
}
26812681
}
26822682
}
26832683
}
2684-
var createRoomNameTopicIsRoomPrivateUserIDsAvatarURLClosure: ((String, String?, Bool, [String], URL?) async -> Result<String, ClientProxyError>)?
2684+
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLClosure: ((String, String?, Bool, Bool, [String], URL?) async -> Result<String, ClientProxyError>)?
26852685

2686-
func createRoom(name: String, topic: String?, isRoomPrivate: Bool, userIDs: [String], avatarURL: URL?) async -> Result<String, ClientProxyError> {
2687-
createRoomNameTopicIsRoomPrivateUserIDsAvatarURLCallsCount += 1
2688-
createRoomNameTopicIsRoomPrivateUserIDsAvatarURLReceivedArguments = (name: name, topic: topic, isRoomPrivate: isRoomPrivate, userIDs: userIDs, avatarURL: avatarURL)
2686+
func createRoom(name: String, topic: String?, isRoomPrivate: Bool, isKnockingOnly: Bool, userIDs: [String], avatarURL: URL?) async -> Result<String, ClientProxyError> {
2687+
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLCallsCount += 1
2688+
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLReceivedArguments = (name: name, topic: topic, isRoomPrivate: isRoomPrivate, isKnockingOnly: isKnockingOnly, userIDs: userIDs, avatarURL: avatarURL)
26892689
DispatchQueue.main.async {
2690-
self.createRoomNameTopicIsRoomPrivateUserIDsAvatarURLReceivedInvocations.append((name: name, topic: topic, isRoomPrivate: isRoomPrivate, userIDs: userIDs, avatarURL: avatarURL))
2690+
self.createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLReceivedInvocations.append((name: name, topic: topic, isRoomPrivate: isRoomPrivate, isKnockingOnly: isKnockingOnly, userIDs: userIDs, avatarURL: avatarURL))
26912691
}
2692-
if let createRoomNameTopicIsRoomPrivateUserIDsAvatarURLClosure = createRoomNameTopicIsRoomPrivateUserIDsAvatarURLClosure {
2693-
return await createRoomNameTopicIsRoomPrivateUserIDsAvatarURLClosure(name, topic, isRoomPrivate, userIDs, avatarURL)
2692+
if let createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLClosure = createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLClosure {
2693+
return await createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLClosure(name, topic, isRoomPrivate, isKnockingOnly, userIDs, avatarURL)
26942694
} else {
2695-
return createRoomNameTopicIsRoomPrivateUserIDsAvatarURLReturnValue
2695+
return createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLReturnValue
26962696
}
26972697
}
26982698
//MARK: - joinRoom

ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ final class CreateRoomCoordinator: CoordinatorProtocol {
3737
createRoomParameters: parameters.createRoomParameters,
3838
selectedUsers: parameters.selectedUsers,
3939
analytics: ServiceLocator.shared.analytics,
40-
userIndicatorController: parameters.userIndicatorController)
40+
userIndicatorController: parameters.userIndicatorController,
41+
appSettings: ServiceLocator.shared.settings)
4142
}
4243

4344
func start() {

ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ enum CreateRoomViewModelAction {
2525
}
2626

2727
struct CreateRoomViewState: BindableState {
28+
let isKnockingFeatureEnabled: Bool
2829
var selectedUsers: [UserProfileProxy]
2930
var bindings: CreateRoomViewStateBindings
3031
var avatarURL: URL?
@@ -37,6 +38,7 @@ struct CreateRoomViewStateBindings {
3738
var roomName: String
3839
var roomTopic: String
3940
var isRoomPrivate: Bool
41+
var isKnockingOnly = false
4042
var showAttachmentConfirmationDialog = false
4143

4244
/// Information describing the currently displayed alert.

ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol
2626
createRoomParameters: CurrentValuePublisher<CreateRoomFlowParameters, Never>,
2727
selectedUsers: CurrentValuePublisher<[UserProfileProxy], Never>,
2828
analytics: AnalyticsService,
29-
userIndicatorController: UserIndicatorControllerProtocol) {
29+
userIndicatorController: UserIndicatorControllerProtocol,
30+
appSettings: AppSettings) {
3031
let parameters = createRoomParameters.value
3132

3233
self.userSession = userSession
@@ -36,7 +37,7 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol
3637

3738
let bindings = CreateRoomViewStateBindings(roomName: parameters.name, roomTopic: parameters.topic, isRoomPrivate: parameters.isRoomPrivate)
3839

39-
super.init(initialViewState: CreateRoomViewState(selectedUsers: selectedUsers.value, bindings: bindings), mediaProvider: userSession.mediaProvider)
40+
super.init(initialViewState: CreateRoomViewState(isKnockingFeatureEnabled: appSettings.knockingEnabled, selectedUsers: selectedUsers.value, bindings: bindings), mediaProvider: userSession.mediaProvider)
4041

4142
createRoomParameters
4243
.map(\.avatarImageMedia)
@@ -92,21 +93,28 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol
9293
old.roomName == new.roomName && old.roomTopic == new.roomTopic && old.isRoomPrivate == new.isRoomPrivate
9394
}
9495
.sink { [weak self] bindings in
95-
guard let self else { return }
96-
createRoomParameters.name = bindings.roomName
97-
createRoomParameters.topic = bindings.roomTopic
98-
createRoomParameters.isRoomPrivate = bindings.isRoomPrivate
96+
guard let self = self else { return }
97+
updateParameters(bindings: bindings)
9998
actionsSubject.send(.updateDetails(createRoomParameters))
10099
}
101100
.store(in: &cancellables)
102101
}
103102

103+
private func updateParameters(bindings: CreateRoomViewStateBindings) {
104+
createRoomParameters.name = bindings.roomName
105+
createRoomParameters.topic = bindings.roomTopic
106+
createRoomParameters.isRoomPrivate = bindings.isRoomPrivate
107+
createRoomParameters.isKnockingOnly = bindings.isKnockingOnly
108+
}
109+
104110
private func createRoom() async {
105111
defer {
106112
hideLoadingIndicator()
107113
}
108114
showLoadingIndicator()
109115

116+
// Since the parameters are throttled, we need to make sure that the latest values are used
117+
updateParameters(bindings: state.bindings)
110118
let avatarURL: URL?
111119
if let media = createRoomParameters.avatarImageMedia {
112120
switch await userSession.clientProxy.uploadMedia(media) {
@@ -136,6 +144,8 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol
136144
switch await userSession.clientProxy.createRoom(name: createRoomParameters.name,
137145
topic: createRoomParameters.topic,
138146
isRoomPrivate: createRoomParameters.isRoomPrivate,
147+
// As of right now we don't want to make private rooms with the knock rule
148+
isKnockingOnly: createRoomParameters.isRoomPrivate ? false : createRoomParameters.isKnockingOnly,
139149
userIDs: state.selectedUsers.map(\.userID),
140150
avatarURL: avatarURL) {
141151
case .success(let roomId):

ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ struct CreateRoomScreen: View {
2222
roomSection
2323
topicSection
2424
securitySection
25+
if context.viewState.isKnockingFeatureEnabled,
26+
!context.isRoomPrivate {
27+
roomAccessSection
28+
}
2529
}
2630
.compoundList()
2731
.track(screen: .CreateRoom)
@@ -151,6 +155,20 @@ struct CreateRoomScreen: View {
151155
}
152156
}
153157

158+
private var roomAccessSection: some View {
159+
Section {
160+
ListRow(label: .plain(title: L10n.screenCreateRoomAccessSectionAnyoneOptionTitle,
161+
description: L10n.screenCreateRoomAccessSectionAnyoneOptionDescription),
162+
kind: .selection(isSelected: !context.isKnockingOnly) { context.isKnockingOnly = false })
163+
ListRow(label: .plain(title: L10n.screenCreateRoomAccessSectionKnockingOptionTitle,
164+
description: L10n.screenCreateRoomAccessSectionKnockingOptionDescription),
165+
kind: .selection(isSelected: context.isKnockingOnly) { context.isKnockingOnly = true })
166+
} header: {
167+
Text(L10n.screenCreateRoomAccessSectionHeader.uppercased())
168+
.compoundListSectionHeader()
169+
}
170+
}
171+
154172
private var toolbar: some ToolbarContent {
155173
ToolbarItem(placement: .confirmationAction) {
156174
Button(L10n.actionCreate) {
@@ -174,7 +192,8 @@ struct CreateRoom_Previews: PreviewProvider, TestablePreview {
174192
createRoomParameters: .init(parameters),
175193
selectedUsers: .init(selectedUsers),
176194
analytics: ServiceLocator.shared.analytics,
177-
userIndicatorController: UserIndicatorControllerMock())
195+
userIndicatorController: UserIndicatorControllerMock(),
196+
appSettings: ServiceLocator.shared.settings)
178197
}()
179198

180199
static let emtpyViewModel = {
@@ -184,7 +203,21 @@ struct CreateRoom_Previews: PreviewProvider, TestablePreview {
184203
createRoomParameters: .init(parameters),
185204
selectedUsers: .init([]),
186205
analytics: ServiceLocator.shared.analytics,
187-
userIndicatorController: UserIndicatorControllerMock())
206+
userIndicatorController: UserIndicatorControllerMock(),
207+
appSettings: ServiceLocator.shared.settings)
208+
}()
209+
210+
static let publicRoomViewModel = {
211+
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com"))))
212+
let parameters = CreateRoomFlowParameters(isRoomPrivate: false)
213+
let selectedUsers: [UserProfileProxy] = [.mockAlice, .mockBob, .mockCharlie]
214+
ServiceLocator.shared.settings.knockingEnabled = true
215+
return CreateRoomViewModel(userSession: userSession,
216+
createRoomParameters: .init(parameters),
217+
selectedUsers: .init([]),
218+
analytics: ServiceLocator.shared.analytics,
219+
userIndicatorController: UserIndicatorControllerMock(),
220+
appSettings: ServiceLocator.shared.settings)
188221
}()
189222

190223
static var previews: some View {
@@ -196,5 +229,9 @@ struct CreateRoom_Previews: PreviewProvider, TestablePreview {
196229
CreateRoomScreen(context: emtpyViewModel.context)
197230
}
198231
.previewDisplayName("Create Room without users")
232+
NavigationStack {
233+
CreateRoomScreen(context: publicRoomViewModel.context)
234+
}
235+
.previewDisplayName("Create Public Room")
199236
}
200237
}

ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
4949
var enableOnlySignedDeviceIsolationMode: Bool { get set }
5050
var elementCallBaseURLOverride: URL? { get set }
5151
var identityPinningViolationNotificationsEnabled: Bool { get set }
52+
var knockingEnabled: Bool { get set }
5253
}
5354

5455
extension AppSettings: DeveloperOptionsProtocol { }

ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ struct DeveloperOptionsScreen: View {
5555
}
5656
}
5757

58+
Section("Join rules") {
59+
Toggle(isOn: $context.knockingEnabled) {
60+
Text("Knocking")
61+
Text("Experimental, still using mocked data")
62+
}
63+
}
64+
5865
Section {
5966
Toggle(isOn: $context.enableOnlySignedDeviceIsolationMode) {
6067
Text("Exclude insecure devices when sending/receiving messages")

ElementX/Sources/Services/Client/ClientProxy.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,9 @@ class ClientProxy: ClientProxyProtocol {
365365
}
366366
}
367367

368-
func createRoom(name: String, topic: String?, isRoomPrivate: Bool, userIDs: [String], avatarURL: URL?) async -> Result<String, ClientProxyError> {
368+
func createRoom(name: String, topic: String?, isRoomPrivate: Bool, isKnockingOnly: Bool, userIDs: [String], avatarURL: URL?) async -> Result<String, ClientProxyError> {
369369
do {
370+
// TODO: Revisit once the SDK supports the knocking API
370371
let parameters = CreateRoomParameters(name: name,
371372
topic: topic,
372373
isEncrypted: isRoomPrivate,

0 commit comments

Comments
 (0)