From 86f978a439a16780e61300baef15c6636f683463 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 3 Sep 2025 12:32:11 +0100 Subject: [PATCH] Stop storing the openURL action to fix a retain cycle. Uses the existing timeline action instead. --- .../Sources/Screens/Timeline/TimelineModels.swift | 4 ---- .../Sources/Screens/Timeline/TimelineViewModel.swift | 12 +++++------- .../Sources/Screens/Timeline/View/TimelineView.swift | 5 ----- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/ElementX/Sources/Screens/Timeline/TimelineModels.swift b/ElementX/Sources/Screens/Timeline/TimelineModels.swift index 4f1918f02..1c9b1141b 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineModels.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineModels.swift @@ -77,7 +77,6 @@ enum TimelineViewAction { case hasSwitchedTimeline case hasScrolled(direction: ScrollDirection) - case setOpenURLAction(OpenURLAction) case displayPredecessorRoom } @@ -117,9 +116,6 @@ struct TimelineViewState: BindableState { // It's updated from the room info, so it's faster than using the timeline var pinnedEventIDs: Set = [] - /// an openURL closure which opens URLs first using the App's environment rather than skipping out to external apps - var openURL: OpenURLAction? - /// A closure providing the associated audio player state for an item in the timeline. var audioPlayerStateProvider: (@MainActor (_ itemId: TimelineItemIdentifier) -> AudioPlayerState?)? diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index 84634ec68..2bb667c72 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -202,8 +202,6 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { Task { state.timelineState.isSwitchingTimelines = false } case let .hasScrolled(direction): actionsSubject.send(.hasScrolled(direction: direction)) - case .setOpenURLAction(let action): - state.openURL = action case .displayPredecessorRoom: guard let predecessorID = roomProxy.predecessorRoom?.roomId else { fatalError("Predecessor room should exist if this action is triggered.") @@ -656,15 +654,15 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { return nil } - private func handleJoinCommand(message: String) { + private func handleJoinCommand(message: String) async { guard let alias = String(message.dropFirst(SlashCommand.join.rawValue.count)) .components(separatedBy: .whitespacesAndNewlines) .first, - let urlString = try? matrixToRoomAliasPermalink(roomAlias: alias), - let url = URL(string: urlString) else { + case let .success(resolvedAlias) = await userSession.clientProxy.resolveRoomAlias(alias) else { return } - state.openURL?(url) + + actionsSubject.send(.displayRoom(roomID: resolvedAlias.roomId, via: resolvedAlias.servers)) } private func sendCurrentMessage(_ message: String, html: String?, mode: ComposerMode, intentionalMentions: IntentionalMentions) async { @@ -694,7 +692,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { case .default: switch slashCommand(message: message) { case .join: - handleJoinCommand(message: message) + await handleJoinCommand(message: message) case .none: await timelineController.sendMessage(message, html: html, diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineView.swift index d16f5359c..956099e0b 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineView.swift @@ -70,16 +70,11 @@ struct TimelineView: View { /// A table view wrapper that displays the timeline of a room. struct TimelineViewRepresentable: UIViewControllerRepresentable { @EnvironmentObject private var viewModelContext: TimelineViewModel.Context - @Environment(\.openURL) var openURL func makeUIViewController(context: Context) -> TimelineTableViewController { let tableViewController = TimelineTableViewController(coordinator: context.coordinator, isScrolledToBottom: $viewModelContext.isScrolledToBottom, scrollToBottomPublisher: viewModelContext.viewState.timelineState.scrollToBottomPublisher) - // Needs to be dispatched on main asynchronously otherwise we get a runtime warning - DispatchQueue.main.async { - viewModelContext.send(viewAction: .setOpenURLAction(openURL)) - } return tableViewController }