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
+14 -11
View File
@@ -181,6 +181,16 @@ final class AppSettings {
}
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
@@ -240,17 +250,10 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.elementCallBaseURL, defaultValue: "https://call.element.io", storageType: .userDefaults(store))
var elementCallBaseURL: URL
// MARK: - Notifications
@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: - Users
/// Whether to hide the display name and avatar of ignored users as these may contain objectionable content.
let hideIgnoredUserProfiles = true
// MARK: - Maps
@@ -201,7 +201,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
}
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))
navigationStackCoordinator.push(coordinator)
}
@@ -18,7 +18,9 @@ import Combine
import SwiftUI
struct BlockedUsersScreenCoordinatorParameters {
let hideProfiles: Bool
let clientProxy: ClientProxyProtocol
let mediaProvider: MediaProviderProtocol
let userIndicatorController: UserIndicatorControllerProtocol
}
@@ -26,7 +28,9 @@ final class BlockedUsersScreenCoordinator: CoordinatorProtocol {
private let viewModel: BlockedUsersScreenViewModelProtocol
init(parameters: BlockedUsersScreenCoordinatorParameters) {
viewModel = BlockedUsersScreenViewModel(clientProxy: parameters.clientProxy,
viewModel = BlockedUsersScreenViewModel(hideProfiles: parameters.hideProfiles,
clientProxy: parameters.clientProxy,
mediaProvider: parameters.mediaProvider,
userIndicatorController: parameters.userIndicatorController)
}
@@ -17,7 +17,7 @@
import Foundation
struct BlockedUsersScreenViewState: BindableState {
var blockedUsers: [String]
var blockedUsers: [UserProfileProxy]
var processingUserID: String?
var bindings = BlockedUsersScreenViewStateBindings()
@@ -28,7 +28,7 @@ struct BlockedUsersScreenViewStateBindings {
}
enum BlockedUsersScreenViewAction {
case unblockUser(userID: String)
case unblockUser(UserProfileProxy)
}
enum BlockedUsersScreenViewStateAlertType: Hashable {
@@ -20,27 +20,30 @@ import SwiftUI
typealias BlockedUsersScreenViewModelType = StateStoreViewModel<BlockedUsersScreenViewState, BlockedUsersScreenViewAction>
class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsersScreenViewModelProtocol {
let hideProfiles: Bool
let clientProxy: ClientProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
init(clientProxy: ClientProxyProtocol,
init(hideProfiles: Bool,
clientProxy: ClientProxyProtocol,
mediaProvider: MediaProviderProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
self.hideProfiles = hideProfiles
self.clientProxy = clientProxy
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()
clientProxy.ignoredUsersPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] blockedUsers in
guard let self else { return }
if let blockedUsers {
hideLoadingIndicator()
state.blockedUsers = blockedUsers
}
guard let blockedUsers else { return }
Task { await self?.updateUsers(blockedUsers) }
}
.store(in: &cancellables)
}
@@ -49,12 +52,12 @@ class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsers
override func process(viewAction: BlockedUsersScreenViewAction) {
switch viewAction {
case .unblockUser(let userID):
case .unblockUser(let user):
state.bindings.alertInfo = .init(id: .unblock,
title: L10n.screenBlockedUsersUnblockAlertTitle,
message: L10n.screenBlockedUsersUnblockAlertDescription,
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))
}
@@ -66,12 +69,35 @@ class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsers
// 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()
state.processingUserID = userID
state.processingUserID = user.userID
Task {
if case .failure = await clientProxy.unignoreUser(userID) {
if case .failure = await clientProxy.unignoreUser(user.userID) {
state.bindings.alertInfo = .init(id: .error)
}
@@ -40,21 +40,21 @@ struct BlockedUsersScreen: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
Form {
ForEach(context.viewState.blockedUsers, id: \.self) { userID in
ListRow(label: .avatar(title: userID, icon: avatar(for: userID)),
details: .isWaiting(context.viewState.processingUserID == userID),
kind: .button(action: { context.send(viewAction: .unblockUser(userID: userID)) }))
ForEach(context.viewState.blockedUsers, id: \.self) { user in
ListRow(label: .avatar(title: user.displayName ?? user.userID, icon: avatar(for: user)),
details: .isWaiting(context.viewState.processingUserID == user.userID),
kind: .button(action: { context.send(viewAction: .unblockUser(user)) }))
}
}
}
}
private func avatar(for userID: String) -> some View {
LoadableAvatarImage(url: nil,
name: String(userID.dropFirst()),
contentID: userID,
private func avatar(for user: UserProfileProxy) -> some View {
LoadableAvatarImage(url: user.avatarURL,
name: user.displayName,
contentID: user.userID,
avatarSize: .user(on: .blockedUsers),
imageProvider: nil)
imageProvider: context.imageProvider)
.accessibilityHidden(true)
}
}
@@ -62,7 +62,9 @@ struct BlockedUsersScreen: View {
// MARK: - Previews
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())
static var previews: some View {
@@ -21,12 +21,33 @@ import XCTest
@MainActor
class BlockedUsersScreenViewModelTests: XCTestCase {
func testInitialState() {
func testInitialState() async throws {
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)
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(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