Skip to content

Commit

Permalink
Security and privacy part 1 (#3617)
Browse files Browse the repository at this point in the history
* added the security and settings button in details

* added content to the view

* added enable encryption alert

* updated preview tests and the UI

* removed wrong plists committed by mistake

* pr suggestions
  • Loading branch information
Velin92 authored Dec 13, 2024
1 parent e5da7eb commit b11fbc6
Show file tree
Hide file tree
Showing 20 changed files with 398 additions and 16 deletions.
40 changes: 40 additions & 0 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
return .mediaEventsTimeline(previousState: fromState)
case (.mediaEventsTimeline(let previousState), .dismissMediaEventsTimeline):
return previousState

case (.roomDetails, .presentSecurityAndPrivacyScreen):
return .securityAndPrivacy(previousState: fromState)
case (.securityAndPrivacy(let previousState), .dismissSecurityAndPrivacyScreen):
return previousState

default:
return nil
Expand Down Expand Up @@ -571,6 +576,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
Task { await self.startMediaEventsTimelineFlow() }
case (.mediaEventsTimeline, .dismissMediaEventsTimeline, .roomDetails):
break

case (.roomDetails, .presentSecurityAndPrivacyScreen, .securityAndPrivacy):
presentSecurityAndPrivacyScreen()
case (.securityAndPrivacy, .dismissSecurityAndPrivacyScreen, .roomDetails):
break

// Child flow
case (_, .startChildFlow(let roomID, let via, let entryPoint), .presentingChild):
Expand Down Expand Up @@ -849,6 +859,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.presentKnockRequestsListScreen)
case .presentMediaEventsTimeline:
stateMachine.tryEvent(.presentMediaEventsTimeline)
case .presentSecurityAndPrivacyScreen:
stateMachine.tryEvent(.presentSecurityAndPrivacyScreen)
}
}
.store(in: &cancellables)
Expand Down Expand Up @@ -1453,6 +1465,24 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
}
}

private func presentSecurityAndPrivacyScreen() {
let coordinator = SecurityAndPrivacyScreenCoordinator(parameters: .init(roomProxy: roomProxy))

coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }

switch action {
case .done:
break
}
}
.store(in: &cancellables)

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

// MARK: - Other flows

private func startChildFlow(for roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint) async {
Expand Down Expand Up @@ -1604,6 +1634,7 @@ private extension RoomFlowCoordinator {
case resolveSendFailure
case knockRequestsList(previousState: State)
case mediaEventsTimeline(previousState: State)
case securityAndPrivacy(previousState: State)

/// A child flow is in progress.
case presentingChild(childRoomID: String, previousState: State)
Expand Down Expand Up @@ -1687,6 +1718,9 @@ private extension RoomFlowCoordinator {

case presentMediaEventsTimeline
case dismissMediaEventsTimeline

case presentSecurityAndPrivacyScreen
case dismissSecurityAndPrivacyScreen
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum RoomDetailsScreenCoordinatorAction {
case presentPinnedEventsTimeline
case presentMediaEventsTimeline
case presentKnockingRequestsListScreen
case presentSecurityAndPrivacyScreen
}

final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
Expand Down Expand Up @@ -85,6 +86,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.presentMediaEventsTimeline)
case .displayKnockingRequests:
actionsSubject.send(.presentKnockingRequestsListScreen)
case .displaySecurityAndPrivacy:
actionsSubject.send(.presentSecurityAndPrivacyScreen)
}
}
.store(in: &cancellables)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum RoomDetailsScreenViewModelAction {
case displayPinnedEventsTimeline
case displayMediaEventsTimeline
case displayKnockingRequests
case displaySecurityAndPrivacy
}

// MARK: View
Expand Down Expand Up @@ -56,6 +57,10 @@ struct RoomDetailsScreenViewState: BindableState {
knockingEnabled && dmRecipient == nil && isKnockableRoom && (canInviteUsers || canKickUsers || canBanUsers)
}

var canSeeSecurityAndPrivacy: Bool {
knockingEnabled && dmRecipient == nil && canEditRolesOrPermissions
}

var mediaBrowserEnabled = false

