Expose Element Call settings in the developer options. Start using th… (#2039)

* Expose Element Call settings in the developer options. Start using the encryption widget parameter.

* Remove the Element Call feature flag
This commit is contained in:
Stefan Ceriu
2023-11-07 16:12:03 +02:00
committed by GitHub
parent b13abf9cdf
commit 5dcb24add7
13 changed files with 76 additions and 56 deletions
@@ -37,12 +37,14 @@ final class AppSettings {
case viewSourceEnabled case viewSourceEnabled
case richTextEditorEnabled case richTextEditorEnabled
case elementCallBaseURL
case elementCallEncryptionEnabled
// Feature flags // Feature flags
case shouldCollapseRoomStateEvents case shouldCollapseRoomStateEvents
case userSuggestionsEnabled case userSuggestionsEnabled
case readReceiptsEnabled case readReceiptsEnabled
case swiftUITimelineEnabled case swiftUITimelineEnabled
case elementCallEnabled
case chatBackupEnabled case chatBackupEnabled
} }
@@ -215,7 +217,11 @@ final class AppSettings {
// MARK: - Element Call // MARK: - Element Call
let elementCallBaseURL: URL = "https://call.element.dev" @UserPreference(key: UserDefaultsKeys.elementCallBaseURL, defaultValue: "https://call.element.io", storageType: .userDefaults(store))
var elementCallBaseURL: URL
@UserPreference(key: UserDefaultsKeys.elementCallEncryptionEnabled, defaultValue: true, storageType: .userDefaults(store))
var elementCallUseEncryption
// MARK: - Notifications // MARK: - Notifications
@@ -267,9 +273,6 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.swiftUITimelineEnabled, defaultValue: false, storageType: .volatile) @UserPreference(key: UserDefaultsKeys.swiftUITimelineEnabled, defaultValue: false, storageType: .volatile)
var swiftUITimelineEnabled var swiftUITimelineEnabled
@UserPreference(key: UserDefaultsKeys.elementCallEnabled, defaultValue: true, storageType: .userDefaults(store))
var elementCallEnabled
@UserPreference(key: UserDefaultsKeys.chatBackupEnabled, defaultValue: false, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.chatBackupEnabled, defaultValue: false, storageType: .userDefaults(store))
var chatBackupEnabled var chatBackupEnabled
} }
@@ -510,7 +510,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private func presentCallScreen(roomProxy: RoomProxyProtocol) { private func presentCallScreen(roomProxy: RoomProxyProtocol) {
let callScreenCoordinator = CallScreenCoordinator(parameters: .init(roomProxy: roomProxy, let callScreenCoordinator = CallScreenCoordinator(parameters: .init(roomProxy: roomProxy,
callBaseURL: appSettings.elementCallBaseURL, callBaseURL: appSettings.elementCallBaseURL,
clientID: InfoPlistReader.main.bundleIdentifier)) clientID: InfoPlistReader.main.bundleIdentifier,
useEncryption: appSettings.elementCallUseEncryption))
callScreenCoordinator.actions callScreenCoordinator.actions
.sink { [weak self] action in .sink { [weak self] action in
@@ -723,23 +723,23 @@ class ElementCallWidgetDriverMock: ElementCallWidgetDriverProtocol {
//MARK: - start //MARK: - start
var startBaseURLClientIDCallsCount = 0 var startBaseURLClientIDUseEncryptionCallsCount = 0
var startBaseURLClientIDCalled: Bool { var startBaseURLClientIDUseEncryptionCalled: Bool {
return startBaseURLClientIDCallsCount > 0 return startBaseURLClientIDUseEncryptionCallsCount > 0
} }
var startBaseURLClientIDReceivedArguments: (baseURL: URL, clientID: String)? var startBaseURLClientIDUseEncryptionReceivedArguments: (baseURL: URL, clientID: String, useEncryption: Bool)?
var startBaseURLClientIDReceivedInvocations: [(baseURL: URL, clientID: String)] = [] var startBaseURLClientIDUseEncryptionReceivedInvocations: [(baseURL: URL, clientID: String, useEncryption: Bool)] = []
var startBaseURLClientIDReturnValue: Result<URL, ElementCallWidgetDriverError>! var startBaseURLClientIDUseEncryptionReturnValue: Result<URL, ElementCallWidgetDriverError>!
var startBaseURLClientIDClosure: ((URL, String) async -> Result<URL, ElementCallWidgetDriverError>)? var startBaseURLClientIDUseEncryptionClosure: ((URL, String, Bool) async -> Result<URL, ElementCallWidgetDriverError>)?
func start(baseURL: URL, clientID: String) async -> Result<URL, ElementCallWidgetDriverError> { func start(baseURL: URL, clientID: String, useEncryption: Bool) async -> Result<URL, ElementCallWidgetDriverError> {
startBaseURLClientIDCallsCount += 1 startBaseURLClientIDUseEncryptionCallsCount += 1
startBaseURLClientIDReceivedArguments = (baseURL: baseURL, clientID: clientID) startBaseURLClientIDUseEncryptionReceivedArguments = (baseURL: baseURL, clientID: clientID, useEncryption: useEncryption)
startBaseURLClientIDReceivedInvocations.append((baseURL: baseURL, clientID: clientID)) startBaseURLClientIDUseEncryptionReceivedInvocations.append((baseURL: baseURL, clientID: clientID, useEncryption: useEncryption))
if let startBaseURLClientIDClosure = startBaseURLClientIDClosure { if let startBaseURLClientIDUseEncryptionClosure = startBaseURLClientIDUseEncryptionClosure {
return await startBaseURLClientIDClosure(baseURL, clientID) return await startBaseURLClientIDUseEncryptionClosure(baseURL, clientID, useEncryption)
} else { } else {
return startBaseURLClientIDReturnValue return startBaseURLClientIDUseEncryptionReturnValue
} }
} }
//MARK: - sendMessage //MARK: - sendMessage
@@ -23,6 +23,8 @@ struct CallScreenCoordinatorParameters {
let callBaseURL: URL let callBaseURL: URL
/// A way to identify the current client against Element Call /// A way to identify the current client against Element Call
let clientID: String let clientID: String
/// Whether encryption should be used within the call
let useEncryption: Bool
} }
enum CallScreenCoordinatorAction { enum CallScreenCoordinatorAction {
@@ -44,7 +46,8 @@ final class CallScreenCoordinator: CoordinatorProtocol {
viewModel = CallScreenViewModel(roomProxy: parameters.roomProxy, viewModel = CallScreenViewModel(roomProxy: parameters.roomProxy,
callBaseURL: parameters.callBaseURL, callBaseURL: parameters.callBaseURL,
clientID: parameters.clientID) clientID: parameters.clientID,
useEncryption: parameters.useEncryption)
} }
func start() { func start() {
@@ -46,7 +46,10 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
/// - roomProxy: The room in which the call should be created /// - roomProxy: The room in which the call should be created
/// - callBaseURL: Which Element Call instance should be used /// - callBaseURL: Which Element Call instance should be used
/// - clientID: Something to identify the current client on the Element Call side /// - clientID: Something to identify the current client on the Element Call side
init(roomProxy: RoomProxyProtocol, callBaseURL: URL, clientID: String) { init(roomProxy: RoomProxyProtocol,
callBaseURL: URL,
clientID: String,
useEncryption: Bool) {
self.roomProxy = roomProxy self.roomProxy = roomProxy
self.callBaseURL = callBaseURL self.callBaseURL = callBaseURL
self.clientID = clientID self.clientID = clientID
@@ -99,7 +102,7 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
.store(in: &cancellables) .store(in: &cancellables)
Task { Task {
switch await widgetDriver.start(baseURL: callBaseURL, clientID: clientID) { switch await widgetDriver.start(baseURL: callBaseURL, clientID: clientID, useEncryption: useEncryption) {
case .success(let url): case .success(let url):
state.url = url state.url = url
case .failure(let error): case .failure(let error):
@@ -23,8 +23,6 @@ struct CallScreen: View {
var body: some View { var body: some View {
WebView(url: context.viewState.url, viewModelContext: context) WebView(url: context.viewState.url, viewModelContext: context)
.navigationTitle("Call")
.navigationBarTitleDisplayMode(.inline)
.ignoresSafeArea(edges: .bottom) .ignoresSafeArea(edges: .bottom)
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
.environment(\.colorScheme, .dark) .environment(\.colorScheme, .dark)
@@ -164,13 +162,14 @@ struct CallScreen_Previews: PreviewProvider {
let widgetDriver = ElementCallWidgetDriverMock() let widgetDriver = ElementCallWidgetDriverMock()
widgetDriver.underlyingMessagePublisher = .init() widgetDriver.underlyingMessagePublisher = .init()
widgetDriver.underlyingActions = PassthroughSubject<ElementCallWidgetDriverAction, Never>().eraseToAnyPublisher() widgetDriver.underlyingActions = PassthroughSubject<ElementCallWidgetDriverAction, Never>().eraseToAnyPublisher()
widgetDriver.startBaseURLClientIDReturnValue = .success(URL.userDirectory) widgetDriver.startBaseURLClientIDUseEncryptionReturnValue = .success(URL.userDirectory)
roomProxy.elementCallWidgetDriverReturnValue = widgetDriver roomProxy.elementCallWidgetDriverReturnValue = widgetDriver
return CallScreenViewModel(roomProxy: roomProxy, return CallScreenViewModel(roomProxy: roomProxy,
callBaseURL: "https://call.element.io", callBaseURL: "https://call.element.io",
clientID: "io.element.elementx") clientID: "io.element.elementx",
useEncryption: false)
}() }()
static var previews: some View { static var previews: some View {
@@ -116,7 +116,6 @@ struct RoomScreenViewState: BindableState {
var ownUserID: String var ownUserID: String
var showCallButton = false
var isCallOngoing = false var isCallOngoing = false
var bindings: RoomScreenViewStateBindings var bindings: RoomScreenViewStateBindings
@@ -264,11 +264,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
appSettings.$readReceiptsEnabled appSettings.$readReceiptsEnabled
.weakAssign(to: \.state.readReceiptsEnabled, on: self) .weakAssign(to: \.state.readReceiptsEnabled, on: self)
.store(in: &cancellables) .store(in: &cancellables)
appSettings.$elementCallEnabled
.weakAssign(to: \.state.showCallButton, on: self)
.store(in: &cancellables)
roomProxy.members roomProxy.members
.map { members in .map { members in
members.reduce(into: [String: RoomMemberState]()) { dictionary, member in members.reduce(into: [String: RoomMemberState]()) { dictionary, member in
@@ -162,21 +162,19 @@ struct RoomScreen: View {
@ViewBuilder @ViewBuilder
private var callButton: some View { private var callButton: some View {
if context.viewState.showCallButton { if context.viewState.isCallOngoing {
if context.viewState.isCallOngoing { Button {
Button { context.send(viewAction: .presentCall)
context.send(viewAction: .presentCall) } label: {
} label: { Label(L10n.actionJoin, icon: \.videoCallSolid)
Label(L10n.actionJoin, icon: \.videoCallSolid) .labelStyle(.titleAndIcon)
.labelStyle(.titleAndIcon) }
} .buttonStyle(ElementCallButtonStyle())
.buttonStyle(ElementCallButtonStyle()) } else {
} else { Button {
Button { context.send(viewAction: .presentCall)
context.send(viewAction: .presentCall) } label: {
} label: { CompoundIcon(\.videoCallSolid)
CompoundIcon(\.videoCallSolid)
}
} }
} }
} }
@@ -49,8 +49,10 @@ protocol DeveloperOptionsProtocol: AnyObject {
var userSuggestionsEnabled: Bool { get set } var userSuggestionsEnabled: Bool { get set }
var readReceiptsEnabled: Bool { get set } var readReceiptsEnabled: Bool { get set }
var swiftUITimelineEnabled: Bool { get set } var swiftUITimelineEnabled: Bool { get set }
var elementCallEnabled: Bool { get set }
var chatBackupEnabled: Bool { get set } var chatBackupEnabled: Bool { get set }
var elementCallBaseURL: URL { get set }
var elementCallUseEncryption: Bool { get set }
} }
extension AppSettings: DeveloperOptionsProtocol { } extension AppSettings: DeveloperOptionsProtocol { }
@@ -19,6 +19,7 @@ import SwiftUI
struct DeveloperOptionsScreen: View { struct DeveloperOptionsScreen: View {
@ObservedObject var context: DeveloperOptionsScreenViewModel.Context @ObservedObject var context: DeveloperOptionsScreenViewModel.Context
@State private var showConfetti = false @State private var showConfetti = false
@State private var elementCallBaseURLString = ""
var body: some View { var body: some View {
Form { Form {
@@ -52,10 +53,6 @@ struct DeveloperOptionsScreen: View {
Text("SwiftUI Timeline") Text("SwiftUI Timeline")
Text("Resets on reboot") Text("Resets on reboot")
} }
Toggle(isOn: $context.elementCallEnabled) {
Text("Element Call")
}
} }
Section("Room creation") { Section("Room creation") {
@@ -63,6 +60,25 @@ struct DeveloperOptionsScreen: View {
Text("User suggestions") Text("User suggestions")
} }
} }
Section("Element Call") {
TextField(context.elementCallBaseURL.absoluteString, text: $elementCallBaseURLString)
.submitLabel(.done)
.onSubmit {
guard let url = URL(string: elementCallBaseURLString) else {
return
}
context.elementCallBaseURL = url
}
.autocorrectionDisabled(true)
.autocapitalization(.none)
.foregroundColor(URL(string: elementCallBaseURLString) == nil ? .red : .primary)
Toggle(isOn: $context.elementCallUseEncryption) {
Text("Use encryption")
}
}
Section { Section {
Button { Button {
@@ -52,7 +52,7 @@ class ElementCallWidgetDriver: WidgetCapabilitiesProvider, ElementCallWidgetDriv
self.room = room self.room = room
} }
func start(baseURL: URL, clientID: String) async -> Result<URL, ElementCallWidgetDriverError> { func start(baseURL: URL, clientID: String, useEncryption: Bool) async -> Result<URL, ElementCallWidgetDriverError> {
guard let room = room as? Room else { guard let room = room as? Room else {
return .failure(.roomInvalid) return .failure(.roomInvalid)
} }
@@ -68,7 +68,7 @@ class ElementCallWidgetDriver: WidgetCapabilitiesProvider, ElementCallWidgetDriv
confineToRoom: true, confineToRoom: true,
font: nil, font: nil,
analyticsId: nil, analyticsId: nil,
encryption: .unencrypted)) else { encryption: useEncryption ? .perParticipantKeys : .unencrypted)) else {
return .failure(.failedBuildingWidgetSettings) return .failure(.failedBuildingWidgetSettings)
} }
@@ -36,7 +36,7 @@ protocol ElementCallWidgetDriverProtocol {
var messagePublisher: PassthroughSubject<String, Never> { get } var messagePublisher: PassthroughSubject<String, Never> { get }
var actions: AnyPublisher<ElementCallWidgetDriverAction, Never> { get } var actions: AnyPublisher<ElementCallWidgetDriverAction, Never> { get }
func start(baseURL: URL, clientID: String) async -> Result<URL, ElementCallWidgetDriverError> func start(baseURL: URL, clientID: String, useEncryption: Bool) async -> Result<URL, ElementCallWidgetDriverError>
func sendMessage(_ message: String) async -> Result<Bool, ElementCallWidgetDriverError> func sendMessage(_ message: String) async -> Result<Bool, ElementCallWidgetDriverError>
} }