diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index e143e76e4..c14afef9d 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -1029,6 +1029,7 @@ 47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = ""; }; 4959CECEC984B3995616F427 /* DataProtectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProtectionManager.swift; sourceTree = ""; }; 49D2C8E66E83EA578A7F318A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 49E6066092ED45E36BB306F7 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.stringsdict"; sourceTree = ""; }; 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = ""; }; 4A4AD793D50748F8997E5B15 /* TimelineItemMacContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMacContextMenu.swift; sourceTree = ""; }; 4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaLoader.swift; sourceTree = ""; }; @@ -1217,6 +1218,7 @@ 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; 90A55430639712CFACA34F43 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = ""; }; 90F2F8998E5632668B0AD848 /* RoomTimelineItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemView.swift; sourceTree = ""; }; + 91CF6F7D08228D16BA69B63B /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.strings"; sourceTree = ""; }; 923485F85E1D765EF9D20E88 /* UserProfileCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileCell.swift; sourceTree = ""; }; 92390F9FA98255440A6BF5F8 /* OIDCAuthenticationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCAuthenticationPresenter.swift; sourceTree = ""; }; 92FCD9116ADDE820E4E30F92 /* UIKitBackgroundTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTask.swift; sourceTree = ""; }; @@ -3949,6 +3951,7 @@ ro, ru, sk, + "zh-Hant-TW", ); mainGroup = 405B00F139AEE3994601B36A; packageReferences = ( @@ -4906,6 +4909,7 @@ E9D059BFE329BE09B6D96A9F /* ro */, E5F2B6443D1ED8602F328539 /* ru */, 667DD3A9D932D7D9EB380CAA /* sk */, + 49E6066092ED45E36BB306F7 /* zh-Hant-TW */, ); name = Localizable.stringsdict; sourceTree = ""; @@ -4922,6 +4926,7 @@ 33E49C5C6F802B4D94CA78D1 /* ro */, E8294DB9E95C0C0630418466 /* ru */, AD378D580A41E42560C60E9C /* sk */, + 91CF6F7D08228D16BA69B63B /* zh-Hant-TW */, ); name = Localizable.strings; sourceTree = ""; @@ -5460,7 +5465,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = "1.0.107-alpha"; + version = "1.0.108-alpha"; }; }; 821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7a2f8c285..367ac36d8 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -129,8 +129,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-rust-components-swift", "state" : { - "revision" : "28211915a5a5ca926798d12bedc3bf1a352714fd", - "version" : "1.0.107-alpha" + "revision" : "3166b3e6d35779aa78025a0b9143def6a7ee1c51", + "version" : "1.0.108-alpha" } }, { diff --git a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenModels.swift b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenModels.swift index c097d9dfe..6408c0a60 100644 --- a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenModels.swift +++ b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenModels.swift @@ -21,10 +21,6 @@ enum InvitesScreenViewModelAction { struct InvitesScreenViewState: BindableState { var invites: [InvitesScreenRoomDetails] = [] var bindings: InvitesScreenViewStateBindings = .init() - - // ideally we wanted to derive this state from `invites` being `Optional.none`. - // But we needed to make it a non optional array due to a strange crash in release builds (https://github.com/vector-im/element-x-ios/pull/1120) - var isLoading = true } struct InvitesScreenViewStateBindings { @@ -33,7 +29,6 @@ struct InvitesScreenViewStateBindings { struct InvitesScreenRoomDetails: Identifiable { let roomDetails: RoomSummaryDetails - var inviter: RoomMemberProxyProtocol? var isUnread: Bool var isDirect: Bool { diff --git a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift index ca84b994c..6d6b10eb7 100644 --- a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift +++ b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift @@ -28,7 +28,6 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel private let previouslySeenInvites: Set private let actionsSubject: PassthroughSubject = .init() - @CancellableTask private var fetchInvitersTask: Task? var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() @@ -83,58 +82,22 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel .removeDuplicates() .sink { [weak self] roomSummaries in guard let self else { return } - - fetchInvitersTask = Task { [weak self] in - guard let self else { return } - let fullInvites = await self.buildInvites(from: roomSummaries) - guard !Task.isCancelled else { return } - self.state.invites = fullInvites - self.state.isLoading = false - self.appSettings.seenInvites = Set(fullInvites.map(\.roomDetails.id)) + + let invites: [InvitesScreenRoomDetails] = roomSummaries.compactMap { summary in + guard case .filled(let details) = summary else { + return nil + } + return InvitesScreenRoomDetails(roomDetails: details, isUnread: !self.previouslySeenInvites.contains(details.id)) } + + self.state.invites = invites + self.appSettings.seenInvites = Set(invites.map(\.roomDetails.id)) } .store(in: &cancellables) } - private func buildInvites(from summaries: [RoomSummary]) async -> [InvitesScreenRoomDetails] { - await Task.detached { - let invites: [InvitesScreenRoomDetails] = summaries.compactMap { summary in - guard case .filled(let details) = summary else { - return nil - } - return InvitesScreenRoomDetails(roomDetails: details, isUnread: !self.previouslySeenInvites.contains(details.id)) - } - - // fetch the inviters... - return await withTaskGroup(of: (Int, RoomMemberProxyProtocol)?.self) { [clientProxy = self.clientProxy] group in - var invitesWithInviters = invites - - for inviteIndex in 0.. InvitesScreenRoomDetails { + let inviter = RoomMemberProxyMock() + inviter.displayName = "Luca" + inviter.userID = "@jack:somewhi.nl" + inviter.avatarURL = avatarURL + let dmRoom = RoomSummaryDetails(id: "@someone:somewhere.com", name: "Awesome Room", isDirect: false, @@ -201,12 +207,8 @@ private extension InvitesScreenRoomDetails { lastMessage: nil, lastMessageFormattedTimestamp: nil, unreadNotificationCount: 0, - canonicalAlias: alias) - let inviter = RoomMemberProxyMock() - inviter.displayName = "Luca" - inviter.userID = "@jack:somewhi.nl" - inviter.avatarURL = avatarURL - - return .init(roomDetails: dmRoom, inviter: inviter, isUnread: isUnread) + canonicalAlias: alias, + inviter: inviter) + return .init(roomDetails: dmRoom, isUnread: isUnread) } } diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 40c03fe1e..933e4dd3d 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -399,12 +399,14 @@ class ClientProxy: ClientProxyProtocol { roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService, eventStringBuilder: eventStringBuilder, name: "AllRooms", - appSettings: appSettings) + appSettings: appSettings, + backgroundTaskService: backgroundTaskService) try await roomSummaryProvider?.setRoomList(roomListService.allRooms()) inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService, eventStringBuilder: eventStringBuilder, name: "Invites", - appSettings: appSettings) + appSettings: appSettings, + backgroundTaskService: backgroundTaskService) self.syncService = syncService self.roomListService = roomListService diff --git a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift index 525a23701..e2c2cceba 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift @@ -57,7 +57,8 @@ extension Array where Element == RoomSummary { lastMessage: AttributedString("Prosciutto beef ribs pancetta filet mignon kevin hamburger, chuck ham venison picanha. Beef ribs chislic turkey biltong tenderloin."), lastMessageFormattedTimestamp: "Now", unreadNotificationCount: 4, - canonicalAlias: nil)), + canonicalAlias: nil, + inviter: nil)), .filled(details: RoomSummaryDetails(id: "2", name: "Second room", isDirect: true, @@ -65,7 +66,8 @@ extension Array where Element == RoomSummary { lastMessage: nil, lastMessageFormattedTimestamp: nil, unreadNotificationCount: 1, - canonicalAlias: nil)), + canonicalAlias: nil, + inviter: nil)), .filled(details: RoomSummaryDetails(id: "3", name: "Third room", isDirect: true, @@ -73,7 +75,8 @@ extension Array where Element == RoomSummary { lastMessage: try? AttributedString(markdown: "**@mock:client.com**: T-bone beef ribs bacon"), lastMessageFormattedTimestamp: "Later", unreadNotificationCount: 0, - canonicalAlias: nil)), + canonicalAlias: nil, + inviter: nil)), .empty ] @@ -84,7 +87,8 @@ extension Array where Element == RoomSummary { lastMessage: nil, lastMessageFormattedTimestamp: nil, unreadNotificationCount: 0, - canonicalAlias: "#footest:somewhere.org")), + canonicalAlias: "#footest:somewhere.org", + inviter: nil)), .filled(details: RoomSummaryDetails(id: "someAwesomeRoomId2", name: "Second room", isDirect: true, @@ -92,6 +96,7 @@ extension Array where Element == RoomSummary { lastMessage: nil, lastMessageFormattedTimestamp: nil, unreadNotificationCount: 0, - canonicalAlias: nil)) + canonicalAlias: nil, + inviter: nil)) ] } diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryDetails.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryDetails.swift index 9e94ca8c6..08173c384 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryDetails.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryDetails.swift @@ -15,6 +15,7 @@ // import Foundation +import MatrixRustSDK struct RoomSummaryDetails { let id: String @@ -25,6 +26,7 @@ struct RoomSummaryDetails { let lastMessageFormattedTimestamp: String? let unreadNotificationCount: UInt let canonicalAlias: String? + let inviter: RoomMemberProxyProtocol? } extension RoomSummaryDetails: CustomStringConvertible { diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift index bd8ff4b21..0f490097e 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift @@ -23,6 +23,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { private let eventStringBuilder: RoomEventStringBuilder private let name: String private var appSettings: AppSettings + private let backgroundTaskService: BackgroundTaskServiceProtocol private let serialDispatchQueue: DispatchQueue @@ -56,12 +57,14 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { init(roomListService: RoomListServiceProtocol, eventStringBuilder: RoomEventStringBuilder, name: String, - appSettings: AppSettings) { + appSettings: AppSettings, + backgroundTaskService: BackgroundTaskServiceProtocol) { self.roomListService = roomListService serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomsummaryprovider", qos: .utility) self.eventStringBuilder = eventStringBuilder self.name = name self.appSettings = appSettings + self.backgroundTaskService = backgroundTaskService diffsPublisher .receive(on: serialDispatchQueue) @@ -155,20 +158,24 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { MXLog.info("\(name): Finished applying \(diffs.count) diffs, new room list \(rooms.compactMap { $0.id ?? "Empty" })") } - private func fetchLastMessage(roomListItem: RoomListItemProtocol) -> EventTimelineItem? { + private func fetchRoomInfo(roomListItem: RoomListItemProtocol) -> RoomInfo? { class FetchResult { - var latestRoomEvent: EventTimelineItem? + var roomInfo: RoomInfo? } let semaphore = DispatchSemaphore(value: 0) let result = FetchResult() Task { - result.latestRoomEvent = await roomListItem.latestEvent() + do { + result.roomInfo = try await roomListItem.roomInfo() + } catch { + MXLog.error("Failed fetching room info with error: \(error)") + } semaphore.signal() } semaphore.wait() - return result.latestRoomEvent + return result.roomInfo } private func buildRoomSummaryForIdentifier(_ identifier: String, invalidated: Bool) -> RoomSummary { @@ -177,24 +184,34 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { return .empty } + guard let roomInfo = fetchRoomInfo(roomListItem: roomListItem) else { + return .empty + } + var attributedLastMessage: AttributedString? var lastMessageFormattedTimestamp: String? - - if let latestRoomMessage = fetchLastMessage(roomListItem: roomListItem) { + + if let latestRoomMessage = roomInfo.latestEvent { let lastMessage = EventTimelineItemProxy(item: latestRoomMessage, id: 0) lastMessageFormattedTimestamp = lastMessage.timestamp.formattedMinimal() attributedLastMessage = eventStringBuilder.buildAttributedString(for: lastMessage) } - - let details = RoomSummaryDetails(id: roomListItem.id(), - name: roomListItem.name() ?? roomListItem.id(), - isDirect: roomListItem.isDirect(), - avatarURL: roomListItem.avatarUrl().flatMap(URL.init(string:)), + + var inviterProxy: RoomMemberProxyProtocol? + if let inviter = roomInfo.inviter { + inviterProxy = RoomMemberProxy(member: inviter, backgroundTaskService: backgroundTaskService) + } + + let details = RoomSummaryDetails(id: roomInfo.id, + name: roomInfo.name ?? roomInfo.id, + isDirect: roomInfo.isDirect, + avatarURL: roomInfo.avatarUrl.flatMap(URL.init(string:)), lastMessage: attributedLastMessage, lastMessageFormattedTimestamp: lastMessageFormattedTimestamp, - unreadNotificationCount: UInt(roomListItem.unreadNotifications().notificationCount()), - canonicalAlias: roomListItem.canonicalAlias()) - + unreadNotificationCount: UInt(roomInfo.notificationCount), + canonicalAlias: roomInfo.canonicalAlias, + inviter: inviterProxy) + return invalidated ? .invalidated(details: details) : .filled(details: details) } diff --git a/project.yml b/project.yml index cdcbf8d43..16875fec0 100644 --- a/project.yml +++ b/project.yml @@ -45,7 +45,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/matrix-org/matrix-rust-components-swift - exactVersion: 1.0.107-alpha + exactVersion: 1.0.108-alpha # path: ../matrix-rust-sdk DesignKit: path: DesignKit