var canEdit: Bool {
Expand Down Expand Up @@ -198,6 +203,7 @@ enum RoomDetailsScreenViewAction {
case processTapPolls
case toggleFavourite(isFavourite: Bool)
case processTapRolesAndPermissions
case processTapSecurityAndPrivacy
case processTapCall
case processTapPinnedEvents
case processTapMediaEvents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
actionsSubject.send(.displayMediaEventsTimeline)
case .processTapRequestsToJoin:
actionsSubject.send(.displayKnockingRequests)
case .processTapSecurityAndPrivacy:
actionsSubject.send(.displaySecurityAndPrivacy)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,14 @@ struct RoomDetailsScreen: View {
.onChange(of: context.isFavourite) { _, newValue in
context.send(viewAction: .toggleFavourite(isFavourite: newValue))
}

if context.viewState.canSeeSecurityAndPrivacy {
ListRow(label: .default(title: L10n.screenRoomDetailsSecurityAndPrivacyTitle,
icon: \.lock),
kind: .navigationLink {
context.send(viewAction: .processTapSecurityAndPrivacy)
})
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// 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

struct SecurityAndPrivacyScreenCoordinatorParameters {
let roomProxy: JoinedRoomProxyProtocol
}

enum SecurityAndPrivacyScreenCoordinatorAction {
case done

// Consider adding CustomStringConvertible conformance if the actions contain PII
}

final class SecurityAndPrivacyScreenCoordinator: CoordinatorProtocol {
private let parameters: SecurityAndPrivacyScreenCoordinatorParameters
private let viewModel: SecurityAndPrivacyScreenViewModelProtocol

private var cancellables = Set<AnyCancellable>()

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

init(parameters: SecurityAndPrivacyScreenCoordinatorParameters) {
self.parameters = parameters

viewModel = SecurityAndPrivacyScreenViewModel(roomProxy: parameters.roomProxy)
}

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

guard let self else { return }
switch action {
case .done:
actionsSubject.send(.done)
}
}
.store(in: &cancellables)
}

func toPresentable() -> AnyView {
AnyView(SecurityAndPrivacyScreen(context: viewModel.context))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// 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 SecurityAndPrivacyScreenViewModelAction {
case done
}

struct SecurityAndPrivacyScreenViewState: BindableState {
var bindings: SecurityAndPrivacyScreenViewStateBindings

var currentSettings: SecurityAndPrivacySettings

var hasChanges: Bool {
currentSettings != bindings.desiredSettings
}

init(accessType: SecurityAndPrivacyRoomAccessType,
isEncryptionEnabled: Bool) {
let settings = SecurityAndPrivacySettings(accessType: accessType, isEncryptionEnabled: isEncryptionEnabled)
currentSettings = settings
bindings = SecurityAndPrivacyScreenViewStateBindings(desiredSettings: settings)
}
}

struct SecurityAndPrivacyScreenViewStateBindings {
var desiredSettings: SecurityAndPrivacySettings
var alertInfo: AlertInfo<SecurityAndPrivacyAlertType>?
}

struct SecurityAndPrivacySettings: Equatable {
var accessType: SecurityAndPrivacyRoomAccessType
var isEncryptionEnabled: Bool
}

enum SecurityAndPrivacyRoomAccessType {
case inviteOnly
case askToJoin
case anyone
}

enum SecurityAndPrivacyAlertType {
case enableEncryption
}

enum SecurityAndPrivacyScreenViewAction {
case save
case tryUpdatingEncryption(Bool)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// 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 SecurityAndPrivacyScreenViewModelType = StateStoreViewModel<SecurityAndPrivacyScreenViewState, SecurityAndPrivacyScreenViewAction>

class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType, SecurityAndPrivacyScreenViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol

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

init(roomProxy: JoinedRoomProxyProtocol) {
self.roomProxy = roomProxy
super.init(initialViewState: SecurityAndPrivacyScreenViewState(accessType: roomProxy.infoPublisher.value.roomAccessType,
isEncryptionEnabled: roomProxy.isEncrypted))
}

// MARK: - Public

override func process(viewAction: SecurityAndPrivacyScreenViewAction) {
MXLog.info("View model: received view action: \(viewAction)")

switch viewAction {
case .save:
actionsSubject.send(.done)
case .tryUpdatingEncryption(let updatedValue):
if updatedValue {
state.bindings.alertInfo = .init(id: .enableEncryption,
title: L10n.screenSecurityAndPrivacyEnableEncryptionAlertTitle,
message: L10n.screenSecurityAndPrivacyEnableEncryptionAlertDescription,
primaryButton: .init(title: L10n.screenSecurityAndPrivacyEnableEncryptionAlertConfirmButtonTitle,
action: { [weak self] in self?.state.bindings.desiredSettings.isEncryptionEnabled = true }),
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
} else {
state.bindings.desiredSettings.isEncryptionEnabled = false
}
}
}
}

private extension RoomInfoProxy {
var roomAccessType: SecurityAndPrivacyRoomAccessType {
switch joinRule {
case .invite, .restricted:
return .inviteOnly
case .knock, .knockRestricted:
return .askToJoin
default:
return .anyone
}
}
}
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 SecurityAndPrivacyScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<SecurityAndPrivacyScreenViewModelAction, Never> { get }
var context: SecurityAndPrivacyScreenViewModelType.Context { get }
}
Loading

0 comments on commit b11fbc6

Please sign in to comment.