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
+10 -11
View File
@@ -144,7 +144,6 @@
22882C710BC99EC34A5024A0 /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; };
234E2C782981003971ABE96E /* PermalinkBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */; };
2352C541AF857241489756FF /* MockRoomSummaryProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F7D42E66E939B709C1EC390 /* MockRoomSummaryProvider.swift */; };
2355289BB0146231DD8AFFC0 /* AnalyticsMessageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2133A5FF0C14986E60326115 /* AnalyticsMessageType.swift */; };
23701DE32ACD6FD40AA992C3 /* MediaUploadingPreprocessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE203026B9AD3DB412439866 /* MediaUploadingPreprocessorTests.swift */; };
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */; };
245F7FE5961BD10C145A26E0 /* UITimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA689E792E679F5E3956F21 /* UITimelineView.swift */; };
@@ -838,7 +837,6 @@
D1E29F345F1220E1AF1BE9DF /* ReadReceiptsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0A77874B29D79DDFC051AC /* ReadReceiptsSummaryView.swift */; };
D1EEF0CB0F5D9C15E224E670 /* landscape_test_video.mov in Resources */ = {isa = PBXBuildFile; fileRef = 9A2AC7BE17C05CF7D2A22338 /* landscape_test_video.mov */; };
D2048FD56760BDABA3DB5FC2 /* AppLockServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */; };
D2A15D03F81342A09340BD56 /* AnalyticsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFEEE93B82937B2E86F92EB /* AnalyticsScreen.swift */; };
D2D70B5DB1A5E4AF0CD88330 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 033DB41C51865A2E83174E87 /* target.yml */; };
D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */; };
D34E328E9E65904358248FDD /* GlobalSearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436A0D98D372B17EAE9AA999 /* GlobalSearchScreenModels.swift */; };
@@ -1192,7 +1190,6 @@
1FD51B4D5173F7FC886F5360 /* NoticeRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineItemContent.swift; sourceTree = "<group>"; };
201305507D7DFD16E544563A /* EmojiLoaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiLoaderProtocol.swift; sourceTree = "<group>"; };
20872C3887F835958CE2F1D0 /* MapTilerStaticMapProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStaticMapProtocol.swift; sourceTree = "<group>"; };
2133A5FF0C14986E60326115 /* AnalyticsMessageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsMessageType.swift; sourceTree = "<group>"; };
2141693488CE5446BB391964 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
216F0DDC98F2A2C162D09C28 /* FileRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItemContent.swift; sourceTree = "<group>"; };
218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineItemProtocol.swift; sourceTree = "<group>"; };
@@ -1266,6 +1263,8 @@
340179A0FC1AD4AEDA7FC134 /* CreateRoomViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModelProtocol.swift; sourceTree = "<group>"; };
342BEBC3C5FC3F9943C41C4C /* TemplateScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModelProtocol.swift; sourceTree = "<group>"; };
347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenViewModelTests.swift; sourceTree = "<group>"; };
34E0FA38BD473FFA6F1AB7A5 /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = be; path = be.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
34ED3AB7E0287552A5648AB3 /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/InfoPlist.strings; sourceTree = "<group>"; };
351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReactionsView.swift; sourceTree = "<group>"; };
3558A15CFB934F9229301527 /* RestorationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationToken.swift; sourceTree = "<group>"; };
35AFCF4C05DEED04E3DB1A16 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -1945,6 +1944,7 @@
EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewFrameReader.swift; sourceTree = "<group>"; };
EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceTests.swift; sourceTree = "<group>"; };
F012CB5EE3F2B67359F6CC52 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
F0205C03F98BE861EDABCB0D /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/Localizable.strings; sourceTree = "<group>"; };
F08776C48FFB47CACF64ED10 /* ServerConfirmationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModelTests.swift; sourceTree = "<group>"; };
F0B9F5BC4C80543DE7228B9D /* MapTilerStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStyle.swift; sourceTree = "<group>"; };
F0E14FF533D25A0692F7CEB0 /* RoomPollsHistoryScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModel.swift; sourceTree = "<group>"; };
@@ -1994,7 +1994,6 @@
FDF73F49E6B6683F7E2D26F0 /* SecureBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenCoordinator.swift; sourceTree = "<group>"; };
FE87C931165F5E201CACBB87 /* MediaPlayerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerProtocol.swift; sourceTree = "<group>"; };
FEC2E8E1B20BB2EA07B0B61E /* WelcomeScreenScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreenScreenViewModel.swift; sourceTree = "<group>"; };
FEFEEE93B82937B2E86F92EB /* AnalyticsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsScreen.swift; sourceTree = "<group>"; };
FFECCE59967018204876D0A5 /* LocationMarkerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationMarkerView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -2932,8 +2931,6 @@
E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */,
D77B3D4950F1707E66E4A45A /* AnalyticsConfiguration.swift */,
57B6B383F1FD04CC0E7B60C6 /* AnalyticsConsentState.swift */,
2133A5FF0C14986E60326115 /* AnalyticsMessageType.swift */,
FEFEEE93B82937B2E86F92EB /* AnalyticsScreen.swift */,
5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */,
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */,
1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */,
@@ -4979,6 +4976,7 @@
hasScannedForEncodings = 0;
knownRegions = (
Base,
be,
cs,
de,
en,
@@ -5388,13 +5386,11 @@
F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */,
54C774874BED4A8FAD1F22FE /* AnalyticsConfiguration.swift in Sources */,
8DDC6F28C797D8685F2F8E32 /* AnalyticsConsentState.swift in Sources */,
2355289BB0146231DD8AFFC0 /* AnalyticsMessageType.swift in Sources */,
9DF3F6318A4402305F5EB869 /* AnalyticsPromptScreen.swift in Sources */,
5F28C9146694B381BB82E18C /* AnalyticsPromptScreenCoordinator.swift in Sources */,
496CC9D59ACFAB84FD9B3B5F /* AnalyticsPromptScreenModels.swift in Sources */,
0AA0477E063E72B786A983CF /* AnalyticsPromptScreenViewModel.swift in Sources */,
D4ACF3276F5D0DA28D4028C9 /* AnalyticsPromptScreenViewModelProtocol.swift in Sources */,
D2A15D03F81342A09340BD56 /* AnalyticsScreen.swift in Sources */,
3C73442084BF8A6939F0F80B /* AnalyticsService.swift in Sources */,
020F7E70167FB2833266F2F0 /* AnalyticsSettingsScreen.swift in Sources */,
95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */,
@@ -6190,6 +6186,7 @@
187853A7E643995EE49FAD43 /* Localizable.stringsdict */ = {
isa = PBXVariantGroup;
children = (
34E0FA38BD473FFA6F1AB7A5 /* be */,
6654859746B0BE9611459391 /* cs */,
AE5DDBEBBA17973ED4638823 /* de */,
13802897C7AFA360EA74C0B0 /* en */,
@@ -6209,6 +6206,7 @@
7109E709A7738E6BCC4553E6 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
F0205C03F98BE861EDABCB0D /* be */,
8D8169443E5AC5FF71BFB3DB /* cs */,
35AFCF4C05DEED04E3DB1A16 /* de */,
CACA846B3E3E9A521D98B178 /* en */,
@@ -6228,6 +6226,7 @@
91DE43B8815918E590912DDA /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
34ED3AB7E0287552A5648AB3 /* be */,
8D1FA20DAB853C1156054912 /* cs */,
84311D707B09854D67F78BBF /* de */,
1215A4FC53D2319E81AE8970 /* en */,
@@ -6764,7 +6763,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
requirement = {
kind = exactVersion;
version = 1.1.36;
version = 1.1.37;
};
};
821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = {
@@ -6811,8 +6810,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/matrix-org/matrix-analytics-events";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 0.9.0;
kind = revision;
revision = 28d1e65d306420b3affc32f719cb516f74a7406f;
};
};
C13F55E4518415CB4C278E73 /* XCRemoteSwiftPackageReference "DTCoreText" */ = {
@@ -121,8 +121,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-analytics-events",
"state" : {
"revision" : "aa14cbcdf81af2746d20a71779ec751f971e1d7f",
"version" : "0.9.0"
"revision" : "28d1e65d306420b3affc32f719cb516f74a7406f"
}
},
{
@@ -130,8 +129,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
"state" : {
"revision" : "e6aecad747a12e399ee718b5fb2ece45b7b6e23a",
"version" : "1.1.36"
"revision" : "4d0d75004f8361530d7424ab198e363027823718",
"version" : "1.1.37"
}
},
{
@@ -47,6 +47,7 @@ final class AppSettings {
case userSuggestionsEnabled
case mentionsBadgeEnabled
case roomListFiltersEnabled
case markAsUnreadEnabled
}
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
@@ -275,6 +276,9 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.roomListFiltersEnabled, defaultValue: false, storageType: .userDefaults(store))
var roomListFiltersEnabled
@UserPreference(key: UserDefaultsKeys.markAsUnreadEnabled, defaultValue: false, storageType: .userDefaults(store))
var markAsUnreadEnabled
#endif
// MARK: - Shared
@@ -417,6 +417,12 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
self.roomProxy = roomProxy
Task {
// Mark the room as read on entering but don't send read receipts
// as those will be handled by the timeline
await roomProxy.markAsRead(sendReadReceipts: false, receiptType: appSettings.sendReadReceiptsEnabled ? .read : .readPrivate)
}
let userID = userSession.clientProxy.userID
let timelineItemFactory = RoomTimelineItemFactory(userID: userID,
@@ -772,7 +778,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
self.analytics.trackComposer(inThread: false,
isEditing: false,
isReply: false,
messageType: isUserLocation ? .location(.user) : .location(.pin),
messageType: isUserLocation ? .LocationUser : .LocationPin,
startsThread: nil)
case .close:
self.navigationSplitCoordinator.setSheetCoordinator(nil)
@@ -833,7 +839,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
self.analytics.trackComposer(inThread: false,
isEditing: false,
isReply: false,
messageType: .poll,
messageType: .Poll,
startsThread: nil)
self.analytics.trackPollCreated(isUndisclosed: pollKind == .undisclosed, numberOfAnswers: options.count)
@@ -1847,11 +1847,6 @@ class RoomProxyMock: RoomProxyProtocol {
}
var underlyingHasOngoingCall: Bool!
var canonicalAlias: String?
var hasUnreadNotifications: Bool {
get { return underlyingHasUnreadNotifications }
set(value) { underlyingHasUnreadNotifications = value }
}
var underlyingHasUnreadNotifications: Bool!
var ownUserID: String {
get { return underlyingOwnUserID }
set(value) { underlyingOwnUserID = value }
@@ -2222,6 +2217,44 @@ class RoomProxyMock: RoomProxyProtocol {
return canUserTriggerRoomNotificationUserIDReturnValue
}
}
//MARK: - markAsUnread
var markAsUnreadCallsCount = 0
var markAsUnreadCalled: Bool {
return markAsUnreadCallsCount > 0
}
var markAsUnreadReturnValue: Result<Void, RoomProxyError>!
var markAsUnreadClosure: (() async -> Result<Void, RoomProxyError>)?
func markAsUnread() async -> Result<Void, RoomProxyError> {
markAsUnreadCallsCount += 1
if let markAsUnreadClosure = markAsUnreadClosure {
return await markAsUnreadClosure()
} else {
return markAsUnreadReturnValue
}
}
//MARK: - markAsRead
var markAsReadSendReadReceiptsReceiptTypeCallsCount = 0
var markAsReadSendReadReceiptsReceiptTypeCalled: Bool {
return markAsReadSendReadReceiptsReceiptTypeCallsCount > 0
}
var markAsReadSendReadReceiptsReceiptTypeReceivedArguments: (sendReadReceipts: Bool, receiptType: ReceiptType)?
var markAsReadSendReadReceiptsReceiptTypeReceivedInvocations: [(sendReadReceipts: Bool, receiptType: ReceiptType)] = []
var markAsReadSendReadReceiptsReceiptTypeReturnValue: Result<Void, RoomProxyError>!
var markAsReadSendReadReceiptsReceiptTypeClosure: ((Bool, ReceiptType) async -> Result<Void, RoomProxyError>)?
func markAsRead(sendReadReceipts: Bool, receiptType: ReceiptType) async -> Result<Void, RoomProxyError> {
markAsReadSendReadReceiptsReceiptTypeCallsCount += 1
markAsReadSendReadReceiptsReceiptTypeReceivedArguments = (sendReadReceipts: sendReadReceipts, receiptType: receiptType)
markAsReadSendReadReceiptsReceiptTypeReceivedInvocations.append((sendReadReceipts: sendReadReceipts, receiptType: receiptType))
if let markAsReadSendReadReceiptsReceiptTypeClosure = markAsReadSendReadReceiptsReceiptTypeClosure {
return await markAsReadSendReadReceiptsReceiptTypeClosure(sendReadReceipts, receiptType)
} else {
return markAsReadSendReadReceiptsReceiptTypeReturnValue
}
}
//MARK: - canUserJoinCall
var canUserJoinCallUserIDCallsCount = 0
@@ -1,4 +1,4 @@
// Generated using Sourcery 2.1.2 — https://github.com/krzysztofzablocki/Sourcery
// Generated using Sourcery 2.1.3 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
// swiftlint:disable all
@@ -29,7 +29,6 @@ struct RoomProxyMockConfiguration {
var isEncrypted = true
var hasOngoingCall = true
var canonicalAlias: String?
var hasUnreadNotifications = Bool.random()
var timeline = {
let mock = TimelineProxyMock()
@@ -64,7 +63,6 @@ extension RoomProxyMock {
isEncrypted = configuration.isEncrypted
hasOngoingCall = configuration.hasOngoingCall
canonicalAlias = configuration.canonicalAlias
hasUnreadNotifications = configuration.hasUnreadNotifications
timeline = configuration.timeline
@@ -14,13 +14,14 @@
// limitations under the License.
//
import AnalyticsEvents
import SwiftUI
/// `ScreenTrackerViewModifier` is a helper class used to track PostHog screen from SwiftUI screens.
struct ScreenTrackerViewModifier: ViewModifier {
@Environment(\.analyticsService) private var analyticsService
let screen: AnalyticsScreen
let screen: AnalyticsEvent.MobileScreen.ScreenName
@ViewBuilder
func body(content: Content) -> some View {
@@ -32,7 +33,7 @@ struct ScreenTrackerViewModifier: ViewModifier {
}
extension View {
func track(screen: AnalyticsScreen) -> some View {
func track(screen: AnalyticsEvent.MobileScreen.ScreenName) -> some View {
modifier(ScreenTrackerViewModifier(screen: screen))
}
}
@@ -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)
@@ -1,53 +0,0 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import AnalyticsEvents
enum AnalyticsMessageType {
case location(LocationType)
case poll
case text
case voiceMessage
enum LocationType {
case pin
case user
}
}
extension AnalyticsEvent.Composer.MessageType {
init(_ analyticsMessageType: AnalyticsMessageType) {
switch analyticsMessageType {
case .location(let locationType):
self = .init(locationType)
case .poll:
self = .Poll
case .text:
self = .Text
case .voiceMessage:
self = .VoiceMessage
}
}
private init(_ locationType: AnalyticsMessageType.LocationType) {
switch locationType {
case .pin:
self = .LocationPin
case .user:
self = .LocationUser
}
}
}
@@ -1,159 +0,0 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import AnalyticsEvents
import Foundation
enum AnalyticsScreen: Int {
case welcome
case login
case register
case forgotPassword
case sidebar
case home
case favourites
case people
case rooms
case searchRooms
case searchMessages
case searchPeople
case searchFiles
case room
case roomPreview
case roomDetails
case roomMembers
case user
case roomSearch
case roomUploads
case roomSettings
case roomNotifications
case roomDirectory
case switchDirectory
case startChat
case createRoom
case settings
case settingsSecurity
case settingsDefaultNotifications
case settingsMentionsAndKeywords
case settingsNotifications
case deactivateAccount
case inviteFriends
case threadList
case spaceMenu
case spaceMembers
case spaceExploreRooms
case dialpad
case spaceBottomSheet
case invites
case createSpace
case locationSend
case locationView
case createPoll
/// The screen name reported to the AnalyticsEvent.
var screenName: AnalyticsEvent.MobileScreen.ScreenName {
switch self {
case .welcome:
return .Welcome
case .login:
return .Login
case .register:
return .Register
case .forgotPassword:
return .ForgotPassword
case .sidebar:
return .Sidebar
case .home:
return .Home
case .favourites:
return .Favourites
case .people:
return .People
case .rooms:
return .Rooms
case .searchRooms:
return .SearchRooms
case .searchMessages:
return .SearchMessages
case .searchPeople:
return .SearchPeople
case .searchFiles:
return .SearchFiles
case .room:
return .Room
case .roomDetails:
return .RoomDetails
case .roomMembers:
return .RoomMembers
case .user:
return .User
case .roomPreview:
return .RoomPreview
case .roomSearch:
return .RoomSearch
case .roomUploads:
return .RoomUploads
case .roomSettings:
return .RoomSettings
case .roomNotifications:
return .RoomNotifications
case .roomDirectory:
return .RoomDirectory
case .switchDirectory:
return .SwitchDirectory
case .startChat:
return .StartChat
case .createRoom:
return .CreateRoom
case .settings:
return .Settings
case .settingsSecurity:
return .SettingsSecurity
case .settingsDefaultNotifications:
return .SettingsDefaultNotifications
case .settingsMentionsAndKeywords:
return .SettingsMentionsAndKeywords
case .settingsNotifications:
return .SettingsNotifications
case .deactivateAccount:
return .DeactivateAccount
case .inviteFriends:
return .InviteFriends
case .threadList:
return .ThreadList
case .spaceMenu:
return .SpaceMenu
case .spaceMembers:
return .SpaceMembers
case .spaceExploreRooms:
return .SpaceExploreRooms
case .dialpad:
return .Dialpad
case .spaceBottomSheet:
return .SpaceBottomSheet
case .invites:
return .Invites
case .createSpace:
return .CreateSpace
case .locationSend:
return .LocationSend
case .locationView:
return .LocationView
case .createPoll:
return .CreatePollView
}
}
}
@@ -121,11 +121,15 @@ extension AnalyticsService {
/// Track the presentation of a screen
/// - Parameter screen: The screen that was shown
/// - Parameter duration: An optional value representing how long the screen was shown for in milliseconds.
func track(screen: AnalyticsScreen, duration milliseconds: Int? = nil) {
func track(screen: AnalyticsEvent.MobileScreen.ScreenName, duration milliseconds: Int? = nil) {
MXLog.debug("\(screen)")
let event = AnalyticsEvent.MobileScreen(durationMs: milliseconds, screenName: screen.screenName)
let event = AnalyticsEvent.MobileScreen(durationMs: milliseconds, screenName: screen)
client.screen(event)
}
func trackInteraction(index: Int? = nil, name: AnalyticsEvent.Interaction.Name) {
capture(event: AnalyticsEvent.Interaction(index: index, interactionType: .Touch, name: name))
}
/// Track the creation of a room
/// - Parameter isDM: true if the created room is a direct message, false otherwise
@@ -143,12 +147,12 @@ extension AnalyticsService {
func trackComposer(inThread: Bool,
isEditing: Bool,
isReply: Bool,
messageType: AnalyticsMessageType = .text,
messageType: AnalyticsEvent.Composer.MessageType = .Text,
startsThread: Bool?) {
capture(event: AnalyticsEvent.Composer(inThread: inThread,
isEditing: isEditing,
isReply: isReply,
messageType: .init(messageType),
messageType: messageType,
startsThread: startsThread))
}
@@ -587,20 +587,20 @@ class ClientProxy: ClientProxyProtocol {
}
private let eventFilters: TimelineEventTypeFilter = {
let stateEventFilters: [FilterStateEventType] = [.roomAliases,
.roomCanonicalAlias,
.roomGuestAccess,
.roomHistoryVisibility,
.roomJoinRules,
.roomPinnedEvents,
.roomPowerLevels,
.roomServerAcl,
.roomTombstone,
.spaceChild,
.spaceParent,
.policyRuleRoom,
.policyRuleServer,
.policyRuleUser]
let stateEventFilters: [StateEventType] = [.roomAliases,
.roomCanonicalAlias,
.roomGuestAccess,
.roomHistoryVisibility,
.roomJoinRules,
.roomPinnedEvents,
.roomPowerLevels,
.roomServerAcl,
.roomTombstone,
.spaceChild,
.spaceParent,
.policyRuleRoom,
.policyRuleServer,
.policyRuleUser]
return .exclude(eventTypes: stateEventFilters.map { FilterTimelineEventType.state(eventType: $0) })
}()
+44 -13
View File
@@ -45,18 +45,24 @@ class RoomProxy: RoomProxyProtocol {
var actions: AnyPublisher<RoomProxyAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
var ownUserID: String {
room.ownUserId()
}
init(roomListItem: RoomListItemProtocol,
room: RoomProtocol,
backgroundTaskService: BackgroundTaskServiceProtocol) async {
init?(roomListItem: RoomListItemProtocol,
room: RoomProtocol,
backgroundTaskService: BackgroundTaskServiceProtocol) async {
self.roomListItem = roomListItem
self.room = room
self.backgroundTaskService = backgroundTaskService
timeline = await TimelineProxy(timeline: room.timeline(), backgroundTaskService: backgroundTaskService)
do {
timeline = try await TimelineProxy(timeline: room.timeline(), backgroundTaskService: backgroundTaskService)
} catch {
MXLog.error("Failed creating timeline with error: \(error)")
return nil
}
Task {
await updateMembers()
@@ -124,11 +130,7 @@ class RoomProxy: RoomProxyProtocol {
var canonicalAlias: String? {
room.canonicalAlias()
}
var hasUnreadNotifications: Bool {
roomListItem.hasUnreadNotifications()
}
var avatarURL: URL? {
roomListItem.avatarUrl().flatMap(URL.init(string:))
}
@@ -352,6 +354,37 @@ class RoomProxy: RoomProxyProtocol {
}
}
func markAsUnread() async -> Result<Void, RoomProxyError> {
MXLog.info("Marking room \(id) as unread")
do {
try await room.markAsUnread()
return .success(())
} catch {
MXLog.error("Failed marking room \(id) as unread with error: \(error)")
return .failure(.failedMarkingAsUnread)
}
}
func markAsRead(sendReadReceipts: Bool, receiptType: ReceiptType) async -> Result<Void, RoomProxyError> {
MXLog.info("Marking room \(id) as read, sending read receipts: \(sendReadReceipts)")
do {
if sendReadReceipts {
try await room.markAsReadAndSendReadReceipt(receiptType: receiptType)
} else {
try await room.markAsRead()
}
return .success(())
} catch {
MXLog.error("Failed marking room \(id) as read with error: \(error)")
return .failure(.failedMarkingAsRead)
}
}
// MARK: - Element Call
func canUserJoinCall(userID: String) async -> Result<Bool, RoomProxyError> {
do {
return try await .success(room.canUserSendState(userId: userID, stateEvent: .callMember))
@@ -361,8 +394,6 @@ class RoomProxy: RoomProxyProtocol {
}
}
// MARK: - Element Call
func elementCallWidgetDriver() -> ElementCallWidgetDriverProtocol {
ElementCallWidgetDriver(room: room)
}
@@ -31,6 +31,8 @@ enum RoomProxyError: Error, Equatable {
case failedRemovingAvatar
case failedUploadingAvatar
case failedCheckingPermission
case failedMarkingAsRead
case failedMarkingAsUnread
}
enum RoomProxyAction {
@@ -47,7 +49,6 @@ protocol RoomProxyProtocol {
var membership: Membership { get }
var hasOngoingCall: Bool { get }
var canonicalAlias: String? { get }
var hasUnreadNotifications: Bool { get }
var ownUserID: String { get }
var name: String? { get }
@@ -103,10 +104,14 @@ protocol RoomProxyProtocol {
func canUserTriggerRoomNotification(userID: String) async -> Result<Bool, RoomProxyError>
func canUserJoinCall(userID: String) async -> Result<Bool, RoomProxyError>
func markAsUnread() async -> Result<Void, RoomProxyError>
func markAsRead(sendReadReceipts: Bool, receiptType: ReceiptType) async -> Result<Void, RoomProxyError>
// MARK: - Element Call
func canUserJoinCall(userID: String) async -> Result<Bool, RoomProxyError>
func elementCallWidgetDriver() -> ElementCallWidgetDriverProtocol
}
@@ -83,6 +83,7 @@ extension Array where Element == RoomSummary {
avatarURL: nil,
lastMessage: AttributedString("I do not wish to take the trouble to understand mysticism"),
lastMessageFormattedTimestamp: "14:56",
isMarkedUnread: false,
unreadMessagesCount: 0,
unreadMentionsCount: 0,
unreadNotificationsCount: 0,
@@ -96,6 +97,7 @@ extension Array where Element == RoomSummary {
avatarURL: URL.picturesDirectory,
lastMessage: AttributedString("How do you see the Emperor then? You think he keeps office hours?"),
lastMessageFormattedTimestamp: "2:56 PM",
isMarkedUnread: false,
unreadMessagesCount: 2,
unreadMentionsCount: 0,
unreadNotificationsCount: 2,
@@ -109,6 +111,7 @@ extension Array where Element == RoomSummary {
avatarURL: nil,
lastMessage: try? AttributedString(markdown: "He certainly seemed no *mental genius* to me"),
lastMessageFormattedTimestamp: "Some time ago",
isMarkedUnread: false,
unreadMessagesCount: 3,
unreadMentionsCount: 0,
unreadNotificationsCount: 0,
@@ -122,6 +125,7 @@ extension Array where Element == RoomSummary {
avatarURL: nil,
lastMessage: AttributedString("There's an archaic measure of time that's called the month"),
lastMessageFormattedTimestamp: "Just now",
isMarkedUnread: false,
unreadMessagesCount: 2,
unreadMentionsCount: 2,
unreadNotificationsCount: 2,
@@ -135,6 +139,7 @@ extension Array where Element == RoomSummary {
avatarURL: nil,
lastMessage: AttributedString("Clearly, if Earth is powerful enough to do that, it might also be capable of adjusting minds in order to force belief in its radioactivity"),
lastMessageFormattedTimestamp: "1986",
isMarkedUnread: false,
unreadMessagesCount: 1,
unreadMentionsCount: 1,
unreadNotificationsCount: 1,
@@ -148,6 +153,7 @@ extension Array where Element == RoomSummary {
avatarURL: nil,
lastMessage: AttributedString("Are you groping for the word 'paranoia'?"),
lastMessageFormattedTimestamp: "きょうはじゅういちがつじゅういちにちです",
isMarkedUnread: false,
unreadMessagesCount: 6,
unreadMentionsCount: 0,
unreadNotificationsCount: 0,
@@ -161,6 +167,7 @@ extension Array where Element == RoomSummary {
avatarURL: nil,
lastMessage: nil,
lastMessageFormattedTimestamp: nil,
isMarkedUnread: false,
unreadMessagesCount: 0,
unreadMentionsCount: 0,
unreadNotificationsCount: 0,
@@ -206,6 +213,7 @@ extension Array where Element == RoomSummary {
avatarURL: URL.picturesDirectory,
lastMessage: nil,
lastMessageFormattedTimestamp: nil,
isMarkedUnread: false,
unreadMessagesCount: 0,
unreadMentionsCount: 0,
unreadNotificationsCount: 0,
@@ -219,6 +227,7 @@ extension Array where Element == RoomSummary {
avatarURL: nil,
lastMessage: nil,
lastMessageFormattedTimestamp: nil,
isMarkedUnread: false,
unreadMessagesCount: 0,
unreadMentionsCount: 0,
unreadNotificationsCount: 0,
@@ -24,6 +24,7 @@ struct RoomSummaryDetails {
let avatarURL: URL?
let lastMessage: AttributedString?
let lastMessageFormattedTimestamp: String?
let isMarkedUnread: Bool
let unreadMessagesCount: UInt
let unreadMentionsCount: UInt
let unreadNotificationsCount: UInt
@@ -54,6 +55,7 @@ extension RoomSummaryDetails {
avatarURL = nil
lastMessage = AttributedString(string)
lastMessageFormattedTimestamp = "Now"
isMarkedUnread = false
unreadMessagesCount = hasUnreadMessages ? 1 : 0
unreadMentionsCount = hasUnreadMentions ? 1 : 0
unreadNotificationsCount = hasUnreadNotifications ? 1 : 0
@@ -248,6 +248,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
avatarURL: roomInfo.avatarUrl.flatMap(URL.init(string:)),
lastMessage: attributedLastMessage,
lastMessageFormattedTimestamp: lastMessageFormattedTimestamp,
isMarkedUnread: appSettings.markAsUnreadEnabled ? roomInfo.isMarkedUnread : false,
unreadMessagesCount: appSettings.mentionsBadgeEnabled ? UInt(roomInfo.numUnreadMessages) : 0,
unreadMentionsCount: appSettings.mentionsBadgeEnabled ? UInt(roomInfo.numUnreadMentions) : 0,
unreadNotificationsCount: appSettings.mentionsBadgeEnabled ? UInt(roomInfo.numUnreadNotifications) : UInt(roomInfo.notificationCount),
@@ -92,8 +92,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
}
func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result<Void, RoomTimelineControllerError> {
guard roomProxy.hasUnreadNotifications,
let eventID = itemID.eventID
guard let eventID = itemID.eventID
else { return .success(()) }
switch await roomProxy.timeline.sendReadReceipt(for: eventID,
+1
View File
@@ -229,6 +229,7 @@ class LoggingTests: XCTestCase {
avatarURL: nil,
lastMessage: AttributedString(lastMessage),
lastMessageFormattedTimestamp: "Now",
isMarkedUnread: false,
unreadMessagesCount: 0,
unreadMentionsCount: 0,
unreadNotificationsCount: 0,
@@ -579,7 +579,6 @@ class RoomScreenViewModelTests: XCTestCase {
timelineProxy.sendReadReceiptForTypeReturnValue = .success(())
roomProxy.underlyingHasUnreadNotifications = true
timelineController.timelineItems = items
timelineController.roomProxy = roomProxy
+1
View File
@@ -0,0 +1 @@
Add support for manually marking a room as read/unread
+1
View File
@@ -0,0 +1 @@
Remove unnecessary analytics abstraction levels, directly create analytics events in the analytics service
+3 -2
View File
@@ -47,7 +47,7 @@ packages:
# Element/Matrix dependencies
MatrixRustSDK:
url: https://github.com/matrix-org/matrix-rust-components-swift
exactVersion: 1.1.36
exactVersion: 1.1.37
# path: ../matrix-rust-sdk
Compound:
url: https://github.com/element-hq/compound-ios
@@ -55,7 +55,8 @@ packages:
# path: ../compound-ios
AnalyticsEvents:
url: https://github.com/matrix-org/matrix-analytics-events
minorVersion: 0.9.0
# minorVersion: 0.9.0
revision: 28d1e65d306420b3affc32f719cb516f74a7406f
Emojibase:
url: https://github.com/matrix-org/emojibase-bindings
minorVersion: 1.0.0