Fixes #2360 - Add support for manually marking rooms as unread (#2408)

* Fixes #2360 - Add support for manually marking rooms as unread

* Add analytics for marking rooms as (un)read

* Remove unnecessary analytics abstraction levels

* Fix breaking API changes following SDK bump

* Add changelog

* Handle PR comments

* Fix unit tests
This commit is contained in:
Stefan Ceriu
2024-02-05 18:06:50 +02:00
committed by GitHub
parent e80612b968
commit 9915a33964
46 changed files with 250 additions and 305 deletions
@@ -156,7 +156,7 @@ private extension View {
case .edit:
self
case .new:
track(screen: .createPoll)
track(screen: .CreatePollView)
}
}
}
@@ -33,7 +33,7 @@ struct CreateRoomScreen: View {
securitySection
}
.compoundList()
.track(screen: .createRoom)
.track(screen: .CreateRoom)
.scrollDismissesKeyboard(.immediately)
.navigationTitle(L10n.screenCreateRoomTitle)
.navigationBarTitleDisplayMode(.inline)
@@ -51,6 +51,8 @@ enum HomeScreenViewAction {
case updateVisibleItemRange(range: Range<Int>, isScrolling: Bool)
case selectInvites
case globalSearch
case markRoomAsUnread(roomIdentifier: String)
case markRoomAsRead(roomIdentifier: String)
}
enum HomeScreenRoomListMode: CustomStringConvertible {
@@ -102,7 +104,9 @@ struct HomeScreenViewState: BindableState {
var rooms: [HomeScreenRoom] = []
var roomListMode: HomeScreenRoomListMode = .skeletons
var shouldShowFilters = false
var markAsUnreadEnabled = false
var hasPendingInvitations = false
var hasUnreadPendingInvitations = false
@@ -153,6 +157,8 @@ struct HomeScreenRoom: Identifiable, Equatable {
var name = ""
var isMarkedUnread: Bool
var hasUnreadMessages = false
var hasUnreadMentions = false
@@ -171,10 +177,15 @@ struct HomeScreenRoom: Identifiable, Equatable {
var isPlaceholder = false
var hasNewContent: Bool {
hasUnreadMessages || hasUnreadMentions || hasUnreadNotifications || isMarkedUnread
}
static func placeholder() -> HomeScreenRoom {
HomeScreenRoom(id: UUID().uuidString,
roomId: nil,
name: "Placeholder room name",
isMarkedUnread: false,
hasUnreadMessages: false,
hasUnreadMentions: false,
hasUnreadNotifications: false,
@@ -86,6 +86,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
.weakAssign(to: \.state.shouldShowFilters, on: self)
.store(in: &cancellables)
appSettings.$markAsUnreadEnabled
.weakAssign(to: \.state.markAsUnreadEnabled, on: self)
.store(in: &cancellables)
let isSearchFieldFocused = context.$viewState.map(\.bindings.isSearchFieldFocused)
let searchQuery = context.$viewState.map(\.bindings.searchQuery)
isSearchFieldFocused
@@ -143,6 +147,34 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
actionsSubject.send(.presentInvitesScreen)
case .globalSearch:
actionsSubject.send(.presentGlobalSearch)
case .markRoomAsUnread(let roomIdentifier):
Task {
guard let roomProxy = await userSession.clientProxy.roomForIdentifier(roomIdentifier) else {
MXLog.error("Failed retrieving room for identifier: \(roomIdentifier)")
return
}
switch await roomProxy.markAsUnread() {
case .success:
ServiceLocator.shared.analytics.trackInteraction(name: .MobileRoomListRoomContextMenuUnreadToggle)
case .failure(let error):
MXLog.error("Failed marking room \(roomIdentifier) as unread with error: \(error)")
}
}
case .markRoomAsRead(let roomIdentifier):
Task {
guard let roomProxy = await userSession.clientProxy.roomForIdentifier(roomIdentifier) else {
MXLog.error("Failed retrieving room for identifier: \(roomIdentifier)")
return
}
switch await roomProxy.markAsRead(sendReadReceipts: true, receiptType: appSettings.sendReadReceiptsEnabled ? .read : .readPrivate) {
case .success:
ServiceLocator.shared.analytics.trackInteraction(name: .MobileRoomListRoomContextMenuUnreadToggle)
case .failure(let error):
MXLog.error("Failed marking room \(roomIdentifier) as read with error: \(error)")
}
}
}
}
@@ -327,6 +359,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
return HomeScreenRoom(id: identifier,
roomId: details.id,
name: details.name,
isMarkedUnread: details.isMarkedUnread,
hasUnreadMessages: details.unreadMessagesCount > 0,
hasUnreadMentions: details.unreadMentionsCount > 0,
hasUnreadNotifications: details.unreadNotificationsCount > 0,
@@ -40,7 +40,7 @@ struct HomeScreen: View {
.navigationTitle(L10n.screenRoomlistMainSpaceTitle)
.toolbar { toolbar }
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.track(screen: .home)
.track(screen: .Home)
.introspect(.viewController, on: .supportedVersions) { controller in
Task {
if bloomView == nil {
@@ -133,7 +133,7 @@ struct HomeScreenRoomCell: View {
.foregroundColor(.compound.iconAccentTertiary)
}
if hasNewContent {
if room.hasNewContent {
Circle()
.frame(width: 12, height: 12)
.foregroundColor(isHighlighted ? .compound.iconAccentTertiary : .compound.iconQuaternary)
@@ -141,21 +141,15 @@ struct HomeScreenRoomCell: View {
}
}
}
private var hasNewContent: Bool {
room.hasUnreadMessages ||
room.hasUnreadMentions ||
room.hasUnreadNotifications
}
private var isHighlighted: Bool {
guard !room.isPlaceholder &&
room.notificationMode != .mute else {
guard !room.isPlaceholder && room.notificationMode != .mute else {
return false
}
return room.hasUnreadNotifications || room.hasUnreadMentions
}
return room.hasUnreadNotifications || room.hasUnreadMentions || room.isMarkedUnread
}
private var mentionIcon: some View {
CompoundIcon(\.mention, size: .custom(15), relativeTo: .compound.bodyMD)
.accessibilityLabel(L10n.a11yNotificationsMentionsOnly)
@@ -223,6 +217,7 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
return HomeScreenRoom(id: UUID().uuidString,
roomId: details.id,
name: details.name,
isMarkedUnread: details.isMarkedUnread,
hasUnreadMessages: details.unreadMessagesCount > 0,
hasUnreadMentions: details.unreadMentionsCount > 0,
hasUnreadNotifications: details.unreadNotificationsCount > 0,
@@ -44,8 +44,25 @@ struct HomeScreenRoomList: View {
.redacted(reason: .placeholder)
} else {
let isSelected = context.viewState.selectedRoomID == room.id
HomeScreenRoomCell(room: room, context: context, isSelected: isSelected)
.contextMenu {
if context.viewState.markAsUnreadEnabled {
if room.hasNewContent {
Button {
context.send(viewAction: .markRoomAsRead(roomIdentifier: room.id))
} label: {
Text(L10n.screenRoomlistMarkAsRead)
}
} else {
Button {
context.send(viewAction: .markRoomAsUnread(roomIdentifier: room.id))
} label: {
Text(L10n.screenRoomlistMarkAsUnread)
}
}
}
Button {
context.send(viewAction: .showRoomDetails(roomIdentifier: room.id))
} label: {
@@ -24,7 +24,7 @@ struct InvitesScreen: View {
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.navigationTitle(L10n.actionInvitesList)
.alert(item: $context.alertInfo)
.track(screen: .invites)
.track(screen: .Invites)
}
// MARK: - Private
@@ -186,6 +186,7 @@ private extension InvitesScreenRoomDetails {
avatarURL: nil,
lastMessage: nil,
lastMessageFormattedTimestamp: nil,
isMarkedUnread: false,
unreadMessagesCount: 0,
unreadMentionsCount: 0,
unreadNotificationsCount: 0,
@@ -208,6 +209,7 @@ private extension InvitesScreenRoomDetails {
avatarURL: avatarURL,
lastMessage: nil,
lastMessageFormattedTimestamp: nil,
isMarkedUnread: false,
unreadMessagesCount: 0,
unreadMentionsCount: 0,
unreadNotificationsCount: 0,
@@ -34,7 +34,7 @@ struct StaticLocationScreen: View {
}
mapView
}
.track(screen: context.viewState.isLocationPickerMode ? .locationSend : .locationView)
.track(screen: context.viewState.isLocationPickerMode ? .LocationSend : .LocationView)
.navigationTitle(context.viewState.navigationTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
@@ -37,7 +37,7 @@ struct RoomDetailsEditScreen: View {
.navigationTitle(L10n.screenRoomDetailsEditRoomTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.track(screen: .roomSettings)
.track(screen: .RoomSettings)
}
// MARK: - Private
@@ -61,7 +61,7 @@ struct RoomDetailsScreen: View {
}
}
}
.track(screen: .roomDetails)
.track(screen: .RoomDetails)
.interactiveQuickLook(item: $context.mediaPreviewItem, shouldHideControls: true)
}
@@ -32,7 +32,7 @@ struct RoomMemberDetailsScreen: View {
.compoundList()
.alert(item: $context.ignoreUserAlert, actions: blockUserAlertActions, message: blockUserAlertMessage)
.alert(item: $context.alertInfo)
.track(screen: .user)
.track(screen: .User)
.interactiveQuickLook(item: $context.mediaPreviewItem, shouldHideControls: true)
}
@@ -37,7 +37,7 @@ struct RoomMembersListScreen: View {
inviteButton
}
}
.track(screen: .roomMembers)
.track(screen: .RoomMembers)
}
// MARK: - Private
@@ -33,7 +33,7 @@ struct RoomNotificationSettingsScreen: View {
.compoundList()
.navigationTitle(context.viewState.navigationTitle)
.alert(item: $context.alertInfo)
.track(screen: .roomNotifications)
.track(screen: .RoomNotifications)
}
// MARK: - Private
@@ -29,7 +29,7 @@ struct RoomNotificationSettingsUserDefinedScreen: View {
.compoundList()
.navigationTitle(context.viewState.navigationTitle)
.alert(item: $context.alertInfo)
.track(screen: .roomNotifications)
.track(screen: .RoomNotifications)
}
// MARK: - Private
@@ -428,7 +428,7 @@ class RoomScreenInteractionHandler {
analyticsService.trackComposer(inThread: false,
isEditing: false,
isReply: false,
messageType: .voiceMessage,
messageType: .VoiceMessage,
startsThread: nil)
actionsSubject.send(.composer(action: .setMode(mode: .previewVoiceMessage(state: audioPlayerState, waveform: .url(recordingURL), isUploading: true))))
@@ -70,7 +70,7 @@ struct RoomScreen: View {
.environmentObject(context)
}
.interactiveQuickLook(item: $context.mediaPreviewItem)
.track(screen: .room)
.track(screen: .Room)
.onDrop(of: ["public.item", "public.file-url"], isTargeted: $dragOver) { providers -> Bool in
guard let provider = providers.first,
provider.isSupportedForPasteOrDrop else {
@@ -50,6 +50,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
var userSuggestionsEnabled: Bool { get set }
var mentionsBadgeEnabled: Bool { get set }
var roomListFiltersEnabled: Bool { get set }
var markAsUnreadEnabled: Bool { get set }
var elementCallBaseURL: URL { get set }
}
@@ -55,6 +55,10 @@ struct DeveloperOptionsScreen: View {
Toggle(isOn: $context.roomListFiltersEnabled) {
Text("Show filters")
}
Toggle(isOn: $context.markAsUnreadEnabled) {
Text("Mark as unread")
}
}
Section("Element Call") {
@@ -31,7 +31,7 @@ struct NotificationSettingsEditScreen: View {
.compoundList()
.navigationTitle(context.viewState.strings.navigationTitle)
.alert(item: $context.alertInfo)
.track(screen: .settingsDefaultNotifications)
.track(screen: .SettingsDefaultNotifications)
}
// MARK: - Private
@@ -48,7 +48,7 @@ struct NotificationSettingsScreen: View {
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.alert(item: $context.alertInfo)
.track(screen: .settingsNotifications)
.track(screen: .SettingsNotifications)
}
// MARK: - Private
@@ -29,7 +29,7 @@ struct StartChatScreen: View {
}
}
.compoundList()
.track(screen: .startChat)
.track(screen: .StartChat)
.scrollDismissesKeyboard(.immediately)
.navigationTitle(L10n.actionStartChat)
.navigationBarTitleDisplayMode(.inline)