mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Fix some concurrency warnings, update missed licence headers. (#3741)
* Switch the TimelineController to an async sequence and fix the warnings on the UserIndicatorController
This commit is contained in:
parent
f86a5a2bb9
commit
922ebf47e6
@ -1,3 +1,3 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
|
command -v git-lfs >/dev/null 2>&1 || { printf >&2 "\n%s\n\n" "This repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks')."; exit 2; }
|
||||||
git lfs pre-push "$@"
|
git lfs pre-push "$@"
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
@MainActor
|
||||||
protocol AppCoordinatorProtocol: CoordinatorProtocol {
|
protocol AppCoordinatorProtocol: CoordinatorProtocol {
|
||||||
var windowManager: SecureWindowManagerProtocol { get }
|
var windowManager: SecureWindowManagerProtocol { get }
|
||||||
|
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2024 New Vector Ltd
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
// you may not use this file except in compliance with the License.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
// 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 Combine
|
import Combine
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// Represents a specific portion of the ViewState that can be bound to with SwiftUI's [2-way binding](https://developer.apple.com/documentation/swiftui/binding).
|
/// Represents a specific portion of the ViewState that can be bound to with SwiftUI's [2-way binding](https://developer.apple.com/documentation/swiftui/binding).
|
||||||
|
@MainActor
|
||||||
protocol BindableState {
|
protocol BindableState {
|
||||||
/// The associated type of the Bindable State. Defaults to Void.
|
/// The associated type of the Bindable State. Defaults to Void.
|
||||||
associatedtype BindStateType = Void
|
associatedtype BindStateType = Void
|
||||||
|
@ -5,10 +5,11 @@
|
|||||||
// Please see LICENSE files in the repository root for full details.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol {
|
class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol {
|
||||||
private var dismissalTimer: Timer?
|
private var timerCancellable: AnyCancellable?
|
||||||
private var displayTimes = [String: Date]()
|
private var displayTimes = [String: Date]()
|
||||||
private var delayedIndicators = Set<String>()
|
private var delayedIndicators = Set<String>()
|
||||||
|
|
||||||
@ -21,10 +22,11 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol
|
|||||||
activeIndicator = indicatorQueue.last
|
activeIndicator = indicatorQueue.last
|
||||||
|
|
||||||
if let activeIndicator, !activeIndicator.persistent {
|
if let activeIndicator, !activeIndicator.persistent {
|
||||||
dismissalTimer?.invalidate()
|
timerCancellable?.cancel()
|
||||||
dismissalTimer = Timer.scheduledTimer(withTimeInterval: nonPersistentDisplayDuration, repeats: false) { [weak self] _ in
|
timerCancellable = Task { [weak self, nonPersistentDisplayDuration] in
|
||||||
|
try await Task.sleep(for: .seconds(nonPersistentDisplayDuration))
|
||||||
self?.retractIndicatorWithId(activeIndicator.id)
|
self?.retractIndicatorWithId(activeIndicator.id)
|
||||||
}
|
}.asCancellable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,8 +49,8 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol
|
|||||||
if let delay {
|
if let delay {
|
||||||
delayedIndicators.insert(indicator.id)
|
delayedIndicators.insert(indicator.id)
|
||||||
|
|
||||||
Timer.scheduledTimer(withTimeInterval: delay.seconds, repeats: false) { [weak self] _ in
|
Task {
|
||||||
guard let self else { return }
|
try await Task.sleep(for: .seconds(delay.seconds))
|
||||||
|
|
||||||
guard delayedIndicators.contains(indicator.id) else {
|
guard delayedIndicators.contains(indicator.id) else {
|
||||||
return
|
return
|
||||||
@ -76,9 +78,10 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer.scheduledTimer(withTimeInterval: minimumDisplayDuration, repeats: false) { [weak self] _ in
|
Task {
|
||||||
self?.indicatorQueue.removeAll { $0.id == id }
|
try? await Task.sleep(for: .seconds(minimumDisplayDuration))
|
||||||
self?.displayTimes[id] = nil
|
indicatorQueue.removeAll { $0.id == id }
|
||||||
|
displayTimes[id] = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
|
@MainActor
|
||||||
protocol SoftLogoutScreenViewModelProtocol {
|
protocol SoftLogoutScreenViewModelProtocol {
|
||||||
var actions: AnyPublisher<SoftLogoutScreenViewModelAction, Never> { get }
|
var actions: AnyPublisher<SoftLogoutScreenViewModelAction, Never> { get }
|
||||||
var context: SoftLogoutScreenViewModelType.Context { get }
|
var context: SoftLogoutScreenViewModelType.Context { get }
|
||||||
|
@ -81,7 +81,7 @@ struct WebRegistrationWebView: UIViewRepresentable {
|
|||||||
webView.load(URLRequest(url: url))
|
webView.load(URLRequest(url: url))
|
||||||
}
|
}
|
||||||
|
|
||||||
nonisolated func userContentController(_ userContentController: WKUserContentController,
|
func userContentController(_ userContentController: WKUserContentController,
|
||||||
didReceive message: WKScriptMessage) {
|
didReceive message: WKScriptMessage) {
|
||||||
guard let jsonString = message.body as? String, let jsonData = jsonString.data(using: .utf8) else {
|
guard let jsonString = message.body as? String, let jsonData = jsonString.data(using: .utf8) else {
|
||||||
MXLog.error("Unexpected response.")
|
MXLog.error("Unexpected response.")
|
||||||
@ -94,7 +94,7 @@ struct WebRegistrationWebView: UIViewRepresentable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MXLog.info("Received login credentials.")
|
MXLog.info("Received login credentials.")
|
||||||
Task { await viewModelContext.send(viewAction: .signedIn(credentials)) }
|
viewModelContext.send(viewAction: .signedIn(credentials))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: WKUIDelegate
|
// MARK: WKUIDelegate
|
||||||
@ -120,7 +120,7 @@ struct WebRegistrationWebView: UIViewRepresentable {
|
|||||||
|
|
||||||
// MARK: WKScriptMessageHandler
|
// MARK: WKScriptMessageHandler
|
||||||
|
|
||||||
nonisolated func userContentController(_ userContentController: WKUserContentController,
|
func userContentController(_ userContentController: WKUserContentController,
|
||||||
didReceive message: WKScriptMessage) {
|
didReceive message: WKScriptMessage) {
|
||||||
coordinator?.userContentController(userContentController, didReceive: message)
|
coordinator?.userContentController(userContentController, didReceive: message)
|
||||||
}
|
}
|
||||||
|
@ -152,11 +152,8 @@ private struct CallView: UIViewRepresentable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nonisolated func userContentController(_ userContentController: WKUserContentController,
|
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||||
didReceive message: WKScriptMessage) {
|
viewModelContext?.javaScriptMessageHandler?(message.body)
|
||||||
Task { @MainActor [weak self] in
|
|
||||||
self?.viewModelContext?.javaScriptMessageHandler?(message.body)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - WKUIDelegate
|
// MARK: - WKUIDelegate
|
||||||
@ -191,11 +188,9 @@ private struct CallView: UIViewRepresentable {
|
|||||||
return .cancel
|
return .cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
nonisolated func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||||
Task { @MainActor in
|
|
||||||
viewModelContext?.send(viewAction: .urlChanged(webView.url))
|
viewModelContext?.send(viewAction: .urlChanged(webView.url))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Picture in Picture
|
// MARK: - Picture in Picture
|
||||||
|
|
||||||
@ -271,8 +266,7 @@ private struct CallView: UIViewRepresentable {
|
|||||||
|
|
||||||
// MARK: - WKScriptMessageHandler
|
// MARK: - WKScriptMessageHandler
|
||||||
|
|
||||||
nonisolated func userContentController(_ userContentController: WKUserContentController,
|
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||||
didReceive message: WKScriptMessage) {
|
|
||||||
coordinator?.userContentController(userContentController, didReceive: message)
|
coordinator?.userContentController(userContentController, didReceive: message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
protocol ReportContentScreenViewModelProtocol {
|
protocol ReportContentScreenViewModelProtocol {
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2022 New Vector Ltd
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
// you may not use this file except in compliance with the License.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
// 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 Combine
|
import Combine
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2022 New Vector Ltd
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
// you may not use this file except in compliance with the License.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
// 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 Foundation
|
import Foundation
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2022 New Vector Ltd
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
// you may not use this file except in compliance with the License.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
// 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 Combine
|
import Combine
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2022 New Vector Ltd
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
// you may not use this file except in compliance with the License.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
// 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 Combine
|
import Combine
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2022 New Vector Ltd
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
// you may not use this file except in compliance with the License.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
// 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 Compound
|
import Compound
|
||||||
|
@ -9,6 +9,7 @@ import Combine
|
|||||||
import WysiwygComposer
|
import WysiwygComposer
|
||||||
|
|
||||||
// periphery: ignore - markdown protocol
|
// periphery: ignore - markdown protocol
|
||||||
|
@MainActor
|
||||||
protocol ComposerToolbarViewModelProtocol {
|
protocol ComposerToolbarViewModelProtocol {
|
||||||
var actions: AnyPublisher<ComposerToolbarViewModelAction, Never> { get }
|
var actions: AnyPublisher<ComposerToolbarViewModelAction, Never> { get }
|
||||||
var context: ComposerToolbarViewModelType.Context { get }
|
var context: ComposerToolbarViewModelType.Context { get }
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
|
||||||
|
|
||||||
|
@MainActor
|
||||||
protocol RoomScreenViewModelProtocol {
|
protocol RoomScreenViewModelProtocol {
|
||||||
var actions: AnyPublisher<RoomScreenViewModelAction, Never> { get }
|
var actions: AnyPublisher<RoomScreenViewModelAction, Never> { get }
|
||||||
var context: RoomScreenViewModel.Context { get }
|
var context: RoomScreenViewModel.Context { get }
|
||||||
|
@ -324,16 +324,12 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
// Note: This isn't strictly necessary now given the unwrap above, but leaving the code as
|
// Note: This isn't strictly necessary now given the unwrap above, but leaving the code as
|
||||||
// documentation. SE-0371 will allow us to fix this by using an async deinit.
|
// documentation. SE-0371 will allow us to fix this by using an async deinit.
|
||||||
Task { [syncService] in
|
Task { [syncService] in
|
||||||
do {
|
|
||||||
defer {
|
defer {
|
||||||
completion?()
|
completion?()
|
||||||
}
|
}
|
||||||
|
|
||||||
try await syncService.stop()
|
await syncService.stop()
|
||||||
MXLog.info("Sync stopped")
|
MXLog.info("Sync stopped")
|
||||||
} catch {
|
|
||||||
MXLog.error("Failed stopping the sync service with error: \(error)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ final class ComposerDraftService: ComposerDraftServiceProtocol {
|
|||||||
func getReply(eventID: String) async -> Result<TimelineItemReply, ComposerDraftServiceError> {
|
func getReply(eventID: String) async -> Result<TimelineItemReply, ComposerDraftServiceError> {
|
||||||
switch await roomProxy.timeline.getLoadedReplyDetails(eventID: eventID) {
|
switch await roomProxy.timeline.getLoadedReplyDetails(eventID: eventID) {
|
||||||
case .success(let replyDetails):
|
case .success(let replyDetails):
|
||||||
return await .success(timelineItemfactory.buildReply(details: replyDetails))
|
return .success(timelineItemfactory.buildReply(details: replyDetails))
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
MXLog.error("Could not load reply: \(error)")
|
MXLog.error("Could not load reply: \(error)")
|
||||||
return .failure(.failedToLoadReply)
|
return .failure(.failedToLoadReply)
|
||||||
|
@ -62,8 +62,6 @@ class TimelineController: TimelineControllerProtocol {
|
|||||||
activeTimeline = timelineProxy
|
activeTimeline = timelineProxy
|
||||||
activeTimelineProvider = liveTimelineProvider
|
activeTimelineProvider = liveTimelineProvider
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
|
||||||
|
|
||||||
guard let initialFocussedEventID else {
|
guard let initialFocussedEventID else {
|
||||||
configureActiveTimelineProvider()
|
configureActiveTimelineProvider()
|
||||||
return
|
return
|
||||||
@ -148,7 +146,9 @@ class TimelineController: TimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol {
|
if let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol {
|
||||||
fetchEventDetails(for: messageTimelineItem, refetchOnError: true)
|
fetchEventDetails(for: messageTimelineItem,
|
||||||
|
refetchOnError: true,
|
||||||
|
activeTimeline: activeTimeline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,35 +375,37 @@ class TimelineController: TimelineControllerProtocol {
|
|||||||
paginationState = PaginationState(backward: .paginating, forward: .paginating)
|
paginationState = PaginationState(backward: .paginating, forward: .paginating)
|
||||||
callbacks.send(.isLive(activeTimelineProvider.kind == .live))
|
callbacks.send(.isLive(activeTimelineProvider.kind == .live))
|
||||||
|
|
||||||
updateTimelineItemsCancellable = activeTimelineProvider
|
updateTimelineItemsCancellable = Task { [weak self, activeTimelineProvider] in
|
||||||
.updatePublisher
|
let contentSizeChangePublisher = NotificationCenter.default.publisher(for: UIContentSizeCategory.didChangeNotification)
|
||||||
.receive(on: serialDispatchQueue)
|
let timelineUpdates = activeTimelineProvider.updatePublisher.merge(with: contentSizeChangePublisher.map { _ in
|
||||||
.sink { [weak self] items, paginationState in
|
(activeTimelineProvider.itemProxies, activeTimelineProvider.paginationState)
|
||||||
self?.updateTimelineItems(itemProxies: items, paginationState: paginationState)
|
})
|
||||||
|
|
||||||
|
for await (items, paginationState) in timelineUpdates.values {
|
||||||
|
await self?.updateTimelineItems(itemProxies: items, paginationState: paginationState)
|
||||||
}
|
}
|
||||||
|
}.asCancellable()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func contentSizeCategoryDidChange() {
|
private func updateTimelineItems(itemProxies: [TimelineItemProxy], paginationState: PaginationState) async {
|
||||||
// Recompute all attributed strings on content size changes -> DynamicType support
|
|
||||||
serialDispatchQueue.async { [activeTimelineProvider] in
|
|
||||||
self.updateTimelineItems(itemProxies: activeTimelineProvider.itemProxies, paginationState: activeTimelineProvider.paginationState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateTimelineItems(itemProxies: [TimelineItemProxy], paginationState: PaginationState) {
|
|
||||||
var newTimelineItems = [RoomTimelineItemProtocol]()
|
|
||||||
|
|
||||||
let isNewTimeline = isSwitchingTimelines
|
let isNewTimeline = isSwitchingTimelines
|
||||||
isSwitchingTimelines = false
|
isSwitchingTimelines = false
|
||||||
|
|
||||||
let collapsibleChunks = itemProxies.groupBy { isItemCollapsible($0) }
|
let isDM = roomProxy.isDirectOneToOneRoom
|
||||||
|
|
||||||
|
var newTimelineItems = await Task.detached { [timelineItemFactory, activeTimeline] in
|
||||||
|
var newTimelineItems = [RoomTimelineItemProtocol]()
|
||||||
|
|
||||||
|
let collapsibleChunks = itemProxies.groupBy { $0.isItemCollapsible }
|
||||||
|
|
||||||
for (index, collapsibleChunk) in collapsibleChunks.enumerated() {
|
for (index, collapsibleChunk) in collapsibleChunks.enumerated() {
|
||||||
let isLastItem = index == collapsibleChunks.indices.last
|
let isLastItem = index == collapsibleChunks.indices.last
|
||||||
|
|
||||||
let items = collapsibleChunk.compactMap { itemProxy in
|
let items = collapsibleChunk.compactMap { itemProxy in
|
||||||
|
let timelineItem = self.buildTimelineItem(for: itemProxy,
|
||||||
let timelineItem = buildTimelineItem(for: itemProxy)
|
isDM: isDM,
|
||||||
|
timelineItemFactory: timelineItemFactory,
|
||||||
|
activeTimeline: activeTimeline)
|
||||||
|
|
||||||
return timelineItem
|
return timelineItem
|
||||||
}
|
}
|
||||||
@ -425,6 +427,9 @@ class TimelineController: TimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return newTimelineItems
|
||||||
|
}.value
|
||||||
|
|
||||||
// Check if we need to add anything to the top of the timeline.
|
// Check if we need to add anything to the top of the timeline.
|
||||||
switch paginationState.backward {
|
switch paginationState.backward {
|
||||||
case .timelineEndReached:
|
case .timelineEndReached:
|
||||||
@ -445,23 +450,26 @@ class TimelineController: TimelineControllerProtocol {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
timelineItems = newTimelineItems
|
timelineItems = newTimelineItems
|
||||||
}
|
|
||||||
|
|
||||||
callbacks.send(.updatedTimelineItems(timelineItems: newTimelineItems, isSwitchingTimelines: isNewTimeline))
|
callbacks.send(.updatedTimelineItems(timelineItems: newTimelineItems, isSwitchingTimelines: isNewTimeline))
|
||||||
self.paginationState = paginationState
|
self.paginationState = paginationState
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildTimelineItem(for itemProxy: TimelineItemProxy) -> RoomTimelineItemProtocol? {
|
private nonisolated func buildTimelineItem(for itemProxy: TimelineItemProxy,
|
||||||
|
isDM: Bool,
|
||||||
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
|
activeTimeline: TimelineProxyProtocol) -> RoomTimelineItemProtocol? {
|
||||||
switch itemProxy {
|
switch itemProxy {
|
||||||
case .event(let eventTimelineItem):
|
case .event(let eventTimelineItem):
|
||||||
let timelineItem = timelineItemFactory.buildTimelineItem(for: eventTimelineItem, isDM: roomProxy.isDirectOneToOneRoom)
|
let timelineItem = timelineItemFactory.buildTimelineItem(for: eventTimelineItem, isDM: isDM)
|
||||||
|
|
||||||
if let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol {
|
if let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol {
|
||||||
// Avoid fetching this over and over again as it changes states if it keeps failing to load
|
// Avoid fetching this over and over again as it changes states if it keeps failing to load
|
||||||
// Errors will be handled again on appearance
|
// Errors will be handled again on appearance
|
||||||
fetchEventDetails(for: messageTimelineItem, refetchOnError: false)
|
fetchEventDetails(for: messageTimelineItem,
|
||||||
|
refetchOnError: false,
|
||||||
|
activeTimeline: activeTimeline)
|
||||||
}
|
}
|
||||||
|
|
||||||
return timelineItem
|
return timelineItem
|
||||||
@ -478,20 +486,9 @@ class TimelineController: TimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func isItemCollapsible(_ item: TimelineItemProxy) -> Bool {
|
private nonisolated func fetchEventDetails(for timelineItem: EventBasedMessageTimelineItemProtocol,
|
||||||
if case let .event(eventItem) = item {
|
refetchOnError: Bool,
|
||||||
switch eventItem.content {
|
activeTimeline: TimelineProxyProtocol) {
|
||||||
case .profileChange, .roomMembership, .state:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private func fetchEventDetails(for timelineItem: EventBasedMessageTimelineItemProtocol, refetchOnError: Bool) {
|
|
||||||
guard let eventID = timelineItem.id.eventID else {
|
guard let eventID = timelineItem.id.eventID else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -524,3 +521,18 @@ class TimelineController: TimelineControllerProtocol {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension TimelineItemProxy {
|
||||||
|
var isItemCollapsible: Bool {
|
||||||
|
if case let .event(eventItem) = self {
|
||||||
|
switch eventItem.content {
|
||||||
|
case .profileChange, .roomMembership, .state:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,7 +9,6 @@ import Foundation
|
|||||||
|
|
||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
@MainActor
|
|
||||||
protocol RoomTimelineItemFactoryProtocol {
|
protocol RoomTimelineItemFactoryProtocol {
|
||||||
func buildTimelineItem(for eventItemProxy: EventTimelineItemProxy, isDM: Bool) -> RoomTimelineItemProtocol?
|
func buildTimelineItem(for eventItemProxy: EventTimelineItemProxy, isDM: Bool) -> RoomTimelineItemProtocol?
|
||||||
func buildReply(details: InReplyToDetails) -> TimelineItemReply
|
func buildReply(details: InReplyToDetails) -> TimelineItemReply
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2022 New Vector Ltd
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
// you may not use this file except in compliance with the License.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
// 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 XCTest
|
import XCTest
|
||||||
|
@ -9,6 +9,7 @@ import XCTest
|
|||||||
|
|
||||||
@testable import ElementX
|
@testable import ElementX
|
||||||
|
|
||||||
|
@MainActor
|
||||||
class ServerConfirmationScreenViewStateTests: XCTestCase {
|
class ServerConfirmationScreenViewStateTests: XCTestCase {
|
||||||
func testLoginMessageString() {
|
func testLoginMessageString() {
|
||||||
let matrixDotOrgLogin = ServerConfirmationScreenViewState(homeserverAddress: LoginHomeserver.mockMatrixDotOrg.address,
|
let matrixDotOrgLogin = ServerConfirmationScreenViewState(homeserverAddress: LoginHomeserver.mockMatrixDotOrg.address,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user