mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Swift 6 Migration (#5733)
* Enable Swift 6.2 with default main actor isolation for the app target Turn on SWIFT_VERSION 6.2, SWIFT_APPROACHABLE_CONCURRENCY and SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor for the ElementX target, matching the NSE and Share Extension (which already use 6.2 without the main actor default). # Conflicts: # ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved * Update the code generation templates for Swift 6 Emit nonisolated/@unchecked Sendable mocks and nonisolated string enums, and mark async mock methods @concurrent so they keep suspending like the real implementations (otherwise intermediate states are unobservable in tests). Includes the regenerated mocks and strings. # Conflicts: # ElementX/Sources/Mocks/Generated/GeneratedMocks.swift * Make the data layer and services nonisolated and Sendable Annotate the value-type models, Rust SDK proxy wrappers, timeline item graph, settings, logging and NSE-shared types as nonisolated, and conform the service protocols to Sendable so their existentials can cross isolation boundaries. These types run off the main actor and no longer inherit the target's default main actor isolation. * Adopt the new isolation across call sites, mocks and signatures Update call sites, closure signatures and mock configuration helpers to match the nonisolated/Sendable data layer, including the main actor isolated mock inits and the @Sendable filter on recentlyVisitedRooms. ^ Conflicts: ^ ElementX/Sources/Services/SecureBackup/SecureBackupController.swift * Remove redundant @MainActor annotations With the app target defaulting to main actor isolation, the explicit @MainActor on view models, coordinators, views and their protocols is no longer required. * Fix concurrency hotspots surfaced by the migration Resolve the room summary main-thread deadlock by moving diff processing onto AsyncStreams, add the SDKListener.onMainActor FIFO bridge for SDK callbacks, hop KVO/timer/UIKit callbacks to the main actor via assumeIsolated, add isolated deinits, and apply the few justified nonisolated(unsafe) escapes for queue-confined Apple APIs. * Fix the unit tests for Swift 6 concurrency Hop the waitForConfirmation body onto the caller's actor (Swift Testing runs confirmation closures off-actor), and adjust the affected suites and async signatures. * offload diffs on concurrent threads instead of main actor * make room info protocols sendable * use a task group for fetching data so that everything runs concurrently in the TextRoomTimelineView when getting the preview. * use the generic onMainActor implementation, which already makes a stream and send the continuation on the main actor. * small pr comments issues being addressed * update enterprise * Update StaticCode to 1.1.0 and specify the required Swift isolation. * fix ElementCallServiceTests * update enterprise * re-generate mocks * update enterprise * enterprise new code conformed to swift 6 --------- Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol AppLockScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<AppLockScreenViewModelAction, Never> { get }
|
||||
var context: AppLockScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol AppLockSetupBiometricsScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<AppLockSetupBiometricsScreenViewModelAction, Never> { get }
|
||||
var context: AppLockSetupBiometricsScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol AppLockSetupPINScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<AppLockSetupPINScreenViewModelAction, Never> { get }
|
||||
var context: AppLockSetupPINScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol AppLockSetupSettingsScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<AppLockSetupSettingsScreenViewModelAction, Never> { get }
|
||||
var context: AppLockSetupSettingsScreenViewModelType.Context { get }
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
/// The supported forms of login that a homeserver allows.
|
||||
enum LoginMode: Equatable {
|
||||
nonisolated enum LoginMode: Equatable {
|
||||
/// The login mode hasn't been determined yet.
|
||||
case unknown
|
||||
/// The homeserver supports login via OpenID Connect.
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol LoginScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<LoginScreenViewModelAction, Never> { get }
|
||||
var context: LoginScreenViewModelType.Context { get }
|
||||
|
||||
@@ -13,7 +13,6 @@ import AuthenticationServices
|
||||
/// In certain instances the URL may require opening an external app instead of using a WAS. Because of this
|
||||
/// it is recommended to not encode the OAuth authentication within any state machines, as there is no guarantee
|
||||
/// that any cancellations/failures will be communicated upwards.
|
||||
@MainActor
|
||||
class OAuthAuthenticationPresenter: NSObject {
|
||||
private let authenticationService: AuthenticationServiceProtocol
|
||||
private let redirectURL: URL
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol ServerConfirmationScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<ServerConfirmationScreenViewModelAction, Never> { get }
|
||||
var context: ServerConfirmationScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol ServerSelectionScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<ServerSelectionScreenViewModelAction, Never> { get }
|
||||
var context: ServerSelectionScreenViewModelType.Context { get }
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ enum MockSoftLogoutScreenState: String, CaseIterable {
|
||||
case keyBackupNeeded
|
||||
|
||||
/// Generate the view struct for the screen state.
|
||||
@MainActor var viewModel: SoftLogoutScreenViewModel {
|
||||
var viewModel: SoftLogoutScreenViewModel {
|
||||
let credentials = SoftLogoutScreenCredentials(userID: "@mock:matrix.org",
|
||||
homeserverName: "matrix.org",
|
||||
userDisplayName: "mock",
|
||||
|
||||
+4
-4
@@ -106,7 +106,7 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
|
||||
private static let loadingIndicatorIdentifier = "\(SoftLogoutScreenCoordinator.self)-Loading"
|
||||
|
||||
/// Show an activity indicator whilst loading.
|
||||
@MainActor private func startLoading() {
|
||||
private func startLoading() {
|
||||
parameters.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
|
||||
type: .modal,
|
||||
title: L10n.commonLoading,
|
||||
@@ -114,17 +114,17 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
|
||||
/// Hide the currently displayed activity indicator.
|
||||
@MainActor private func stopLoading() {
|
||||
private func stopLoading() {
|
||||
parameters.userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
|
||||
}
|
||||
|
||||
/// Shows the forgot password screen.
|
||||
@MainActor private func showForgotPasswordScreen() {
|
||||
private func showForgotPasswordScreen() {
|
||||
viewModel.displayError(.alert("Not implemented."))
|
||||
}
|
||||
|
||||
/// Login with the supplied username and password.
|
||||
@MainActor private func login(withPassword password: String) {
|
||||
private func login(withPassword password: String) {
|
||||
let username = parameters.credentials.userID
|
||||
|
||||
startLoading()
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ class SoftLogoutScreenViewModel: SoftLogoutScreenViewModelType, SoftLogoutScreen
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor func displayError(_ type: SoftLogoutScreenErrorType) {
|
||||
func displayError(_ type: SoftLogoutScreenErrorType) {
|
||||
switch type {
|
||||
case .alert(let message):
|
||||
state.bindings.alertInfo = AlertInfo(id: type,
|
||||
|
||||
+1
-2
@@ -8,11 +8,10 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol SoftLogoutScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<SoftLogoutScreenViewModelAction, Never> { get }
|
||||
var context: SoftLogoutScreenViewModelType.Context { get }
|
||||
|
||||
/// Display an error to the user.
|
||||
@MainActor func displayError(_ type: SoftLogoutScreenErrorType)
|
||||
func displayError(_ type: SoftLogoutScreenErrorType)
|
||||
}
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol AuthenticationStartScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<AuthenticationStartScreenViewModelAction, Never> { get }
|
||||
var context: AuthenticationStartScreenViewModelType.Context { get }
|
||||
|
||||
@@ -69,20 +69,28 @@ class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsers
|
||||
state.blockedUsers = await withTaskGroup(of: UserProfileProxy.self) { group in
|
||||
for userID in blockedUsers {
|
||||
group.addTask {
|
||||
switch await self.clientProxy.profile(for: userID) {
|
||||
case .success(let profile): profile
|
||||
case .failure: UserProfileProxy(userID: userID)
|
||||
}
|
||||
await self.profile(for: userID)
|
||||
}
|
||||
}
|
||||
|
||||
return await group.reduce(into: []) { partialResult, profile in
|
||||
partialResult.append(profile)
|
||||
var profiles = [UserProfileProxy]()
|
||||
for await profile in group {
|
||||
profiles.append(profile)
|
||||
}
|
||||
return profiles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The client proxy isn't Sendable, fetch through this helper so that it
|
||||
/// never leaves the main actor when running calls in parallel.
|
||||
private func profile(for userID: String) async -> UserProfileProxy {
|
||||
switch await clientProxy.profile(for: userID) {
|
||||
case .success(let profile): profile
|
||||
case .failure: UserProfileProxy(userID: userID)
|
||||
}
|
||||
}
|
||||
|
||||
private func unblockUser(_ user: UserProfileProxy) {
|
||||
showLoadingIndicator()
|
||||
state.processingUserID = user.userID
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol BlockedUsersScreenViewModelProtocol {
|
||||
var context: BlockedUsersScreenViewModelType.Context { get }
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol BugReportScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<BugReportScreenViewModelAction, Never> { get }
|
||||
var context: BugReportScreenViewModelType.Context { get }
|
||||
|
||||
@@ -100,7 +100,10 @@ struct BugReportScreen: View {
|
||||
PhotosPicker(selection: $selectedScreenshot,
|
||||
matching: .screenshots,
|
||||
photoLibrary: .shared()) {
|
||||
ListRowLabel.plain(title: photosPickerTitle)
|
||||
// The label builder isn't isolated in SwiftUI's signature but is only ever called on the main actor.
|
||||
MainActor.assumeIsolated {
|
||||
ListRowLabel.plain(title: photosPickerTitle)
|
||||
}
|
||||
}
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.attachScreenshot)
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol CallScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<CallScreenViewModelAction, Never> { get }
|
||||
var context: CallScreenViewModelType.Context { get }
|
||||
|
||||
@@ -71,7 +71,6 @@ private struct CallView: UIViewRepresentable {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate, AVPictureInPictureControllerDelegate {
|
||||
private weak var viewModelContext: CallScreenViewModel.Context?
|
||||
|
||||
@@ -168,6 +167,9 @@ private struct CallView: UIViewRepresentable {
|
||||
if let error {
|
||||
continuaton.resume(throwing: error)
|
||||
} else {
|
||||
// The completion is called on the main thread which the continuation
|
||||
// also resumes on, so the result never actually crosses threads.
|
||||
nonisolated(unsafe) let result = result
|
||||
continuaton.resume(returning: result)
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -7,7 +7,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol ChatsSpaceFiltersScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<ChatsSpaceFiltersScreenViewModelAction, Never> { get }
|
||||
var context: ChatsSpaceFiltersScreenViewModelType.Context { get }
|
||||
|
||||
@@ -49,7 +49,7 @@ struct ChatsSpaceFilterCell: View {
|
||||
.padding(.horizontal, horizontalInsets)
|
||||
}
|
||||
|
||||
@ViewBuilder @MainActor
|
||||
@ViewBuilder
|
||||
private var avatar: some View {
|
||||
if dynamicTypeSize < .accessibility3 {
|
||||
RoomAvatarImage(avatar: filter.room.avatar,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol PollFormScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<PollFormScreenViewModelAction, Never> { get }
|
||||
var context: PollFormScreenViewModelType.Context { get }
|
||||
|
||||
@@ -148,7 +148,7 @@ struct PollFormScreen: View {
|
||||
}
|
||||
|
||||
private extension View {
|
||||
@MainActor @ViewBuilder
|
||||
@ViewBuilder
|
||||
func trackAnalyticsIfNeeded(context: PollFormScreenViewModel.Context) -> some View {
|
||||
switch context.viewState.mode {
|
||||
case .edit:
|
||||
|
||||
@@ -303,9 +303,9 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen
|
||||
|
||||
private func addRoomToSpace(roomProxy: JoinedRoomProxyProtocol, spaceID: String) async {
|
||||
roomProxy.subscribeToRoomInfoUpdates()
|
||||
let runner = ExpiringTaskRunner {
|
||||
let runner = ExpiringTaskRunner { @MainActor in
|
||||
// Necessary to build the room cache so that the space can be added as a parent.
|
||||
_ = await roomProxy.infoPublisher.values.first { $0.powerLevels != nil }
|
||||
_ = await roomProxy.infoPublisher.values.first { @Sendable in $0.powerLevels != nil }
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
protocol CreateRoomScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<CreateRoomScreenViewModelAction, Never> { get }
|
||||
var context: CreateRoomScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol DeclineAndBlockScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<DeclineAndBlockScreenViewModelAction, Never> { get }
|
||||
var context: DeclineAndBlockScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol EditRoomAddressScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<EditRoomAddressScreenViewModelAction, Never> { get }
|
||||
var context: EditRoomAddressScreenViewModelType.Context { get }
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol EmojiPickerScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<EmojiPickerScreenViewModelAction, Never> { get }
|
||||
var context: EmojiPickerScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol EncryptionResetPasswordScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<EncryptionResetPasswordScreenViewModelAction, Never> { get }
|
||||
var context: EncryptionResetPasswordScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol EncryptionResetScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<EncryptionResetScreenViewModelAction, Never> { get }
|
||||
var context: EncryptionResetScreenViewModelType.Context { get }
|
||||
|
||||
+4
-1
@@ -153,7 +153,10 @@ class TimelineMediaPreviewController: QLPreviewController {
|
||||
// Ridiculous hack to undo the controller's attempt to replace our info button with the list button.
|
||||
if barButtonTimer == nil {
|
||||
barButtonTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
|
||||
self?.updateBarButtons()
|
||||
// The timer is scheduled on the main run loop so it always fires on the main actor.
|
||||
MainActor.assumeIsolated {
|
||||
self?.updateBarButtons()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ enum GlobalSearchControllerAction {
|
||||
case select(roomID: String)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class GlobalSearchScreenCoordinator: CoordinatorProtocol {
|
||||
private let viewModel: GlobalSearchScreenViewModelProtocol
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol GlobalSearchScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<GlobalSearchScreenViewModelAction, Never> { get }
|
||||
var context: GlobalSearchScreenViewModelType.Context { get }
|
||||
|
||||
@@ -24,7 +24,7 @@ struct GlobalSearchScreenListRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder @MainActor
|
||||
@ViewBuilder
|
||||
var avatar: some View {
|
||||
if dynamicTypeSize < .accessibility3 {
|
||||
RoomAvatarImage(avatar: room.avatar,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol HomeScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<HomeScreenViewModelAction, Never> { get }
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import Combine
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
struct HomeScreenInviteCell: View {
|
||||
@Environment(\.dynamicTypeSize) var dynamicTypeSize
|
||||
|
||||
@@ -209,7 +208,6 @@ struct HomeScreenInviteCell_Previews: PreviewProvider, TestablePreview {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private extension HomeScreenRoom {
|
||||
static var dmInvite: HomeScreenRoom {
|
||||
let inviter = RoomMemberProxyMock()
|
||||
|
||||
@@ -10,7 +10,6 @@ import Combine
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
struct HomeScreenKnockedCell: View {
|
||||
@Environment(\.dynamicTypeSize) var dynamicTypeSize
|
||||
|
||||
@@ -141,7 +140,6 @@ struct HomeScreenKnockedCell_Previews: PreviewProvider, TestablePreview {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private extension HomeScreenRoom {
|
||||
static var dmInvite: HomeScreenRoom {
|
||||
let inviter = RoomMemberProxyMock()
|
||||
|
||||
@@ -44,7 +44,7 @@ struct HomeScreenRoomCell: View {
|
||||
.accessibilityHidden(redactionReasons.contains(.placeholder) ? true : false)
|
||||
}
|
||||
|
||||
@ViewBuilder @MainActor
|
||||
@ViewBuilder
|
||||
private var avatar: some View {
|
||||
if dynamicTypeSize < .accessibility3 {
|
||||
RoomAvatarImage(avatar: room.avatar,
|
||||
@@ -211,10 +211,10 @@ import MatrixRustSDKMocks
|
||||
|
||||
struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
|
||||
static let summaryProviderGeneric = RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))
|
||||
static let genericRooms = summaryProviderGeneric.roomListPublisher.value.compactMap(mockRoom)
|
||||
static let genericRooms = summaryProviderGeneric.roomListPublisher.value.compactMap { mockRoom(summary: $0) }
|
||||
|
||||
static let summaryProviderForNotificationsState = RoomSummaryProviderMock(.init(state: .loaded(.mockRoomsWithNotificationsState)))
|
||||
static let notificationsStateRooms = summaryProviderForNotificationsState.roomListPublisher.value.compactMap(mockRoom)
|
||||
static let notificationsStateRooms = summaryProviderForNotificationsState.roomListPublisher.value.compactMap { mockRoom(summary: $0) }
|
||||
|
||||
static let lastMessageStateRooms = [makeRoom(lastMessageState: .sending), makeRoom(lastMessageState: .failed)]
|
||||
|
||||
|
||||
@@ -156,9 +156,11 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
|
||||
}
|
||||
}
|
||||
|
||||
return await group.first { inviteResult in
|
||||
inviteResult.isFailure
|
||||
} ?? .success(())
|
||||
for await inviteResult in group where inviteResult.isFailure {
|
||||
return inviteResult
|
||||
}
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
guard case .failure = result else {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol InviteUsersScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<InviteUsersScreenViewModelAction, Never> { get }
|
||||
var context: InviteUsersScreenViewModelType.Context { get }
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol JoinRoomScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<JoinRoomScreenViewModelAction, Never> { get }
|
||||
var context: JoinRoomScreenViewModelType.Context { get }
|
||||
|
||||
@@ -386,7 +386,6 @@ struct JoinRoomScreenSpace_Previews: PreviewProvider, TestablePreview {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct JoinRoomScreenPreviewWrapper: Identifiable {
|
||||
let id = UUID()
|
||||
let viewModel: JoinRoomScreenViewModel
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol KnockRequestsListScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<KnockRequestsListScreenViewModelAction, Never> { get }
|
||||
var context: KnockRequestsListScreenViewModelType.Context { get }
|
||||
|
||||
@@ -29,9 +29,9 @@ struct KnockRequestCellInfo: Equatable {
|
||||
struct KnockRequestCell: View {
|
||||
let cellInfo: KnockRequestCellInfo
|
||||
var mediaProvider: MediaProviderProtocol?
|
||||
let onAccept: ((String) -> Void)?
|
||||
let onDecline: ((String) -> Void)?
|
||||
let onDeclineAndBan: ((String) -> Void)?
|
||||
let onAccept: (@MainActor (String) -> Void)?
|
||||
let onDecline: (@MainActor (String) -> Void)?
|
||||
let onDeclineAndBan: (@MainActor (String) -> Void)?
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top, spacing: 16) {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol LabsScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<LabsScreenViewModelAction, Never> { get }
|
||||
var context: LabsScreenViewModelType.Context { get }
|
||||
|
||||
@@ -14,7 +14,7 @@ enum LinkNewDeviceScreenViewModelAction {
|
||||
}
|
||||
|
||||
struct LinkNewDeviceScreenViewState: BindableState {
|
||||
enum Mode: Equatable {
|
||||
nonisolated enum Mode: Equatable {
|
||||
case loading
|
||||
case readyToLink(isGeneratingCode: Bool)
|
||||
case error(QRCodeLoginState.ErrorState)
|
||||
|
||||
@@ -69,13 +69,12 @@ class LinkNewDeviceScreenViewModel: LinkNewDeviceScreenViewModelType, LinkNewDev
|
||||
let progressPublisher = linkNewDeviceService.linkMobileDevice()
|
||||
|
||||
do {
|
||||
_ = try await progressPublisher.values
|
||||
.first { progress in
|
||||
switch progress {
|
||||
case .qrReady: true
|
||||
default: false
|
||||
}
|
||||
var iterator = progressPublisher.values.makeAsyncIterator()
|
||||
while let progress = try await iterator.next(isolation: #isolation) {
|
||||
if case .qrReady = progress {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
actionsSubject.send(.linkMobileDevice(progressPublisher))
|
||||
state.mode = .readyToLink(isGeneratingCode: false)
|
||||
|
||||
-1
@@ -7,7 +7,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol LinkNewDeviceScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<LinkNewDeviceScreenViewModelAction, Never> { get }
|
||||
var context: LinkNewDeviceScreenViewModelType.Context { get }
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol LocationSharingScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<LocationSharingScreenViewModelAction, Never> { get }
|
||||
var context: LocationSharingScreenViewModelType.Context { get }
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol LogViewerScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<LogViewerScreenViewModelAction, Never> { get }
|
||||
var context: LogViewerScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -7,7 +7,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol ManageAuthorizedSpacesScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<ManageAuthorizedSpacesScreenViewModelAction, Never> { get }
|
||||
var context: ManageAuthorizedSpacesScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol ManageRoomMemberSheetViewModelProtocol {
|
||||
var actions: AnyPublisher<ManageRoomMemberSheetViewModelAction, Never> { get }
|
||||
var context: ManageRoomMemberSheetViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol MediaEventsTimelineScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<MediaEventsTimelineScreenViewModelAction, Never> { get }
|
||||
var context: MediaEventsTimelineScreenViewModelType.Context { get }
|
||||
|
||||
+1
-1
@@ -125,7 +125,7 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
||||
}
|
||||
|
||||
private func sendAttachment(mediaInfo: MediaInfo, caption: String?) async -> Result<Void, TimelineControllerError> {
|
||||
let requestHandle: ((SendAttachmentJoinHandleProtocol) -> Void) = { [weak self] handle in
|
||||
let requestHandle: (@MainActor @Sendable (SendAttachmentJoinHandleProtocol) -> Void) = { [weak self] handle in
|
||||
self?.requestHandle = handle
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol MediaUploadPreviewScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<MediaUploadPreviewScreenViewModelAction, Never> { get }
|
||||
var context: MediaUploadPreviewScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol MessageForwardingScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<MessageForwardingScreenViewModelAction, Never> { get }
|
||||
var context: MessageForwardingScreenViewModelType.Context { get }
|
||||
|
||||
@@ -79,7 +79,7 @@ private struct MessageForwardingListRow: View {
|
||||
})
|
||||
}
|
||||
|
||||
@ViewBuilder @MainActor
|
||||
@ViewBuilder
|
||||
var avatar: some View {
|
||||
if dynamicTypeSize < .accessibility3 {
|
||||
RoomAvatarImage(avatar: room.avatar,
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol AnalyticsPromptScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<AnalyticsPromptScreenViewModelAction, Never> { get }
|
||||
var context: AnalyticsPromptScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol IdentityConfirmationScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<IdentityConfirmationScreenViewModelAction, Never> { get }
|
||||
var context: IdentityConfirmationScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -7,7 +7,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol IdentityConfirmedScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<IdentityConfirmedScreenViewModelAction, Never> { get }
|
||||
var context: IdentityConfirmedScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol NotificationPermissionsScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<NotificationPermissionsScreenViewModelAction, Never> { get }
|
||||
var context: NotificationPermissionsScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol SessionVerificationScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<SessionVerificationScreenViewModelAction, Never> { get }
|
||||
var context: SessionVerificationViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol PinnedEventsTimelineScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<PinnedEventsTimelineScreenViewModelAction, Never> { get }
|
||||
var context: PinnedEventsTimelineScreenViewModelType.Context { get }
|
||||
|
||||
@@ -95,7 +95,7 @@ enum QRCodeLoginScreenViewAction {
|
||||
case errorAction(QRCodeErrorView.Action)
|
||||
}
|
||||
|
||||
enum QRCodeLoginState: Equatable {
|
||||
nonisolated enum QRCodeLoginState: Equatable {
|
||||
/// Initial state where the user is informed how to login this device by scanning a QR code.
|
||||
case loginInstructions
|
||||
/// Initial state where the user is informed how to link another device by scanning it's QR code.
|
||||
@@ -114,7 +114,7 @@ enum QRCodeLoginState: Equatable {
|
||||
/// Any full screen error state
|
||||
case error(ErrorState)
|
||||
|
||||
enum ErrorState: Equatable, CaseIterable {
|
||||
nonisolated enum ErrorState: Equatable, CaseIterable {
|
||||
/// The account provider doesn't support the use of QR codes.
|
||||
case notSupported
|
||||
case noCameraPermission
|
||||
@@ -131,7 +131,7 @@ enum QRCodeLoginState: Equatable {
|
||||
case unknown
|
||||
}
|
||||
|
||||
enum ScanningState: Equatable {
|
||||
nonisolated enum ScanningState: Equatable {
|
||||
/// The QR code is scanning.
|
||||
case scanning
|
||||
/// The QR code has been detected and is being processed.
|
||||
@@ -139,7 +139,7 @@ enum QRCodeLoginState: Equatable {
|
||||
/// The QR code was scanned, but an error occurred.
|
||||
case scanFailed(Error)
|
||||
|
||||
enum Error: Equatable {
|
||||
nonisolated enum Error: Equatable {
|
||||
/// The QR code has been processed and is invalid.
|
||||
case invalid
|
||||
/// The QR code has been processed but it is for an account provider that isn't allowed.
|
||||
@@ -171,14 +171,14 @@ enum QRCodeLoginState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
enum DisplayQRState: Equatable {
|
||||
nonisolated enum DisplayQRState: Equatable {
|
||||
/// The QR code is available and is being shown.
|
||||
case active(UIImage)
|
||||
/// The QR code being shown has expired. We will need to generate a new one.
|
||||
case expired
|
||||
}
|
||||
|
||||
enum DisplayCodeState: Equatable {
|
||||
nonisolated enum DisplayCodeState: Equatable {
|
||||
case deviceCode(String)
|
||||
case verificationCode(String)
|
||||
|
||||
@@ -190,7 +190,7 @@ enum QRCodeLoginState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
enum CheckCodeState: Equatable {
|
||||
nonisolated enum CheckCodeState: Equatable {
|
||||
/// The user needs to input the confirmation code.
|
||||
case inputCode(CheckCodeSenderProxy)
|
||||
/// The code supplied by the user didn't pass local validation.
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol QRCodeLoginScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<QRCodeLoginScreenViewModelAction, Never> { get }
|
||||
var context: QRCodeLoginScreenViewModelType.Context { get }
|
||||
|
||||
@@ -109,17 +109,19 @@ final class QRScannerController: UIViewController {
|
||||
}
|
||||
|
||||
func startScan() {
|
||||
// Start video capture.
|
||||
// AVCaptureSession start/stop are blocking calls that Apple documents should be
|
||||
// made off the main thread, the session synchronises access internally.
|
||||
nonisolated(unsafe) let captureSession = captureSession
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
self.captureSession.startRunning()
|
||||
captureSession.startRunning()
|
||||
MXLog.info("QRCodeScannerView: capture session started")
|
||||
}
|
||||
}
|
||||
|
||||
func stopScan() {
|
||||
// Stop video capture.
|
||||
nonisolated(unsafe) let captureSession = captureSession
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
self.captureSession.stopRunning()
|
||||
captureSession.stopRunning()
|
||||
MXLog.info("QRCodeScannerView: capture session stopped")
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol ReportContentScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<ReportContentScreenViewModelAction, Never> { get }
|
||||
var context: ReportContentScreenViewModelType.Context { get }
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol ReportRoomScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<ReportRoomScreenViewModelAction, Never> { get }
|
||||
var context: ReportRoomScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -111,7 +111,6 @@ class ResolveVerifiedUserSendFailureScreenViewModel: ResolveVerifiedUserSendFail
|
||||
|
||||
// MARK: - Iterators
|
||||
|
||||
@MainActor
|
||||
private protocol VerifiedUserSendFailureIterator {
|
||||
func next() -> (userID: String, failure: TimelineItemSendFailure.VerifiedUser)?
|
||||
}
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol ResolveVerifiedUserSendFailureScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<ResolveVerifiedUserSendFailureScreenViewModelAction, Never> { get }
|
||||
var context: ResolveVerifiedUserSendFailureScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol RoomChangePermissionsScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<RoomChangePermissionsScreenViewModelAction, Never> { get }
|
||||
var context: RoomChangePermissionsScreenViewModelType.Context { get }
|
||||
|
||||
@@ -165,7 +165,11 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
||||
|
||||
// A task we can await until the room's info gets modified with the new power levels.
|
||||
// Note: Ignore the first value as the publisher is backed by a current value subject.
|
||||
let infoTask = Task { await roomProxy.infoPublisher.dropFirst().values.first { _ in true } }
|
||||
let infoTask = Task {
|
||||
var iterator = roomProxy.infoPublisher.values.makeAsyncIterator()
|
||||
_ = await iterator.next(isolation: #isolation) // The publisher's current value.
|
||||
_ = await iterator.next(isolation: #isolation)
|
||||
}
|
||||
|
||||
switch await roomProxy.updatePowerLevelsForUsers(promotingUpdates + demotingUpdates) {
|
||||
case .success:
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol RoomChangeRolesScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<RoomChangeRolesScreenViewModelAction, Never> { get }
|
||||
var context: RoomChangeRolesScreenViewModelType.Context { get }
|
||||
|
||||
+19
-7
@@ -138,23 +138,19 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe
|
||||
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||
if state.avatarDidChange {
|
||||
group.addTask {
|
||||
if let localMedia = await self.state.localMedia {
|
||||
try await self.roomProxy.uploadAvatar(media: localMedia).get()
|
||||
} else if await self.state.avatarURL == nil {
|
||||
try await self.roomProxy.removeAvatar().get()
|
||||
}
|
||||
try await self.saveAvatar()
|
||||
}
|
||||
}
|
||||
|
||||
if state.nameDidChange {
|
||||
group.addTask {
|
||||
try await self.roomProxy.setName(self.state.bindings.name).get()
|
||||
try await self.saveName()
|
||||
}
|
||||
}
|
||||
|
||||
if state.topicDidChange {
|
||||
group.addTask {
|
||||
try await self.roomProxy.setTopic(self.state.bindings.topic).get()
|
||||
try await self.saveTopic()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,4 +164,20 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe
|
||||
message: L10n.screenRoomDetailsEditionError)
|
||||
}
|
||||
}
|
||||
|
||||
private func saveAvatar() async throws {
|
||||
if let localMedia = state.localMedia {
|
||||
try await roomProxy.uploadAvatar(media: localMedia).get()
|
||||
} else if state.avatarURL == nil {
|
||||
try await roomProxy.removeAvatar().get()
|
||||
}
|
||||
}
|
||||
|
||||
private func saveName() async throws {
|
||||
try await roomProxy.setName(state.bindings.name).get()
|
||||
}
|
||||
|
||||
private func saveTopic() async throws {
|
||||
try await roomProxy.setTopic(state.bindings.topic).get()
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -9,7 +9,6 @@
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
protocol RoomDetailsEditScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<RoomDetailsEditScreenViewModelAction, Never> { get }
|
||||
var context: RoomDetailsEditScreenViewModelType.Context { get }
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol RoomDetailsScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<RoomDetailsScreenViewModelAction, Never> { get }
|
||||
var context: RoomDetailsScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol RoomDirectorySearchScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<RoomDirectorySearchScreenViewModelAction, Never> { get }
|
||||
var context: RoomDirectorySearchScreenViewModelType.Context { get }
|
||||
|
||||
+1
-2
@@ -142,7 +142,6 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func unignoreUser() async {
|
||||
guard let roomMemberProxy else {
|
||||
fatalError()
|
||||
@@ -164,7 +163,7 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
|
||||
}
|
||||
|
||||
private func updateMembers() {
|
||||
Task.detached {
|
||||
Task {
|
||||
await self.roomProxy.updateMembers()
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol RoomMemberDetailsScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<RoomMemberDetailsScreenViewModelAction, Never> { get }
|
||||
var context: RoomMemberDetailsScreenViewModelType.Context { get }
|
||||
|
||||
@@ -30,7 +30,7 @@ enum RoomMembersListScreenMode {
|
||||
case banned
|
||||
}
|
||||
|
||||
struct RoomMemberListScreenEntry: Equatable {
|
||||
nonisolated struct RoomMemberListScreenEntry: Equatable {
|
||||
let member: RoomMemberDetails
|
||||
let verificationState: UserIdentityVerificationState
|
||||
}
|
||||
|
||||
@@ -123,7 +123,10 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
}
|
||||
|
||||
private func buildMembersDetails(members: [RoomMemberProxyProtocol]) async -> RoomMembersDetails {
|
||||
await Task.detached { [userSession, roomProxy] in
|
||||
// We don't care about identity statuses on non-encrypted rooms
|
||||
let isEncrypted = roomProxy.infoPublisher.value.isEncrypted
|
||||
|
||||
return await Task.detached { [weak self] in
|
||||
// accessing RoomMember's properties is very slow. We need to do it in a background thread.
|
||||
var invitedMembers: [RoomMemberListScreenEntry] = .init()
|
||||
var joinedMembers: [RoomMemberListScreenEntry] = .init()
|
||||
@@ -131,10 +134,8 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
|
||||
for member in members {
|
||||
var verificationState: UserIdentityVerificationState = .notVerified
|
||||
if roomProxy.infoPublisher.value.isEncrypted, // We don't care about identity statuses on non-encrypted rooms
|
||||
case let .success(userIdentity) = await userSession.clientProxy.userIdentity(for: member.userID, fallBackToServer: false),
|
||||
let userIdentity {
|
||||
verificationState = userIdentity.verificationState
|
||||
if isEncrypted, let fetchedState = await self?.userIdentityVerificationState(for: member.userID) {
|
||||
verificationState = fetchedState
|
||||
}
|
||||
|
||||
switch member.membership {
|
||||
@@ -156,6 +157,15 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
.value
|
||||
}
|
||||
|
||||
/// The client proxy isn't Sendable, fetch identities through this helper so
|
||||
/// that it never leaves the main actor.
|
||||
private func userIdentityVerificationState(for userID: String) async -> UserIdentityVerificationState? {
|
||||
guard case let .success(userIdentity) = await userSession.clientProxy.userIdentity(for: userID, fallBackToServer: false) else {
|
||||
return nil
|
||||
}
|
||||
return userIdentity?.verificationState
|
||||
}
|
||||
|
||||
private func selectMember(_ member: RoomMemberDetails) {
|
||||
guard currentUserProxy?.userID != member.id else {
|
||||
showMemberDetails(member)
|
||||
@@ -226,7 +236,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
}
|
||||
}
|
||||
|
||||
private struct RoomMembersDetails {
|
||||
private nonisolated struct RoomMembersDetails {
|
||||
var invitedMembers: [RoomMemberListScreenEntry]
|
||||
var joinedMembers: [RoomMemberListScreenEntry]
|
||||
var bannedMembers: [RoomMemberListScreenEntry]
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol RoomMembersListScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<RoomMembersListScreenViewModelAction, Never> { get }
|
||||
var context: RoomMembersListScreenViewModelType.Context { get }
|
||||
|
||||
@@ -128,7 +128,6 @@ struct RoomMembersListScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private enum MembersSection {
|
||||
case joined
|
||||
case invited
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol RoomNotificationSettingsScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<RoomNotificationSettingsScreenViewModelAction, Never> { get }
|
||||
var context: RoomNotificationSettingsScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol RoomPollsHistoryScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<RoomPollsHistoryScreenViewModelAction, Never> { get }
|
||||
var context: RoomPollsHistoryScreenViewModelType.Context { get }
|
||||
|
||||
+5
-1
@@ -100,7 +100,11 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
|
||||
|
||||
// A task we can await until the room's info gets modified with the new power levels.
|
||||
// Note: Ignore the first value as the publisher is backed by a current value subject.
|
||||
let infoTask = Task { await roomProxy.infoPublisher.dropFirst().values.first { _ in true } }
|
||||
let infoTask = Task {
|
||||
var iterator = roomProxy.infoPublisher.values.makeAsyncIterator()
|
||||
_ = await iterator.next(isolation: #isolation) // The publisher's current value.
|
||||
_ = await iterator.next(isolation: #isolation)
|
||||
}
|
||||
|
||||
switch await roomProxy.updatePowerLevelsForUsers([(userID: roomProxy.ownUserID, powerLevel: role.powerLevelValue)]) {
|
||||
case .success:
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol RoomRolesAndPermissionsScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<RoomRolesAndPermissionsScreenViewModelAction, Never> { get }
|
||||
var context: RoomRolesAndPermissionsScreenViewModelType.Context { get }
|
||||
|
||||
@@ -405,7 +405,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
||||
|
||||
repeat { // Don't enumerate and mutate at the same time, big no no
|
||||
shouldMakeAnotherPass = false
|
||||
attributedString.enumerateAttribute(.link, in: .init(location: 0, length: attributedString.length), options: []) { value, range, stop in
|
||||
attributedString.enumerateAttribute(.link, in: .init(location: 0, length: attributedString.length), options: []) { @Sendable value, range, stop in
|
||||
guard let value else { return }
|
||||
shouldMakeAnotherPass = true
|
||||
|
||||
@@ -429,7 +429,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
||||
|
||||
repeat {
|
||||
shouldMakeAnotherPass = false
|
||||
attributedString.enumerateAttribute(.MatrixAllUsersMention, in: .init(location: 0, length: attributedString.length), options: []) { value, range, stop in
|
||||
attributedString.enumerateAttribute(.MatrixAllUsersMention, in: .init(location: 0, length: attributedString.length), options: []) { @Sendable value, range, stop in
|
||||
guard value != nil else { return }
|
||||
|
||||
shouldMakeAnotherPass = true
|
||||
|
||||
-1
@@ -10,7 +10,6 @@ import Combine
|
||||
import WysiwygComposer
|
||||
|
||||
// periphery: ignore - markdown protocol
|
||||
@MainActor
|
||||
protocol ComposerToolbarViewModelProtocol {
|
||||
var actions: AnyPublisher<ComposerToolbarViewModelAction, Never> { get }
|
||||
var context: ComposerToolbarViewModelType.Context { get }
|
||||
|
||||
@@ -38,7 +38,6 @@ struct FormattingToolbar: View {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private extension FormatItem {
|
||||
var foregroundColor: Color {
|
||||
switch state {
|
||||
|
||||
@@ -120,7 +120,6 @@ enum RoomScreenFooterViewDetails {
|
||||
case verificationViolation(member: RoomMemberProxyProtocol, learnMoreURL: URL)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
enum PinnedEventsBannerState: Equatable {
|
||||
case loading(numbersOfEvents: Int)
|
||||
case loaded(state: PinnedEventsState)
|
||||
@@ -225,7 +224,6 @@ enum PinnedEventsBannerState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct PinnedEventsState: Equatable {
|
||||
var pinnedEventContents: OrderedDictionary<String, AttributedString> = [:] {
|
||||
didSet {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol RoomScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<RoomScreenViewModelAction, Never> { get }
|
||||
var context: RoomScreenViewModel.Context { get }
|
||||
|
||||
@@ -19,9 +19,9 @@ struct KnockRequestInfo: Equatable {
|
||||
|
||||
struct KnockRequestsBannerView: View {
|
||||
let requests: [KnockRequestInfo]
|
||||
let onDismiss: () -> Void
|
||||
let onAccept: ((String) -> Void)?
|
||||
let onViewAll: () -> Void
|
||||
let onDismiss: @MainActor () -> Void
|
||||
let onAccept: (@MainActor (String) -> Void)?
|
||||
let onViewAll: @MainActor () -> Void
|
||||
var mediaProvider: MediaProviderProtocol?
|
||||
|
||||
var body: some View {
|
||||
@@ -50,9 +50,9 @@ struct KnockRequestsBannerView: View {
|
||||
|
||||
private struct SingleKnockRequestBannerContent: View {
|
||||
let request: KnockRequestInfo
|
||||
let onDismiss: () -> Void
|
||||
let onAccept: ((String) -> Void)?
|
||||
let onViewAll: () -> Void
|
||||
let onDismiss: @MainActor () -> Void
|
||||
let onAccept: (@MainActor (String) -> Void)?
|
||||
let onViewAll: @MainActor () -> Void
|
||||
var mediaProvider: MediaProviderProtocol?
|
||||
|
||||
var body: some View {
|
||||
@@ -118,8 +118,8 @@ private struct SingleKnockRequestBannerContent: View {
|
||||
|
||||
private struct MultipleKnockRequestsBannerContent: View {
|
||||
let requests: [KnockRequestInfo]
|
||||
let onDismiss: () -> Void
|
||||
let onViewAll: () -> Void
|
||||
let onDismiss: @MainActor () -> Void
|
||||
let onViewAll: @MainActor () -> Void
|
||||
var mediaProvider: MediaProviderProtocol?
|
||||
|
||||
private var avatars: [StackedAvatarInfo] {
|
||||
@@ -162,7 +162,7 @@ private struct MultipleKnockRequestsBannerContent: View {
|
||||
}
|
||||
|
||||
private struct KnockRequestsBannerDismissButton: View {
|
||||
let onDismiss: () -> Void
|
||||
let onDismiss: @MainActor () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol RoomSelectionScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<RoomSelectionScreenViewModelAction, Never> { get }
|
||||
var context: RoomSelectionScreenViewModelType.Context { get }
|
||||
|
||||
@@ -78,7 +78,7 @@ private struct RoomSelectionListRow: View {
|
||||
})
|
||||
}
|
||||
|
||||
@ViewBuilder @MainActor
|
||||
@ViewBuilder
|
||||
var avatar: some View {
|
||||
if dynamicTypeSize < .accessibility3 {
|
||||
RoomAvatarImage(avatar: room.avatar,
|
||||
|
||||
-1
@@ -7,7 +7,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol RoomThreadListScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<RoomThreadListScreenViewModelAction, Never> { get }
|
||||
var context: RoomThreadListScreenViewModelType.Context { get }
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol SecureBackupKeyBackupScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<SecureBackupKeyBackupScreenViewModelAction, Never> { get }
|
||||
var context: SecureBackupKeyBackupScreenViewModelType.Context { get }
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ enum SecureBackupLogoutConfirmationScreenViewModelAction {
|
||||
case logout
|
||||
}
|
||||
|
||||
enum SecureBackupLogoutConfirmationScreenViewMode: Equatable {
|
||||
nonisolated enum SecureBackupLogoutConfirmationScreenViewMode: Equatable {
|
||||
case saveRecoveryKey
|
||||
case waitingToStart(hasStalled: Bool)
|
||||
case backupOngoing(progress: Double)
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol SecureBackupLogoutConfirmationScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<SecureBackupLogoutConfirmationScreenViewModelAction, Never> { get }
|
||||
var context: SecureBackupLogoutConfirmationScreenViewModelType.Context { get }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user