Filters, Mark Unread/Read/Favourites FF removals + code and tests clean up (#2541)

This commit is contained in:
Mauro 2024-03-07 17:56:09 +01:00 committed by GitHub
parent da0fcfe052
commit c66ddfb71e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
69 changed files with 105 additions and 374 deletions

View File

@ -4,11 +4,12 @@ schemes:
- IntegrationTests - IntegrationTests
- UITests - UITests
- UnitTests - UnitTests
- PreviewTests
targets: targets:
- ElementX - ElementX
- IntegrationTests - IntegrationTests
- NSE - NSE
# - NCE - PreviewTests
- UITests - UITests
- UnitTests - UnitTests
report_exclude: report_exclude:

View File

@ -275,21 +275,6 @@ final class AppSettings {
// MARK: - Feature Flags // MARK: - Feature Flags
@UserPreference(key: UserDefaultsKeys.userSuggestionsEnabled, defaultValue: false, storageType: .volatile)
var userSuggestionsEnabled
@UserPreference(key: UserDefaultsKeys.mentionsBadgeEnabled, defaultValue: true, storageType: .userDefaults(store))
var mentionsBadgeEnabled
@UserPreference(key: UserDefaultsKeys.roomListFiltersEnabled, defaultValue: false, storageType: .userDefaults(store))
var roomListFiltersEnabled
@UserPreference(key: UserDefaultsKeys.markAsUnreadEnabled, defaultValue: false, storageType: .userDefaults(store))
var markAsUnreadEnabled
@UserPreference(key: UserDefaultsKeys.markAsFavouriteEnabled, defaultValue: false, storageType: .userDefaults(store))
var markAsFavouriteEnabled
@UserPreference(key: UserDefaultsKeys.roomModerationEnabled, defaultValue: false, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.roomModerationEnabled, defaultValue: false, storageType: .userDefaults(store))
var roomModerationEnabled var roomModerationEnabled

View File

@ -3787,23 +3787,6 @@ class UserDiscoveryServiceMock: UserDiscoveryServiceProtocol {
return searchProfilesWithReturnValue return searchProfilesWithReturnValue
} }
} }
//MARK: - fetchSuggestions
var fetchSuggestionsCallsCount = 0
var fetchSuggestionsCalled: Bool {
return fetchSuggestionsCallsCount > 0
}
var fetchSuggestionsReturnValue: Result<[UserProfileProxy], UserDiscoveryErrorType>!
var fetchSuggestionsClosure: (() async -> Result<[UserProfileProxy], UserDiscoveryErrorType>)?
func fetchSuggestions() async -> Result<[UserProfileProxy], UserDiscoveryErrorType> {
fetchSuggestionsCallsCount += 1
if let fetchSuggestionsClosure = fetchSuggestionsClosure {
return await fetchSuggestionsClosure()
} else {
return fetchSuggestionsReturnValue
}
}
} }
class UserIndicatorControllerMock: UserIndicatorControllerProtocol { class UserIndicatorControllerMock: UserIndicatorControllerProtocol {
var window: UIWindow? var window: UIWindow?

View File

@ -22,18 +22,9 @@ struct BlockedUsersScreenCoordinatorParameters {
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
} }
enum BlockedUsersScreenCoordinatorAction { }
final class BlockedUsersScreenCoordinator: CoordinatorProtocol { final class BlockedUsersScreenCoordinator: CoordinatorProtocol {
private let viewModel: BlockedUsersScreenViewModelProtocol private let viewModel: BlockedUsersScreenViewModelProtocol
private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<BlockedUsersScreenCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<BlockedUsersScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: BlockedUsersScreenCoordinatorParameters) { init(parameters: BlockedUsersScreenCoordinatorParameters) {
viewModel = BlockedUsersScreenViewModel(clientProxy: parameters.clientProxy, viewModel = BlockedUsersScreenViewModel(clientProxy: parameters.clientProxy,
userIndicatorController: parameters.userIndicatorController) userIndicatorController: parameters.userIndicatorController)

View File

@ -16,8 +16,6 @@
import Foundation import Foundation
enum BlockedUsersScreenViewModelAction { }
struct BlockedUsersScreenViewState: BindableState { struct BlockedUsersScreenViewState: BindableState {
var blockedUsers: [String] var blockedUsers: [String]
var processingUserID: String? var processingUserID: String?

View File

@ -22,11 +22,6 @@ typealias BlockedUsersScreenViewModelType = StateStoreViewModel<BlockedUsersScre
class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsersScreenViewModelProtocol { class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsersScreenViewModelProtocol {
let clientProxy: ClientProxyProtocol let clientProxy: ClientProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
private let actionsSubject: PassthroughSubject<BlockedUsersScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<BlockedUsersScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(clientProxy: ClientProxyProtocol, init(clientProxy: ClientProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol) { userIndicatorController: UserIndicatorControllerProtocol) {

View File

@ -18,7 +18,6 @@ import Combine
@MainActor @MainActor
protocol BlockedUsersScreenViewModelProtocol { protocol BlockedUsersScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<BlockedUsersScreenViewModelAction, Never> { get }
var context: BlockedUsersScreenViewModelType.Context { get } var context: BlockedUsersScreenViewModelType.Context { get }
func stop() func stop()

View File

@ -89,10 +89,6 @@ struct HomeScreenViewState: BindableState {
var rooms: [HomeScreenRoom] = [] var rooms: [HomeScreenRoom] = []
var roomListMode: HomeScreenRoomListMode = .skeletons var roomListMode: HomeScreenRoomListMode = .skeletons
var areFiltersEnabled = false
var markAsUnreadEnabled = false
var markAsFavouriteEnabled = false
var hasPendingInvitations = false var hasPendingInvitations = false
var hasUnreadPendingInvitations = false var hasUnreadPendingInvitations = false
@ -120,11 +116,7 @@ struct HomeScreenViewState: BindableState {
} }
var shouldShowEmptyFilterState: Bool { var shouldShowEmptyFilterState: Bool {
shouldShowFilters && bindings.filtersState.isFiltering && visibleRooms.isEmpty !bindings.isSearchFieldFocused && bindings.filtersState.isFiltering && visibleRooms.isEmpty
}
var shouldShowFilters: Bool {
areFiltersEnabled && !bindings.isSearchFieldFocused
} }
} }

View File

@ -100,28 +100,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
.weakAssign(to: \.state.selectedRoomID, on: self) .weakAssign(to: \.state.selectedRoomID, on: self)
.store(in: &cancellables) .store(in: &cancellables)
appSettings.$roomListFiltersEnabled
.sink { [weak self] value in
guard let self else {
return
}
if !value {
state.areFiltersEnabled = false
state.bindings.filtersState.clearFilters()
} else {
state.areFiltersEnabled = true
}
}
.store(in: &cancellables)
appSettings.$markAsUnreadEnabled
.weakAssign(to: \.state.markAsUnreadEnabled, on: self)
.store(in: &cancellables)
appSettings.$markAsFavouriteEnabled
.weakAssign(to: \.state.markAsFavouriteEnabled, on: self)
.store(in: &cancellables)
appSettings.$hideUnreadMessagesBadge appSettings.$hideUnreadMessagesBadge
.sink { [weak self] _ in self?.updateRooms() } .sink { [weak self] _ in self?.updateRooms() }
.store(in: &cancellables) .store(in: &cancellables)
@ -235,7 +213,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
if state.bindings.isSearchFieldFocused { if state.bindings.isSearchFieldFocused {
roomSummaryProvider?.setFilter(.search(query: state.bindings.searchQuery)) roomSummaryProvider?.setFilter(.search(query: state.bindings.searchQuery))
} else { } else {
roomSummaryProvider?.setFilter(.all(filters: state.areFiltersEnabled ? state.bindings.filtersState.activeFilters.set : [])) roomSummaryProvider?.setFilter(.all(filters: state.bindings.filtersState.activeFilters.set))
} }
} }
} }

View File

@ -89,10 +89,6 @@ struct RoomListFiltersState {
return availableFilters.elements return availableFilters.elements
} }
var orderedFilters: [RoomListFilter] {
activeFilters.elements + availableFilters
}
var isFiltering: Bool { var isFiltering: Bool {
!activeFilters.isEmpty !activeFilters.isEmpty
} }

View File

@ -53,36 +53,24 @@ struct HomeScreenContent: View {
.layoutPriority(1) .layoutPriority(1)
} }
case .rooms: case .rooms:
if context.viewState.areFiltersEnabled { // Showing empty views in pinned headers makes the room list spasm when reaching the top
// Showing empty views in pinned headers makes the room list spasm when reaching the top LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) {
LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) { Section {
Section { if context.viewState.shouldShowEmptyFilterState {
if context.viewState.shouldShowEmptyFilterState { RoomListFiltersEmptyStateView(state: context.filtersState)
RoomListFiltersEmptyStateView(state: context.filtersState) .frame(height: geometry.size.height - topSectionFrame.height)
.frame(height: geometry.size.height - topSectionFrame.height) } else {
} else { HomeScreenRoomList(context: context)
HomeScreenRoomList(context: context)
}
} header: {
topSection
.readFrame($topSectionFrame)
} }
} header: {
topSection
.readFrame($topSectionFrame)
} }
.isSearching($context.isSearchFieldFocused)
.searchable(text: $context.searchQuery)
.compoundSearchField()
.disableAutocorrection(true)
} else {
topSection
LazyVStack(spacing: 0) {
HomeScreenRoomList(context: context)
.isSearching($context.isSearchFieldFocused)
}
.searchable(text: $context.searchQuery)
.compoundSearchField()
.disableAutocorrection(true)
} }
.isSearching($context.isSearchFieldFocused)
.searchable(text: $context.searchQuery)
.compoundSearchField()
.disableAutocorrection(true)
case .migration: case .migration:
EmptyView() EmptyView()
} }
@ -121,7 +109,7 @@ struct HomeScreenContent: View {
/// The session verification banner and invites button if either are needed. /// The session verification banner and invites button if either are needed.
private var topSection: some View { private var topSection: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
if context.viewState.shouldShowFilters { if !context.isSearchFieldFocused {
filters filters
} }

View File

@ -39,35 +39,31 @@ struct HomeScreenRoomList: View {
HomeScreenRoomCell(room: room, context: context, isSelected: isSelected) HomeScreenRoomCell(room: room, context: context, isSelected: isSelected)
.contextMenu { .contextMenu {
if context.viewState.markAsUnreadEnabled { if room.badges.isDotShown {
if room.badges.isDotShown { Button {
Button { context.send(viewAction: .markRoomAsRead(roomIdentifier: room.id))
context.send(viewAction: .markRoomAsRead(roomIdentifier: room.id)) } label: {
} label: { Text(L10n.screenRoomlistMarkAsRead)
Text(L10n.screenRoomlistMarkAsRead) }
} } else {
} else { Button {
Button { context.send(viewAction: .markRoomAsUnread(roomIdentifier: room.id))
context.send(viewAction: .markRoomAsUnread(roomIdentifier: room.id)) } label: {
} label: { Text(L10n.screenRoomlistMarkAsUnread)
Text(L10n.screenRoomlistMarkAsUnread)
}
} }
} }
if context.viewState.markAsFavouriteEnabled { if room.isFavourite {
if room.isFavourite { Button {
Button { context.send(viewAction: .markRoomAsFavourite(roomIdentifier: room.id, isFavourite: false))
context.send(viewAction: .markRoomAsFavourite(roomIdentifier: room.id, isFavourite: false)) } label: {
} label: { Label(L10n.commonFavourited, icon: \.favouriteSolid)
Label(L10n.commonFavourited, icon: \.favouriteSolid) }
} } else {
} else { Button {
Button { context.send(viewAction: .markRoomAsFavourite(roomIdentifier: room.id, isFavourite: true))
context.send(viewAction: .markRoomAsFavourite(roomIdentifier: room.id, isFavourite: true)) } label: {
} label: { Label(L10n.commonFavourite, icon: \.favourite)
Label(L10n.commonFavourite, icon: \.favourite)
}
} }
} }

View File

@ -46,7 +46,6 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol {
roomType: parameters.roomType, roomType: parameters.roomType,
mediaProvider: parameters.mediaProvider, mediaProvider: parameters.mediaProvider,
userDiscoveryService: parameters.userDiscoveryService, userDiscoveryService: parameters.userDiscoveryService,
appSettings: ServiceLocator.shared.settings,
userIndicatorController: parameters.userIndicatorController) userIndicatorController: parameters.userIndicatorController)
} }

View File

@ -23,7 +23,6 @@ typealias InviteUsersScreenViewModelType = StateStoreViewModel<InviteUsersScreen
class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScreenViewModelProtocol { class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScreenViewModelProtocol {
private let roomType: InviteUsersScreenRoomType private let roomType: InviteUsersScreenRoomType
private let userDiscoveryService: UserDiscoveryServiceProtocol private let userDiscoveryService: UserDiscoveryServiceProtocol
private let appSettings: AppSettings
private let userIndicatorController: UserIndicatorControllerProtocol private let userIndicatorController: UserIndicatorControllerProtocol
private let actionsSubject: PassthroughSubject<InviteUsersScreenViewModelAction, Never> = .init() private let actionsSubject: PassthroughSubject<InviteUsersScreenViewModelAction, Never> = .init()
@ -36,11 +35,9 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
roomType: InviteUsersScreenRoomType, roomType: InviteUsersScreenRoomType,
mediaProvider: MediaProviderProtocol, mediaProvider: MediaProviderProtocol,
userDiscoveryService: UserDiscoveryServiceProtocol, userDiscoveryService: UserDiscoveryServiceProtocol,
appSettings: AppSettings,
userIndicatorController: UserIndicatorControllerProtocol) { userIndicatorController: UserIndicatorControllerProtocol) {
self.roomType = roomType self.roomType = roomType
self.userDiscoveryService = userDiscoveryService self.userDiscoveryService = userDiscoveryService
self.appSettings = appSettings
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
super.init(initialViewState: InviteUsersScreenViewState(selectedUsers: selectedUsers.value, isCreatingRoom: roomType.isCreatingRoom), imageProvider: mediaProvider) super.init(initialViewState: InviteUsersScreenViewState(selectedUsers: selectedUsers.value, isCreatingRoom: roomType.isCreatingRoom), imageProvider: mediaProvider)
@ -130,7 +127,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
private func fetchUsers() { private func fetchUsers() {
guard searchQuery.count >= 3 else { guard searchQuery.count >= 3 else {
fetchSuggestions() state.usersSection = .init(type: .suggestions, users: [])
return return
} }
@ -142,20 +139,6 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
} }
} }
private func fetchSuggestions() {
guard appSettings.userSuggestionsEnabled else {
state.usersSection = .init(type: .suggestions, users: [])
return
}
state.isSearching = true
fetchUsersTask = Task {
let result = await userDiscoveryService.fetchSuggestions()
guard !Task.isCancelled else { return }
handleResult(for: .suggestions, result: result)
}
}
private func handleResult(for sectionType: UserDiscoverySectionType, result: Result<[UserProfileProxy], UserDiscoveryErrorType>) { private func handleResult(for sectionType: UserDiscoverySectionType, result: Result<[UserProfileProxy], UserDiscoveryErrorType>) {
state.isSearching = false state.isSearching = false

View File

@ -159,13 +159,11 @@ struct InviteUsersScreen: View {
struct InviteUsersScreen_Previews: PreviewProvider, TestablePreview { struct InviteUsersScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = { static let viewModel = {
let userDiscoveryService = UserDiscoveryServiceMock() let userDiscoveryService = UserDiscoveryServiceMock()
userDiscoveryService.fetchSuggestionsReturnValue = .success([.mockAlice])
userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice]) userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice])
return InviteUsersScreenViewModel(selectedUsers: .init([]), return InviteUsersScreenViewModel(selectedUsers: .init([]),
roomType: .draft, roomType: .draft,
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
userDiscoveryService: userDiscoveryService, userDiscoveryService: userDiscoveryService,
appSettings: ServiceLocator.shared.settings,
userIndicatorController: UserIndicatorControllerMock()) userIndicatorController: UserIndicatorControllerMock())
}() }()

View File

@ -47,15 +47,8 @@ protocol DeveloperOptionsProtocol: AnyObject {
var logLevel: TracingConfiguration.LogLevel { get set } var logLevel: TracingConfiguration.LogLevel { get set }
var otlpTracingEnabled: Bool { get set } var otlpTracingEnabled: Bool { get set }
var shouldCollapseRoomStateEvents: Bool { get set } var shouldCollapseRoomStateEvents: Bool { get set }
var userSuggestionsEnabled: Bool { get set }
var mentionsBadgeEnabled: Bool { get set }
var roomListFiltersEnabled: Bool { get set }
var hideUnreadMessagesBadge: Bool { get set } var hideUnreadMessagesBadge: Bool { get set }
var markAsUnreadEnabled: Bool { get set }
var markAsFavouriteEnabled: Bool { get set }
var roomModerationEnabled: Bool { get set } var roomModerationEnabled: Bool { get set }
var elementCallBaseURL: URL { get set } var elementCallBaseURL: URL { get set }
} }

View File

@ -41,32 +41,7 @@ struct DeveloperOptionsScreen: View {
} }
} }
Section("Room creation") {
Toggle(isOn: $context.userSuggestionsEnabled) {
Text("User suggestions")
}
}
Section("Mentions") {
Toggle(isOn: $context.mentionsBadgeEnabled) {
Text("Mentions badge")
Text("Requires app reboot")
}
}
Section("Room List") { Section("Room List") {
Toggle(isOn: $context.roomListFiltersEnabled) {
Text("Show filters")
}
Toggle(isOn: $context.markAsUnreadEnabled) {
Text("Mark as unread")
}
Toggle(isOn: $context.markAsFavouriteEnabled) {
Text("Mark as favourite")
}
Toggle(isOn: $context.hideUnreadMessagesBadge) { Toggle(isOn: $context.hideUnreadMessagesBadge) {
Text("Hide grey dots") Text("Hide grey dots")
} }

View File

@ -54,7 +54,6 @@ final class StartChatScreenCoordinator: CoordinatorProtocol {
self.parameters = parameters self.parameters = parameters
viewModel = StartChatScreenViewModel(userSession: parameters.userSession, viewModel = StartChatScreenViewModel(userSession: parameters.userSession,
userSuggestionsEnabled: ServiceLocator.shared.settings.userSuggestionsEnabled,
analytics: ServiceLocator.shared.analytics, analytics: ServiceLocator.shared.analytics,
userIndicatorController: parameters.userIndicatorController, userIndicatorController: parameters.userIndicatorController,
userDiscoveryService: parameters.userDiscoveryService) userDiscoveryService: parameters.userDiscoveryService)

View File

@ -21,7 +21,6 @@ typealias StartChatScreenViewModelType = StateStoreViewModel<StartChatScreenView
class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenViewModelProtocol { class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenViewModelProtocol {
private let userSession: UserSessionProtocol private let userSession: UserSessionProtocol
private let userSuggestionsEnabled: Bool
private let analytics: AnalyticsService private let analytics: AnalyticsService
private let userIndicatorController: UserIndicatorControllerProtocol private let userIndicatorController: UserIndicatorControllerProtocol
private let userDiscoveryService: UserDiscoveryServiceProtocol private let userDiscoveryService: UserDiscoveryServiceProtocol
@ -33,12 +32,10 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie
} }
init(userSession: UserSessionProtocol, init(userSession: UserSessionProtocol,
userSuggestionsEnabled: Bool,
analytics: AnalyticsService, analytics: AnalyticsService,
userIndicatorController: UserIndicatorControllerProtocol, userIndicatorController: UserIndicatorControllerProtocol,
userDiscoveryService: UserDiscoveryServiceProtocol) { userDiscoveryService: UserDiscoveryServiceProtocol) {
self.userSession = userSession self.userSession = userSession
self.userSuggestionsEnabled = userSuggestionsEnabled
self.analytics = analytics self.analytics = analytics
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
self.userDiscoveryService = userDiscoveryService self.userDiscoveryService = userDiscoveryService
@ -105,7 +102,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie
private func fetchUsers() { private func fetchUsers() {
guard searchQuery.count >= 3 else { guard searchQuery.count >= 3 else {
fetchSuggestions() state.usersSection = .init(type: .suggestions, users: [])
return return
} }
fetchUsersTask = Task { fetchUsersTask = Task {
@ -115,18 +112,6 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie
} }
} }
private func fetchSuggestions() {
guard userSuggestionsEnabled else {
state.usersSection = .init(type: .suggestions, users: [])
return
}
fetchUsersTask = Task {
let result = await userDiscoveryService.fetchSuggestions()
guard !Task.isCancelled else { return }
handleResult(for: .suggestions, result: result)
}
}
private func handleResult(for sectionType: UserDiscoverySectionType, result: Result<[UserProfileProxy], UserDiscoveryErrorType>) { private func handleResult(for sectionType: UserDiscoverySectionType, result: Result<[UserProfileProxy], UserDiscoveryErrorType>) {
switch result { switch result {
case .success(let users): case .success(let users):

View File

@ -133,10 +133,8 @@ struct StartChatScreen_Previews: PreviewProvider, TestablePreview {
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock()) voiceMessageMediaManager: VoiceMessageMediaManagerMock())
let userDiscoveryService = UserDiscoveryServiceMock() let userDiscoveryService = UserDiscoveryServiceMock()
userDiscoveryService.fetchSuggestionsReturnValue = .success([.mockAlice])
userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice]) userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice])
let viewModel = StartChatScreenViewModel(userSession: userSession, let viewModel = StartChatScreenViewModel(userSession: userSession,
userSuggestionsEnabled: true,
analytics: ServiceLocator.shared.analytics, analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
userDiscoveryService: userDiscoveryService) userDiscoveryService: userDiscoveryService)

View File

@ -575,25 +575,19 @@ class ClientProxy: ClientProxyProtocol {
eventStringBuilder: eventStringBuilder, eventStringBuilder: eventStringBuilder,
name: "AllRooms", name: "AllRooms",
shouldUpdateVisibleRange: true, shouldUpdateVisibleRange: true,
notificationSettings: notificationSettings, notificationSettings: notificationSettings)
backgroundTaskService: backgroundTaskService,
appSettings: appSettings)
try await roomSummaryProvider?.setRoomList(roomListService.allRooms()) try await roomSummaryProvider?.setRoomList(roomListService.allRooms())
alternateRoomSummaryProvider = RoomSummaryProvider(roomListService: roomListService, alternateRoomSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
eventStringBuilder: eventStringBuilder, eventStringBuilder: eventStringBuilder,
name: "MessageForwarding", name: "MessageForwarding",
notificationSettings: notificationSettings, notificationSettings: notificationSettings)
backgroundTaskService: backgroundTaskService,
appSettings: appSettings)
try await alternateRoomSummaryProvider?.setRoomList(roomListService.allRooms()) try await alternateRoomSummaryProvider?.setRoomList(roomListService.allRooms())
inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService, inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
eventStringBuilder: eventStringBuilder, eventStringBuilder: eventStringBuilder,
name: "Invites", name: "Invites",
notificationSettings: notificationSettings, notificationSettings: notificationSettings)
backgroundTaskService: backgroundTaskService,
appSettings: appSettings)
try await inviteSummaryProvider?.setRoomList(roomListService.invites()) try await inviteSummaryProvider?.setRoomList(roomListService.invites())
self.syncService = syncService self.syncService = syncService

