Adopt StateStoreViewModelV2 in more screens. (#4275)

* Use StateStoreViewModelV2 in AnalyticsPromptScreen.

* Use StateStoreViewModelV2 in UserProfileScreen.

* Use StateStoreViewModelV2 in MediaUploadPreviewScreen.

* Use StateStoreViewModelV2 in the Roles & Permissions screens.

* Add the asyncStream variant of deferFailure.

* Use StateStoreViewModelV2 in BlockedUsersScreen.

* Use StateStoreViewModelV2 in ManageRoomMemberSheet.

* Use StateStoreViewModelV2 in ResolveVerifiedUserSendFailureScreen.

* Use StateStoreViewModelV2 in ReportContentScreen.

* Use StateStoreViewModelV2 in ReportRoomScreen.

* Use StateStoreViewModelV2 in StaticLocationScreen.

* Use StateStoreViewModelV2 in EmojiPickerScreen.

* Use StateStoreViewModelV2 in PollFormScreen.

* Use StateStoreViewModelV2 in DeclineAndBlockScreen.

* Use StateStoreViewModelV2 in RoomDetailsScreen.

* Fix a random compilation error that just popped up 🤷‍♂️

* Fix expectation message.
This commit is contained in:
Doug
2025-07-02 15:13:42 +01:00
committed by GitHub
parent db3c9f4382
commit c7b562250b
47 changed files with 404 additions and 163 deletions
@@ -158,6 +158,36 @@ extension XCTestCase {
}
}
/// XCTest utility that assists in subscribing to an async stream and deferring the failure for a particular value until some other actions have been performed.
/// - Parameters:
/// - asyncStream: The stream to wait on.
/// - timeout: A timeout after which we give up.
/// - message: An optional custom expectation message
/// - until: callback that evaluates outputs until some condition is reached
/// - Returns: The deferred fulfilment to be executed after some actions. The stream's result is not returned from this fulfilment.
func deferFailure<Value>(_ asyncStream: AsyncStream<Value>,
timeout: TimeInterval,
message: String? = nil,
until condition: @escaping (Value) -> Bool) -> DeferredFulfillment<Void> {
let expectation = expectation(description: message ?? "Awaiting stream")
expectation.isInverted = true
var hasFulfilled = false
let task = Task {
for await value in asyncStream {
if condition(value), !hasFulfilled {
expectation.fulfill()
hasFulfilled = true
}
}
}
return DeferredFulfillment<Void> {
await self.fulfillment(of: [expectation], timeout: timeout)
task.cancel()
}
}
struct DeferredFulfillment<T> {
let closure: () async throws -> T
@discardableResult func fulfill() async throws -> T {
@@ -8,7 +8,7 @@
import Combine
import SwiftUI
typealias BlockedUsersScreenViewModelType = StateStoreViewModel<BlockedUsersScreenViewState, BlockedUsersScreenViewAction>
typealias BlockedUsersScreenViewModelType = StateStoreViewModelV2<BlockedUsersScreenViewState, BlockedUsersScreenViewAction>
class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsersScreenViewModelProtocol {
let hideProfiles: Bool
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct BlockedUsersScreen: View {
@ObservedObject var context: BlockedUsersScreenViewModel.Context
@Bindable var context: BlockedUsersScreenViewModel.Context
var body: some View {
content
@@ -8,7 +8,7 @@
import Combine
import SwiftUI
typealias PollFormScreenViewModelType = StateStoreViewModel<PollFormScreenViewState, PollFormScreenViewAction>
typealias PollFormScreenViewModelType = StateStoreViewModelV2<PollFormScreenViewState, PollFormScreenViewAction>
class PollFormScreenViewModel: PollFormScreenViewModelType, PollFormScreenViewModelProtocol {
private var actionsSubject: PassthroughSubject<PollFormScreenViewModelAction, Never> = .init()
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct PollFormScreen: View {
@ObservedObject var context: PollFormScreenViewModel.Context
@Bindable var context: PollFormScreenViewModel.Context
@FocusState var focus: Focus?
enum Focus: Hashable {
@@ -8,7 +8,7 @@
import Combine
import SwiftUI
typealias DeclineAndBlockScreenViewModelType = StateStoreViewModel<DeclineAndBlockScreenViewState, DeclineAndBlockScreenViewAction>
typealias DeclineAndBlockScreenViewModelType = StateStoreViewModelV2<DeclineAndBlockScreenViewState, DeclineAndBlockScreenViewAction>
class DeclineAndBlockScreenViewModel: DeclineAndBlockScreenViewModelType, DeclineAndBlockScreenViewModelProtocol {
let userID: String
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct DeclineAndBlockScreen: View {
@ObservedObject var context: DeclineAndBlockScreenViewModel.Context
@Bindable var context: DeclineAndBlockScreenViewModel.Context
var body: some View {
Form {
@@ -8,7 +8,7 @@
import Combine
import SwiftUI
typealias EmojiPickerScreenViewModelType = StateStoreViewModel<EmojiPickerScreenViewState, EmojiPickerScreenViewAction>
typealias EmojiPickerScreenViewModelType = StateStoreViewModelV2<EmojiPickerScreenViewState, EmojiPickerScreenViewAction>
class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScreenViewModelProtocol {
private var actionsSubject: PassthroughSubject<EmojiPickerScreenViewModelAction, Never> = .init()
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct EmojiPickerScreen: View {
@ObservedObject var context: EmojiPickerScreenViewModel.Context
let context: EmojiPickerScreenViewModel.Context
var selectedEmojis = Set<String>()
@State var searchString = ""
@@ -106,9 +106,7 @@ struct EmojiPickerScreen_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
EmojiPickerScreen(context: viewModel.context, selectedEmojis: ["😀", "😄"])
.previewDisplayName("Screen")
.snapshotPreferences(expect: viewModel.context.$viewState.map { state in
!state.categories.isEmpty
})
.snapshotPreferences(expect: viewModel.context.observe(\.viewState.categories).map { !$0.isEmpty }.eraseToStream())
}
}
@@ -8,7 +8,7 @@
import Combine
import Foundation
typealias StaticLocationScreenViewModelType = StateStoreViewModel<StaticLocationScreenViewState, StaticLocationScreenViewAction>
typealias StaticLocationScreenViewModelType = StateStoreViewModelV2<StaticLocationScreenViewState, StaticLocationScreenViewAction>
class StaticLocationScreenViewModel: StaticLocationScreenViewModelType, StaticLocationScreenViewModelProtocol {
private let actionsSubject: PassthroughSubject<StaticLocationScreenViewModelAction, Never> = .init()
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct StaticLocationScreen: View {
@ObservedObject var context: StaticLocationScreenViewModel.Context
@Bindable var context: StaticLocationScreenViewModel.Context
var body: some View {
VStack(spacing: 0) {
@@ -9,7 +9,7 @@ import Combine
import Foundation
import SwiftUI
typealias ManageRoomMemberSheetViewModelType = StateStoreViewModel<ManageRoomMemberSheetViewState, ManageRoomMemberSheetViewAction>
typealias ManageRoomMemberSheetViewModelType = StateStoreViewModelV2<ManageRoomMemberSheetViewState, ManageRoomMemberSheetViewAction>
class ManageRoomMemberSheetViewModel: ManageRoomMemberSheetViewModelType, ManageRoomMemberSheetViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct ManageRoomMemberSheetView: View {
@ObservedObject var context: ManageRoomMemberSheetViewModelType.Context
@Bindable var context: ManageRoomMemberSheetViewModelType.Context
var body: some View {
Form {
@@ -9,7 +9,7 @@ import Combine
import MatrixRustSDK
import SwiftUI
typealias MediaUploadPreviewScreenViewModelType = StateStoreViewModel<MediaUploadPreviewScreenViewState, MediaUploadPreviewScreenViewAction>
typealias MediaUploadPreviewScreenViewModelType = StateStoreViewModelV2<MediaUploadPreviewScreenViewState, MediaUploadPreviewScreenViewAction>
class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType, MediaUploadPreviewScreenViewModelProtocol {
private let userIndicatorController: UserIndicatorControllerProtocol
@@ -13,7 +13,7 @@ import SwiftUI
struct MediaUploadPreviewScreen: View {
@Environment(\.colorScheme) private var colorScheme
@ObservedObject var context: MediaUploadPreviewScreenViewModel.Context
@Bindable var context: MediaUploadPreviewScreenViewModel.Context
@State private var captionWarningFrame: CGRect = .zero
@FocusState private var isComposerFocussed
@@ -8,7 +8,7 @@
import Combine
import SwiftUI
typealias AnalyticsPromptScreenViewModelType = StateStoreViewModel<AnalyticsPromptScreenViewState, AnalyticsPromptScreenViewAction>
typealias AnalyticsPromptScreenViewModelType = StateStoreViewModelV2<AnalyticsPromptScreenViewState, AnalyticsPromptScreenViewAction>
class AnalyticsPromptScreenViewModel: AnalyticsPromptScreenViewModelType, AnalyticsPromptScreenViewModelProtocol {
private var actionsSubject: PassthroughSubject<AnalyticsPromptScreenViewModelAction, Never> = .init()
@@ -10,7 +10,7 @@ import SwiftUI
/// A prompt that asks the user whether they would like to enable Analytics or not.
struct AnalyticsPromptScreen: View {
@ObservedObject var context: AnalyticsPromptScreenViewModel.Context
let context: AnalyticsPromptScreenViewModel.Context
var body: some View {
FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding, background: .gradient) {
@@ -8,7 +8,7 @@
import Combine
import SwiftUI
typealias ReportContentScreenViewModelType = StateStoreViewModel<ReportContentScreenViewState, ReportContentScreenViewAction>
typealias ReportContentScreenViewModelType = StateStoreViewModelV2<ReportContentScreenViewState, ReportContentScreenViewAction>
class ReportContentScreenViewModel: ReportContentScreenViewModelType, ReportContentScreenViewModelProtocol {
private let eventID: String
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct ReportContentScreen: View {
@ObservedObject var context: ReportContentScreenViewModel.Context
@Bindable var context: ReportContentScreenViewModel.Context
var body: some View {
Form {
@@ -8,7 +8,7 @@
import Combine
import SwiftUI
typealias ReportRoomScreenViewModelType = StateStoreViewModel<ReportRoomScreenViewState, ReportRoomScreenViewAction>
typealias ReportRoomScreenViewModelType = StateStoreViewModelV2<ReportRoomScreenViewState, ReportRoomScreenViewAction>
class ReportRoomScreenViewModel: ReportRoomScreenViewModelType, ReportRoomScreenViewModelProtocol {
let roomProxy: JoinedRoomProxyProtocol
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct ReportRoomScreen: View {
@ObservedObject var context: ReportRoomScreenViewModel.Context
@Bindable var context: ReportRoomScreenViewModel.Context
var body: some View {
Form {
@@ -8,7 +8,7 @@
import Combine
import SwiftUI
typealias ResolveVerifiedUserSendFailureScreenViewModelType = StateStoreViewModel<ResolveVerifiedUserSendFailureScreenViewState, ResolveVerifiedUserSendFailureScreenViewAction>
typealias ResolveVerifiedUserSendFailureScreenViewModelType = StateStoreViewModelV2<ResolveVerifiedUserSendFailureScreenViewState, ResolveVerifiedUserSendFailureScreenViewAction>
class ResolveVerifiedUserSendFailureScreenViewModel: ResolveVerifiedUserSendFailureScreenViewModelType, ResolveVerifiedUserSendFailureScreenViewModelProtocol {
private let iterator: VerifiedUserSendFailureIterator
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct ResolveVerifiedUserSendFailureScreen: View {
@ObservedObject var context: ResolveVerifiedUserSendFailureScreenViewModel.Context
let context: ResolveVerifiedUserSendFailureScreenViewModel.Context
@State private var sheetFrame: CGRect = .zero
var body: some View {
@@ -9,7 +9,7 @@ import Combine
import MatrixRustSDK
import SwiftUI
typealias RoomChangePermissionsScreenViewModelType = StateStoreViewModel<RoomChangePermissionsScreenViewState, RoomChangePermissionsScreenViewAction>
typealias RoomChangePermissionsScreenViewModelType = StateStoreViewModelV2<RoomChangePermissionsScreenViewState, RoomChangePermissionsScreenViewAction>
class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModelType, RoomChangePermissionsScreenViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct RoomChangePermissionsScreen: View {
@ObservedObject var context: RoomChangePermissionsScreenViewModel.Context
@Bindable var context: RoomChangePermissionsScreenViewModel.Context
var body: some View {
Form {
@@ -8,7 +8,7 @@
import Combine
import SwiftUI
typealias RoomChangeRolesScreenViewModelType = StateStoreViewModel<RoomChangeRolesScreenViewState, RoomChangeRolesScreenViewAction>
typealias RoomChangeRolesScreenViewModelType = StateStoreViewModelV2<RoomChangeRolesScreenViewState, RoomChangeRolesScreenViewAction>
class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomChangeRolesScreenViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct RoomChangeRolesScreen: View {
@ObservedObject var context: RoomChangeRolesScreenViewModel.Context
@Bindable var context: RoomChangeRolesScreenViewModel.Context
var showTopSection: Bool { !context.viewState.membersWithRole.isEmpty }
@@ -13,7 +13,7 @@ struct RoomChangeRolesScreenSection: View {
let title: String
var isAdministratorsSection = false
@ObservedObject var context: RoomChangeRolesScreenViewModel.Context
let context: RoomChangeRolesScreenViewModel.Context
var body: some View {
if !members.isEmpty {
@@ -8,7 +8,7 @@
import Combine
import SwiftUI
typealias RoomDetailsScreenViewModelType = StateStoreViewModel<RoomDetailsScreenViewState, RoomDetailsScreenViewAction>
typealias RoomDetailsScreenViewModelType = StateStoreViewModelV2<RoomDetailsScreenViewState, RoomDetailsScreenViewAction>
class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScreenViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct RoomDetailsScreen: View {
@ObservedObject var context: RoomDetailsScreenViewModel.Context
@Bindable var context: RoomDetailsScreenViewModel.Context
@State private var isTopicExpanded = false
@@ -338,33 +338,23 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
RoomDetailsScreen(context: genericRoomViewModel.context)
.snapshotPreferences(expect: genericRoomViewModel.context.$viewState.map { state in
state.permalink != nil
})
.snapshotPreferences(expect: genericRoomViewModel.context.observe(\.viewState.permalink).map { $0 != nil }.eraseToStream())
.previewDisplayName("Generic Room")
RoomDetailsScreen(context: simpleRoomViewModel.context)
.snapshotPreferences(expect: simpleRoomViewModel.context.$viewState.map { state in
state.permalink != nil
})
.snapshotPreferences(expect: simpleRoomViewModel.context.observe(\.viewState.permalink).map { $0 != nil }.eraseToStream())
.previewDisplayName("Simple Room")
RoomDetailsScreen(context: dmRoomViewModel.context)
.snapshotPreferences(expect: dmRoomViewModel.context.$viewState.map { state in
state.accountOwner != nil
})
.snapshotPreferences(expect: dmRoomViewModel.context.observe(\.viewState.accountOwner).map { $0 != nil }.eraseToStream())
.previewDisplayName("DM Room")
RoomDetailsScreen(context: dmRoomVerifiedViewModel.context)
.snapshotPreferences(expect: dmRoomVerifiedViewModel.context.$viewState.map { state in
state.dmRecipientInfo?.verificationState == .verified
})
.snapshotPreferences(expect: dmRoomVerifiedViewModel.context.observe(\.viewState.dmRecipientInfo?.verificationState).map { $0 == .verified }.eraseToStream())
.previewDisplayName("DM Room Verified")
RoomDetailsScreen(context: dmRoomVerificationViolationViewModel.context)
.snapshotPreferences(expect: dmRoomVerificationViolationViewModel.context.$viewState.map { state in
state.accountOwner != nil
})
.snapshotPreferences(expect: dmRoomVerificationViolationViewModel.context.observe(\.viewState.accountOwner).map { $0 != nil }.eraseToStream())
.previewDisplayName("DM Room Verification Violation")
}
@@ -8,7 +8,7 @@
import Combine
import SwiftUI
typealias RoomRolesAndPermissionsScreenViewModelType = StateStoreViewModel<RoomRolesAndPermissionsScreenViewState, RoomRolesAndPermissionsScreenViewAction>
typealias RoomRolesAndPermissionsScreenViewModelType = StateStoreViewModelV2<RoomRolesAndPermissionsScreenViewState, RoomRolesAndPermissionsScreenViewAction>
class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewModelType, RoomRolesAndPermissionsScreenViewModelProtocol {
private let roomProxy: JoinedRoomProxyProtocol
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct RoomRolesAndPermissionsScreen: View {
@ObservedObject var context: RoomRolesAndPermissionsScreenViewModel.Context
@Bindable var context: RoomRolesAndPermissionsScreenViewModel.Context
var body: some View {
Form {
@@ -9,7 +9,7 @@ import Combine
import MatrixRustSDK
import SwiftUI
typealias UserProfileScreenViewModelType = StateStoreViewModel<UserProfileScreenViewState, UserProfileScreenViewAction>
typealias UserProfileScreenViewModelType = StateStoreViewModelV2<UserProfileScreenViewState, UserProfileScreenViewAction>
class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScreenViewModelProtocol {
private let clientProxy: ClientProxyProtocol
@@ -9,7 +9,7 @@ import Compound
import SwiftUI
struct UserProfileScreen: View {
@ObservedObject var context: UserProfileScreenViewModel.Context
@Bindable var context: UserProfileScreenViewModel.Context
var body: some View {
Form {
@@ -103,21 +103,15 @@ struct UserProfileScreen_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
UserProfileScreen(context: verifiedUserViewModel.context)
.snapshotPreferences(expect: verifiedUserViewModel.context.$viewState.map { state in
state.isVerified != nil
})
.snapshotPreferences(expect: verifiedUserViewModel.context.observe(\.viewState.isVerified).map { $0 != nil }.eraseToStream())
.previewDisplayName("Verified User")
UserProfileScreen(context: otherUserViewModel.context)
.snapshotPreferences(expect: otherUserViewModel.context.$viewState.map { state in
state.isVerified != nil
})
.snapshotPreferences(expect: otherUserViewModel.context.observe(\.viewState.isVerified).map { $0 != nil }.eraseToStream())
.previewDisplayName("Other User")
UserProfileScreen(context: accountOwnerViewModel.context)
.snapshotPreferences(expect: accountOwnerViewModel.context.$viewState.map { state in
state.isVerified != nil
})
.snapshotPreferences(expect: accountOwnerViewModel.context.observe(\.viewState.isVerified).map { $0 != nil }.eraseToStream())
.previewDisplayName("Account Owner")
}