Add a flag to disable hidden profiles for ignored users. (#2892)

This commit is contained in:
Doug
2024-05-31 13:37:59 +01:00
committed by GitHub
parent ea08fad530
commit 6deedb593b
8 changed files with 99 additions and 40 deletions
+13 -10
View File
@@ -182,6 +182,16 @@ final class AppSettings {
let pushGatewayBaseURL: URL = "https://matrix.org/_matrix/push/v1/notify" let pushGatewayBaseURL: URL = "https://matrix.org/_matrix/push/v1/notify"
@UserPreference(key: UserDefaultsKeys.enableNotifications, defaultValue: true, storageType: .userDefaults(store))
var enableNotifications
@UserPreference(key: UserDefaultsKeys.enableInAppNotifications, defaultValue: true, storageType: .userDefaults(store))
var enableInAppNotifications
/// Tag describing which set of device specific rules a pusher executes.
@UserPreference(key: UserDefaultsKeys.pusherProfileTag, storageType: .userDefaults(store))
var pusherProfileTag: String?
// MARK: - Bug report // MARK: - Bug report
let bugReportServiceBaseURL: URL = "https://riot.im/bugreports" let bugReportServiceBaseURL: URL = "https://riot.im/bugreports"
@@ -240,17 +250,10 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.elementCallBaseURL, defaultValue: "https://call.element.io", storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.elementCallBaseURL, defaultValue: "https://call.element.io", storageType: .userDefaults(store))
var elementCallBaseURL: URL var elementCallBaseURL: URL
// MARK: - Notifications // MARK: - Users
@UserPreference(key: UserDefaultsKeys.enableNotifications, defaultValue: true, storageType: .userDefaults(store)) /// Whether to hide the display name and avatar of ignored users as these may contain objectionable content.
var enableNotifications let hideIgnoredUserProfiles = true
@UserPreference(key: UserDefaultsKeys.enableInAppNotifications, defaultValue: true, storageType: .userDefaults(store))
var enableInAppNotifications
/// Tag describing which set of device specific rules a pusher executes.
@UserPreference(key: UserDefaultsKeys.pusherProfileTag, storageType: .userDefaults(store))
var pusherProfileTag: String?
// MARK: - Maps // MARK: - Maps
@@ -201,7 +201,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
} }
private func presentBlockedUsersScreen() { private func presentBlockedUsersScreen() {
let coordinator = BlockedUsersScreenCoordinator(parameters: .init(clientProxy: parameters.userSession.clientProxy, let coordinator = BlockedUsersScreenCoordinator(parameters: .init(hideProfiles: parameters.appSettings.hideIgnoredUserProfiles,
clientProxy: parameters.userSession.clientProxy,
mediaProvider: parameters.userSession.mediaProvider,
userIndicatorController: parameters.userIndicatorController)) userIndicatorController: parameters.userIndicatorController))
navigationStackCoordinator.push(coordinator) navigationStackCoordinator.push(coordinator)
} }
@@ -18,7 +18,9 @@ import Combine
import SwiftUI import SwiftUI
struct BlockedUsersScreenCoordinatorParameters { struct BlockedUsersScreenCoordinatorParameters {
let hideProfiles: Bool
let clientProxy: ClientProxyProtocol let clientProxy: ClientProxyProtocol
let mediaProvider: MediaProviderProtocol
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
} }
@@ -26,7 +28,9 @@ final class BlockedUsersScreenCoordinator: CoordinatorProtocol {
private let viewModel: BlockedUsersScreenViewModelProtocol private let viewModel: BlockedUsersScreenViewModelProtocol
init(parameters: BlockedUsersScreenCoordinatorParameters) { init(parameters: BlockedUsersScreenCoordinatorParameters) {
viewModel = BlockedUsersScreenViewModel(clientProxy: parameters.clientProxy, viewModel = BlockedUsersScreenViewModel(hideProfiles: parameters.hideProfiles,
clientProxy: parameters.clientProxy,
mediaProvider: parameters.mediaProvider,
userIndicatorController: parameters.userIndicatorController) userIndicatorController: parameters.userIndicatorController)
} }
@@ -17,7 +17,7 @@
import Foundation import Foundation
struct BlockedUsersScreenViewState: BindableState { struct BlockedUsersScreenViewState: BindableState {
var blockedUsers: [String] var blockedUsers: [UserProfileProxy]
var processingUserID: String? var processingUserID: String?
var bindings = BlockedUsersScreenViewStateBindings() var bindings = BlockedUsersScreenViewStateBindings()
@@ -28,7 +28,7 @@ struct BlockedUsersScreenViewStateBindings {
} }
enum BlockedUsersScreenViewAction { enum BlockedUsersScreenViewAction {
case unblockUser(userID: String) case unblockUser(UserProfileProxy)
} }
enum BlockedUsersScreenViewStateAlertType: Hashable { enum BlockedUsersScreenViewStateAlertType: Hashable {
@@ -20,27 +20,30 @@ import SwiftUI
typealias BlockedUsersScreenViewModelType = StateStoreViewModel<BlockedUsersScreenViewState, BlockedUsersScreenViewAction> typealias BlockedUsersScreenViewModelType = StateStoreViewModel<BlockedUsersScreenViewState, BlockedUsersScreenViewAction>
class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsersScreenViewModelProtocol { class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsersScreenViewModelProtocol {
let hideProfiles: Bool
let clientProxy: ClientProxyProtocol let clientProxy: ClientProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
init(clientProxy: ClientProxyProtocol, init(hideProfiles: Bool,
clientProxy: ClientProxyProtocol,
mediaProvider: MediaProviderProtocol,
userIndicatorController: UserIndicatorControllerProtocol) { userIndicatorController: UserIndicatorControllerProtocol) {
self.hideProfiles = hideProfiles
self.clientProxy = clientProxy self.clientProxy = clientProxy
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
super.init(initialViewState: BlockedUsersScreenViewState(blockedUsers: clientProxy.ignoredUsersPublisher.value ?? [])) let ignoredUsers = clientProxy.ignoredUsersPublisher.value?.map { UserProfileProxy(userID: $0) }
super.init(initialViewState: BlockedUsersScreenViewState(blockedUsers: ignoredUsers ?? []),
imageProvider: mediaProvider)
showLoadingIndicator() showLoadingIndicator()
clientProxy.ignoredUsersPublisher clientProxy.ignoredUsersPublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] blockedUsers in .sink { [weak self] blockedUsers in
guard let self else { return } guard let blockedUsers else { return }
Task { await self?.updateUsers(blockedUsers) }
if let blockedUsers {
hideLoadingIndicator()
state.blockedUsers = blockedUsers
}
} }
.store(in: &cancellables) .store(in: &cancellables)
} }
@@ -49,12 +52,12 @@ class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsers
override func process(viewAction: BlockedUsersScreenViewAction) { override func process(viewAction: BlockedUsersScreenViewAction) {
switch viewAction { switch viewAction {
case .unblockUser(let userID): case .unblockUser(let user):
state.bindings.alertInfo = .init(id: .unblock, state.bindings.alertInfo = .init(id: .unblock,
title: L10n.screenBlockedUsersUnblockAlertTitle, title: L10n.screenBlockedUsersUnblockAlertTitle,
message: L10n.screenBlockedUsersUnblockAlertDescription, message: L10n.screenBlockedUsersUnblockAlertDescription,
primaryButton: .init(title: L10n.screenBlockedUsersUnblockAlertAction, role: .destructive) { [weak self] in primaryButton: .init(title: L10n.screenBlockedUsersUnblockAlertAction, role: .destructive) { [weak self] in
self?.unblockUser(userID) self?.unblockUser(user)
}, },
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil)) secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
} }
@@ -66,12 +69,35 @@ class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsers
// MARK: - Private // MARK: - Private
private func unblockUser(_ userID: String) { private func updateUsers(_ blockedUsers: [String]) async {
defer { hideLoadingIndicator() }
if hideProfiles {
state.blockedUsers = blockedUsers.map { UserProfileProxy(userID: $0) }
} else {
state.blockedUsers = await withTaskGroup(of: UserProfileProxy.self) { group in
for userID in blockedUsers {
group.addTask {
switch await self.clientProxy.profile(for: userID) {
case .success(let profile): profile
case .failure: UserProfileProxy(userID: userID)
}
}
}
return await group.reduce(into: []) { partialResult, profile in
partialResult.append(profile)
}
}
}
}
private func unblockUser(_ user: UserProfileProxy) {
showLoadingIndicator() showLoadingIndicator()
state.processingUserID = userID state.processingUserID = user.userID
Task { Task {
if case .failure = await clientProxy.unignoreUser(userID) { if case .failure = await clientProxy.unignoreUser(user.userID) {
state.bindings.alertInfo = .init(id: .error) state.bindings.alertInfo = .init(id: .error)
} }
@@ -40,21 +40,21 @@ struct BlockedUsersScreen: View {
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
} else { } else {
Form { Form {
ForEach(context.viewState.blockedUsers, id: \.self) { userID in ForEach(context.viewState.blockedUsers, id: \.self) { user in
ListRow(label: .avatar(title: userID, icon: avatar(for: userID)), ListRow(label: .avatar(title: user.displayName ?? user.userID, icon: avatar(for: user)),
details: .isWaiting(context.viewState.processingUserID == userID), details: .isWaiting(context.viewState.processingUserID == user.userID),
kind: .button(action: { context.send(viewAction: .unblockUser(userID: userID)) })) kind: .button(action: { context.send(viewAction: .unblockUser(user)) }))
} }
} }
} }
} }
private func avatar(for userID: String) -> some View { private func avatar(for user: UserProfileProxy) -> some View {
LoadableAvatarImage(url: nil, LoadableAvatarImage(url: user.avatarURL,
name: String(userID.dropFirst()), name: user.displayName,
contentID: userID, contentID: user.userID,
avatarSize: .user(on: .blockedUsers), avatarSize: .user(on: .blockedUsers),
imageProvider: nil) imageProvider: context.imageProvider)
.accessibilityHidden(true) .accessibilityHidden(true)
} }
} }
@@ -62,7 +62,9 @@ struct BlockedUsersScreen: View {
// MARK: - Previews // MARK: - Previews
struct BlockedUsersScreen_Previews: PreviewProvider, TestablePreview { struct BlockedUsersScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = BlockedUsersScreenViewModel(clientProxy: ClientProxyMock(.init(userID: RoomMemberProxyMock.mockMe.userID)), static let viewModel = BlockedUsersScreenViewModel(hideProfiles: true,
clientProxy: ClientProxyMock(.init(userID: RoomMemberProxyMock.mockMe.userID)),
mediaProvider: MockMediaProvider(),
userIndicatorController: UserIndicatorControllerMock()) userIndicatorController: UserIndicatorControllerMock())
static var previews: some View { static var previews: some View {
@@ -21,12 +21,33 @@ import XCTest
@MainActor @MainActor
class BlockedUsersScreenViewModelTests: XCTestCase { class BlockedUsersScreenViewModelTests: XCTestCase {
func testInitialState() { func testInitialState() async throws {
let clientProxy = ClientProxyMock(.init(userID: RoomMemberProxyMock.mockMe.userID)) let clientProxy = ClientProxyMock(.init(userID: RoomMemberProxyMock.mockMe.userID))
let viewModel = BlockedUsersScreenViewModel(clientProxy: clientProxy, let viewModel = BlockedUsersScreenViewModel(hideProfiles: true,
clientProxy: clientProxy,
mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController) userIndicatorController: ServiceLocator.shared.userIndicatorController)
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { $0.blockedUsers.contains(where: { $0.displayName != nil }) }
try await deferred.fulfill()
XCTAssertFalse(viewModel.context.viewState.blockedUsers.isEmpty) XCTAssertFalse(viewModel.context.viewState.blockedUsers.isEmpty)
XCTAssertFalse(clientProxy.profileForCalled)
}
func testProfiles() async throws {
let clientProxy = ClientProxyMock(.init(userID: RoomMemberProxyMock.mockMe.userID))
let viewModel = BlockedUsersScreenViewModel(hideProfiles: false,
clientProxy: clientProxy,
mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController)
let deferred = deferFulfillment(viewModel.context.$viewState) { $0.blockedUsers.contains(where: { $0.displayName != nil }) }
try await deferred.fulfill()
XCTAssertFalse(viewModel.context.viewState.blockedUsers.isEmpty)
XCTAssertTrue(clientProxy.profileForCalled)
} }
} }
+1
View File
@@ -0,0 +1 @@
Add a flag for Forks to disable hidden profiles for ignored users