View File

@ -18,16 +18,9 @@ import Foundation
import MatrixRustSDK import MatrixRustSDK
final class RoomMemberProxy: RoomMemberProxyProtocol { final class RoomMemberProxy: RoomMemberProxyProtocol {
private let backgroundTaskService: BackgroundTaskServiceProtocol
private let member: RoomMember private let member: RoomMember
private let backgroundAccountDataTaskName = "SendAccountDataEvent" init(member: RoomMember) {
private var sendAccountDataEventBackgroundTask: BackgroundTaskProtocol?
private let userInitiatedDispatchQueue = DispatchQueue(label: "io.element.elementx.roommemberproxy.userinitiated", qos: .userInitiated)
init(member: RoomMember, backgroundTaskService: BackgroundTaskServiceProtocol) {
self.backgroundTaskService = backgroundTaskService
self.member = member self.member = member
} }

View File

@ -17,11 +17,6 @@
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
enum RoomMemberProxyError: Error {
case ignoreUserFailed
case unignoreUserFailed
}
// sourcery: AutoMockable // sourcery: AutoMockable
protocol RoomMemberProxyProtocol: AnyObject { protocol RoomMemberProxyProtocol: AnyObject {
var userID: String { get } var userID: String { get }

View File

@ -195,9 +195,7 @@ class RoomProxy: RoomProxyProtocol {
do { do {
let membersNoSyncIterator = try await room.membersNoSync() let membersNoSyncIterator = try await room.membersNoSync()
if let members = membersNoSyncIterator.nextChunk(chunkSize: membersNoSyncIterator.len()) { if let members = membersNoSyncIterator.nextChunk(chunkSize: membersNoSyncIterator.len()) {
membersSubject.value = members.map { membersSubject.value = members.map(RoomMemberProxy.init)
RoomMemberProxy(member: $0, backgroundTaskService: self.backgroundTaskService)
}
} }
} catch { } catch {
MXLog.error("[RoomProxy] Failed to update members using no sync API: \(error)") MXLog.error("[RoomProxy] Failed to update members using no sync API: \(error)")
@ -207,9 +205,7 @@ class RoomProxy: RoomProxyProtocol {
// Then we update members using the sync API, this is slower but will get us the latest members // Then we update members using the sync API, this is slower but will get us the latest members
let membersIterator = try await room.members() let membersIterator = try await room.members()
if let members = membersIterator.nextChunk(chunkSize: membersIterator.len()) { if let members = membersIterator.nextChunk(chunkSize: membersIterator.len()) {
membersSubject.value = members.map { membersSubject.value = members.map(RoomMemberProxy.init)
RoomMemberProxy(member: $0, backgroundTaskService: self.backgroundTaskService)
}
} }
} catch { } catch {
MXLog.error("[RoomProxy] Failed to update members using sync API: \(error)") MXLog.error("[RoomProxy] Failed to update members using sync API: \(error)")
@ -228,7 +224,7 @@ class RoomProxy: RoomProxyProtocol {
do { do {
let member = try await room.member(userId: userID) let member = try await room.member(userId: userID)
return .success(RoomMemberProxy(member: member, backgroundTaskService: backgroundTaskService)) return .success(RoomMemberProxy(member: member))
} catch { } catch {
return .failure(.failedRetrievingMember) return .failure(.failedRetrievingMember)
} }

View File

@ -21,7 +21,6 @@ import MatrixRustSDK
enum RoomProxyError: Error, Equatable { enum RoomProxyError: Error, Equatable {
case failedRedactingEvent case failedRedactingEvent
case failedReportingContent case failedReportingContent
case failedIgnoringUser
case failedRetrievingMember case failedRetrievingMember
case failedLeavingRoom case failedLeavingRoom
case failedAcceptingInvite case failedAcceptingInvite

View File

@ -24,9 +24,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
private let name: String private let name: String
private let shouldUpdateVisibleRange: Bool private let shouldUpdateVisibleRange: Bool
private let notificationSettings: NotificationSettingsProxyProtocol private let notificationSettings: NotificationSettingsProxyProtocol
private let backgroundTaskService: BackgroundTaskServiceProtocol
private let appSettings: AppSettings
private let roomListPageSize = 200 private let roomListPageSize = 200
private let serialDispatchQueue: DispatchQueue private let serialDispatchQueue: DispatchQueue
@ -67,17 +65,13 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
eventStringBuilder: RoomEventStringBuilder, eventStringBuilder: RoomEventStringBuilder,
name: String, name: String,
shouldUpdateVisibleRange: Bool = false, shouldUpdateVisibleRange: Bool = false,
notificationSettings: NotificationSettingsProxyProtocol, notificationSettings: NotificationSettingsProxyProtocol) {
backgroundTaskService: BackgroundTaskServiceProtocol,
appSettings: AppSettings) {
self.roomListService = roomListService self.roomListService = roomListService
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomsummaryprovider", qos: .default) serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomsummaryprovider", qos: .default)
self.eventStringBuilder = eventStringBuilder self.eventStringBuilder = eventStringBuilder
self.name = name self.name = name
self.shouldUpdateVisibleRange = shouldUpdateVisibleRange self.shouldUpdateVisibleRange = shouldUpdateVisibleRange
self.notificationSettings = notificationSettings self.notificationSettings = notificationSettings
self.backgroundTaskService = backgroundTaskService
self.appSettings = appSettings
diffsPublisher diffsPublisher
.receive(on: serialDispatchQueue) .receive(on: serialDispatchQueue)
@ -240,7 +234,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
var inviterProxy: RoomMemberProxyProtocol? var inviterProxy: RoomMemberProxyProtocol?
if let inviter = roomInfo.inviter { if let inviter = roomInfo.inviter {
inviterProxy = RoomMemberProxy(member: inviter, backgroundTaskService: backgroundTaskService) inviterProxy = RoomMemberProxy(member: inviter)
} }
let notificationMode = roomInfo.userDefinedNotificationMode.flatMap { RoomNotificationModeProxy.from(roomNotificationMode: $0) } let notificationMode = roomInfo.userDefinedNotificationMode.flatMap { RoomNotificationModeProxy.from(roomNotificationMode: $0) }
@ -251,14 +245,14 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
avatarURL: roomInfo.avatarUrl.flatMap(URL.init(string:)), avatarURL: roomInfo.avatarUrl.flatMap(URL.init(string:)),
lastMessage: attributedLastMessage, lastMessage: attributedLastMessage,
lastMessageFormattedTimestamp: lastMessageFormattedTimestamp, lastMessageFormattedTimestamp: lastMessageFormattedTimestamp,
unreadMessagesCount: appSettings.mentionsBadgeEnabled ? UInt(roomInfo.numUnreadMessages) : 0, unreadMessagesCount: UInt(roomInfo.numUnreadMessages),
unreadMentionsCount: appSettings.mentionsBadgeEnabled ? UInt(roomInfo.numUnreadMentions) : 0, unreadMentionsCount: UInt(roomInfo.numUnreadMentions),
unreadNotificationsCount: appSettings.mentionsBadgeEnabled ? UInt(roomInfo.numUnreadNotifications) : UInt(roomInfo.notificationCount), unreadNotificationsCount: UInt(roomInfo.numUnreadNotifications),
notificationMode: notificationMode, notificationMode: notificationMode,
canonicalAlias: roomInfo.canonicalAlias, canonicalAlias: roomInfo.canonicalAlias,
inviter: inviterProxy, inviter: inviterProxy,
hasOngoingCall: roomInfo.hasRoomCall, hasOngoingCall: roomInfo.hasRoomCall,
isMarkedUnread: appSettings.markAsUnreadEnabled ? roomInfo.isMarkedUnread : false, isMarkedUnread: roomInfo.isMarkedUnread,
isFavourite: roomInfo.isFavourite) isFavourite: roomInfo.isFavourite)
return invalidated ? .invalidated(details: details) : .filled(details: details) return invalidated ? .invalidated(details: details) : .filled(details: details)

View File

@ -23,10 +23,6 @@ final class UserDiscoveryService: UserDiscoveryServiceProtocol {
self.clientProxy = clientProxy self.clientProxy = clientProxy
} }
func fetchSuggestions() async -> Result<[UserProfileProxy], UserDiscoveryErrorType> {
.success(filterAccountOwner([.mockAlice, .mockBob, .mockCharlie]))
}
func searchProfiles(with searchQuery: String) async -> Result<[UserProfileProxy], UserDiscoveryErrorType> { func searchProfiles(with searchQuery: String) async -> Result<[UserProfileProxy], UserDiscoveryErrorType> {
async let queriedProfile = profileIfPossible(with: searchQuery) async let queriedProfile = profileIfPossible(with: searchQuery)

View File

@ -23,5 +23,4 @@ enum UserDiscoveryErrorType: Error {
// sourcery: AutoMockable // sourcery: AutoMockable
protocol UserDiscoveryServiceProtocol { protocol UserDiscoveryServiceProtocol {
func searchProfiles(with searchQuery: String) async -> Result<[UserProfileProxy], UserDiscoveryErrorType> func searchProfiles(with searchQuery: String) async -> Result<[UserProfileProxy], UserDiscoveryErrorType>
func fetchSuggestions() async -> Result<[UserProfileProxy], UserDiscoveryErrorType>
} }

View File

@ -743,10 +743,8 @@ class MockScreen: Identifiable {
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .startChat: case .startChat:
ServiceLocator.shared.settings.userSuggestionsEnabled = true
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let userDiscoveryMock = UserDiscoveryServiceMock() let userDiscoveryMock = UserDiscoveryServiceMock()
userDiscoveryMock.fetchSuggestionsReturnValue = .success([.mockAlice, .mockBob, .mockCharlie])
userDiscoveryMock.searchProfilesWithReturnValue = .success([]) userDiscoveryMock.searchProfilesWithReturnValue = .success([])
let userSession = MockUserSession(clientProxy: ClientProxyMock(.init(userID: "@mock:client.com")), let userSession = MockUserSession(clientProxy: ClientProxyMock(.init(userID: "@mock:client.com")),
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
@ -763,7 +761,6 @@ class MockScreen: Identifiable {
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com")) let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com"))
let userDiscoveryMock = UserDiscoveryServiceMock() let userDiscoveryMock = UserDiscoveryServiceMock()
userDiscoveryMock.fetchSuggestionsReturnValue = .success([])
userDiscoveryMock.searchProfilesWithReturnValue = .success([.mockBob, .mockBobby]) userDiscoveryMock.searchProfilesWithReturnValue = .success([.mockBob, .mockBobby])
let userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider(), voiceMessageMediaManager: VoiceMessageMediaManagerMock()) let userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider(), voiceMessageMediaManager: VoiceMessageMediaManagerMock())
let coordinator = StartChatScreenCoordinator(parameters: .init(orientationManager: OrientationManagerMock(), let coordinator = StartChatScreenCoordinator(parameters: .init(orientationManager: OrientationManagerMock(),
@ -862,15 +859,13 @@ class MockScreen: Identifiable {
let coordinator = InvitesScreenCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider(), voiceMessageMediaManager: VoiceMessageMediaManagerMock()))) let coordinator = InvitesScreenCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider(), voiceMessageMediaManager: VoiceMessageMediaManagerMock())))
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .inviteUsers, .inviteUsersInRoom, .inviteUsersInRoomExistingMembers: case .inviteUsers:
ServiceLocator.shared.settings.userSuggestionsEnabled = true
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let userDiscoveryMock = UserDiscoveryServiceMock() let userDiscoveryMock = UserDiscoveryServiceMock()
userDiscoveryMock.fetchSuggestionsReturnValue = .success([.mockAlice, .mockBob, .mockCharlie])
userDiscoveryMock.searchProfilesWithReturnValue = .success([]) userDiscoveryMock.searchProfilesWithReturnValue = .success([])
let mediaProvider = MockMediaProvider() let mediaProvider = MockMediaProvider()
let usersSubject = CurrentValueSubject<[UserProfileProxy], Never>([]) let usersSubject = CurrentValueSubject<[UserProfileProxy], Never>([])
let members: [RoomMemberProxyMock] = id == .inviteUsersInRoomExistingMembers ? [.mockInvitedAlice, .mockBob] : [] let members: [RoomMemberProxyMock] = []
let roomProxy = RoomProxyMock(with: .init(name: "test", members: members)) let roomProxy = RoomProxyMock(with: .init(name: "test", members: members))
let roomType: InviteUsersScreenRoomType = id == .inviteUsers ? .draft : .room(roomProxy: roomProxy) let roomType: InviteUsersScreenRoomType = id == .inviteUsers ? .draft : .room(roomProxy: roomProxy)
let coordinator = InviteUsersScreenCoordinator(parameters: .init(selectedUsers: usersSubject.asCurrentValuePublisher(), let coordinator = InviteUsersScreenCoordinator(parameters: .init(selectedUsers: usersSubject.asCurrentValuePublisher(),

View File

@ -80,8 +80,6 @@ enum UITestsScreenIdentifier: String {
case invitesWithBadges case invitesWithBadges
case invitesNoInvites case invitesNoInvites
case inviteUsers case inviteUsers
case inviteUsersInRoom
case inviteUsersInRoomExistingMembers
case createRoom case createRoom
case createRoomNoUsers case createRoomNoUsers
case createPoll case createPoll

Binary file not shown.

Binary file not shown.

View File

@ -22,22 +22,4 @@ class InviteUsersScreenUITests: XCTestCase {
let app = Application.launch(.inviteUsers) let app = Application.launch(.inviteUsers)
try await app.assertScreenshot(.inviteUsers) try await app.assertScreenshot(.inviteUsers)
} }
func testSelectedUsers() async throws {
let app = Application.launch(.inviteUsers)
app.buttons[A11yIdentifiers.inviteUsersScreen.userProfile].firstMatch.tap()
try await app.assertScreenshot(.inviteUsers, step: 1)
}
func testInviteUsers() async throws {
let app = Application.launch(.inviteUsersInRoom)
app.buttons[A11yIdentifiers.inviteUsersScreen.userProfile].firstMatch.tap()
try await app.assertScreenshot(.inviteUsersInRoom, step: 1)
}
func testInviteUserExistingMembers() async throws {
let app = Application.launch(.inviteUsersInRoomExistingMembers)
app.buttons[A11yIdentifiers.inviteUsersScreen.userProfile].firstMatch.tap()
try await app.assertScreenshot(.inviteUsersInRoomExistingMembers, step: 1)
}
} }

View File

@ -28,7 +28,6 @@ class HomeScreenViewModelTests: XCTestCase {
var roomSummaryProvider: RoomSummaryProviderMock! var roomSummaryProvider: RoomSummaryProviderMock!
override func setUpWithError() throws { override func setUpWithError() throws {
ServiceLocator.shared.settings.roomListFiltersEnabled = true
cancellables.removeAll() cancellables.removeAll()
roomSummaryProvider = RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))) roomSummaryProvider = RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))
clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", roomSummaryProvider: roomSummaryProvider)) clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", roomSummaryProvider: roomSummaryProvider))
@ -179,7 +178,6 @@ class HomeScreenViewModelTests: XCTestCase {
try await Task.sleep(for: .milliseconds(100)) try await Task.sleep(for: .milliseconds(100))
XCTAssertEqual(roomSummaryProvider.roomListPublisher.value.first?.name, "Prelude to Foundation") XCTAssertEqual(roomSummaryProvider.roomListPublisher.value.first?.name, "Prelude to Foundation")
XCTAssertEqual(roomSummaryProvider.roomListPublisher.value.count, 1) XCTAssertEqual(roomSummaryProvider.roomListPublisher.value.count, 1)
XCTAssertFalse(context.viewState.shouldShowFilters)
} }
func testFiltersEmptyState() async throws { func testFiltersEmptyState() async throws {

View File

@ -96,13 +96,11 @@ class InviteUsersScreenViewModelTests: XCTestCase {
private func setupWithRoomType(roomType: InviteUsersScreenRoomType) { private func setupWithRoomType(roomType: InviteUsersScreenRoomType) {
let usersSubject = CurrentValueSubject<[UserProfileProxy], Never>([]) let usersSubject = CurrentValueSubject<[UserProfileProxy], Never>([])
userDiscoveryService = UserDiscoveryServiceMock() userDiscoveryService = UserDiscoveryServiceMock()
userDiscoveryService.fetchSuggestionsReturnValue = .success([])
userDiscoveryService.searchProfilesWithReturnValue = .success([]) userDiscoveryService.searchProfilesWithReturnValue = .success([])
usersSubject.send([]) usersSubject.send([])
let viewModel = InviteUsersScreenViewModel(selectedUsers: usersSubject.asCurrentValuePublisher(), let viewModel = InviteUsersScreenViewModel(selectedUsers: usersSubject.asCurrentValuePublisher(),
roomType: roomType, mediaProvider: MockMediaProvider(), roomType: roomType, mediaProvider: MockMediaProvider(),
userDiscoveryService: userDiscoveryService, userDiscoveryService: userDiscoveryService,
appSettings: ServiceLocator.shared.settings,
userIndicatorController: UserIndicatorControllerMock()) userIndicatorController: UserIndicatorControllerMock())
viewModel.state.usersSection = .init(type: .suggestions, users: [.mockAlice, .mockBob, .mockCharlie]) viewModel.state.usersSection = .init(type: .suggestions, users: [.mockAlice, .mockBob, .mockCharlie])
self.viewModel = viewModel self.viewModel = viewModel

View File

@ -31,13 +31,11 @@ class StartChatScreenViewModelTests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
clientProxy = .init(.init(userID: "")) clientProxy = .init(.init(userID: ""))
userDiscoveryService = UserDiscoveryServiceMock() userDiscoveryService = UserDiscoveryServiceMock()
userDiscoveryService.fetchSuggestionsReturnValue = .success([])
userDiscoveryService.searchProfilesWithReturnValue = .success([]) userDiscoveryService.searchProfilesWithReturnValue = .success([])
let userSession = MockUserSession(clientProxy: clientProxy, let userSession = MockUserSession(clientProxy: clientProxy,
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock()) voiceMessageMediaManager: VoiceMessageMediaManagerMock())
viewModel = StartChatScreenViewModel(userSession: userSession, viewModel = StartChatScreenViewModel(userSession: userSession,
userSuggestionsEnabled: true,
analytics: ServiceLocator.shared.analytics, analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
userDiscoveryService: userDiscoveryService) userDiscoveryService: userDiscoveryService)
@ -46,7 +44,6 @@ class StartChatScreenViewModelTests: XCTestCase {
func testQueryShowingNoResults() async throws { func testQueryShowingNoResults() async throws {
await search(query: "A") await search(query: "A")
XCTAssertEqual(context.viewState.usersSection.type, .suggestions) XCTAssertEqual(context.viewState.usersSection.type, .suggestions)
XCTAssertTrue(userDiscoveryService.fetchSuggestionsCalled)
await search(query: "AA") await search(query: "AA")
XCTAssertEqual(context.viewState.usersSection.type, .suggestions) XCTAssertEqual(context.viewState.usersSection.type, .suggestions)

View File

@ -0,0 +1 @@
The features: Filters, Mark as Read/Unread/Favourites are now available.