Fixes #2320 - Implement support for (un)marking rooms as favourite from the room list and room detail screens

This commit is contained in:
Stefan Ceriu
2024-02-19 15:13:15 +02:00
parent 3887403150
commit 6d9271ce59
60 changed files with 372 additions and 179 deletions
@@ -21,6 +21,7 @@ struct RoomDetailsScreenCoordinatorParameters {
let accountUserID: String
let roomProxy: RoomProxyProtocol
let mediaProvider: MediaProviderProtocol
let analyticsService: AnalyticsService
let userIndicatorController: UserIndicatorControllerProtocol
let notificationSettings: NotificationSettingsProxyProtocol
let attributedStringBuilder: AttributedStringBuilderProtocol
@@ -49,6 +50,7 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
viewModel = RoomDetailsScreenViewModel(accountUserID: parameters.accountUserID,
roomProxy: parameters.roomProxy,
mediaProvider: parameters.mediaProvider,
analyticsService: parameters.analyticsService,
userIndicatorController: parameters.userIndicatorController,
notificationSettingsProxy: parameters.notificationSettings,
attributedStringBuilder: parameters.attributedStringBuilder)
@@ -129,6 +129,8 @@ struct RoomDetailsScreenViewStateBindings {
}
}
}
var isFavourite = false
/// Information describing the currently displayed alert.
var alertInfo: AlertInfo<RoomDetailsScreenErrorType>?
@@ -180,6 +182,7 @@ enum RoomDetailsScreenViewAction {
case processToogleMuteNotifications
case displayAvatar
case processTapPolls
case toggleFavourite(isFavourite: Bool)
}
enum RoomDetailsScreenViewShortcut {
@@ -22,6 +22,7 @@ typealias RoomDetailsScreenViewModelType = StateStoreViewModel<RoomDetailsScreen
class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScreenViewModelProtocol {
private let accountUserID: String
private let roomProxy: RoomProxyProtocol
private let analyticsService: AnalyticsService
private let mediaProvider: MediaProviderProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let notificationSettingsProxy: NotificationSettingsProxyProtocol
@@ -42,12 +43,14 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
init(accountUserID: String,
roomProxy: RoomProxyProtocol,
mediaProvider: MediaProviderProtocol,
analyticsService: AnalyticsService,
userIndicatorController: UserIndicatorControllerProtocol,
notificationSettingsProxy: NotificationSettingsProxyProtocol,
attributedStringBuilder: AttributedStringBuilderProtocol) {
self.accountUserID = accountUserID
self.roomProxy = roomProxy
self.mediaProvider = mediaProvider
self.analyticsService = analyticsService
self.userIndicatorController = userIndicatorController
self.notificationSettingsProxy = notificationSettingsProxy
self.attributedStringBuilder = attributedStringBuilder
@@ -67,6 +70,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
notificationSettingsState: .loading,
bindings: .init()),
imageProvider: mediaProvider)
updateRoomInfo()
setupRoomSubscription()
fetchMembers()
@@ -122,6 +127,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
displayFullScreenAvatar()
case .processTapPolls:
actionsSubject.send(.requestPollsHistoryPresentation)
case .toggleFavourite(let isFavourite):
Task { await toggleFavourite(isFavourite) }
}
}
@@ -129,21 +136,28 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
private func setupRoomSubscription() {
roomProxy.actions
.filter { $0 == .stateUpdate }
.filter { $0 == .roomInfoUpdate }
.throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true)
.sink { [weak self] _ in
guard let self else { return }
self.state.title = self.roomProxy.roomTitle
let topic = attributedStringBuilder.fromPlain(self.roomProxy.topic)
self.state.topic = topic
self.state.topicSummary = topic?.unattributedStringByReplacingNewlinesWithSpaces()
self.state.avatarURL = self.roomProxy.avatarURL
self.state.joinedMembersCount = self.roomProxy.joinedMembersCount
self?.updateRoomInfo()
}
.store(in: &cancellables)
}
private func updateRoomInfo() {
state.title = roomProxy.roomTitle
let topic = attributedStringBuilder.fromPlain(roomProxy.topic)
state.topic = topic
state.topicSummary = topic?.unattributedStringByReplacingNewlinesWithSpaces()
state.avatarURL = roomProxy.avatarURL
state.joinedMembersCount = roomProxy.joinedMembersCount
Task {
state.bindings.isFavourite = await roomProxy.isFavourite
}
}
private func fetchMembers() {
Task {
await fetchMembersIfNeeded()
@@ -245,6 +259,15 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
}
state.isProcessingMuteToggleAction = false
}
private func toggleFavourite(_ isFavourite: Bool) async {
if case let .failure(error) = await roomProxy.flagAsFavourite(isFavourite) {
MXLog.error("Failed flagging room as favourite with error: \(error)")
state.bindings.isFavourite = !isFavourite
} else {
analyticsService.trackInteraction(name: .MobileRoomFavouriteToggle)
}
}
private static let leaveRoomLoadingID = "LeaveRoomLoading"
@@ -187,6 +187,13 @@ struct RoomDetailsScreen: View {
})
.disabled(context.viewState.notificationSettingsState.isLoading)
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.notifications)
ListRow(label: .default(title: L10n.commonFavourite, icon: \.favourite),
kind: .toggle($context.isFavourite))
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.favourite)
.onChange(of: context.isFavourite) { newValue in
context.send(viewAction: .toggleFavourite(isFavourite: newValue))
}
}
.disabled(context.viewState.notificationSettingsState.isLoading)
}
@@ -306,6 +313,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
return RoomDetailsScreenViewModel(accountUserID: RoomMemberProxyMock.mockMe.userID,
roomProxy: roomProxy,
mediaProvider: MockMediaProvider(),
analyticsService: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
notificationSettingsProxy: notificationSettingsProxy,
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: .userDirectory,
@@ -331,6 +339,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
return RoomDetailsScreenViewModel(accountUserID: RoomMemberProxyMock.mockMe.userID,
roomProxy: roomProxy,
mediaProvider: MockMediaProvider(),
analyticsService: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
notificationSettingsProxy: notificationSettingsProxy,
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: .userDirectory,
@@ -355,6 +364,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
return RoomDetailsScreenViewModel(accountUserID: RoomMemberProxyMock.mockMe.userID,
roomProxy: roomProxy,
mediaProvider: MockMediaProvider(),
analyticsService: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
notificationSettingsProxy: notificationSettingsProxy,
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: .userDirectory,