mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-08 14:20:37 +00:00
Remove the dynamic member lookup from UserSettings in favour of nested access. (#6294)
* Remove the dynamic member lookup from UserSettings in favour of nested access. * Remove forwarding methods from UserSettings. * Update the Enterprise submodule.
This commit is contained in:
@@ -495,8 +495,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
|
|
||||||
if oldVersion < Version(1, 6, 0) {
|
if oldVersion < Version(1, 6, 0) {
|
||||||
MXLog.info("Migrating to v1.6.0, marking identity confirmation onboarding as ran.")
|
MXLog.info("Migrating to v1.6.0, marking identity confirmation onboarding as ran.")
|
||||||
userSession.userSettings.hasRunIdentityConfirmationOnboarding = true
|
userSession.userSettings.account.hasRunIdentityConfirmationOnboarding = true
|
||||||
userSession.userSettings.hasRunNotificationPermissionsOnboarding = true
|
userSession.userSettings.app.hasRunNotificationPermissionsOnboarding = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldVersion < Version(25, 6, 0) {
|
if oldVersion < Version(25, 6, 0) {
|
||||||
@@ -521,8 +521,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
|
|
||||||
if oldVersion < Version(26, 10, 1) {
|
if oldVersion < Version(26, 10, 1) {
|
||||||
MXLog.info("Migrating to version 26.10.1, moving initial 'session specific' settings.")
|
MXLog.info("Migrating to version 26.10.1, moving initial 'session specific' settings.")
|
||||||
userSession.userSettings.migrateAppSettingsValueToAccountSettings(\.hasRunIdentityConfirmationOnboardingKey)
|
userSession.userSettings.account.migrateAppSettingsValue(\.hasRunIdentityConfirmationOnboardingKey)
|
||||||
userSession.userSettings.migrateAppSettingsValueToAccountSettings(\.searchBreadcrumbsKey)
|
userSession.userSettings.account.migrateAppSettingsValue(\.searchBreadcrumbsKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
userSessionMigrationsOldVersion = nil
|
userSessionMigrationsOldVersion = nil
|
||||||
@@ -872,7 +872,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
userSessionStore.logout(userSession: userSession)
|
userSessionStore.logout(userSession: userSession)
|
||||||
tearDownUserSession()
|
tearDownUserSession()
|
||||||
|
|
||||||
userSession.userSettings.resetSessionSpecificSettings()
|
userSession.userSettings.account.reset()
|
||||||
appHooks.remoteSettingsHook.reset(appSettings)
|
appHooks.remoteSettingsHook.reset(appSettings)
|
||||||
|
|
||||||
// Reset analytics
|
// Reset analytics
|
||||||
@@ -1385,7 +1385,7 @@ private extension AppCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scheduleSearchBackfill() {
|
func scheduleSearchBackfill() {
|
||||||
guard let userSession, userSession.userSettings.globalSearchEnabled, #available(iOS 26.0, *) else {
|
guard let userSession, userSession.userSettings.app.globalSearchEnabled, #available(iOS 26.0, *) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,44 +10,16 @@ import Macros
|
|||||||
|
|
||||||
/// The settings for a signed in account, composed of the app wide settings and the settings
|
/// The settings for a signed in account, composed of the app wide settings and the settings
|
||||||
/// specific to that account.
|
/// specific to that account.
|
||||||
@dynamicMemberLookup
|
|
||||||
final nonisolated class UserSettings: Sendable {
|
final nonisolated class UserSettings: Sendable {
|
||||||
// TODO: @pixlwave Make this private after handling Advanced/Labs/Developer screen models.
|
|
||||||
let app: AppSettings
|
let app: AppSettings
|
||||||
private let account: AccountSettings
|
let account: AccountSettings
|
||||||
|
|
||||||
init(appSettings: AppSettings, accountSettings: AccountSettings) {
|
init(appSettings: AppSettings, accountSettings: AccountSettings) {
|
||||||
self.app = appSettings
|
app = appSettings
|
||||||
self.account = accountSettings
|
account = accountSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
static func mock(userID: String = "@me:matrix.org") -> UserSettings {
|
static func mock(userID: String = "@me:matrix.org") -> UserSettings {
|
||||||
AppSettings.volatile().userSettings(for: userID)
|
AppSettings.volatile().userSettings(for: userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
subscript<Value>(dynamicMember keyPath: ReferenceWritableKeyPath<AppSettings, Value>) -> Value {
|
|
||||||
get { app[keyPath: keyPath] }
|
|
||||||
set { app[keyPath: keyPath] = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
subscript<Value>(dynamicMember keyPath: KeyPath<AppSettings, Value>) -> Value {
|
|
||||||
app[keyPath: keyPath]
|
|
||||||
}
|
|
||||||
|
|
||||||
subscript<Value>(dynamicMember keyPath: ReferenceWritableKeyPath<AccountSettings, Value>) -> Value {
|
|
||||||
get { account[keyPath: keyPath] }
|
|
||||||
set { account[keyPath: keyPath] = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
subscript<Value>(dynamicMember keyPath: KeyPath<AccountSettings, Value>) -> Value {
|
|
||||||
account[keyPath: keyPath]
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateAppSettingsValueToAccountSettings(_ keyPath: KeyPath<AccountSettings, UserPreferenceKey>) {
|
|
||||||
account.migrateAppSettingsValue(keyPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetSessionSpecificSettings() {
|
|
||||||
account.reset()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
value == .verified,
|
value == .verified,
|
||||||
stateMachine.state == .identityConfirmation else { return }
|
stateMachine.state == .identityConfirmation else { return }
|
||||||
|
|
||||||
userSettings.hasRunIdentityConfirmationOnboarding = true
|
userSettings.account.hasRunIdentityConfirmationOnboarding = true
|
||||||
stateMachine.tryEvent(.nextSkippingIdentityConfirmed)
|
stateMachine.tryEvent(.nextSkippingIdentityConfirmed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,11 +129,11 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
private var requiresVerification: Bool {
|
private var requiresVerification: Bool {
|
||||||
// We want to make sure onboarding finishes but also every time the user becomes unverified (e.g. account reset)
|
// We want to make sure onboarding finishes but also every time the user becomes unverified (e.g. account reset)
|
||||||
!userSettings.hasRunIdentityConfirmationOnboarding || userSession.sessionSecurityStatePublisher.value.verificationState == .unverified
|
!userSettings.account.hasRunIdentityConfirmationOnboarding || userSession.sessionSecurityStatePublisher.value.verificationState == .unverified
|
||||||
}
|
}
|
||||||
|
|
||||||
private var requiresAppLockSetup: Bool {
|
private var requiresAppLockSetup: Bool {
|
||||||
userSettings.appLockIsMandatory && !appLockService.isEnabled
|
userSettings.app.appLockIsMandatory && !appLockService.isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
private var requiresAnalyticsSetup: Bool {
|
private var requiresAnalyticsSetup: Bool {
|
||||||
@@ -141,7 +141,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var requiresNotificationsSetup: Bool {
|
private var requiresNotificationsSetup: Bool {
|
||||||
!userSettings.hasRunNotificationPermissionsOnboarding
|
!userSettings.app.hasRunNotificationPermissionsOnboarding
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureStateMachine() {
|
private func configureStateMachine() {
|
||||||
@@ -225,7 +225,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
presentNotificationPermissionsScreen()
|
presentNotificationPermissionsScreen()
|
||||||
case (_, _, .finished):
|
case (_, _, .finished):
|
||||||
isNewLogin = false
|
isNewLogin = false
|
||||||
userSettings.hasSignedInBefore = true
|
userSettings.app.hasSignedInBefore = true
|
||||||
actionsSubject.send(.dismiss)
|
actionsSubject.send(.dismiss)
|
||||||
stateMachine.tryState(.initial)
|
stateMachine.tryState(.initial)
|
||||||
case (.finished, _, .initial):
|
case (.finished, _, .initial):
|
||||||
@@ -261,7 +261,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .recoveryKey:
|
case .recoveryKey:
|
||||||
presentRecoveryKeyScreen()
|
presentRecoveryKeyScreen()
|
||||||
case .skip:
|
case .skip:
|
||||||
userSettings.hasRunIdentityConfirmationOnboarding = true
|
userSettings.account.hasRunIdentityConfirmationOnboarding = true
|
||||||
stateMachine.tryEvent(.nextSkippingIdentityConfirmed)
|
stateMachine.tryEvent(.nextSkippingIdentityConfirmed)
|
||||||
case .reset:
|
case .reset:
|
||||||
startEncryptionResetFlow()
|
startEncryptionResetFlow()
|
||||||
@@ -384,7 +384,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentAnalyticsPromptScreen() {
|
private func presentAnalyticsPromptScreen() {
|
||||||
let coordinator = AnalyticsPromptScreenCoordinator(analytics: analyticsService, termsURL: userSettings.analyticsTermsURL)
|
let coordinator = AnalyticsPromptScreenCoordinator(analytics: analyticsService, termsURL: userSettings.app.analyticsTermsURL)
|
||||||
|
|
||||||
coordinator.actions
|
coordinator.actions
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
@@ -407,7 +407,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
case .done:
|
case .done:
|
||||||
userSettings.hasRunNotificationPermissionsOnboarding = true
|
userSettings.app.hasRunNotificationPermissionsOnboarding = true
|
||||||
stateMachine.tryEvent(.next)
|
stateMachine.tryEvent(.next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
let stackCoordinator = NavigationStackCoordinator()
|
let stackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
let params = LocationSharingScreenCoordinatorParameters(interactionMode: interactionMode,
|
let params = LocationSharingScreenCoordinatorParameters(interactionMode: interactionMode,
|
||||||
mapURLBuilder: flowParameters.userSettings.mapTilerConfiguration.publisher.value,
|
mapURLBuilder: flowParameters.userSettings.app.mapTilerConfiguration.publisher.value,
|
||||||
roomProxy: roomProxy,
|
roomProxy: roomProxy,
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
liveLocationManager: flowParameters.userSession.liveLocationManager,
|
liveLocationManager: flowParameters.userSession.liveLocationManager,
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
defer { hideLoadingIndicator() }
|
defer { hideLoadingIndicator() }
|
||||||
switch await roomProxy.loadOrFetchEventDetails(for: eventID) {
|
switch await roomProxy.loadOrFetchEventDetails(for: eventID) {
|
||||||
case .success(let event):
|
case .success(let event):
|
||||||
if flowParameters.userSettings.threadsEnabled, let threadRootEventID = event.threadRootEventId() {
|
if flowParameters.userSettings.app.threadsEnabled, let threadRootEventID = event.threadRootEventId() {
|
||||||
if case .thread(threadRootEventID: threadRootEventID, _) = stateMachine.state, let threadCoordinator = childThreadScreenCoordinators.last {
|
if case .thread(threadRootEventID: threadRootEventID, _) = stateMachine.state, let threadCoordinator = childThreadScreenCoordinators.last {
|
||||||
threadCoordinator.focusOnEvent(eventID: eventID)
|
threadCoordinator.focusOnEvent(eventID: eventID)
|
||||||
} else {
|
} else {
|
||||||
@@ -318,7 +318,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
// Otherwise check if the focussed event exists to handle a possible error or theaded event.
|
// Otherwise check if the focussed event exists to handle a possible error or theaded event.
|
||||||
switch await roomProxy.loadOrFetchEventDetails(for: focusEvent.eventID) {
|
switch await roomProxy.loadOrFetchEventDetails(for: focusEvent.eventID) {
|
||||||
case .success(let event):
|
case .success(let event):
|
||||||
if flowParameters.userSettings.threadsEnabled, let threadRootEventID = event.threadRootEventId() {
|
if flowParameters.userSettings.app.threadsEnabled, let threadRootEventID = event.threadRootEventId() {
|
||||||
stateMachine.tryEvent(.presentRoom(presentationAction: .thread(rootEventID: threadRootEventID,
|
stateMachine.tryEvent(.presentRoom(presentationAction: .thread(rootEventID: threadRootEventID,
|
||||||
focusEvent: .init(eventID: focusEvent.eventID,
|
focusEvent: .init(eventID: focusEvent.eventID,
|
||||||
shouldSetPin: focusEvent.shouldSetPin))),
|
shouldSetPin: focusEvent.shouldSetPin))),
|
||||||
@@ -1119,8 +1119,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
let parameters = MediaUploadPreviewScreenCoordinatorParameters(mediaURLs: mediaURLs,
|
let parameters = MediaUploadPreviewScreenCoordinatorParameters(mediaURLs: mediaURLs,
|
||||||
caption: caption,
|
caption: caption,
|
||||||
title: title,
|
title: title,
|
||||||
shouldShowCaptionWarning: flowParameters.userSettings.shouldShowMediaCaptionWarning,
|
shouldShowCaptionWarning: flowParameters.userSettings.app.shouldShowMediaCaptionWarning,
|
||||||
galleryEnabled: flowParameters.userSettings.galleryEnabled,
|
galleryEnabled: flowParameters.userSettings.app.galleryEnabled,
|
||||||
mediaUploadingPreprocessor: MediaUploadingPreprocessor(userSettings: flowParameters.userSettings),
|
mediaUploadingPreprocessor: MediaUploadingPreprocessor(userSettings: flowParameters.userSettings),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
clientProxy: userSession.clientProxy,
|
clientProxy: userSession.clientProxy,
|
||||||
@@ -1176,7 +1176,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
let stackCoordinator = NavigationStackCoordinator()
|
let stackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
let params = LocationSharingScreenCoordinatorParameters(interactionMode: interactionMode,
|
let params = LocationSharingScreenCoordinatorParameters(interactionMode: interactionMode,
|
||||||
mapURLBuilder: flowParameters.userSettings.mapTilerConfiguration.publisher.value,
|
mapURLBuilder: flowParameters.userSettings.app.mapTilerConfiguration.publisher.value,
|
||||||
roomProxy: roomProxy,
|
roomProxy: roomProxy,
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
liveLocationManager: flowParameters.userSession.liveLocationManager,
|
liveLocationManager: flowParameters.userSession.liveLocationManager,
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentBlockedUsersScreen() {
|
private func presentBlockedUsersScreen() {
|
||||||
let coordinator = BlockedUsersScreenCoordinator(parameters: .init(hideProfiles: flowParameters.userSettings.hideIgnoredUserProfiles,
|
let coordinator = BlockedUsersScreenCoordinator(parameters: .init(hideProfiles: flowParameters.userSettings.app.hideIgnoredUserProfiles,
|
||||||
userSession: flowParameters.userSession,
|
userSession: flowParameters.userSession,
|
||||||
userIndicatorController: flowParameters.userIndicatorController))
|
userIndicatorController: flowParameters.userIndicatorController))
|
||||||
navigationStackCoordinator.push(coordinator)
|
navigationStackCoordinator.push(coordinator)
|
||||||
|
|||||||
@@ -87,19 +87,19 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
navigationTabCoordinator = NavigationTabCoordinator()
|
navigationTabCoordinator = NavigationTabCoordinator()
|
||||||
navigationRootCoordinator.setRootCoordinator(navigationTabCoordinator)
|
navigationRootCoordinator.setRootCoordinator(navigationTabCoordinator)
|
||||||
|
|
||||||
let chatsSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator(hideBrandChrome: flowParameters.userSettings.hideBrandChrome))
|
let chatsSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator(hideBrandChrome: flowParameters.userSettings.app.hideBrandChrome))
|
||||||
chatsTabFlowCoordinator = ChatsTabFlowCoordinator(navigationSplitCoordinator: chatsSplitCoordinator,
|
chatsTabFlowCoordinator = ChatsTabFlowCoordinator(navigationSplitCoordinator: chatsSplitCoordinator,
|
||||||
flowParameters: flowParameters)
|
flowParameters: flowParameters)
|
||||||
chatsTabDetails = .init(tag: HomeTab.chats, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid)
|
chatsTabDetails = .init(tag: HomeTab.chats, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid)
|
||||||
chatsTabDetails.navigationSplitCoordinator = chatsSplitCoordinator
|
chatsTabDetails.navigationSplitCoordinator = chatsSplitCoordinator
|
||||||
|
|
||||||
let spacesSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator(hideBrandChrome: flowParameters.userSettings.hideBrandChrome))
|
let spacesSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator(hideBrandChrome: flowParameters.userSettings.app.hideBrandChrome))
|
||||||
spacesTabFlowCoordinator = SpacesTabFlowCoordinator(navigationSplitCoordinator: spacesSplitCoordinator,
|
spacesTabFlowCoordinator = SpacesTabFlowCoordinator(navigationSplitCoordinator: spacesSplitCoordinator,
|
||||||
flowParameters: flowParameters)
|
flowParameters: flowParameters)
|
||||||
spacesTabDetails = .init(tag: HomeTab.spaces, title: L10n.screenHomeTabSpaces, icon: \.space, selectedIcon: \.spaceSolid)
|
spacesTabDetails = .init(tag: HomeTab.spaces, title: L10n.screenHomeTabSpaces, icon: \.space, selectedIcon: \.spaceSolid)
|
||||||
spacesTabDetails.navigationSplitCoordinator = spacesSplitCoordinator
|
spacesTabDetails.navigationSplitCoordinator = spacesSplitCoordinator
|
||||||
|
|
||||||
if flowParameters.userSettings.globalSearchEnabled, #available(iOS 26.0, *) {
|
if flowParameters.userSettings.app.globalSearchEnabled, #available(iOS 26.0, *) {
|
||||||
let searchCoordinator = SearchScreenCoordinator(parameters: .init(roomSummaryProvider: flowParameters.userSession.clientProxy.alternateRoomSummaryProvider,
|
let searchCoordinator = SearchScreenCoordinator(parameters: .init(roomSummaryProvider: flowParameters.userSession.clientProxy.alternateRoomSummaryProvider,
|
||||||
clientProxy: flowParameters.userSession.clientProxy,
|
clientProxy: flowParameters.userSession.clientProxy,
|
||||||
mediaProvider: flowParameters.userSession.mediaProvider,
|
mediaProvider: flowParameters.userSession.mediaProvider,
|
||||||
@@ -463,8 +463,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
presentCallScreen(configuration: .init(roomProxy: roomProxy,
|
presentCallScreen(configuration: .init(roomProxy: roomProxy,
|
||||||
clientProxy: userSession.clientProxy,
|
clientProxy: userSession.clientProxy,
|
||||||
clientID: InfoPlistReader.main.bundleIdentifier,
|
clientID: InfoPlistReader.main.bundleIdentifier,
|
||||||
elementCallBaseURL: flowParameters.userSettings.elementCallBaseURL,
|
elementCallBaseURL: flowParameters.userSettings.app.elementCallBaseURL,
|
||||||
elementCallBaseURLOverride: flowParameters.userSettings.elementCallBaseURLOverride,
|
elementCallBaseURLOverride: flowParameters.userSettings.app.elementCallBaseURLOverride,
|
||||||
voiceOnly: voiceOnly,
|
voiceOnly: voiceOnly,
|
||||||
colorScheme: colorScheme))
|
colorScheme: colorScheme))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ private struct VisualListItemLabelStyle: LabelStyle {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct VisualListItem_Previews: PreviewProvider, TestablePreview {
|
struct VisualListItem_Previews: PreviewProvider, TestablePreview {
|
||||||
static let strings = AnalyticsPromptScreenStrings(termsURL: UserSettings.mock().analyticsTermsURL)
|
static let strings = AnalyticsPromptScreenStrings(termsURL: AppSettings.volatile().analyticsTermsURL)
|
||||||
|
|
||||||
static var testImage1: some View {
|
static var testImage1: some View {
|
||||||
Image(systemName: "circle")
|
Image(systemName: "circle")
|
||||||
|
|||||||
@@ -165,13 +165,13 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
|
|||||||
|
|
||||||
// We only set the analytics configuration if analytics are enabled
|
// We only set the analytics configuration if analytics are enabled
|
||||||
let analyticsConfiguration: ElementCallAnalyticsConfiguration? = if analyticsService.isEnabled {
|
let analyticsConfiguration: ElementCallAnalyticsConfiguration? = if analyticsService.isEnabled {
|
||||||
.init(posthogAPIHost: userSettings.elementCallPosthogAPIHost,
|
.init(posthogAPIHost: userSettings.app.elementCallPosthogAPIHost,
|
||||||
posthogAPIKey: userSettings.elementCallPosthogAPIKey,
|
posthogAPIKey: userSettings.app.elementCallPosthogAPIKey,
|
||||||
sentryDSN: userSettings.elementCallPosthogSentryDSN)
|
sentryDSN: userSettings.app.elementCallPosthogSentryDSN)
|
||||||
} else {
|
} else {
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
let rageshakeURL: String? = if case let .url(baseURL) = userSettings.bugReportRageshakeURL.publisher.value {
|
let rageshakeURL: String? = if case let .url(baseURL) = userSettings.app.bugReportRageshakeURL.publisher.value {
|
||||||
baseURL.absoluteString
|
baseURL.absoluteString
|
||||||
} else {
|
} else {
|
||||||
nil
|
nil
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen
|
|||||||
shouldShowCancelButton: shouldShowCancelButton,
|
shouldShowCancelButton: shouldShowCancelButton,
|
||||||
roomName: "",
|
roomName: "",
|
||||||
serverName: userSession.clientProxy.userIDServerName ?? "",
|
serverName: userSession.clientProxy.userIDServerName ?? "",
|
||||||
isKnockingFeatureEnabled: userSettings.knockingEnabled,
|
isKnockingFeatureEnabled: userSettings.app.knockingEnabled,
|
||||||
canSelectSpace: canSelectSpace,
|
canSelectSpace: canSelectSpace,
|
||||||
aliasLocalPart: roomAliasNameFromRoomDisplayName(roomName: ""),
|
aliasLocalPart: roomAliasNameFromRoomDisplayName(roomName: ""),
|
||||||
bindings: bindings)
|
bindings: bindings)
|
||||||
|
|||||||
@@ -452,7 +452,7 @@ struct CreateRoom_Previews: PreviewProvider, TestablePreview {
|
|||||||
selectionMode: CreateRoomScreenSpaceSelectionMode = .editableSpacesList(preSelectedSpace: nil),
|
selectionMode: CreateRoomScreenSpaceSelectionMode = .editableSpacesList(preSelectedSpace: nil),
|
||||||
isAliasAvailable: Bool = true) -> CreateRoomScreenViewModel {
|
isAliasAvailable: Bool = true) -> CreateRoomScreenViewModel {
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.knockingEnabled = isKnockingEnabled
|
userSettings.app.knockingEnabled = isKnockingEnabled
|
||||||
|
|
||||||
let clientProxy = ClientProxyMock(.init(userIDServerName: "example.org",
|
let clientProxy = ClientProxyMock(.init(userIDServerName: "example.org",
|
||||||
userID: "@userid:example.com"))
|
userID: "@userid:example.com"))
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
bindings: .init(filtersState: .init(userSettings: userSettings))),
|
bindings: .init(filtersState: .init(userSettings: userSettings))),
|
||||||
mediaProvider: userSession.mediaProvider)
|
mediaProvider: userSession.mediaProvider)
|
||||||
|
|
||||||
if userSettings.globalSearchEnabled, #available(iOS 26.0, *) {
|
if userSettings.app.globalSearchEnabled, #available(iOS 26.0, *) {
|
||||||
state.isRoomListSearchEnabled = false
|
state.isRoomListSearchEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,21 +113,21 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
.weakAssign(to: \.state.selectedRoomID, on: self)
|
.weakAssign(to: \.state.selectedRoomID, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.showAllRoomListActivityPublisher
|
userSettings.app.showAllRoomListActivityPublisher
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
self?.updateRooms()
|
self?.updateRooms()
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.seenInvitesPublisher
|
userSettings.app.seenInvitesPublisher
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
self?.updateRooms()
|
self?.updateRooms()
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.hasSeenNewSoundBannerPublisher
|
userSettings.app.hasSeenNewSoundBannerPublisher
|
||||||
.sink { [weak self] hasSeenNewSoundBanner in
|
.sink { [weak self] hasSeenNewSoundBanner in
|
||||||
self?.state.shouldShowNewSoundBanner = !hasSeenNewSoundBanner
|
self?.state.shouldShowNewSoundBanner = !hasSeenNewSoundBanner
|
||||||
}
|
}
|
||||||
@@ -217,13 +217,13 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
case .skipRecoveryKeyConfirmation:
|
case .skipRecoveryKeyConfirmation:
|
||||||
state.securityBannerMode = .dismissed
|
state.securityBannerMode = .dismissed
|
||||||
case .dismissNewSoundBanner:
|
case .dismissNewSoundBanner:
|
||||||
userSettings.hasSeenNewSoundBanner = true
|
userSettings.app.hasSeenNewSoundBanner = true
|
||||||
case .screenAppeared:
|
case .screenAppeared:
|
||||||
// Use a task otherwise the presentation isn't animated.
|
// Use a task otherwise the presentation isn't animated.
|
||||||
Task { presentMultiAccountAnnouncementIfNeeded() }
|
Task { presentMultiAccountAnnouncementIfNeeded() }
|
||||||
case .multiAccountAnnouncementAppeared:
|
case .multiAccountAnnouncementAppeared:
|
||||||
MXLog.info("The multi-account announcement has been seen.")
|
MXLog.info("The multi-account announcement has been seen.")
|
||||||
userSettings.hasSeenMultiAccountAnnouncement = true
|
userSettings.app.hasSeenMultiAccountAnnouncement = true
|
||||||
case .dismissMultiAccountAnnouncement, .addAccount: // There is no add account flow to start yet.
|
case .dismissMultiAccountAnnouncement, .addAccount: // There is no add account flow to start yet.
|
||||||
state.bindings.isPresentingMultiAccountAnnouncement = false
|
state.bindings.isPresentingMultiAccountAnnouncement = false
|
||||||
case .updateVisibleItemRange(let range):
|
case .updateVisibleItemRange(let range):
|
||||||
@@ -275,7 +275,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
case .success:
|
case .success:
|
||||||
analyticsService.trackInteraction(name: .MobileRoomListRoomContextMenuUnreadToggle)
|
analyticsService.trackInteraction(name: .MobileRoomListRoomContextMenuUnreadToggle)
|
||||||
|
|
||||||
if case .failure(let error) = await roomProxy.markAsRead(receiptType: userSettings.sharePresence ? .read : .readPrivate) {
|
if case .failure(let error) = await roomProxy.markAsRead(receiptType: userSettings.app.sharePresence ? .read : .readPrivate) {
|
||||||
MXLog.error("Failed marking room \(roomIdentifier) as read with error: \(error)")
|
MXLog.error("Failed marking room \(roomIdentifier) as read with error: \(error)")
|
||||||
}
|
}
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
@@ -389,11 +389,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
var rooms = [HomeScreenRoom]()
|
var rooms = [HomeScreenRoom]()
|
||||||
let seenInvites = userSettings.seenInvites
|
let seenInvites = userSettings.app.seenInvites
|
||||||
|
|
||||||
for summary in roomSummaryProvider.roomListPublisher.value {
|
for summary in roomSummaryProvider.roomListPublisher.value {
|
||||||
let room = HomeScreenRoom(summary: summary,
|
let room = HomeScreenRoom(summary: summary,
|
||||||
showAllActivity: userSettings.showAllRoomListActivity,
|
showAllActivity: userSettings.app.showAllRoomListActivity,
|
||||||
seenInvites: seenInvites)
|
seenInvites: seenInvites)
|
||||||
rooms.append(room)
|
rooms.append(room)
|
||||||
}
|
}
|
||||||
@@ -488,8 +488,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
|
|
||||||
private func presentMultiAccountAnnouncementIfNeeded() {
|
private func presentMultiAccountAnnouncementIfNeeded() {
|
||||||
// An unverified session gets the identity confirmation cover first, the room list appears again once it's dismissed.
|
// An unverified session gets the identity confirmation cover first, the room list appears again once it's dismissed.
|
||||||
guard userSettings.multiAccountEnabled,
|
guard userSettings.app.multiAccountEnabled,
|
||||||
!userSettings.hasSeenMultiAccountAnnouncement,
|
!userSettings.app.hasSeenMultiAccountAnnouncement,
|
||||||
userSession.sessionSecurityStatePublisher.value.verificationState == .verified,
|
userSession.sessionSecurityStatePublisher.value.verificationState == .verified,
|
||||||
!state.bindings.isPresentingMultiAccountAnnouncement else {
|
!state.bindings.isPresentingMultiAccountAnnouncement else {
|
||||||
return
|
return
|
||||||
@@ -545,7 +545,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
analyticsService.trackJoinedRoom(isDM: roomProxy.info.isDirect,
|
analyticsService.trackJoinedRoom(isDM: roomProxy.info.isDirect,
|
||||||
isSpace: roomProxy.info.isSpace,
|
isSpace: roomProxy.info.isSpace,
|
||||||
activeMemberCount: UInt(roomProxy.info.activeMembersCount))
|
activeMemberCount: UInt(roomProxy.info.activeMembersCount))
|
||||||
userSettings.seenInvites.remove(roomProxy.id)
|
userSettings.app.seenInvites.remove(roomProxy.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showDeclineInviteConfirmationAlert(roomID: String) async {
|
private func showDeclineInviteConfirmationAlert(roomID: String) async {
|
||||||
@@ -595,7 +595,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success:
|
||||||
await notificationManager.removeDeliveredMessageNotifications(for: roomID) // Normally handled by the room flow, but that's never presented in this case.
|
await notificationManager.removeDeliveredMessageNotifications(for: roomID) // Normally handled by the room flow, but that's never presented in this case.
|
||||||
userSettings.seenInvites.remove(roomID)
|
userSettings.app.seenInvites.remove(roomID)
|
||||||
case .failure:
|
case .failure:
|
||||||
displayError()
|
displayError()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,11 +99,11 @@ struct RoomListFiltersState {
|
|||||||
var availableFilters: [RoomListFilter] {
|
var availableFilters: [RoomListFilter] {
|
||||||
var availableFilters = OrderedSet(RoomListFilter.availableFilters)
|
var availableFilters = OrderedSet(RoomListFilter.availableFilters)
|
||||||
|
|
||||||
if !userSettings.lowPriorityFilterEnabled {
|
if !userSettings.app.lowPriorityFilterEnabled {
|
||||||
availableFilters.remove(.lowPriority)
|
availableFilters.remove(.lowPriority)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !userSettings.mentionsFilterEnabled {
|
if !userSettings.app.mentionsFilterEnabled {
|
||||||
availableFilters.remove(.mentions)
|
availableFilters.remove(.mentions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
|
|||||||
.sink { mode in
|
.sink { mode in
|
||||||
switch mode {
|
switch mode {
|
||||||
case .invited:
|
case .invited:
|
||||||
userSettings.seenInvites.insert(roomID)
|
userSettings.app.seenInvites.insert(roomID)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -350,7 +350,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
|
|||||||
guard !hasSentJoinAction else { return }
|
guard !hasSentJoinAction else { return }
|
||||||
|
|
||||||
let roomID = state.roomID
|
let roomID = state.roomID
|
||||||
userSettings.seenInvites.remove(roomID)
|
userSettings.app.seenInvites.remove(roomID)
|
||||||
|
|
||||||
guard state.roomDetails?.isSpace == true else {
|
guard state.roomDetails?.isSpace == true else {
|
||||||
hasSentJoinAction = true
|
hasSentJoinAction = true
|
||||||
@@ -464,7 +464,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
userSettings.seenInvites.remove(roomID)
|
userSettings.app.seenInvites.remove(roomID)
|
||||||
|
|
||||||
actionsSubject.send(.dismiss)
|
actionsSubject.send(.dismiss)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
|
|||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.liveLocationSharingSessionsByRoomIDPublisher
|
userSettings.app.liveLocationSharingSessionsByRoomIDPublisher
|
||||||
.map { [roomID = roomProxy.id] sessions in sessions[roomID] != nil }
|
.map { [roomID = roomProxy.id] sessions in sessions[roomID] != nil }
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.sink { [weak self] isSharingLiveLocationOnThisDevice in
|
.sink { [weak self] isSharingLiveLocationOnThisDevice in
|
||||||
@@ -261,14 +261,14 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func showLiveLocationFlow() {
|
private func showLiveLocationFlow() {
|
||||||
if userSettings.liveLocationDisclaimerDisplayed {
|
if userSettings.app.liveLocationDisclaimerDisplayed {
|
||||||
showLiveLocationDurationPicker()
|
showLiveLocationDurationPicker()
|
||||||
} else {
|
} else {
|
||||||
state.bindings.alertInfo = .init(alertID: .liveLocationDisclaimer,
|
state.bindings.alertInfo = .init(alertID: .liveLocationDisclaimer,
|
||||||
primaryButton: .init(title: L10n.actionDecline, role: .cancel, action: nil),
|
primaryButton: .init(title: L10n.actionDecline, role: .cancel, action: nil),
|
||||||
secondaryButton: .init(title: L10n.actionAccept) { [weak self] in
|
secondaryButton: .init(title: L10n.actionAccept) { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
userSettings.liveLocationDisclaimerDisplayed = true
|
userSettings.app.liveLocationDisclaimerDisplayed = true
|
||||||
// Delay so SwiftUI finishes dismissing the current alert
|
// Delay so SwiftUI finishes dismissing the current alert
|
||||||
// before presenting the next one.
|
// before presenting the next one.
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@@ -435,7 +435,7 @@ extension LocationSharingScreenViewModel {
|
|||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
|
|
||||||
return LocationSharingScreenViewModel(interactionMode: interactionMode,
|
return LocationSharingScreenViewModel(interactionMode: interactionMode,
|
||||||
mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value,
|
mapURLBuilder: userSettings.app.mapTilerConfiguration.publisher.value,
|
||||||
roomProxy: roomProxy,
|
roomProxy: roomProxy,
|
||||||
timelineController: TimelineControllerMock(.init()),
|
timelineController: TimelineControllerMock(.init()),
|
||||||
liveLocationManager: LiveLocationManagerMock(),
|
liveLocationManager: LiveLocationManagerMock(),
|
||||||
|
|||||||
+1
-1
@@ -102,6 +102,6 @@ struct AnalyticsPromptScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func makeViewModel(showTerms: Bool = true) -> AnalyticsPromptScreenViewModel {
|
static func makeViewModel(showTerms: Bool = true) -> AnalyticsPromptScreenViewModel {
|
||||||
AnalyticsPromptScreenViewModel(termsURL: showTerms ? UserSettings.mock().analyticsTermsURL : nil)
|
AnalyticsPromptScreenViewModel(termsURL: showTerms ? AppSettings.volatile().analyticsTermsURL : nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ class IdentityConfirmationScreenViewModel: IdentityConfirmationScreenViewModelTy
|
|||||||
self.userSession = userSession
|
self.userSession = userSession
|
||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
|
|
||||||
super.init(initialViewState: IdentityConfirmationScreenViewState(learnMoreURL: userSettings.deviceVerificationURL))
|
super.init(initialViewState: IdentityConfirmationScreenViewState(learnMoreURL: userSettings.app.deviceVerificationURL))
|
||||||
|
|
||||||
Task { [weak self] in
|
Task { [weak self] in
|
||||||
for await state in userSession.sessionSecurityStatePublisher.values {
|
for await state in userSession.sessionSecurityStatePublisher.values {
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
stateMachine = SessionVerificationScreenStateMachine(state: verificationState)
|
stateMachine = SessionVerificationScreenStateMachine(state: verificationState)
|
||||||
|
|
||||||
super.init(initialViewState: .init(flow: flow,
|
super.init(initialViewState: .init(flow: flow,
|
||||||
learnMoreURL: userSettings.encryptionURL,
|
learnMoreURL: userSettings.app.encryptionURL,
|
||||||
verificationState: verificationState),
|
verificationState: verificationState),
|
||||||
mediaProvider: mediaProvider)
|
mediaProvider: mediaProvider)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@ class PinnedEventsTimelineScreenViewModel: PinnedEventsTimelineScreenViewModelTy
|
|||||||
private func viewInRoomTimeline(eventID: String) async {
|
private func viewInRoomTimeline(eventID: String) async {
|
||||||
switch await roomProxy.loadOrFetchEventDetails(for: eventID) {
|
switch await roomProxy.loadOrFetchEventDetails(for: eventID) {
|
||||||
case .success(let event):
|
case .success(let event):
|
||||||
let threadRootEventID: String? = if userSettings.threadsEnabled {
|
let threadRootEventID: String? = if userSettings.app.threadsEnabled {
|
||||||
event.threadRootEventId()
|
event.threadRootEventId()
|
||||||
} else {
|
} else {
|
||||||
nil
|
nil
|
||||||
|
|||||||
+1
-1
@@ -107,7 +107,7 @@ struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
let timelineController = TimelineControllerMock(.init(timelineKind: .pinned, timelineItems: timelineItems))
|
let timelineController = TimelineControllerMock(.init(timelineKind: .pinned, timelineItems: timelineItems))
|
||||||
|
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.messageMultiSelectEnabled = isSelecting
|
userSettings.app.messageMultiSelectEnabled = isSelecting
|
||||||
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
||||||
|
|
||||||
let timelineViewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room", pinnedEventIDs: Set(eventIDs))),
|
let timelineViewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room", pinnedEventIDs: Set(eventIDs))),
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
topicSummary: topic?.unattributedStringByReplacingNewlinesWithSpaces(),
|
topicSummary: topic?.unattributedStringByReplacingNewlinesWithSpaces(),
|
||||||
joinedMembersCount: roomProxy.infoPublisher.value.joinedMembersCount,
|
joinedMembersCount: roomProxy.infoPublisher.value.joinedMembersCount,
|
||||||
notificationSettingsState: .loading,
|
notificationSettingsState: .loading,
|
||||||
isNativeCallingEnabled: userSettings.nativeCallEnabled,
|
isNativeCallingEnabled: userSettings.app.nativeCallEnabled,
|
||||||
bindings: .init())
|
bindings: .init())
|
||||||
super.init(initialViewState: appHooks.roomDetailsScreenHook.update(viewState),
|
super.init(initialViewState: appHooks.roomDetailsScreenHook.update(viewState),
|
||||||
mediaProvider: userSession.mediaProvider)
|
mediaProvider: userSession.mediaProvider)
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
|||||||
|
|
||||||
super.init(initialViewState: ComposerToolbarViewState(wysiwygViewModel: wysiwygViewModel,
|
super.init(initialViewState: ComposerToolbarViewState(wysiwygViewModel: wysiwygViewModel,
|
||||||
isRoomEncrypted: roomProxy.infoPublisher.value.isEncrypted,
|
isRoomEncrypted: roomProxy.infoPublisher.value.isEncrypted,
|
||||||
isLocationSharingEnabled: userSettings.mapTilerConfiguration.publisher.value.isEnabled,
|
isLocationSharingEnabled: userSettings.app.mapTilerConfiguration.publisher.value.isEnabled,
|
||||||
bindings: .init()),
|
bindings: .init()),
|
||||||
mediaProvider: mediaProvider)
|
mediaProvider: mediaProvider)
|
||||||
|
|
||||||
|
|||||||
@@ -122,13 +122,13 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||||||
case .displayReportContent(let itemID, let senderID):
|
case .displayReportContent(let itemID, let senderID):
|
||||||
actionsSubject.send(.presentReportContent(itemID: itemID, senderID: senderID))
|
actionsSubject.send(.presentReportContent(itemID: itemID, senderID: senderID))
|
||||||
case .displayCameraPicker:
|
case .displayCameraPicker:
|
||||||
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .camera, selectionType: .multiple(galleryEnabled: userSettings.galleryEnabled)),
|
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .camera, selectionType: .multiple(galleryEnabled: userSettings.app.galleryEnabled)),
|
||||||
caption: composerViewModel.context.plainComposerText))
|
caption: composerViewModel.context.plainComposerText))
|
||||||
case .displayMediaPicker:
|
case .displayMediaPicker:
|
||||||
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .photoLibrary, selectionType: .multiple(galleryEnabled: userSettings.galleryEnabled)),
|
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .photoLibrary, selectionType: .multiple(galleryEnabled: userSettings.app.galleryEnabled)),
|
||||||
caption: composerViewModel.context.plainComposerText))
|
caption: composerViewModel.context.plainComposerText))
|
||||||
case .displayDocumentPicker:
|
case .displayDocumentPicker:
|
||||||
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .documents(), selectionType: .multiple(galleryEnabled: userSettings.galleryEnabled)),
|
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .documents(), selectionType: .multiple(galleryEnabled: userSettings.app.galleryEnabled)),
|
||||||
caption: composerViewModel.context.plainComposerText))
|
caption: composerViewModel.context.plainComposerText))
|
||||||
case .displayMediaPreview(let mediaPreviewViewModel):
|
case .displayMediaPreview(let mediaPreviewViewModel):
|
||||||
roomViewModel.displayMediaPreview(mediaPreviewViewModel)
|
roomViewModel.displayMediaPreview(mediaPreviewViewModel)
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
|
|
||||||
let viewState = RoomScreenViewState(roomTitle: roomProxy.infoPublisher.value.displayNameOrID,
|
let viewState = RoomScreenViewState(roomTitle: roomProxy.infoPublisher.value.displayNameOrID,
|
||||||
roomAvatar: roomProxy.infoPublisher.value.avatar,
|
roomAvatar: roomProxy.infoPublisher.value.avatar,
|
||||||
isNativeCallingEnabled: userSettings.nativeCallEnabled,
|
isNativeCallingEnabled: userSettings.app.nativeCallEnabled,
|
||||||
hasOngoingCall: roomProxy.infoPublisher.value.hasRoomCall,
|
hasOngoingCall: roomProxy.infoPublisher.value.hasRoomCall,
|
||||||
isDM: roomProxy.infoPublisher.value.isDM,
|
isDM: roomProxy.infoPublisher.value.isDM,
|
||||||
hasSuccessor: roomProxy.infoPublisher.value.successor != nil,
|
hasSuccessor: roomProxy.infoPublisher.value.successor != nil,
|
||||||
@@ -127,7 +127,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
Task {
|
Task {
|
||||||
// When navigating away from the room, we need to mark the room as both read
|
// When navigating away from the room, we need to mark the room as both read
|
||||||
// and fully read for Synapse to clear this room from the app's badge count.
|
// and fully read for Synapse to clear this room from the app's badge count.
|
||||||
_ = await roomProxy.markAsRead(receiptType: userSettings.sharePresence ? .read : .readPrivate)
|
_ = await roomProxy.markAsRead(receiptType: userSettings.app.sharePresence ? .read : .readPrivate)
|
||||||
_ = await roomProxy.markAsRead(receiptType: .fullyRead)
|
_ = await roomProxy.markAsRead(receiptType: .fullyRead)
|
||||||
}
|
}
|
||||||
// Work around QLPreviewController dismissal issues, see the InteractiveQuickLookModifier.
|
// Work around QLPreviewController dismissal issues, see the InteractiveQuickLookModifier.
|
||||||
@@ -166,11 +166,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func setupSubscriptions(ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never>) {
|
private func setupSubscriptions(ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never>) {
|
||||||
userSettings.threadsEnabledPublisher
|
userSettings.app.threadsEnabledPublisher
|
||||||
.weakAssign(to: \.state.roomThreadListEnabled, on: self)
|
.weakAssign(to: \.state.roomThreadListEnabled, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.liveLocationSharingSessionsByRoomIDPublisher
|
userSettings.app.liveLocationSharingSessionsByRoomIDPublisher
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] sessionsByRoomID in
|
.sink { [weak self] sessionsByRoomID in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
@@ -264,10 +264,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
|
|
||||||
if let member = identityVerificationViolations.values.first {
|
if let member = identityVerificationViolations.values.first {
|
||||||
state.footerDetails = .verificationViolation(member: member,
|
state.footerDetails = .verificationViolation(member: member,
|
||||||
learnMoreURL: userSettings.identityPinningViolationDetailsURL)
|
learnMoreURL: userSettings.app.identityPinningViolationDetailsURL)
|
||||||
} else if let member = identityPinningViolations.values.first {
|
} else if let member = identityPinningViolations.values.first {
|
||||||
state.footerDetails = .pinViolation(member: member,
|
state.footerDetails = .pinViolation(member: member,
|
||||||
learnMoreURL: userSettings.identityPinningViolationDetailsURL)
|
learnMoreURL: userSettings.app.identityPinningViolationDetailsURL)
|
||||||
} else {
|
} else {
|
||||||
state.footerDetails = nil
|
state.footerDetails = nil
|
||||||
}
|
}
|
||||||
@@ -428,7 +428,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
Task {
|
Task {
|
||||||
switch await roomProxy.loadOrFetchEventDetails(for: eventID) {
|
switch await roomProxy.loadOrFetchEventDetails(for: eventID) {
|
||||||
case .success(let event):
|
case .success(let event):
|
||||||
if userSettings.threadsEnabled,
|
if userSettings.app.threadsEnabled,
|
||||||
let threadRootEventID = event.threadRootEventId() {
|
let threadRootEventID = event.threadRootEventId() {
|
||||||
actionsSubject.send(.focusEvent(eventID: threadRootEventID))
|
actionsSubject.send(.focusEvent(eventID: threadRootEventID))
|
||||||
actionsSubject.send(.displayThread(threadRootEventID: threadRootEventID, focussedEventID: eventID))
|
actionsSubject.send(.displayThread(threadRootEventID: threadRootEventID, focussedEventID: eventID))
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
|
let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
|
||||||
|
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.messageMultiSelectEnabled = isSelecting
|
userSettings.app.messageMultiSelectEnabled = isSelecting
|
||||||
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
||||||
|
|
||||||
let timelineController = TimelineControllerMock(.init())
|
let timelineController = TimelineControllerMock(.init())
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
// The room list is empty on a cold start, so the room breadcrumbs are re-resolved as it loads.
|
// The room list is empty on a cold start, so the room breadcrumbs are re-resolved as it loads.
|
||||||
userSettings.searchBreadcrumbsPublisher
|
userSettings.account.searchBreadcrumbsPublisher
|
||||||
.combineLatest(clientProxy.staticRoomSummaryProvider.roomListPublisher)
|
.combineLatest(clientProxy.staticRoomSummaryProvider.roomListPublisher)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] storedBreadcrumbs, _ in
|
.sink { [weak self] storedBreadcrumbs, _ in
|
||||||
@@ -122,7 +122,7 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateRooms(with: roomSummaryProvider.roomListPublisher.value)
|
updateRooms(with: roomSummaryProvider.roomListPublisher.value)
|
||||||
state.breadcrumbs = makeBreadcrumbs(from: userSettings.searchBreadcrumbs)
|
state.breadcrumbs = makeBreadcrumbs(from: userSettings.account.searchBreadcrumbs)
|
||||||
}
|
}
|
||||||
|
|
||||||
isolated deinit {
|
isolated deinit {
|
||||||
@@ -154,7 +154,7 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro
|
|||||||
case .removeBreadcrumb(let breadcrumb):
|
case .removeBreadcrumb(let breadcrumb):
|
||||||
removeBreadcrumb(breadcrumb)
|
removeBreadcrumb(breadcrumb)
|
||||||
case .clearBreadcrumbs:
|
case .clearBreadcrumbs:
|
||||||
userSettings.searchBreadcrumbs = []
|
userSettings.account.searchBreadcrumbs = []
|
||||||
case .reachedTop:
|
case .reachedTop:
|
||||||
if state.bindings.searchMode == .rooms {
|
if state.bindings.searchMode == .rooms {
|
||||||
updateVisibleRange(edge: .top)
|
updateVisibleRange(edge: .top)
|
||||||
@@ -223,11 +223,11 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro
|
|||||||
|
|
||||||
/// Moves the breadcrumb to the top of the history, adding it if needed.
|
/// Moves the breadcrumb to the top of the history, adding it if needed.
|
||||||
private func recordBreadcrumb(_ breadcrumb: SearchBreadcrumb) {
|
private func recordBreadcrumb(_ breadcrumb: SearchBreadcrumb) {
|
||||||
var breadcrumbs = userSettings.searchBreadcrumbs
|
var breadcrumbs = userSettings.account.searchBreadcrumbs
|
||||||
breadcrumbs.removeAll { $0 == breadcrumb }
|
breadcrumbs.removeAll { $0 == breadcrumb }
|
||||||
breadcrumbs.insert(breadcrumb, at: 0)
|
breadcrumbs.insert(breadcrumb, at: 0)
|
||||||
|
|
||||||
userSettings.searchBreadcrumbs = Array(breadcrumbs.prefix(Self.maximumBreadcrumbCount))
|
userSettings.account.searchBreadcrumbs = Array(breadcrumbs.prefix(Self.maximumBreadcrumbCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func removeBreadcrumb(_ breadcrumb: SearchScreenBreadcrumb) {
|
private func removeBreadcrumb(_ breadcrumb: SearchScreenBreadcrumb) {
|
||||||
@@ -235,7 +235,7 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro
|
|||||||
case .query(let query): .query(query)
|
case .query(let query): .query(query)
|
||||||
case .room(let room): .room(roomID: room.id)
|
case .room(let room): .room(roomID: room.id)
|
||||||
}
|
}
|
||||||
userSettings.searchBreadcrumbs.removeAll { $0 == storedBreadcrumb }
|
userSettings.account.searchBreadcrumbs.removeAll { $0 == storedBreadcrumb }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setActiveTabLoading(_ isLoading: Bool) {
|
private func setActiveTabLoading(_ isLoading: Bool) {
|
||||||
|
|||||||
@@ -709,7 +709,7 @@ struct SearchScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
private static func makeUserSettings(breadcrumbs: [SearchBreadcrumb]) -> UserSettings {
|
private static func makeUserSettings(breadcrumbs: [SearchBreadcrumb]) -> UserSettings {
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.searchBreadcrumbs = breadcrumbs
|
userSettings.account.searchBreadcrumbs = breadcrumbs
|
||||||
return userSettings
|
return userSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol {
|
|||||||
init(parameters: SecureBackupScreenCoordinatorParameters) {
|
init(parameters: SecureBackupScreenCoordinatorParameters) {
|
||||||
viewModel = SecureBackupScreenViewModel(secureBackupController: parameters.clientProxy.secureBackupController,
|
viewModel = SecureBackupScreenViewModel(secureBackupController: parameters.clientProxy.secureBackupController,
|
||||||
userIndicatorController: parameters.userIndicatorController,
|
userIndicatorController: parameters.userIndicatorController,
|
||||||
chatBackupDetailsURL: parameters.userSettings.chatBackupDetailsURL)
|
chatBackupDetailsURL: parameters.userSettings.app.chatBackupDetailsURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
|
|||||||
+4
-4
@@ -37,8 +37,8 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
isEncryptionEnabled: roomProxy.infoPublisher.value.isEncrypted,
|
isEncryptionEnabled: roomProxy.infoPublisher.value.isEncrypted,
|
||||||
historyVisibility: roomProxy.infoPublisher.value.historyVisibility.toSecurityAndPrivacyHistoryVisibility,
|
historyVisibility: roomProxy.infoPublisher.value.historyVisibility.toSecurityAndPrivacyHistoryVisibility,
|
||||||
isSpace: roomProxy.infoPublisher.value.isSpace,
|
isSpace: roomProxy.infoPublisher.value.isSpace,
|
||||||
isKnockingEnabled: userSettings.knockingEnabled,
|
isKnockingEnabled: userSettings.app.knockingEnabled,
|
||||||
historySharingDetailsURL: userSettings.historySharingDetailsURL)
|
historySharingDetailsURL: userSettings.app.historySharingDetailsURL)
|
||||||
|
|
||||||
super.init(initialViewState: appHooks.securityAndPrivacyScreenHook.update(viewState, homeserver: clientProxy.homeserver))
|
super.init(initialViewState: appHooks.securityAndPrivacyScreenHook.update(viewState, homeserver: clientProxy.homeserver))
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
.weakAssign(to: \.state.isSpace, on: self)
|
.weakAssign(to: \.state.isSpace, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.knockingEnabledPublisher
|
userSettings.app.knockingEnabledPublisher
|
||||||
.weakAssign(to: \.state.isKnockingEnabled, on: self)
|
.weakAssign(to: \.state.isKnockingEnabled, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
state.canEditAddress = powerLevels.canOwnUser(sendStateEvent: .roomCanonicalAlias)
|
state.canEditAddress = powerLevels.canOwnUser(sendStateEvent: .roomCanonicalAlias)
|
||||||
state.canEditJoinRule = powerLevels.canOwnUser(sendStateEvent: .roomJoinRules)
|
state.canEditJoinRule = powerLevels.canOwnUser(sendStateEvent: .roomJoinRules)
|
||||||
state.canEditHistoryVisibility = powerLevels.canOwnUser(sendStateEvent: .roomHistoryVisibility)
|
state.canEditHistoryVisibility = powerLevels.canOwnUser(sendStateEvent: .roomHistoryVisibility)
|
||||||
state.canEnableEncryption = powerLevels.canOwnUser(sendStateEvent: .roomEncryption) && !userSettings.forceDisableE2EE.publisher.value
|
state.canEnableEncryption = powerLevels.canOwnUser(sendStateEvent: .roomEncryption) && !userSettings.app.forceDisableE2EE.publisher.value
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupRoomDirectoryVisibility() {
|
private func setupRoomDirectoryVisibility() {
|
||||||
|
|||||||
+3
-3
@@ -292,7 +292,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
static let askToJoinViewModel = {
|
static let askToJoinViewModel = {
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.knockingEnabled = true
|
userSettings.app.knockingEnabled = true
|
||||||
|
|
||||||
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
|
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
|
||||||
canonicalAlias: "#room:matrix.org",
|
canonicalAlias: "#room:matrix.org",
|
||||||
@@ -307,7 +307,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
static let singleAskToJoinSpaceMembersViewModel = {
|
static let singleAskToJoinSpaceMembersViewModel = {
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.knockingEnabled = true
|
userSettings.app.knockingEnabled = true
|
||||||
|
|
||||||
let space = [SpaceServiceRoom].mockSingleRoom[0]
|
let space = [SpaceServiceRoom].mockSingleRoom[0]
|
||||||
|
|
||||||
@@ -325,7 +325,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
static let multipleAskToJoinSpacesMembersViewModel = {
|
static let multipleAskToJoinSpacesMembersViewModel = {
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.knockingEnabled = true
|
userSettings.app.knockingEnabled = true
|
||||||
|
|
||||||
let spaces = [SpaceServiceRoom].mockJoinedSpaces
|
let spaces = [SpaceServiceRoom].mockJoinedSpaces
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ class OAuthAccountSettingsPresenter: NSObject {
|
|||||||
appHooks: AppHooks,
|
appHooks: AppHooks,
|
||||||
continuation: Continuation? = nil) {
|
continuation: Continuation? = nil) {
|
||||||
self.accountURL = accountURL
|
self.accountURL = accountURL
|
||||||
redirectURL = userSettings.oAuthRedirectURL
|
redirectURL = userSettings.app.oAuthRedirectURL
|
||||||
self.presentationAnchor = presentationAnchor
|
self.presentationAnchor = presentationAnchor
|
||||||
self.appMediator = appMediator
|
self.appMediator = appMediator
|
||||||
self.appHooks = appHooks
|
self.appHooks = appHooks
|
||||||
|
|||||||
+2
-2
@@ -17,13 +17,13 @@ class AnalyticsSettingsScreenViewModel: AnalyticsSettingsScreenViewModelType, An
|
|||||||
init(userSettings: UserSettings, analytics: AnalyticsServiceProtocol) {
|
init(userSettings: UserSettings, analytics: AnalyticsServiceProtocol) {
|
||||||
self.analytics = analytics
|
self.analytics = analytics
|
||||||
|
|
||||||
let strings = AnalyticsSettingsScreenStrings(termsURL: userSettings.analyticsTermsURL)
|
let strings = AnalyticsSettingsScreenStrings(termsURL: userSettings.app.analyticsTermsURL)
|
||||||
let bindings = AnalyticsSettingsScreenViewStateBindings(enableAnalytics: analytics.isEnabled)
|
let bindings = AnalyticsSettingsScreenViewStateBindings(enableAnalytics: analytics.isEnabled)
|
||||||
let state = AnalyticsSettingsScreenViewState(strings: strings, bindings: bindings)
|
let state = AnalyticsSettingsScreenViewState(strings: strings, bindings: bindings)
|
||||||
|
|
||||||
super.init(initialViewState: state)
|
super.init(initialViewState: state)
|
||||||
|
|
||||||
userSettings.analyticsConsentStatePublisher
|
userSettings.app.analyticsConsentStatePublisher
|
||||||
.map { $0 == .optedIn }
|
.map { $0 == .optedIn }
|
||||||
.weakAssign(to: \.state.bindings.enableAnalytics, on: self)
|
.weakAssign(to: \.state.bindings.enableAnalytics, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
+3
-3
@@ -13,8 +13,8 @@ typealias LegalInformationScreenViewModelType = StateStoreViewModelV2<LegalInfor
|
|||||||
|
|
||||||
class LegalInformationScreenViewModel: LegalInformationScreenViewModelType, LegalInformationScreenViewModelProtocol {
|
class LegalInformationScreenViewModel: LegalInformationScreenViewModelType, LegalInformationScreenViewModelProtocol {
|
||||||
init(userSettings: UserSettings) {
|
init(userSettings: UserSettings) {
|
||||||
super.init(initialViewState: LegalInformationScreenViewState(copyrightURL: userSettings.copyrightURL,
|
super.init(initialViewState: LegalInformationScreenViewState(copyrightURL: userSettings.app.copyrightURL,
|
||||||
acceptableUseURL: userSettings.acceptableUseURL,
|
acceptableUseURL: userSettings.app.acceptableUseURL,
|
||||||
privacyURL: userSettings.privacyURL))
|
privacyURL: userSettings.app.privacyURL))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -33,8 +33,8 @@ struct LocationSharingSettingsScreenViewStateBindings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var liveLocationMinimumDistanceUpdate: Int {
|
var liveLocationMinimumDistanceUpdate: Int {
|
||||||
get { userSettings.liveLocationMinimumDistanceUpdate }
|
get { userSettings.app.liveLocationMinimumDistanceUpdate }
|
||||||
set { userSettings.liveLocationMinimumDistanceUpdate = newValue }
|
set { userSettings.app.liveLocationMinimumDistanceUpdate = newValue }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -20,8 +20,8 @@ struct MediaUploadQualitySettingsScreenViewStateBindings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var optimizeMediaUploads: Bool {
|
var optimizeMediaUploads: Bool {
|
||||||
get { userSettings.optimizeMediaUploads }
|
get { userSettings.app.optimizeMediaUploads }
|
||||||
set { userSettings.optimizeMediaUploads = newValue }
|
set { userSettings.app.optimizeMediaUploads = newValue }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -32,8 +32,8 @@ struct ModerationAndSafetySettingsScreenViewStateBindings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sharePresence: Bool {
|
var sharePresence: Bool {
|
||||||
get { userSettings.sharePresence }
|
get { userSettings.app.sharePresence }
|
||||||
set { userSettings.sharePresence = newValue }
|
set { userSettings.app.sharePresence = newValue }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -38,21 +38,21 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
|||||||
notificationTonePreviewer = AudioPlayer()
|
notificationTonePreviewer = AudioPlayer()
|
||||||
self.notificationToneManager = notificationToneManager
|
self.notificationToneManager = notificationToneManager
|
||||||
|
|
||||||
let bindings = NotificationSettingsScreenViewStateBindings(enableNotifications: userSettings.enableNotifications,
|
let bindings = NotificationSettingsScreenViewStateBindings(enableNotifications: userSettings.app.enableNotifications,
|
||||||
showAllRoomListActivity: userSettings.showAllRoomListActivity)
|
showAllRoomListActivity: userSettings.app.showAllRoomListActivity)
|
||||||
super.init(initialViewState: NotificationSettingsScreenViewState(bindings: bindings,
|
super.init(initialViewState: NotificationSettingsScreenViewState(bindings: bindings,
|
||||||
isModallyPresented: isModallyPresented,
|
isModallyPresented: isModallyPresented,
|
||||||
selectedAlertTone: userSettings.selectedNotificationTone ?? NotificationToneManager.defaultElementXMessageTone,
|
selectedAlertTone: userSettings.app.selectedNotificationTone ?? NotificationToneManager.defaultElementXMessageTone,
|
||||||
availableCustomTones: notificationToneManager.customTones(),
|
availableCustomTones: notificationToneManager.customTones(),
|
||||||
// macos lacks default sounds and its sandbox Sounds directory is immutable
|
// macos lacks default sounds and its sandbox Sounds directory is immutable
|
||||||
customToneSelectionEnabled: !ProcessInfo.processInfo.isiOSAppOnMac))
|
customToneSelectionEnabled: !ProcessInfo.processInfo.isiOSAppOnMac))
|
||||||
|
|
||||||
// Listen for changes to UserSettings.
|
// Listen for changes to UserSettings.
|
||||||
userSettings.enableNotificationsPublisher
|
userSettings.app.enableNotificationsPublisher
|
||||||
.weakAssign(to: \.state.bindings.enableNotifications, on: self)
|
.weakAssign(to: \.state.bindings.enableNotifications, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.selectedNotificationTonePublisher
|
userSettings.app.selectedNotificationTonePublisher
|
||||||
.map { $0 ?? NotificationToneManager.defaultElementXMessageTone }
|
.map { $0 ?? NotificationToneManager.defaultElementXMessageTone }
|
||||||
.weakAssign(to: \.state.selectedAlertTone, on: self)
|
.weakAssign(to: \.state.selectedAlertTone, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -104,7 +104,7 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
|||||||
}
|
}
|
||||||
Task { await enableInvitations(state.bindings.invitationsEnabled) }
|
Task { await enableInvitations(state.bindings.invitationsEnabled) }
|
||||||
case .showAllRoomListActivityChanged:
|
case .showAllRoomListActivityChanged:
|
||||||
userSettings.showAllRoomListActivity = state.bindings.showAllRoomListActivity
|
userSettings.app.showAllRoomListActivity = state.bindings.showAllRoomListActivity
|
||||||
case .close:
|
case .close:
|
||||||
actionsSubject.send(.close)
|
actionsSubject.send(.close)
|
||||||
case .fixConfigurationMismatchTapped:
|
case .fixConfigurationMismatchTapped:
|
||||||
@@ -127,7 +127,7 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
|||||||
}
|
}
|
||||||
|
|
||||||
func toggleNotifications() {
|
func toggleNotifications() {
|
||||||
userSettings.enableNotifications.toggle()
|
userSettings.app.enableNotifications.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupDidBecomeActiveSubscription() {
|
private func setupDidBecomeActiveSubscription() {
|
||||||
@@ -302,7 +302,7 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
|||||||
try notificationToneManager.deleteCustomTone(tone)
|
try notificationToneManager.deleteCustomTone(tone)
|
||||||
|
|
||||||
if tone == state.selectedAlertTone {
|
if tone == state.selectedAlertTone {
|
||||||
userSettings.selectedNotificationTone = nil
|
userSettings.app.selectedNotificationTone = nil
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("Error deleting alert tone \(tone.label): \(error)")
|
MXLog.error("Error deleting alert tone \(tone.label): \(error)")
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ struct SettingsScreenViewStateBindings {
|
|||||||
var isPresentingAccountDeactivationConfirmation = false
|
var isPresentingAccountDeactivationConfirmation = false
|
||||||
|
|
||||||
var appAppearance: AppAppearance {
|
var appAppearance: AppAppearance {
|
||||||
get { userSettings.appAppearance }
|
get { userSettings.app.appAppearance }
|
||||||
set { userSettings.appAppearance = newValue }
|
set { userSettings.app.appAppearance = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
init(userSettings: UserSettings) {
|
init(userSettings: UserSettings) {
|
||||||
|
|||||||
@@ -29,25 +29,25 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
|
|
||||||
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
|
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
|
||||||
userProfile: userSession.clientProxy.userProfilePublisher.value,
|
userProfile: userSession.clientProxy.userProfilePublisher.value,
|
||||||
showLinkNewDeviceButton: userSettings.linkNewDeviceEnabled,
|
showLinkNewDeviceButton: userSettings.app.linkNewDeviceEnabled,
|
||||||
showAddAccountButton: userSettings.multiAccountEnabled,
|
showAddAccountButton: userSettings.app.multiAccountEnabled,
|
||||||
showAccountDeactivation: userSession.clientProxy.canDeactivateAccount,
|
showAccountDeactivation: userSession.clientProxy.canDeactivateAccount,
|
||||||
showDeveloperOptions: userSettings.developerOptionsEnabled,
|
showDeveloperOptions: userSettings.app.developerOptionsEnabled,
|
||||||
showAnalyticsSettings: userSettings.canPromptForAnalytics,
|
showAnalyticsSettings: userSettings.app.canPromptForAnalytics,
|
||||||
isBugReportServiceEnabled: isBugReportServiceEnabled,
|
isBugReportServiceEnabled: isBugReportServiceEnabled,
|
||||||
navigationBarVisibility: isInSecondaryWindow ? .hidden : .automatic,
|
navigationBarVisibility: isInSecondaryWindow ? .hidden : .automatic,
|
||||||
bindings: .init(userSettings: userSettings)),
|
bindings: .init(userSettings: userSettings)),
|
||||||
mediaProvider: userSession.mediaProvider)
|
mediaProvider: userSession.mediaProvider)
|
||||||
|
|
||||||
userSettings.developerOptionsEnabledPublisher
|
userSettings.app.developerOptionsEnabledPublisher
|
||||||
.weakAssign(to: \.state.showDeveloperOptions, on: self)
|
.weakAssign(to: \.state.showDeveloperOptions, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.linkNewDeviceEnabledPublisher
|
userSettings.app.linkNewDeviceEnabledPublisher
|
||||||
.weakAssign(to: \.state.showLinkNewDeviceButton, on: self)
|
.weakAssign(to: \.state.showLinkNewDeviceButton, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.multiAccountEnabledPublisher
|
userSettings.app.multiAccountEnabledPublisher
|
||||||
.weakAssign(to: \.state.showAddAccountButton, on: self)
|
.weakAssign(to: \.state.showAddAccountButton, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
actionsSubject.send(.about)
|
actionsSubject.send(.about)
|
||||||
|
|
||||||
case .enableDeveloperOptions:
|
case .enableDeveloperOptions:
|
||||||
userSettings.developerOptionsEnabled.toggle()
|
userSettings.app.developerOptionsEnabled.toggle()
|
||||||
case .developerOptions:
|
case .developerOptions:
|
||||||
actionsSubject.send(.developerOptions)
|
actionsSubject.send(.developerOptions)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,8 +318,8 @@ struct SettingsScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
displayName: "Alice Liddell",
|
displayName: "Alice Liddell",
|
||||||
status: .mockFocussing))))
|
status: .mockFocussing))))
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.multiAccountEnabled = true
|
userSettings.app.multiAccountEnabled = true
|
||||||
userSettings.linkNewDeviceEnabled = true
|
userSettings.app.linkNewDeviceEnabled = true
|
||||||
return SettingsScreenViewModel(userSession: userSession,
|
return SettingsScreenViewModel(userSession: userSession,
|
||||||
userSettings: userSettings,
|
userSettings: userSettings,
|
||||||
isBugReportServiceEnabled: isBugReportServiceEnabled,
|
isBugReportServiceEnabled: isBugReportServiceEnabled,
|
||||||
|
|||||||
@@ -109,13 +109,13 @@ final class ThreadTimelineScreenCoordinator: CoordinatorProtocol {
|
|||||||
case .displayReportContent(let itemID, let senderID):
|
case .displayReportContent(let itemID, let senderID):
|
||||||
actionsSubject.send(.presentReportContent(itemID: itemID, senderID: senderID))
|
actionsSubject.send(.presentReportContent(itemID: itemID, senderID: senderID))
|
||||||
case .displayCameraPicker:
|
case .displayCameraPicker:
|
||||||
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .camera, selectionType: .multiple(galleryEnabled: userSettings.galleryEnabled)),
|
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .camera, selectionType: .multiple(galleryEnabled: userSettings.app.galleryEnabled)),
|
||||||
caption: composerViewModel.context.plainComposerText))
|
caption: composerViewModel.context.plainComposerText))
|
||||||
case .displayMediaPicker:
|
case .displayMediaPicker:
|
||||||
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .photoLibrary, selectionType: .multiple(galleryEnabled: userSettings.galleryEnabled)),
|
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .photoLibrary, selectionType: .multiple(galleryEnabled: userSettings.app.galleryEnabled)),
|
||||||
caption: composerViewModel.context.plainComposerText))
|
caption: composerViewModel.context.plainComposerText))
|
||||||
case .displayDocumentPicker:
|
case .displayDocumentPicker:
|
||||||
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .documents(), selectionType: .multiple(galleryEnabled: userSettings.galleryEnabled)),
|
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .documents(), selectionType: .multiple(galleryEnabled: userSettings.app.galleryEnabled)),
|
||||||
caption: composerViewModel.context.plainComposerText))
|
caption: composerViewModel.context.plainComposerText))
|
||||||
case .displayMediaPreview(let mediaPreviewViewModel):
|
case .displayMediaPreview(let mediaPreviewViewModel):
|
||||||
viewModel.displayMediaPreview(mediaPreviewViewModel)
|
viewModel.displayMediaPreview(mediaPreviewViewModel)
|
||||||
|
|||||||
@@ -455,8 +455,8 @@ class TimelineInteractionHandler {
|
|||||||
// MARK: Audio Playback
|
// MARK: Audio Playback
|
||||||
|
|
||||||
func changePlaybackSpeed(for itemID: TimelineItemIdentifier) {
|
func changePlaybackSpeed(for itemID: TimelineItemIdentifier) {
|
||||||
let nextSpeed = userSettings.voiceMessagePlaybackSpeed.next
|
let nextSpeed = userSettings.app.voiceMessagePlaybackSpeed.next
|
||||||
userSettings.voiceMessagePlaybackSpeed = nextSpeed
|
userSettings.app.voiceMessagePlaybackSpeed = nextSpeed
|
||||||
audioPlayerState(for: itemID)?.setPlaybackSpeed(nextSpeed)
|
audioPlayerState(for: itemID)?.setPlaybackSpeed(nextSpeed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,8 +588,8 @@ class TimelineInteractionHandler {
|
|||||||
title: L10n.commonVoiceMessage,
|
title: L10n.commonVoiceMessage,
|
||||||
duration: voiceMessageRoomTimelineItem.content.duration,
|
duration: voiceMessageRoomTimelineItem.content.duration,
|
||||||
waveform: voiceMessageRoomTimelineItem.content.waveform,
|
waveform: voiceMessageRoomTimelineItem.content.waveform,
|
||||||
playbackSpeed: userSettings.voiceMessagePlaybackSpeed,
|
playbackSpeed: userSettings.app.voiceMessagePlaybackSpeed,
|
||||||
playbackSpeedPublisher: userSettings.voiceMessagePlaybackSpeedPublisher)
|
playbackSpeedPublisher: userSettings.app.voiceMessagePlaybackSpeedPublisher)
|
||||||
mediaPlayerProvider.register(audioPlayerState: playerState)
|
mediaPlayerProvider.register(audioPlayerState: playerState)
|
||||||
return playerState
|
return playerState
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,16 +97,16 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
timelineState: TimelineState(focussedEvent: focussedEventID.map { .init(eventID: $0, appearance: .immediate) }),
|
timelineState: TimelineState(focussedEvent: focussedEventID.map { .init(eventID: $0, appearance: .immediate) }),
|
||||||
ownUserID: roomProxy.ownUserID,
|
ownUserID: roomProxy.ownUserID,
|
||||||
hideTimelineMedia: hideTimelineMedia,
|
hideTimelineMedia: hideTimelineMedia,
|
||||||
isViewSourceEnabled: userSettings.viewSourceEnabled,
|
isViewSourceEnabled: userSettings.app.viewSourceEnabled,
|
||||||
areThreadsEnabled: userSettings.threadsEnabled,
|
areThreadsEnabled: userSettings.app.threadsEnabled,
|
||||||
linkPreviewsEnabled: userSettings.linkPreviewsEnabled,
|
linkPreviewsEnabled: userSettings.app.linkPreviewsEnabled,
|
||||||
jumpToReadMarkerEnabled: userSettings.jumpToReadMarkerEnabled,
|
jumpToReadMarkerEnabled: userSettings.app.jumpToReadMarkerEnabled,
|
||||||
messageSelection: .init(isEnabled: userSettings.messageMultiSelectEnabled),
|
messageSelection: .init(isEnabled: userSettings.app.messageMultiSelectEnabled),
|
||||||
hasPredecessor: roomProxy.predecessorRoom != nil,
|
hasPredecessor: roomProxy.predecessorRoom != nil,
|
||||||
pinnedEventIDs: roomProxy.infoPublisher.value.pinnedEventIDs,
|
pinnedEventIDs: roomProxy.infoPublisher.value.pinnedEventIDs,
|
||||||
emojiProvider: emojiProvider,
|
emojiProvider: emojiProvider,
|
||||||
linkMetadataProvider: hideTimelineMedia ? nil : linkMetadataProvider,
|
linkMetadataProvider: hideTimelineMedia ? nil : linkMetadataProvider,
|
||||||
mapTilerConfiguration: userSettings.mapTilerConfiguration.publisher.value,
|
mapTilerConfiguration: userSettings.app.mapTilerConfiguration.publisher.value,
|
||||||
bindings: .init(reactionsCollapsed: [:])),
|
bindings: .init(reactionsCollapsed: [:])),
|
||||||
mediaProvider: userSession.mediaProvider,
|
mediaProvider: userSession.mediaProvider,
|
||||||
contentScannerService: userSession.contentScannerService)
|
contentScannerService: userSession.contentScannerService)
|
||||||
@@ -272,7 +272,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
case .voiceMessage(let voiceMessageAction):
|
case .voiceMessage(let voiceMessageAction):
|
||||||
processVoiceMessageAction(voiceMessageAction)
|
processVoiceMessageAction(voiceMessageAction)
|
||||||
case .contentChanged(let isEmpty):
|
case .contentChanged(let isEmpty):
|
||||||
guard userSettings.sharePresence else {
|
guard userSettings.app.sharePresence else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,7 +508,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
|
|
||||||
roomProxy.typingMembersPublisher
|
roomProxy.typingMembersPublisher
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.filter { [weak self] _ in self?.userSettings.sharePresence ?? false }
|
.filter { [weak self] _ in self?.userSettings.app.sharePresence ?? false }
|
||||||
.weakAssign(to: \.state.typingMembers, on: self)
|
.weakAssign(to: \.state.typingMembers, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
@@ -560,7 +560,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
func viewInRoomTimeline(eventID: String) async {
|
func viewInRoomTimeline(eventID: String) async {
|
||||||
switch await roomProxy.loadOrFetchEventDetails(for: eventID) {
|
switch await roomProxy.loadOrFetchEventDetails(for: eventID) {
|
||||||
case .success(let event):
|
case .success(let event):
|
||||||
let threadRootEventID: String? = if userSettings.threadsEnabled {
|
let threadRootEventID: String? = if userSettings.app.threadsEnabled {
|
||||||
event.threadRootEventId()
|
event.threadRootEventId()
|
||||||
} else {
|
} else {
|
||||||
nil
|
nil
|
||||||
@@ -572,19 +572,19 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupUserSettingsSubscriptions() {
|
private func setupUserSettingsSubscriptions() {
|
||||||
userSettings.sharePresencePublisher
|
userSettings.app.sharePresencePublisher
|
||||||
.weakAssign(to: \.state.showReadReceipts, on: self)
|
.weakAssign(to: \.state.showReadReceipts, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.viewSourceEnabledPublisher
|
userSettings.app.viewSourceEnabledPublisher
|
||||||
.weakAssign(to: \.state.isViewSourceEnabled, on: self)
|
.weakAssign(to: \.state.isViewSourceEnabled, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.threadsEnabledPublisher
|
userSettings.app.threadsEnabledPublisher
|
||||||
.weakAssign(to: \.state.areThreadsEnabled, on: self)
|
.weakAssign(to: \.state.areThreadsEnabled, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.jumpToReadMarkerEnabledPublisher
|
userSettings.app.jumpToReadMarkerEnabledPublisher
|
||||||
.weakAssign(to: \.state.jumpToReadMarkerEnabled, on: self)
|
.weakAssign(to: \.state.jumpToReadMarkerEnabled, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
@@ -1145,7 +1145,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
primaryButton: .init(title: L10n.actionOk, action: nil),
|
primaryButton: .init(title: L10n.actionOk, action: nil),
|
||||||
secondaryButton: .init(title: L10n.actionLearnMore) { [weak self] in
|
secondaryButton: .init(title: L10n.actionLearnMore) { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
appMediator.open(userSettings.historySharingDetailsURL)
|
appMediator.open(userSettings.app.historySharingDetailsURL)
|
||||||
})
|
})
|
||||||
case .inviteAgain:
|
case .inviteAgain:
|
||||||
state.bindings.alertInfo = .init(id: .inviteAgain,
|
state.bindings.alertInfo = .init(id: .inviteAgain,
|
||||||
@@ -1196,7 +1196,7 @@ extension TimelineViewModel {
|
|||||||
|
|
||||||
extension TimelineViewModel {
|
extension TimelineViewModel {
|
||||||
private func setupSelectionSubscriptions() {
|
private func setupSelectionSubscriptions() {
|
||||||
userSettings.messageMultiSelectEnabledPublisher
|
userSettings.app.messageMultiSelectEnabledPublisher
|
||||||
.sink { [weak self] isEnabled in
|
.sink { [weak self] isEnabled in
|
||||||
self?.state.messageSelection.isEnabled = isEnabled
|
self?.state.messageSelection.isEnabled = isEnabled
|
||||||
if !isEnabled {
|
if !isEnabled {
|
||||||
|
|||||||
@@ -427,7 +427,7 @@ private extension TimelineItemKeyForwarder {
|
|||||||
struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview {
|
struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview {
|
||||||
static let viewModel: TimelineViewModel = {
|
static let viewModel: TimelineViewModel = {
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.threadsEnabled = true
|
userSettings.app.threadsEnabled = true
|
||||||
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
||||||
|
|
||||||
let roomProxy = JoinedRoomProxyMock(.init())
|
let roomProxy = JoinedRoomProxyMock(.init())
|
||||||
@@ -446,7 +446,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
|
|
||||||
static let viewModelWithPins: TimelineViewModel = {
|
static let viewModelWithPins: TimelineViewModel = {
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.threadsEnabled = true
|
userSettings.app.threadsEnabled = true
|
||||||
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
||||||
|
|
||||||
let roomProxy = JoinedRoomProxyMock(.init(name: "Preview Room", pinnedEventIDs: ["pinned"]))
|
let roomProxy = JoinedRoomProxyMock(.init(name: "Preview Room", pinnedEventIDs: ["pinned"]))
|
||||||
@@ -467,7 +467,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
|
|
||||||
static let selectingViewModel: TimelineViewModel = {
|
static let selectingViewModel: TimelineViewModel = {
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.messageMultiSelectEnabled = true
|
userSettings.app.messageMultiSelectEnabled = true
|
||||||
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
||||||
|
|
||||||
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init()),
|
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init()),
|
||||||
|
|||||||
@@ -67,12 +67,12 @@ nonisolated struct NotificationToneManager: NotificationToneManagerProtocol {
|
|||||||
try? FileManager.default.removeItem(at: Self.selectedToneLocation)
|
try? FileManager.default.removeItem(at: Self.selectedToneLocation)
|
||||||
let toneLocation = Self.toneLocation(for: alertTone)
|
let toneLocation = Self.toneLocation(for: alertTone)
|
||||||
try FileManager.default.copyItem(at: toneLocation, to: Self.selectedToneLocation)
|
try FileManager.default.copyItem(at: toneLocation, to: Self.selectedToneLocation)
|
||||||
userSettings.selectedNotificationTone = alertTone
|
userSettings.app.selectedNotificationTone = alertTone
|
||||||
return Self.selectedToneLocation
|
return Self.selectedToneLocation
|
||||||
} catch {
|
} catch {
|
||||||
if (try? Self.selectedToneLocation.checkResourceIsReachable()) != true {
|
if (try? Self.selectedToneLocation.checkResourceIsReachable()) != true {
|
||||||
// make sure the selected tone is reset if there's no custom tone present
|
// make sure the selected tone is reset if there's no custom tone present
|
||||||
userSettings.selectedNotificationTone = nil
|
userSettings.app.selectedNotificationTone = nil
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
|||||||
switch await userSession(for: client) {
|
switch await userSession(for: client) {
|
||||||
case .success(let userSession):
|
case .success(let userSession):
|
||||||
// Since the QR code login flow includes verification.
|
// Since the QR code login flow includes verification.
|
||||||
userSession.userSettings.hasRunIdentityConfirmationOnboarding = true
|
userSession.userSettings.account.hasRunIdentityConfirmationOnboarding = true
|
||||||
progressSubject.send(.signedIn(userSession))
|
progressSubject.send(.signedIn(userSession))
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
progressSubject.send(completion: .failure(error))
|
progressSubject.send(completion: .failure(error))
|
||||||
@@ -366,7 +366,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
|||||||
MXLog.info("Classic app account secrets imported.")
|
MXLog.info("Classic app account secrets imported.")
|
||||||
|
|
||||||
// Importing the secrets automatically verifies the session.
|
// Importing the secrets automatically verifies the session.
|
||||||
appSettings.userSettings(for: classicAppAccount.userID).hasRunIdentityConfirmationOnboarding = true
|
appSettings.userSettings(for: classicAppAccount.userID).account.hasRunIdentityConfirmationOnboarding = true
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("Failed to import secrets for Classic app account: \(error)")
|
MXLog.error("Failed to import secrets for Classic app account: \(error)")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
|
|
||||||
// Route media downloads through a content scanner when one has been configured for the server,
|
// Route media downloads through a content scanner when one has been configured for the server,
|
||||||
// and expose a proxy for the active scanning of content in the timeline.
|
// and expose a proxy for the active scanning of content in the timeline.
|
||||||
if let contentScannerURL = userSettings.contentScannerURL.publisher.value, let client = client as? Client {
|
if let contentScannerURL = userSettings.app.contentScannerURL.publisher.value, let client = client as? Client {
|
||||||
let scanner = ContentScanner(scannerUrl: contentScannerURL.absoluteString)
|
let scanner = ContentScanner(scannerUrl: contentScannerURL.absoluteString)
|
||||||
await client.setContentScanner(contentScanner: scanner)
|
await client.setContentScanner(contentScanner: scanner)
|
||||||
contentScanner = ContentScannerProxy(contentScanner: scanner, client: client)
|
contentScanner = ContentScannerProxy(contentScanner: scanner, client: client)
|
||||||
@@ -516,7 +516,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
do {
|
do {
|
||||||
let parameters = CreateRoomParameters(name: nil,
|
let parameters = CreateRoomParameters(name: nil,
|
||||||
topic: nil,
|
topic: nil,
|
||||||
isEncrypted: !userSettings.forceDisableE2EE.publisher.value,
|
isEncrypted: !userSettings.app.forceDisableE2EE.publisher.value,
|
||||||
isDirect: true,
|
isDirect: true,
|
||||||
visibility: .private,
|
visibility: .private,
|
||||||
preset: .trustedPrivateChat,
|
preset: .trustedPrivateChat,
|
||||||
@@ -559,7 +559,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
|
|
||||||
let parameters = CreateRoomParameters(name: name,
|
let parameters = CreateRoomParameters(name: name,
|
||||||
topic: topic,
|
topic: topic,
|
||||||
isEncrypted: !userSettings.forceDisableE2EE.publisher.value && accessType.isEncrypted,
|
isEncrypted: !userSettings.app.forceDisableE2EE.publisher.value && accessType.isEncrypted,
|
||||||
isDirect: false,
|
isDirect: false,
|
||||||
visibility: accessType.visibility,
|
visibility: accessType.visibility,
|
||||||
preset: accessType.preset,
|
preset: accessType.preset,
|
||||||
@@ -1179,7 +1179,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
private func reconcileServiceState() async {
|
private func reconcileServiceState() async {
|
||||||
switch desiredServiceState {
|
switch desiredServiceState {
|
||||||
case .running(let offline):
|
case .running(let offline):
|
||||||
if userSettings.clientPausingAndResumingEnabled {
|
if userSettings.app.clientPausingAndResumingEnabled {
|
||||||
do {
|
do {
|
||||||
MXLog.info("Resuming client")
|
MXLog.info("Resuming client")
|
||||||
try await client.resume()
|
try await client.resume()
|
||||||
@@ -1215,7 +1215,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
await syncService.stop()
|
await syncService.stop()
|
||||||
MXLog.info("Sync stopped")
|
MXLog.info("Sync stopped")
|
||||||
|
|
||||||
if userSettings.clientPausingAndResumingEnabled {
|
if userSettings.app.clientPausingAndResumingEnabled {
|
||||||
do {
|
do {
|
||||||
MXLog.info("Pausing client")
|
MXLog.info("Pausing client")
|
||||||
try await client.pause()
|
try await client.pause()
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class EmojiProvider: EmojiProviderProtocol {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return userSettings.frequentlyUsedSystemEmojis.map(\.key)
|
return userSettings.app.frequentlyUsedSystemEmojis.map(\.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func markEmojiAsFrequentlyUsed(_ emoji: String) {
|
func markEmojiAsFrequentlyUsed(_ emoji: String) {
|
||||||
@@ -70,9 +70,9 @@ class EmojiProvider: EmojiProviderProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let frequentlyUsed = if !frequentlyUsedSystemEmojis().contains(emoji) {
|
let frequentlyUsed = if !frequentlyUsedSystemEmojis().contains(emoji) {
|
||||||
userSettings.frequentlyUsedSystemEmojis + [.init(count: 0, key: emoji)]
|
userSettings.app.frequentlyUsedSystemEmojis + [.init(count: 0, key: emoji)]
|
||||||
} else {
|
} else {
|
||||||
userSettings.frequentlyUsedSystemEmojis.map { frequentlyUsedEmoji in
|
userSettings.app.frequentlyUsedSystemEmojis.map { frequentlyUsedEmoji in
|
||||||
if frequentlyUsedEmoji.key == emoji {
|
if frequentlyUsedEmoji.key == emoji {
|
||||||
return FrequentlyUsedEmoji(count: frequentlyUsedEmoji.count + 1, key: emoji)
|
return FrequentlyUsedEmoji(count: frequentlyUsedEmoji.count + 1, key: emoji)
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ class EmojiProvider: EmojiProviderProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userSettings.frequentlyUsedSystemEmojis = frequentlyUsed.sorted { $0.count > $1.count }
|
userSettings.app.frequentlyUsedSystemEmojis = frequentlyUsed.sorted { $0.count > $1.count }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
// https://developer.apple.com/documentation/corelocation/cllocationmanager/pauseslocationupdatesautomatically
|
// https://developer.apple.com/documentation/corelocation/cllocationmanager/pauseslocationupdatesautomatically
|
||||||
self.locationManager.pausesLocationUpdatesAutomatically = false
|
self.locationManager.pausesLocationUpdatesAutomatically = false
|
||||||
|
|
||||||
setupMinimumDistanceUpdatesAndAccuracy(minimumDistance: userSettings.liveLocationMinimumDistanceUpdate)
|
setupMinimumDistanceUpdatesAndAccuracy(minimumDistance: userSettings.app.liveLocationMinimumDistanceUpdate)
|
||||||
setupSubscriptions()
|
setupSubscriptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,8 +72,8 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func requestAlwaysAuthorizationIfPossible() -> Bool {
|
func requestAlwaysAuthorizationIfPossible() -> Bool {
|
||||||
guard !userSettings.hasRequestedLocationAlwaysLocationAuthorization else { return false }
|
guard !userSettings.app.hasRequestedLocationAlwaysLocationAuthorization else { return false }
|
||||||
userSettings.hasRequestedLocationAlwaysLocationAuthorization = true
|
userSettings.app.hasRequestedLocationAlwaysLocationAuthorization = true
|
||||||
locationManager.requestAlwaysAuthorization()
|
locationManager.requestAlwaysAuthorization()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
func startLiveLocation(roomID: String, duration: Duration) async -> Result<Void, LiveLocationManagerError> {
|
func startLiveLocation(roomID: String, duration: Duration) async -> Result<Void, LiveLocationManagerError> {
|
||||||
// Stop any existing session for this room first
|
// Stop any existing session for this room first
|
||||||
var didAlreadyStopLocalSession = false
|
var didAlreadyStopLocalSession = false
|
||||||
if userSettings.liveLocationSharingSessionsByRoomID[roomID] != nil
|
if userSettings.app.liveLocationSharingSessionsByRoomID[roomID] != nil
|
||||||
|| startingLiveLocationSharingSessionsByRoomID[roomID] != nil {
|
|| startingLiveLocationSharingSessionsByRoomID[roomID] != nil {
|
||||||
await stopLiveLocation(roomID: roomID)
|
await stopLiveLocation(roomID: roomID)
|
||||||
didAlreadyStopLocalSession = true
|
didAlreadyStopLocalSession = true
|
||||||
@@ -114,7 +114,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
var roomProxy: JoinedRoomProxyProtocol?
|
var roomProxy: JoinedRoomProxyProtocol?
|
||||||
let cachedRoomProxy = activeRoomProxies[roomID]
|
let cachedRoomProxy = activeRoomProxies[roomID]
|
||||||
startingLiveLocationSharingSessionsByRoomID.removeValue(forKey: roomID)
|
startingLiveLocationSharingSessionsByRoomID.removeValue(forKey: roomID)
|
||||||
userSettings.liveLocationSharingSessionsByRoomID.removeValue(forKey: roomID)
|
userSettings.app.liveLocationSharingSessionsByRoomID.removeValue(forKey: roomID)
|
||||||
|
|
||||||
if let cachedRoomProxy {
|
if let cachedRoomProxy {
|
||||||
roomProxy = cachedRoomProxy
|
roomProxy = cachedRoomProxy
|
||||||
@@ -137,7 +137,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
// If the system resets authorization to notDetermined (e.g. after app reinstall or
|
// If the system resets authorization to notDetermined (e.g. after app reinstall or
|
||||||
// settings reset), clear the flag so we can request again.
|
// settings reset), clear the flag so we can request again.
|
||||||
if manager.authorizationStatus == .notDetermined {
|
if manager.authorizationStatus == .notDetermined {
|
||||||
userSettings.hasRequestedLocationAlwaysLocationAuthorization = false
|
userSettings.app.hasRequestedLocationAlwaysLocationAuthorization = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// If authorization was revoked, stop all active sessions.
|
// If authorization was revoked, stop all active sessions.
|
||||||
@@ -146,7 +146,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Accuracy authorization may have changed, reapply new accuracy settings.
|
// Accuracy authorization may have changed, reapply new accuracy settings.
|
||||||
setupMinimumDistanceUpdatesAndAccuracy(minimumDistance: userSettings.liveLocationMinimumDistanceUpdate)
|
setupMinimumDistanceUpdatesAndAccuracy(minimumDistance: userSettings.app.liveLocationMinimumDistanceUpdate)
|
||||||
|
|
||||||
authorizationStatusSubject.send(manager.authorizationStatus)
|
authorizationStatusSubject.send(manager.authorizationStatus)
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.liveLocationSharingSessionsByRoomIDPublisher
|
userSettings.app.liveLocationSharingSessionsByRoomIDPublisher
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.sink { [weak self] sessions in
|
.sink { [weak self] sessions in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
@@ -197,7 +197,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
userSettings.liveLocationMinimumDistanceUpdatePublisher
|
userSettings.app.liveLocationMinimumDistanceUpdatePublisher
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.debounce(for: .seconds(1), scheduler: DispatchQueue.main)
|
.debounce(for: .seconds(1), scheduler: DispatchQueue.main)
|
||||||
.sink { [weak self] newValue in
|
.sink { [weak self] newValue in
|
||||||
@@ -210,7 +210,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
// A new beaconInfo has been received in a room with existing active session.
|
// A new beaconInfo has been received in a room with existing active session.
|
||||||
// This is either a new start or a new stop from a different device, so we
|
// This is either a new start or a new stop from a different device, so we
|
||||||
// should remove the session from the current local one.
|
// should remove the session from the current local one.
|
||||||
userSettings.liveLocationSharingSessionsByRoomID.removeValue(forKey: update.roomID)
|
userSettings.app.liveLocationSharingSessionsByRoomID.removeValue(forKey: update.roomID)
|
||||||
|
|
||||||
// Instead if we receive a new isLiveUpdate
|
// Instead if we receive a new isLiveUpdate
|
||||||
guard update.isLive else { return }
|
guard update.isLive else { return }
|
||||||
@@ -224,7 +224,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
// This means the server has echoed the start of the session and we can safely promote it
|
// This means the server has echoed the start of the session and we can safely promote it
|
||||||
// to a started session and start sending live locations.
|
// to a started session and start sending live locations.
|
||||||
startingLiveLocationSharingSessionsByRoomID.removeValue(forKey: update.roomID)
|
startingLiveLocationSharingSessionsByRoomID.removeValue(forKey: update.roomID)
|
||||||
userSettings.liveLocationSharingSessionsByRoomID[update.roomID] = session
|
userSettings.app.liveLocationSharingSessionsByRoomID[update.roomID] = session
|
||||||
|
|
||||||
if isUpdatingLocation, let lastLocation {
|
if isUpdatingLocation, let lastLocation {
|
||||||
locationUpdateSubject.send(lastLocation)
|
locationUpdateSubject.send(lastLocation)
|
||||||
@@ -281,7 +281,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
/// discarding any intermediate updates while always keeping the last one.
|
/// discarding any intermediate updates while always keeping the last one.
|
||||||
private func processLocationUpdateIfNeeded() {
|
private func processLocationUpdateIfNeeded() {
|
||||||
guard !isProcessingLocationUpdate, let location = latestPendingLocation else { return }
|
guard !isProcessingLocationUpdate, let location = latestPendingLocation else { return }
|
||||||
guard !userSettings.liveLocationSharingSessionsByRoomID.isEmpty else { return }
|
guard !userSettings.app.liveLocationSharingSessionsByRoomID.isEmpty else { return }
|
||||||
|
|
||||||
latestPendingLocation = nil
|
latestPendingLocation = nil
|
||||||
isProcessingLocationUpdate = true
|
isProcessingLocationUpdate = true
|
||||||
@@ -306,7 +306,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func sendLocationToActiveRooms(_ coordinate: CLLocationCoordinate2D) async {
|
private func sendLocationToActiveRooms(_ coordinate: CLLocationCoordinate2D) async {
|
||||||
let sessions = userSettings.liveLocationSharingSessionsByRoomID
|
let sessions = userSettings.app.liveLocationSharingSessionsByRoomID
|
||||||
let geoURI = GeoURI(coordinate: coordinate, uncertainty: nil)
|
let geoURI = GeoURI(coordinate: coordinate, uncertainty: nil)
|
||||||
|
|
||||||
for (roomID, session) in sessions {
|
for (roomID, session) in sessions {
|
||||||
@@ -351,7 +351,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func stopAllSessions() {
|
private func stopAllSessions() {
|
||||||
let roomIDs = Array(Set(userSettings.liveLocationSharingSessionsByRoomID.keys)
|
let roomIDs = Array(Set(userSettings.app.liveLocationSharingSessionsByRoomID.keys)
|
||||||
.union(startingLiveLocationSharingSessionsByRoomID.keys))
|
.union(startingLiveLocationSharingSessionsByRoomID.keys))
|
||||||
Task { [weak self] in
|
Task { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ struct MediaUploadingPreprocessor {
|
|||||||
try stripLocationFromImage(at: url, type: type)
|
try stripLocationFromImage(at: url, type: type)
|
||||||
|
|
||||||
var mimeType = mimeType
|
var mimeType = mimeType
|
||||||
if userSettings.optimizeMediaUploads, !type.conforms(to: .gif) {
|
if userSettings.app.optimizeMediaUploads, !type.conforms(to: .gif) {
|
||||||
let outputType = type.conforms(to: .png) ? UTType.png : .jpeg
|
let outputType = type.conforms(to: .png) ? UTType.png : .jpeg
|
||||||
mimeType = outputType.preferredMIMEType ?? "application/octet-stream"
|
mimeType = outputType.preferredMIMEType ?? "application/octet-stream"
|
||||||
try resizeImage(at: url, maxPixelSize: Constants.optimizedMaxPixelSize, destination: url, type: outputType)
|
try resizeImage(at: url, maxPixelSize: Constants.optimizedMaxPixelSize, destination: url, type: outputType)
|
||||||
@@ -439,7 +439,7 @@ struct MediaUploadingPreprocessor {
|
|||||||
/// - Returns: the URL for the resulting video and its media info as a `VideoProcessingResult`
|
/// - Returns: the URL for the resulting video and its media info as a `VideoProcessingResult`
|
||||||
private func convertVideoToMP4(_ url: URL, targetFileSize: UInt) async throws(MediaUploadingPreprocessorError) -> VideoProcessingInfo {
|
private func convertVideoToMP4(_ url: URL, targetFileSize: UInt) async throws(MediaUploadingPreprocessorError) -> VideoProcessingInfo {
|
||||||
let asset = AVURLAsset(url: url)
|
let asset = AVURLAsset(url: url)
|
||||||
let presetName = userSettings.optimizeMediaUploads ? AVAssetExportPreset1280x720 : AVAssetExportPreset1920x1080
|
let presetName = userSettings.app.optimizeMediaUploads ? AVAssetExportPreset1280x720 : AVAssetExportPreset1920x1080
|
||||||
|
|
||||||
guard let exportSession = AVAssetExportSession(asset: asset, presetName: presetName) else {
|
guard let exportSession = AVAssetExportSession(asset: asset, presetName: presetName) else {
|
||||||
throw .failedConvertingVideo
|
throw .failedConvertingVideo
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ final class PresenceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var desiredPresence: ClientProxyPresence {
|
private var desiredPresence: ClientProxyPresence {
|
||||||
switch (isForegroundActive, userSettings.sharePresence) {
|
switch (isForegroundActive, userSettings.app.sharePresence) {
|
||||||
case (true, true):
|
case (true, true):
|
||||||
.online
|
.online
|
||||||
case (true, false):
|
case (true, false):
|
||||||
@@ -68,7 +68,7 @@ final class PresenceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func observeSharePresence() {
|
private func observeSharePresence() {
|
||||||
userSettings.sharePresencePublisher
|
userSettings.app.sharePresencePublisher
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
self?.reportCurrentState()
|
self?.reportCurrentState()
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
|||||||
infoSubject = try await .init(RoomInfoProxy(roomInfo: room.roomInfo()))
|
infoSubject = try await .init(RoomInfoProxy(roomInfo: room.roomInfo()))
|
||||||
|
|
||||||
let openRoomSpan = analyticsService.signpost.addSpan(.timelineLoad, toTransaction: .openRoom)
|
let openRoomSpan = analyticsService.signpost.addSpan(.timelineLoad, toTransaction: .openRoom)
|
||||||
timeline = try await TimelineProxy(timeline: room.timelineWithConfiguration(configuration: .init(focus: .live(hideThreadedEvents: userSettings.threadsEnabled),
|
timeline = try await TimelineProxy(timeline: room.timelineWithConfiguration(configuration: .init(focus: .live(hideThreadedEvents: userSettings.app.threadsEnabled),
|
||||||
filter: .eventFilter(filter: Self.excludedEventsFilter),
|
filter: .eventFilter(filter: Self.excludedEventsFilter),
|
||||||
internalIdPrefix: nil,
|
internalIdPrefix: nil,
|
||||||
dateDividerMode: .daily,
|
dateDividerMode: .daily,
|
||||||
@@ -150,7 +150,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
|||||||
let openRoomSpan = analyticsService.signpost.addSpan(.timelineLoad, toTransaction: .notificationToMessage)
|
let openRoomSpan = analyticsService.signpost.addSpan(.timelineLoad, toTransaction: .notificationToMessage)
|
||||||
let sdkTimeline = try await room.timelineWithConfiguration(configuration: .init(focus: .event(eventId: eventID,
|
let sdkTimeline = try await room.timelineWithConfiguration(configuration: .init(focus: .event(eventId: eventID,
|
||||||
numContextEvents: numberOfEvents,
|
numContextEvents: numberOfEvents,
|
||||||
threadMode: .automatic(hideThreadedEvents: userSettings.threadsEnabled)),
|
threadMode: .automatic(hideThreadedEvents: userSettings.app.threadsEnabled)),
|
||||||
filter: .all,
|
filter: .all,
|
||||||
internalIdPrefix: UUID().uuidString,
|
internalIdPrefix: UUID().uuidString,
|
||||||
dateDividerMode: .daily,
|
dateDividerMode: .daily,
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
|
|
||||||
rustFilters.append(.identifiers(identifiers: Array(roomIDs)))
|
rustFilters.append(.identifiers(identifiers: Array(roomIDs)))
|
||||||
|
|
||||||
if !filters.contains(.lowPriority), userSettings.lowPriorityFilterEnabled {
|
if !filters.contains(.lowPriority), userSettings.app.lowPriorityFilterEnabled {
|
||||||
rustFilters.append(.nonLowPriority)
|
rustFilters.append(.nonLowPriority)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
case let .all(filters):
|
case let .all(filters):
|
||||||
var rustFilters = filters.map(\.rustFilter) + baseFilter
|
var rustFilters = filters.map(\.rustFilter) + baseFilter
|
||||||
|
|
||||||
if !filters.contains(.lowPriority), userSettings.lowPriorityFilterEnabled {
|
if !filters.contains(.lowPriority), userSettings.app.lowPriorityFilterEnabled {
|
||||||
rustFilters.append(.nonLowPriority)
|
rustFilters.append(.nonLowPriority)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func nameFilter(for query: String) -> [RoomListEntriesDynamicFilterKind] {
|
private func nameFilter(for query: String) -> [RoomListEntriesDynamicFilterKind] {
|
||||||
userSettings.fuzzyRoomListSearchEnabled ? [.fuzzyMatchRoomName(pattern: query)] : [.normalizedMatchRoomName(pattern: query)]
|
userSettings.app.fuzzyRoomListSearchEnabled ? [.fuzzyMatchRoomName(pattern: query)] : [.normalizedMatchRoomName(pattern: query)]
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupVisibleRangeObservers() {
|
private func setupVisibleRangeObservers() {
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class TimelineController: TimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendReadReceipt(for itemID: TimelineItemIdentifier) async {
|
func sendReadReceipt(for itemID: TimelineItemIdentifier) async {
|
||||||
let receiptType: MatrixRustSDK.ReceiptType = userSettings.sharePresence ? .read : .readPrivate
|
let receiptType: MatrixRustSDK.ReceiptType = userSettings.app.sharePresence ? .read : .readPrivate
|
||||||
|
|
||||||
guard let eventID = itemID.eventID else {
|
guard let eventID = itemID.eventID else {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -603,9 +603,9 @@ class MockScreen: Identifiable {
|
|||||||
case .userSessionScreen, .userSessionScreenReply, .userSessionSpacesFlow:
|
case .userSessionScreen, .userSessionScreenReply, .userSessionSpacesFlow:
|
||||||
let userID = "@mock:client.com"
|
let userID = "@mock:client.com"
|
||||||
let userSettings = appSettings.userSettings(for: userID)
|
let userSettings = appSettings.userSettings(for: userID)
|
||||||
userSettings.hasRunIdentityConfirmationOnboarding = true
|
userSettings.account.hasRunIdentityConfirmationOnboarding = true
|
||||||
userSettings.hasRunNotificationPermissionsOnboarding = true
|
userSettings.app.hasRunNotificationPermissionsOnboarding = true
|
||||||
userSettings.analyticsConsentState = .optedOut
|
userSettings.app.analyticsConsentState = .optedOut
|
||||||
|
|
||||||
let roomSummaries: [RoomSummary] = if id == .userSessionSpacesFlow {
|
let roomSummaries: [RoomSummary] = if id == .userSessionSpacesFlow {
|
||||||
[[RoomSummary].mockSpaceInvites[0]] + .mockRooms
|
[[RoomSummary].mockSpaceInvites[0]] + .mockRooms
|
||||||
@@ -828,9 +828,9 @@ class MockScreen: Identifiable {
|
|||||||
case .autoUpdatingTimeline:
|
case .autoUpdatingTimeline:
|
||||||
let userID = "@mock:client.com"
|
let userID = "@mock:client.com"
|
||||||
let userSettings = appSettings.userSettings(for: userID)
|
let userSettings = appSettings.userSettings(for: userID)
|
||||||
userSettings.hasRunIdentityConfirmationOnboarding = true
|
userSettings.account.hasRunIdentityConfirmationOnboarding = true
|
||||||
userSettings.hasRunNotificationPermissionsOnboarding = true
|
userSettings.app.hasRunNotificationPermissionsOnboarding = true
|
||||||
userSettings.analyticsConsentState = .optedOut
|
userSettings.app.analyticsConsentState = .optedOut
|
||||||
let navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator(hideBrandChrome: false))
|
let navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator(hideBrandChrome: false))
|
||||||
navigationRootCoordinator.setRootCoordinator(navigationSplitCoordinator)
|
navigationRootCoordinator.setRootCoordinator(navigationSplitCoordinator)
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule Enterprise updated: 5628ab611f...5792d2c63d
@@ -18,7 +18,7 @@ final class BugReportServiceTests {
|
|||||||
|
|
||||||
init() throws {
|
init() throws {
|
||||||
userSettings = UserSettings.mock()
|
userSettings = UserSettings.mock()
|
||||||
userSettings.bugReportRageshakeURL.reset()
|
userSettings.app.bugReportRageshakeURL.reset()
|
||||||
|
|
||||||
let bugReportServiceMock = BugReportServiceMock()
|
let bugReportServiceMock = BugReportServiceMock()
|
||||||
bugReportServiceMock.lastCrashEventIDSubject = .init(nil)
|
bugReportServiceMock.lastCrashEventIDSubject = .init(nil)
|
||||||
@@ -27,7 +27,7 @@ final class BugReportServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
userSettings.bugReportRageshakeURL.reset()
|
userSettings.app.bugReportRageshakeURL.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -104,22 +104,22 @@ final class BugReportServiceTests {
|
|||||||
@Test
|
@Test
|
||||||
@MainActor
|
@MainActor
|
||||||
func configurations() async throws {
|
func configurations() async throws {
|
||||||
guard case let .url(initialURL) = userSettings.bugReportRageshakeURL.publisher.value else {
|
guard case let .url(initialURL) = userSettings.app.bugReportRageshakeURL.publisher.value else {
|
||||||
Issue.record("Unexpected initial configuration.")
|
Issue.record("Unexpected initial configuration.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let service = BugReportService(rageshakeURLPublisher: userSettings.bugReportRageshakeURL.publisher,
|
let service = BugReportService(rageshakeURLPublisher: userSettings.app.bugReportRageshakeURL.publisher,
|
||||||
applicationID: "mock_app_id",
|
applicationID: "mock_app_id",
|
||||||
sdkGitSHA: "1234",
|
sdkGitSHA: "1234",
|
||||||
session: .mock,
|
session: .mock,
|
||||||
appHooks: AppHooks())
|
appHooks: AppHooks())
|
||||||
#expect(service.isEnabled)
|
#expect(service.isEnabled)
|
||||||
|
|
||||||
userSettings.bugReportRageshakeURL.applyRemoteValue(.disabled)
|
userSettings.app.bugReportRageshakeURL.applyRemoteValue(.disabled)
|
||||||
#expect(!service.isEnabled)
|
#expect(!service.isEnabled)
|
||||||
|
|
||||||
userSettings.bugReportRageshakeURL.applyRemoteValue(.url("https://bugs.server.net/submit"))
|
userSettings.app.bugReportRageshakeURL.applyRemoteValue(.url("https://bugs.server.net/submit"))
|
||||||
#expect(service.isEnabled)
|
#expect(service.isEnabled)
|
||||||
|
|
||||||
let bugReport = BugReport(userID: "@mock:client.com",
|
let bugReport = BugReport(userID: "@mock:client.com",
|
||||||
@@ -136,7 +136,7 @@ final class BugReportServiceTests {
|
|||||||
|
|
||||||
#expect(customConfigurationResponse.reportURL == "https://bugs.server.net/123")
|
#expect(customConfigurationResponse.reportURL == "https://bugs.server.net/123")
|
||||||
|
|
||||||
userSettings.bugReportRageshakeURL.reset()
|
userSettings.app.bugReportRageshakeURL.reset()
|
||||||
#expect(service.isEnabled)
|
#expect(service.isEnabled)
|
||||||
|
|
||||||
let defaultConfigurationResponse = try await service.submitBugReport(bugReport, progressListener: progressSubject).get()
|
let defaultConfigurationResponse = try await service.submitBugReport(bugReport, progressListener: progressSubject).get()
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ final class CreateRoomScreenViewModelTests {
|
|||||||
spaceServiceConfiguration: .init(spaceRoomLists: ["1": .init()])))
|
spaceServiceConfiguration: .init(spaceRoomLists: ["1": .init()])))
|
||||||
clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID))) }
|
clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID))) }
|
||||||
userSession = UserSessionMock(.init(clientProxy: clientProxy))
|
userSession = UserSessionMock(.init(clientProxy: clientProxy))
|
||||||
userSettings.knockingEnabled = true
|
userSettings.app.knockingEnabled = true
|
||||||
let viewModel = CreateRoomScreenViewModel(isSpace: true,
|
let viewModel = CreateRoomScreenViewModel(isSpace: true,
|
||||||
spaceSelectionMode: .none,
|
spaceSelectionMode: .none,
|
||||||
shouldShowCancelButton: false,
|
shouldShowCancelButton: false,
|
||||||
@@ -336,7 +336,7 @@ final class CreateRoomScreenViewModelTests {
|
|||||||
clientProxy.spaceService = spaceService
|
clientProxy.spaceService = spaceService
|
||||||
clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID))) }
|
clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID))) }
|
||||||
userSession = UserSessionMock(.init(clientProxy: clientProxy))
|
userSession = UserSessionMock(.init(clientProxy: clientProxy))
|
||||||
userSettings.knockingEnabled = true
|
userSettings.app.knockingEnabled = true
|
||||||
let viewModel = CreateRoomScreenViewModel(isSpace: isSpace,
|
let viewModel = CreateRoomScreenViewModel(isSpace: isSpace,
|
||||||
spaceSelectionMode: spacesSelectionMode,
|
spaceSelectionMode: spacesSelectionMode,
|
||||||
shouldShowCancelButton: false,
|
shouldShowCancelButton: false,
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ final class HomeScreenViewModelTests {
|
|||||||
room.roomID == invites[0].roomID && room.badges.isDotShown == false
|
room.roomID == invites[0].roomID && room.badges.isDotShown == false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
userSettings.seenInvites = Set(invites.compactMap(\.roomID))
|
userSettings.app.seenInvites = Set(invites.compactMap(\.roomID))
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
invites = context.viewState.rooms.invites
|
invites = context.viewState.rooms.invites
|
||||||
|
|
||||||
@@ -289,14 +289,14 @@ final class HomeScreenViewModelTests {
|
|||||||
setupViewModel(invites: .rooms)
|
setupViewModel(invites: .rooms)
|
||||||
|
|
||||||
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
|
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
|
||||||
userSettings.seenInvites = Set(invitedRoomIDs)
|
userSettings.app.seenInvites = Set(invitedRoomIDs)
|
||||||
#expect(invitedRoomIDs.count == 2)
|
#expect(invitedRoomIDs.count == 2)
|
||||||
|
|
||||||
let deferred = deferFulfillment(viewModel.actions) { $0 == .presentRoom(roomIdentifier: invitedRoomIDs[0]) }
|
let deferred = deferFulfillment(viewModel.actions) { $0 == .presentRoom(roomIdentifier: invitedRoomIDs[0]) }
|
||||||
context.send(viewAction: .acceptInvite(roomIdentifier: invitedRoomIDs[0]))
|
context.send(viewAction: .acceptInvite(roomIdentifier: invitedRoomIDs[0]))
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
#expect(userSettings.seenInvites == [invitedRoomIDs[1]])
|
#expect(userSettings.app.seenInvites == [invitedRoomIDs[1]])
|
||||||
#expect(!notificationManager.removeDeliveredMessageNotificationsForCalled, "The notification will be dismissed when opening the room.")
|
#expect(!notificationManager.removeDeliveredMessageNotificationsForCalled, "The notification will be dismissed when opening the room.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +305,7 @@ final class HomeScreenViewModelTests {
|
|||||||
setupViewModel(invites: .spaces)
|
setupViewModel(invites: .spaces)
|
||||||
|
|
||||||
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
|
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
|
||||||
userSettings.seenInvites = Set(invitedRoomIDs)
|
userSettings.app.seenInvites = Set(invitedRoomIDs)
|
||||||
#expect(invitedRoomIDs.count == 2)
|
#expect(invitedRoomIDs.count == 2)
|
||||||
|
|
||||||
let deferred = deferFulfillment(viewModel.actions) {
|
let deferred = deferFulfillment(viewModel.actions) {
|
||||||
@@ -314,7 +314,7 @@ final class HomeScreenViewModelTests {
|
|||||||
context.send(viewAction: .acceptInvite(roomIdentifier: invitedRoomIDs[0]))
|
context.send(viewAction: .acceptInvite(roomIdentifier: invitedRoomIDs[0]))
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
#expect(userSettings.seenInvites == [invitedRoomIDs[1]])
|
#expect(userSettings.app.seenInvites == [invitedRoomIDs[1]])
|
||||||
#expect(!notificationManager.removeDeliveredMessageNotificationsForCalled, "The notification will be dismissed when opening the room.")
|
#expect(!notificationManager.removeDeliveredMessageNotificationsForCalled, "The notification will be dismissed when opening the room.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,7 +322,7 @@ final class HomeScreenViewModelTests {
|
|||||||
func declineInvite() async throws {
|
func declineInvite() async throws {
|
||||||
setupViewModel(invites: .rooms)
|
setupViewModel(invites: .rooms)
|
||||||
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
|
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
|
||||||
userSettings.seenInvites = Set(invitedRoomIDs)
|
userSettings.app.seenInvites = Set(invitedRoomIDs)
|
||||||
#expect(invitedRoomIDs.count == 2)
|
#expect(invitedRoomIDs.count == 2)
|
||||||
|
|
||||||
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
||||||
@@ -345,7 +345,7 @@ final class HomeScreenViewModelTests {
|
|||||||
try await Task.sleep(for: .milliseconds(100))
|
try await Task.sleep(for: .milliseconds(100))
|
||||||
#expect(rejectCalled)
|
#expect(rejectCalled)
|
||||||
|
|
||||||
#expect(userSettings.seenInvites == [invitedRoomIDs[1]])
|
#expect(userSettings.app.seenInvites == [invitedRoomIDs[1]])
|
||||||
#expect(notificationManager.removeDeliveredMessageNotificationsForCalled)
|
#expect(notificationManager.removeDeliveredMessageNotificationsForCalled)
|
||||||
#expect(notificationManager.removeDeliveredMessageNotificationsForReceivedInvocations == [invitedRoomIDs[0]])
|
#expect(notificationManager.removeDeliveredMessageNotificationsForReceivedInvocations == [invitedRoomIDs[0]])
|
||||||
}
|
}
|
||||||
@@ -354,7 +354,7 @@ final class HomeScreenViewModelTests {
|
|||||||
func declineAndBlockInvite() async throws {
|
func declineAndBlockInvite() async throws {
|
||||||
setupViewModel(invites: .rooms)
|
setupViewModel(invites: .rooms)
|
||||||
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
|
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
|
||||||
userSettings.seenInvites = Set(invitedRoomIDs)
|
userSettings.app.seenInvites = Set(invitedRoomIDs)
|
||||||
#expect(invitedRoomIDs.count == 2)
|
#expect(invitedRoomIDs.count == 2)
|
||||||
|
|
||||||
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
||||||
@@ -368,7 +368,7 @@ final class HomeScreenViewModelTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func newSoundBanner() {
|
func newSoundBanner() {
|
||||||
userSettings.hasSeenNewSoundBanner = false
|
userSettings.app.hasSeenNewSoundBanner = false
|
||||||
|
|
||||||
setupViewModel()
|
setupViewModel()
|
||||||
#expect(context.viewState.shouldShowBanner)
|
#expect(context.viewState.shouldShowBanner)
|
||||||
@@ -377,26 +377,26 @@ final class HomeScreenViewModelTests {
|
|||||||
context.send(viewAction: .dismissNewSoundBanner)
|
context.send(viewAction: .dismissNewSoundBanner)
|
||||||
#expect(!context.viewState.shouldShowBanner)
|
#expect(!context.viewState.shouldShowBanner)
|
||||||
#expect(!context.viewState.shouldShowNewSoundBanner)
|
#expect(!context.viewState.shouldShowNewSoundBanner)
|
||||||
#expect(userSettings.hasSeenNewSoundBanner)
|
#expect(userSettings.app.hasSeenNewSoundBanner)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func multiAccountAnnouncement() async throws {
|
func multiAccountAnnouncement() async throws {
|
||||||
userSettings.multiAccountEnabled = true
|
userSettings.app.multiAccountEnabled = true
|
||||||
setupViewModel()
|
setupViewModel()
|
||||||
|
|
||||||
// Arming it after the view model exists (Developer options) doesn't present it on its own.
|
// Arming it after the view model exists (Developer options) doesn't present it on its own.
|
||||||
userSettings.hasSeenMultiAccountAnnouncement = false
|
userSettings.app.hasSeenMultiAccountAnnouncement = false
|
||||||
let deferredFailure = deferFailure(context.$viewState, timeout: .seconds(1)) { $0.bindings.isPresentingMultiAccountAnnouncement }
|
let deferredFailure = deferFailure(context.$viewState, timeout: .seconds(1)) { $0.bindings.isPresentingMultiAccountAnnouncement }
|
||||||
try await deferredFailure.fulfill()
|
try await deferredFailure.fulfill()
|
||||||
|
|
||||||
let deferred = deferFulfillment(context.$viewState) { $0.bindings.isPresentingMultiAccountAnnouncement }
|
let deferred = deferFulfillment(context.$viewState) { $0.bindings.isPresentingMultiAccountAnnouncement }
|
||||||
context.send(viewAction: .screenAppeared)
|
context.send(viewAction: .screenAppeared)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
#expect(!userSettings.hasSeenMultiAccountAnnouncement)
|
#expect(!userSettings.app.hasSeenMultiAccountAnnouncement)
|
||||||
|
|
||||||
context.send(viewAction: .multiAccountAnnouncementAppeared)
|
context.send(viewAction: .multiAccountAnnouncementAppeared)
|
||||||
#expect(userSettings.hasSeenMultiAccountAnnouncement)
|
#expect(userSettings.app.hasSeenMultiAccountAnnouncement)
|
||||||
#expect(context.viewState.bindings.isPresentingMultiAccountAnnouncement)
|
#expect(context.viewState.bindings.isPresentingMultiAccountAnnouncement)
|
||||||
|
|
||||||
// A swipe down dismisses it without any of the announcement's actions.
|
// A swipe down dismisses it without any of the announcement's actions.
|
||||||
@@ -408,8 +408,8 @@ final class HomeScreenViewModelTests {
|
|||||||
|
|
||||||
@Test(arguments: [HomeScreenViewAction.dismissMultiAccountAnnouncement, .addAccount])
|
@Test(arguments: [HomeScreenViewAction.dismissMultiAccountAnnouncement, .addAccount])
|
||||||
func multiAccountAnnouncementActionsDismissIt(_ action: HomeScreenViewAction) async throws {
|
func multiAccountAnnouncementActionsDismissIt(_ action: HomeScreenViewAction) async throws {
|
||||||
userSettings.multiAccountEnabled = true
|
userSettings.app.multiAccountEnabled = true
|
||||||
userSettings.hasSeenMultiAccountAnnouncement = false
|
userSettings.app.hasSeenMultiAccountAnnouncement = false
|
||||||
setupViewModel()
|
setupViewModel()
|
||||||
|
|
||||||
let deferred = deferFulfillment(context.$viewState) { $0.bindings.isPresentingMultiAccountAnnouncement }
|
let deferred = deferFulfillment(context.$viewState) { $0.bindings.isPresentingMultiAccountAnnouncement }
|
||||||
@@ -422,8 +422,8 @@ final class HomeScreenViewModelTests {
|
|||||||
|
|
||||||
@Test(arguments: MultiAccountAnnouncementHiddenCase.allCases)
|
@Test(arguments: MultiAccountAnnouncementHiddenCase.allCases)
|
||||||
func multiAccountAnnouncementHidden(_ hiddenCase: MultiAccountAnnouncementHiddenCase) async throws {
|
func multiAccountAnnouncementHidden(_ hiddenCase: MultiAccountAnnouncementHiddenCase) async throws {
|
||||||
userSettings.multiAccountEnabled = hiddenCase != .flagOff
|
userSettings.app.multiAccountEnabled = hiddenCase != .flagOff
|
||||||
userSettings.hasSeenMultiAccountAnnouncement = hiddenCase == .alreadySeen
|
userSettings.app.hasSeenMultiAccountAnnouncement = hiddenCase == .alreadySeen
|
||||||
let verificationState: SessionVerificationState = switch hiddenCase {
|
let verificationState: SessionVerificationState = switch hiddenCase {
|
||||||
case .unverified: .unverified
|
case .unverified: .unverified
|
||||||
case .unknownVerificationState: .unknown
|
case .unknownVerificationState: .unknown
|
||||||
@@ -435,13 +435,13 @@ final class HomeScreenViewModelTests {
|
|||||||
let deferred = deferFailure(context.$viewState, timeout: .seconds(1)) { $0.bindings.isPresentingMultiAccountAnnouncement }
|
let deferred = deferFailure(context.$viewState, timeout: .seconds(1)) { $0.bindings.isPresentingMultiAccountAnnouncement }
|
||||||
context.send(viewAction: .screenAppeared)
|
context.send(viewAction: .screenAppeared)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
#expect(userSettings.hasSeenMultiAccountAnnouncement == (hiddenCase == .alreadySeen))
|
#expect(userSettings.app.hasSeenMultiAccountAnnouncement == (hiddenCase == .alreadySeen))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func multiAccountAnnouncementPresentsOnTheNextAppearanceOnceVerified() async throws {
|
func multiAccountAnnouncementPresentsOnTheNextAppearanceOnceVerified() async throws {
|
||||||
userSettings.multiAccountEnabled = true
|
userSettings.app.multiAccountEnabled = true
|
||||||
userSettings.hasSeenMultiAccountAnnouncement = false
|
userSettings.app.hasSeenMultiAccountAnnouncement = false
|
||||||
let securityStateSubject = CurrentValueSubject<SessionSecurityState, Never>(.init(verificationState: .unverified, recoveryState: .enabled))
|
let securityStateSubject = CurrentValueSubject<SessionSecurityState, Never>(.init(verificationState: .unverified, recoveryState: .enabled))
|
||||||
setupViewModel(securityStatePublisher: securityStateSubject.asCurrentValuePublisher())
|
setupViewModel(securityStatePublisher: securityStateSubject.asCurrentValuePublisher())
|
||||||
|
|
||||||
|
|||||||
@@ -45,12 +45,12 @@ final class JoinRoomScreenViewModelTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func interaction() async throws {
|
func interaction() async throws {
|
||||||
#expect(userSettings.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
|
#expect(userSettings.app.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
|
||||||
|
|
||||||
setupViewModel()
|
setupViewModel()
|
||||||
try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .joinable }.fulfill()
|
try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .joinable }.fulfill()
|
||||||
|
|
||||||
#expect(userSettings.seenInvites.isEmpty, "Only an invited room should register the room ID as a seen invite.")
|
#expect(userSettings.app.seenInvites.isEmpty, "Only an invited room should register the room ID as a seen invite.")
|
||||||
|
|
||||||
let deferred = deferFulfillment(viewModel.actionsPublisher) { $0 == .joined(.roomID("1")) }
|
let deferred = deferFulfillment(viewModel.actionsPublisher) { $0 == .joined(.roomID("1")) }
|
||||||
context.send(viewAction: .join)
|
context.send(viewAction: .join)
|
||||||
@@ -59,28 +59,28 @@ final class JoinRoomScreenViewModelTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func acceptInviteInteraction() async throws {
|
func acceptInviteInteraction() async throws {
|
||||||
#expect(userSettings.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
|
#expect(userSettings.app.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
|
||||||
|
|
||||||
setupViewModel(mode: .invited)
|
setupViewModel(mode: .invited)
|
||||||
try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .invited(isDM: false) }.fulfill()
|
try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .invited(isDM: false) }.fulfill()
|
||||||
|
|
||||||
#expect(userSettings.seenInvites == ["1"], "The invited room's ID should be registered as a seen invite.")
|
#expect(userSettings.app.seenInvites == ["1"], "The invited room's ID should be registered as a seen invite.")
|
||||||
|
|
||||||
let deferred = deferFulfillment(viewModel.actionsPublisher) { $0 == .joined(.roomID("1")) }
|
let deferred = deferFulfillment(viewModel.actionsPublisher) { $0 == .joined(.roomID("1")) }
|
||||||
context.send(viewAction: .acceptInvite)
|
context.send(viewAction: .acceptInvite)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
#expect(userSettings.seenInvites.isEmpty, "The after accepting an invite the invite should be forgotten in case the user leaves.")
|
#expect(userSettings.app.seenInvites.isEmpty, "The after accepting an invite the invite should be forgotten in case the user leaves.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func declineInviteInteraction() async throws {
|
func declineInviteInteraction() async throws {
|
||||||
#expect(userSettings.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
|
#expect(userSettings.app.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
|
||||||
|
|
||||||
setupViewModel(mode: .invited)
|
setupViewModel(mode: .invited)
|
||||||
|
|
||||||
try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .invited(isDM: false) }.fulfill()
|
try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .invited(isDM: false) }.fulfill()
|
||||||
#expect(userSettings.seenInvites == ["1"], "The invited room's ID should be registered as a seen invite.")
|
#expect(userSettings.app.seenInvites == ["1"], "The invited room's ID should be registered as a seen invite.")
|
||||||
|
|
||||||
context.send(viewAction: .declineInvite)
|
context.send(viewAction: .declineInvite)
|
||||||
|
|
||||||
@@ -89,17 +89,17 @@ final class JoinRoomScreenViewModelTests {
|
|||||||
context.alertInfo?.secondaryButton?.action?()
|
context.alertInfo?.secondaryButton?.action?()
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
#expect(userSettings.seenInvites.isEmpty, "The after declining an invite the invite should be forgotten in case another invite is received.")
|
#expect(userSettings.app.seenInvites.isEmpty, "The after declining an invite the invite should be forgotten in case another invite is received.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func knockedState() async throws {
|
func knockedState() async throws {
|
||||||
#expect(userSettings.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
|
#expect(userSettings.app.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.")
|
||||||
setupViewModel(mode: .knocked)
|
setupViewModel(mode: .knocked)
|
||||||
|
|
||||||
try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .knocked }.fulfill()
|
try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .knocked }.fulfill()
|
||||||
|
|
||||||
#expect(userSettings.seenInvites.isEmpty, "Only an invited room should register the room ID as a seen invite.")
|
#expect(userSettings.app.seenInvites.isEmpty, "Only an invited room should register the room ID as a seen invite.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ final class LiveLocationManagerTests {
|
|||||||
|
|
||||||
try result.get()
|
try result.get()
|
||||||
#expect(callOrder == ["stop", "start"])
|
#expect(callOrder == ["stop", "start"])
|
||||||
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
|
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
|
||||||
|
|
||||||
try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org")
|
try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org")
|
||||||
|
|
||||||
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil)
|
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil)
|
||||||
#expect(locationManagerMock.startUpdatingLocationCalled)
|
#expect(locationManagerMock.startUpdatingLocationCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ final class LiveLocationManagerTests {
|
|||||||
setUp()
|
setUp()
|
||||||
let roomProxy = makeRoomProxy(roomID: "!room:matrix.org")
|
let roomProxy = makeRoomProxy(roomID: "!room:matrix.org")
|
||||||
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
|
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
|
||||||
userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] = LiveLocationSession(eventID: "$old_event:matrix.org", expirationDate: Date().addingTimeInterval(300))
|
userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] = LiveLocationSession(eventID: "$old_event:matrix.org", expirationDate: Date().addingTimeInterval(300))
|
||||||
|
|
||||||
var callOrder: [String] = []
|
var callOrder: [String] = []
|
||||||
roomProxy.stopLiveLocationShareClosure = {
|
roomProxy.stopLiveLocationShareClosure = {
|
||||||
@@ -73,7 +73,7 @@ final class LiveLocationManagerTests {
|
|||||||
|
|
||||||
try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org")
|
try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org")
|
||||||
|
|
||||||
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil)
|
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -82,12 +82,12 @@ final class LiveLocationManagerTests {
|
|||||||
let roomProxy = makeRoomProxy(roomID: "!room1:matrix.org")
|
let roomProxy = makeRoomProxy(roomID: "!room1:matrix.org")
|
||||||
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
|
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
|
||||||
|
|
||||||
userSettings.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
|
userSettings.app.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
|
||||||
|
|
||||||
_ = await manager.startLiveLocation(roomID: "!room1:matrix.org", duration: .seconds(300))
|
_ = await manager.startLiveLocation(roomID: "!room1:matrix.org", duration: .seconds(300))
|
||||||
|
|
||||||
#expect(roomProxy.stopLiveLocationShareCalled)
|
#expect(roomProxy.stopLiveLocationShareCalled)
|
||||||
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] != nil)
|
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -98,7 +98,7 @@ final class LiveLocationManagerTests {
|
|||||||
let result = await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300))
|
let result = await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300))
|
||||||
|
|
||||||
#expect(throws: LiveLocationManagerError.roomNotJoined) { try result.get() }
|
#expect(throws: LiveLocationManagerError.roomNotJoined) { try result.get() }
|
||||||
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
|
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -111,7 +111,7 @@ final class LiveLocationManagerTests {
|
|||||||
let result = await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300))
|
let result = await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300))
|
||||||
|
|
||||||
#expect(throws: LiveLocationManagerError.startFailed) { try result.get() }
|
#expect(throws: LiveLocationManagerError.startFailed) { try result.get() }
|
||||||
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
|
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -127,7 +127,7 @@ final class LiveLocationManagerTests {
|
|||||||
|
|
||||||
try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org")
|
try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org")
|
||||||
|
|
||||||
let storedSession = try #require(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"])
|
let storedSession = try #require(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"])
|
||||||
let expectedMinTimeout = beforeStart.addingTimeInterval(TimeInterval(duration.seconds))
|
let expectedMinTimeout = beforeStart.addingTimeInterval(TimeInterval(duration.seconds))
|
||||||
let expectedMaxTimeout = afterStart.addingTimeInterval(TimeInterval(duration.seconds))
|
let expectedMaxTimeout = afterStart.addingTimeInterval(TimeInterval(duration.seconds))
|
||||||
|
|
||||||
@@ -142,12 +142,12 @@ final class LiveLocationManagerTests {
|
|||||||
setUp()
|
setUp()
|
||||||
let roomProxy = makeRoomProxy(roomID: "!room:matrix.org")
|
let roomProxy = makeRoomProxy(roomID: "!room:matrix.org")
|
||||||
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
|
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
|
||||||
userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
|
userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
|
||||||
|
|
||||||
await manager.stopLiveLocation(roomID: "!room:matrix.org")
|
await manager.stopLiveLocation(roomID: "!room:matrix.org")
|
||||||
|
|
||||||
#expect(roomProxy.stopLiveLocationShareCalled)
|
#expect(roomProxy.stopLiveLocationShareCalled)
|
||||||
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
|
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
|
||||||
// Setting the timeout date above starts tracking; removing it stops tracking.
|
// Setting the timeout date above starts tracking; removing it stops tracking.
|
||||||
#expect(locationManagerMock.startUpdatingLocationCalled)
|
#expect(locationManagerMock.startUpdatingLocationCalled)
|
||||||
#expect(locationManagerMock.stopUpdatingLocationCalled)
|
#expect(locationManagerMock.stopUpdatingLocationCalled)
|
||||||
@@ -169,13 +169,13 @@ final class LiveLocationManagerTests {
|
|||||||
setUp()
|
setUp()
|
||||||
let roomProxy = makeRoomProxy(roomID: "!room1:matrix.org")
|
let roomProxy = makeRoomProxy(roomID: "!room1:matrix.org")
|
||||||
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
|
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
|
||||||
userSettings.liveLocationSharingSessionsByRoomID["!room1:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
|
userSettings.app.liveLocationSharingSessionsByRoomID["!room1:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
|
||||||
userSettings.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
|
userSettings.app.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300))
|
||||||
|
|
||||||
await manager.stopLiveLocation(roomID: "!room1:matrix.org")
|
await manager.stopLiveLocation(roomID: "!room1:matrix.org")
|
||||||
|
|
||||||
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room1:matrix.org"] == nil)
|
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room1:matrix.org"] == nil)
|
||||||
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] != nil)
|
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Beacon info updates
|
// MARK: - Beacon info updates
|
||||||
@@ -188,13 +188,13 @@ final class LiveLocationManagerTests {
|
|||||||
|
|
||||||
try await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300)).get()
|
try await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300)).get()
|
||||||
try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org")
|
try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org")
|
||||||
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil)
|
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil)
|
||||||
|
|
||||||
let deferred = deferFulfillment(userSettings.liveLocationSharingSessionsByRoomIDPublisher) { $0["!room:matrix.org"] == nil }
|
let deferred = deferFulfillment(userSettings.app.liveLocationSharingSessionsByRoomIDPublisher) { $0["!room:matrix.org"] == nil }
|
||||||
beaconInfoSubject.send(LiveLocationOwnInfoUpdate(roomID: "!room:matrix.org", eventID: "$external_event:matrix.org", isLive: true))
|
beaconInfoSubject.send(LiveLocationOwnInfoUpdate(roomID: "!room:matrix.org", eventID: "$external_event:matrix.org", isLive: true))
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
#expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
|
#expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Reduced accuracy
|
// MARK: - Reduced accuracy
|
||||||
@@ -237,7 +237,7 @@ final class LiveLocationManagerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func simulateBeaconEcho(roomID: String, eventID: String) async throws {
|
private func simulateBeaconEcho(roomID: String, eventID: String) async throws {
|
||||||
let deferred = deferFulfillment(userSettings.liveLocationSharingSessionsByRoomIDPublisher) { $0[roomID] != nil }
|
let deferred = deferFulfillment(userSettings.app.liveLocationSharingSessionsByRoomIDPublisher) { $0[roomID] != nil }
|
||||||
beaconInfoSubject.send(LiveLocationOwnInfoUpdate(roomID: roomID, eventID: eventID, isLive: true))
|
beaconInfoSubject.send(LiveLocationOwnInfoUpdate(roomID: roomID, eventID: eventID, isLive: true))
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -420,7 +420,7 @@ struct LocationSharingScreenViewModelTests {
|
|||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
|
|
||||||
viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: nil, initialLiveLocationShare: nil),
|
viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: nil, initialLiveLocationShare: nil),
|
||||||
mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value,
|
mapURLBuilder: userSettings.app.mapTilerConfiguration.publisher.value,
|
||||||
roomProxy: roomProxyMock,
|
roomProxy: roomProxyMock,
|
||||||
timelineController: TimelineControllerMock(.init(timelineProxy: TimelineProxyMock(.init()))),
|
timelineController: TimelineControllerMock(.init(timelineProxy: TimelineProxyMock(.init()))),
|
||||||
liveLocationManager: LiveLocationManagerMock(.init()),
|
liveLocationManager: LiveLocationManagerMock(.init()),
|
||||||
@@ -546,7 +546,7 @@ struct LocationSharingScreenViewModelTests {
|
|||||||
#expect(context.showsUserLocationMode == .hide)
|
#expect(context.showsUserLocationMode == .hide)
|
||||||
|
|
||||||
// Another device takes over: this device's session is removed and a new own share arrives.
|
// Another device takes over: this device's session is removed and a new own share arrives.
|
||||||
userSettings.liveLocationSharingSessionsByRoomID.removeValue(forKey: Self.roomID)
|
userSettings.app.liveLocationSharingSessionsByRoomID.removeValue(forKey: Self.roomID)
|
||||||
let newOwnShare = makeLiveLocationShare(userID: RoomMemberProxyMock.mockMe.userID, latitude: 48.8, longitude: 2.3)
|
let newOwnShare = makeLiveLocationShare(userID: RoomMemberProxyMock.mockMe.userID, latitude: 48.8, longitude: 2.3)
|
||||||
|
|
||||||
let deferred = deferFulfillment(context.observe(\.showsUserLocationMode)) { $0 == .show }
|
let deferred = deferFulfillment(context.observe(\.showsUserLocationMode)) { $0 == .show }
|
||||||
@@ -633,7 +633,7 @@ struct LocationSharingScreenViewModelTests {
|
|||||||
userSettings = UserSettings.mock()
|
userSettings = UserSettings.mock()
|
||||||
timelineProxy = TimelineProxyMock(.init())
|
timelineProxy = TimelineProxyMock(.init())
|
||||||
viewModel = LocationSharingScreenViewModel(interactionMode: .picker(shouldShowLiveLocationOption: true),
|
viewModel = LocationSharingScreenViewModel(interactionMode: .picker(shouldShowLiveLocationOption: true),
|
||||||
mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value,
|
mapURLBuilder: userSettings.app.mapTilerConfiguration.publisher.value,
|
||||||
roomProxy: JoinedRoomProxyMock(.init(members: members)),
|
roomProxy: JoinedRoomProxyMock(.init(members: members)),
|
||||||
timelineController: TimelineControllerMock(.init(timelineProxy: timelineProxy)),
|
timelineController: TimelineControllerMock(.init(timelineProxy: timelineProxy)),
|
||||||
liveLocationManager: liveLocationManagerMock,
|
liveLocationManager: liveLocationManagerMock,
|
||||||
@@ -651,7 +651,7 @@ struct LocationSharingScreenViewModelTests {
|
|||||||
isSharingLiveLocationFromThisDevice: Bool = false) {
|
isSharingLiveLocationFromThisDevice: Bool = false) {
|
||||||
userSettings = UserSettings.mock()
|
userSettings = UserSettings.mock()
|
||||||
if isSharingLiveLocationFromThisDevice {
|
if isSharingLiveLocationFromThisDevice {
|
||||||
userSettings.liveLocationSharingSessionsByRoomID[Self.roomID] = .init(eventID: "$event:matrix.org", expirationDate: .distantFuture)
|
userSettings.app.liveLocationSharingSessionsByRoomID[Self.roomID] = .init(eventID: "$event:matrix.org", expirationDate: .distantFuture)
|
||||||
}
|
}
|
||||||
|
|
||||||
let liveLocationServiceMock = RoomLiveLocationServiceMock()
|
let liveLocationServiceMock = RoomLiveLocationServiceMock()
|
||||||
@@ -661,7 +661,7 @@ struct LocationSharingScreenViewModelTests {
|
|||||||
roomProxyMock.makeLiveLocationServiceReturnValue = liveLocationServiceMock
|
roomProxyMock.makeLiveLocationServiceReturnValue = liveLocationServiceMock
|
||||||
|
|
||||||
viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: sender, initialLiveLocationShare: initialShare),
|
viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: sender, initialLiveLocationShare: initialShare),
|
||||||
mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value,
|
mapURLBuilder: userSettings.app.mapTilerConfiguration.publisher.value,
|
||||||
roomProxy: roomProxyMock,
|
roomProxy: roomProxyMock,
|
||||||
timelineController: TimelineControllerMock(.init(timelineProxy: TimelineProxyMock(.init()))),
|
timelineController: TimelineControllerMock(.init(timelineProxy: TimelineProxyMock(.init()))),
|
||||||
liveLocationManager: LiveLocationManagerMock(.init()),
|
liveLocationManager: LiveLocationManagerMock(.init()),
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ final class MediaUploadPreviewScreenViewModelTests {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
userSettings = UserSettings.mock()
|
userSettings = UserSettings.mock()
|
||||||
userSettings.optimizeMediaUploads = false
|
userSettings.app.optimizeMediaUploads = false
|
||||||
|
|
||||||
userIndicatorController = UserIndicatorControllerMock()
|
userIndicatorController = UserIndicatorControllerMock()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ final class MediaUploadingPreprocessorTests {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
userSettings = UserSettings.mock()
|
userSettings = UserSettings.mock()
|
||||||
userSettings.optimizeMediaUploads = false
|
userSettings.app.optimizeMediaUploads = false
|
||||||
mediaUploadingPreprocessor = MediaUploadingPreprocessor(userSettings: userSettings)
|
mediaUploadingPreprocessor = MediaUploadingPreprocessor(userSettings: userSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ final class MediaUploadingPreprocessorTests {
|
|||||||
func landscapeMovVideoOptimizedProcessing() async throws {
|
func landscapeMovVideoOptimizedProcessing() async throws {
|
||||||
let url = try #require(Bundle(for: Self.self).url(forResource: "landscape_test_video.mov", withExtension: nil), "Failed retrieving test asset")
|
let url = try #require(Bundle(for: Self.self).url(forResource: "landscape_test_video.mov", withExtension: nil), "Failed retrieving test asset")
|
||||||
|
|
||||||
userSettings.optimizeMediaUploads = true
|
userSettings.app.optimizeMediaUploads = true
|
||||||
|
|
||||||
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
||||||
case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else {
|
case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else {
|
||||||
@@ -139,7 +139,7 @@ final class MediaUploadingPreprocessorTests {
|
|||||||
func portraitMp4VideoOptimizedProcessing() async throws {
|
func portraitMp4VideoOptimizedProcessing() async throws {
|
||||||
let url = try #require(Bundle(for: Self.self).url(forResource: "portrait_test_video.mp4", withExtension: nil), "Failed retrieving test asset")
|
let url = try #require(Bundle(for: Self.self).url(forResource: "portrait_test_video.mp4", withExtension: nil), "Failed retrieving test asset")
|
||||||
|
|
||||||
userSettings.optimizeMediaUploads = true
|
userSettings.app.optimizeMediaUploads = true
|
||||||
|
|
||||||
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
||||||
case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else {
|
case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else {
|
||||||
@@ -184,7 +184,7 @@ final class MediaUploadingPreprocessorTests {
|
|||||||
#expect(imageInfo.thumbnailInfo?.height == 344)
|
#expect(imageInfo.thumbnailInfo?.height == 344)
|
||||||
|
|
||||||
// Repeat with optimised media setting
|
// Repeat with optimised media setting
|
||||||
userSettings.optimizeMediaUploads = true
|
userSettings.app.optimizeMediaUploads = true
|
||||||
|
|
||||||
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
||||||
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
|
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
|
||||||
@@ -228,7 +228,7 @@ final class MediaUploadingPreprocessorTests {
|
|||||||
#expect(imageInfo.thumbnailInfo?.height == 800)
|
#expect(imageInfo.thumbnailInfo?.height == 800)
|
||||||
|
|
||||||
// Repeat with optimised media setting
|
// Repeat with optimised media setting
|
||||||
userSettings.optimizeMediaUploads = true
|
userSettings.app.optimizeMediaUploads = true
|
||||||
|
|
||||||
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
||||||
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
|
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
|
||||||
@@ -274,7 +274,7 @@ final class MediaUploadingPreprocessorTests {
|
|||||||
#expect(imageInfo.thumbnailInfo?.height == 240)
|
#expect(imageInfo.thumbnailInfo?.height == 240)
|
||||||
|
|
||||||
// Repeat with optimised media setting
|
// Repeat with optimised media setting
|
||||||
userSettings.optimizeMediaUploads = true
|
userSettings.app.optimizeMediaUploads = true
|
||||||
|
|
||||||
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
||||||
case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else {
|
case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else {
|
||||||
@@ -325,7 +325,7 @@ final class MediaUploadingPreprocessorTests {
|
|||||||
#expect(imageInfo.thumbnailInfo?.height == 800)
|
#expect(imageInfo.thumbnailInfo?.height == 800)
|
||||||
|
|
||||||
// Repeat with optimised media setting
|
// Repeat with optimised media setting
|
||||||
userSettings.optimizeMediaUploads = true
|
userSettings.app.optimizeMediaUploads = true
|
||||||
|
|
||||||
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
||||||
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
|
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
|
||||||
@@ -377,7 +377,7 @@ final class MediaUploadingPreprocessorTests {
|
|||||||
#expect(imageInfo.thumbnailInfo?.height == 472)
|
#expect(imageInfo.thumbnailInfo?.height == 472)
|
||||||
|
|
||||||
// Repeat with optimised media setting
|
// Repeat with optimised media setting
|
||||||
userSettings.optimizeMediaUploads = true
|
userSettings.app.optimizeMediaUploads = true
|
||||||
|
|
||||||
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
||||||
case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else {
|
case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else {
|
||||||
@@ -419,7 +419,7 @@ final class MediaUploadingPreprocessorTests {
|
|||||||
#expect(imageInfo.thumbnailInfo?.height == 800)
|
#expect(imageInfo.thumbnailInfo?.height == 800)
|
||||||
|
|
||||||
// Repeat with optimised media setting
|
// Repeat with optimised media setting
|
||||||
userSettings.optimizeMediaUploads = true
|
userSettings.app.optimizeMediaUploads = true
|
||||||
|
|
||||||
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
|
||||||
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
|
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
|
||||||
@@ -453,7 +453,7 @@ final class MediaUploadingPreprocessorTests {
|
|||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if userSettings.optimizeMediaUploads {
|
if userSettings.app.optimizeMediaUploads {
|
||||||
// Check that new image has been scaled within the requirements for an optimised image
|
// Check that new image has been scaled within the requirements for an optimised image
|
||||||
#expect(convertedImage.size.width <= MediaUploadingPreprocessor.Constants.optimizedMaxPixelSize)
|
#expect(convertedImage.size.width <= MediaUploadingPreprocessor.Constants.optimizedMaxPixelSize)
|
||||||
#expect(convertedImage.size.height <= MediaUploadingPreprocessor.Constants.optimizedMaxPixelSize)
|
#expect(convertedImage.size.height <= MediaUploadingPreprocessor.Constants.optimizedMaxPixelSize)
|
||||||
|
|||||||
@@ -42,16 +42,16 @@ struct NotificationSettingsScreenViewModelTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func enableNotifications() {
|
func enableNotifications() {
|
||||||
userSettings.enableNotifications = false
|
userSettings.app.enableNotifications = false
|
||||||
context.send(viewAction: .changedEnableNotifications)
|
context.send(viewAction: .changedEnableNotifications)
|
||||||
#expect(userSettings.enableNotifications)
|
#expect(userSettings.app.enableNotifications)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func disableNotifications() {
|
func disableNotifications() {
|
||||||
userSettings.enableNotifications = true
|
userSettings.app.enableNotifications = true
|
||||||
context.send(viewAction: .changedEnableNotifications)
|
context.send(viewAction: .changedEnableNotifications)
|
||||||
#expect(!userSettings.enableNotifications)
|
#expect(!userSettings.app.enableNotifications)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -411,7 +411,7 @@ struct NotificationSettingsScreenViewModelTests {
|
|||||||
// Given a tone and a manager that persists the selection to app settings
|
// Given a tone and a manager that persists the selection to app settings
|
||||||
let tone = NotificationTone.createBundledSound(label: "Test", filename: "test.caf")
|
let tone = NotificationTone.createBundledSound(label: "Test", filename: "test.caf")
|
||||||
toneManager.setSelectedToneClosure = { [userSettings] selectedTone in
|
toneManager.setSelectedToneClosure = { [userSettings] selectedTone in
|
||||||
userSettings.selectedNotificationTone = selectedTone
|
userSettings.app.selectedNotificationTone = selectedTone
|
||||||
return NotificationToneManager.libraryLocation
|
return NotificationToneManager.libraryLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,21 +419,21 @@ struct NotificationSettingsScreenViewModelTests {
|
|||||||
context.send(viewAction: .selectAlertTone(tone))
|
context.send(viewAction: .selectAlertTone(tone))
|
||||||
|
|
||||||
// Then it is reflected in app settings
|
// Then it is reflected in app settings
|
||||||
#expect(userSettings.selectedNotificationTone == tone)
|
#expect(userSettings.app.selectedNotificationTone == tone)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func deletingActiveToneResetsSelection() {
|
func deletingActiveToneResetsSelection() {
|
||||||
// Given the active tone is a custom tone
|
// Given the active tone is a custom tone
|
||||||
let customTone = NotificationTone.createCustomUserSound(filename: "custom.caf")
|
let customTone = NotificationTone.createCustomUserSound(filename: "custom.caf")
|
||||||
userSettings.selectedNotificationTone = customTone
|
userSettings.app.selectedNotificationTone = customTone
|
||||||
toneManager.customTonesReturnValue = [customTone]
|
toneManager.customTonesReturnValue = [customTone]
|
||||||
|
|
||||||
// When that tone is deleted
|
// When that tone is deleted
|
||||||
context.send(viewAction: .deleteCustomAlertTones([customTone]))
|
context.send(viewAction: .deleteCustomAlertTones([customTone]))
|
||||||
|
|
||||||
// Then the selection is cleared, falling back to the default
|
// Then the selection is cleared, falling back to the default
|
||||||
#expect(userSettings.selectedNotificationTone == nil)
|
#expect(userSettings.app.selectedNotificationTone == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -441,14 +441,14 @@ struct NotificationSettingsScreenViewModelTests {
|
|||||||
// Given a custom tone is active and a different custom tone also exists
|
// Given a custom tone is active and a different custom tone also exists
|
||||||
let activeTone = NotificationTone.createCustomUserSound(filename: "active.caf")
|
let activeTone = NotificationTone.createCustomUserSound(filename: "active.caf")
|
||||||
let otherTone = NotificationTone.createCustomUserSound(filename: "other.caf")
|
let otherTone = NotificationTone.createCustomUserSound(filename: "other.caf")
|
||||||
userSettings.selectedNotificationTone = activeTone
|
userSettings.app.selectedNotificationTone = activeTone
|
||||||
toneManager.customTonesReturnValue = [activeTone, otherTone]
|
toneManager.customTonesReturnValue = [activeTone, otherTone]
|
||||||
|
|
||||||
// When the non-active tone is deleted
|
// When the non-active tone is deleted
|
||||||
context.send(viewAction: .deleteCustomAlertTones([otherTone]))
|
context.send(viewAction: .deleteCustomAlertTones([otherTone]))
|
||||||
|
|
||||||
// Then the active selection is unchanged
|
// Then the active selection is unchanged
|
||||||
#expect(userSettings.selectedNotificationTone == activeTone)
|
#expect(userSettings.app.selectedNotificationTone == activeTone)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -476,7 +476,7 @@ struct NotificationSettingsScreenViewModelTests {
|
|||||||
func deleteCustomToneFailurePreservesSelection() {
|
func deleteCustomToneFailurePreservesSelection() {
|
||||||
// Given the active tone is a custom tone and deletion will fail
|
// Given the active tone is a custom tone and deletion will fail
|
||||||
let customTone = NotificationTone.createCustomUserSound(filename: "custom.caf")
|
let customTone = NotificationTone.createCustomUserSound(filename: "custom.caf")
|
||||||
userSettings.selectedNotificationTone = customTone
|
userSettings.app.selectedNotificationTone = customTone
|
||||||
toneManager.deleteCustomToneThrowableError = NSError(domain: "test", code: 1)
|
toneManager.deleteCustomToneThrowableError = NSError(domain: "test", code: 1)
|
||||||
toneManager.customTonesReturnValue = [customTone]
|
toneManager.customTonesReturnValue = [customTone]
|
||||||
|
|
||||||
@@ -484,7 +484,7 @@ struct NotificationSettingsScreenViewModelTests {
|
|||||||
context.send(viewAction: .deleteCustomAlertTones([customTone]))
|
context.send(viewAction: .deleteCustomAlertTones([customTone]))
|
||||||
|
|
||||||
// Then the selection is unchanged
|
// Then the selection is unchanged
|
||||||
#expect(userSettings.selectedNotificationTone == customTone)
|
#expect(userSettings.app.selectedNotificationTone == customTone)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ struct PresenceServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func serviceSeededActiveWhileSharingReportsOnline() async throws {
|
func serviceSeededActiveWhileSharingReportsOnline() async throws {
|
||||||
userSettings.sharePresence = true
|
userSettings.app.sharePresence = true
|
||||||
|
|
||||||
let deferred = deferFulfillment(sendsSubject) { $0.presence == .online }
|
let deferred = deferFulfillment(sendsSubject) { $0.presence == .online }
|
||||||
let service = makeService(initialApplicationState: .active)
|
let service = makeService(initialApplicationState: .active)
|
||||||
@@ -53,7 +53,7 @@ struct PresenceServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func serviceSeededBackgroundedWhileSharingReportsUnavailable() async throws {
|
func serviceSeededBackgroundedWhileSharingReportsUnavailable() async throws {
|
||||||
userSettings.sharePresence = true
|
userSettings.app.sharePresence = true
|
||||||
|
|
||||||
let deferred = deferFulfillment(sendsSubject) { $0.presence == .unavailable }
|
let deferred = deferFulfillment(sendsSubject) { $0.presence == .unavailable }
|
||||||
let service = makeService(initialApplicationState: .background)
|
let service = makeService(initialApplicationState: .background)
|
||||||
@@ -66,7 +66,7 @@ struct PresenceServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func serviceSeededActiveWhileNotSharingReportsOffline() async throws {
|
func serviceSeededActiveWhileNotSharingReportsOffline() async throws {
|
||||||
userSettings.sharePresence = false
|
userSettings.app.sharePresence = false
|
||||||
|
|
||||||
let deferred = deferFulfillment(sendsSubject) { $0.presence == .offline }
|
let deferred = deferFulfillment(sendsSubject) { $0.presence == .offline }
|
||||||
let service = makeService(initialApplicationState: .active)
|
let service = makeService(initialApplicationState: .active)
|
||||||
@@ -79,7 +79,7 @@ struct PresenceServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func becomingActiveNotificationWhileSharingReportsOnlineImmediately() async throws {
|
func becomingActiveNotificationWhileSharingReportsOnlineImmediately() async throws {
|
||||||
userSettings.sharePresence = true
|
userSettings.app.sharePresence = true
|
||||||
let service = makeService(initialApplicationState: .inactive)
|
let service = makeService(initialApplicationState: .inactive)
|
||||||
|
|
||||||
let deferred = deferFulfillment(sendsSubject) { $0.presence == .online }
|
let deferred = deferFulfillment(sendsSubject) { $0.presence == .online }
|
||||||
@@ -93,7 +93,7 @@ struct PresenceServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func resigningActiveNotificationWhileSharingReportsUnavailableImmediately() async throws {
|
func resigningActiveNotificationWhileSharingReportsUnavailableImmediately() async throws {
|
||||||
userSettings.sharePresence = true
|
userSettings.app.sharePresence = true
|
||||||
let initialDeferred = deferFulfillment(sendsSubject) { $0.presence == .online }
|
let initialDeferred = deferFulfillment(sendsSubject) { $0.presence == .online }
|
||||||
let service = makeService(initialApplicationState: .active)
|
let service = makeService(initialApplicationState: .active)
|
||||||
_ = try await initialDeferred.fulfill()
|
_ = try await initialDeferred.fulfill()
|
||||||
@@ -109,19 +109,19 @@ struct PresenceServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func togglingSharingWhileActiveDrivesOnlineAndOffline() async throws {
|
func togglingSharingWhileActiveDrivesOnlineAndOffline() async throws {
|
||||||
userSettings.sharePresence = false
|
userSettings.app.sharePresence = false
|
||||||
let initialDeferred = deferFulfillment(sendsSubject) { $0.presence == .offline }
|
let initialDeferred = deferFulfillment(sendsSubject) { $0.presence == .offline }
|
||||||
let service = makeService(initialApplicationState: .active)
|
let service = makeService(initialApplicationState: .active)
|
||||||
_ = try await initialDeferred.fulfill()
|
_ = try await initialDeferred.fulfill()
|
||||||
|
|
||||||
let onlineDeferred = deferFulfillment(sendsSubject) { $0.presence == .online }
|
let onlineDeferred = deferFulfillment(sendsSubject) { $0.presence == .online }
|
||||||
userSettings.sharePresence = true
|
userSettings.app.sharePresence = true
|
||||||
let online = try await onlineDeferred.fulfill()
|
let online = try await onlineDeferred.fulfill()
|
||||||
#expect(online.presence == .online)
|
#expect(online.presence == .online)
|
||||||
#expect(online.sendImmediately)
|
#expect(online.sendImmediately)
|
||||||
|
|
||||||
let offlineDeferred = deferFulfillment(sendsSubject) { $0.presence == .offline }
|
let offlineDeferred = deferFulfillment(sendsSubject) { $0.presence == .offline }
|
||||||
userSettings.sharePresence = false
|
userSettings.app.sharePresence = false
|
||||||
let offline = try await offlineDeferred.fulfill()
|
let offline = try await offlineDeferred.fulfill()
|
||||||
#expect(offline.presence == .offline)
|
#expect(offline.presence == .offline)
|
||||||
#expect(offline.sendImmediately)
|
#expect(offline.sendImmediately)
|
||||||
@@ -131,7 +131,7 @@ struct PresenceServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func repeatingTheSameStateSendsOnlyOnce() async throws {
|
func repeatingTheSameStateSendsOnlyOnce() async throws {
|
||||||
userSettings.sharePresence = true
|
userSettings.app.sharePresence = true
|
||||||
|
|
||||||
let initialDeferred = deferFulfillment(sendsSubject) { $0.presence == .online }
|
let initialDeferred = deferFulfillment(sendsSubject) { $0.presence == .online }
|
||||||
let service = makeService(initialApplicationState: .active)
|
let service = makeService(initialApplicationState: .active)
|
||||||
@@ -148,7 +148,7 @@ struct PresenceServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func failedImmediateSendIsNotRetriedUntilTheDesiredPresenceChanges() async throws {
|
func failedImmediateSendIsNotRetriedUntilTheDesiredPresenceChanges() async throws {
|
||||||
userSettings.sharePresence = true
|
userSettings.app.sharePresence = true
|
||||||
clientProxy.configurePresenceSendImmediatelyClosure = { [sendsSubject] presence, sendImmediately in
|
clientProxy.configurePresenceSendImmediatelyClosure = { [sendsSubject] presence, sendImmediately in
|
||||||
sendsSubject.send((presence, sendImmediately))
|
sendsSubject.send((presence, sendImmediately))
|
||||||
return presence == .online ? .failure(.forbiddenAccess) : .success(())
|
return presence == .online ? .failure(.forbiddenAccess) : .success(())
|
||||||
@@ -173,7 +173,7 @@ struct PresenceServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func newerTransitionWinsWhenAnEarlierSendIsInFlight() async throws {
|
func newerTransitionWinsWhenAnEarlierSendIsInFlight() async throws {
|
||||||
userSettings.sharePresence = true
|
userSettings.app.sharePresence = true
|
||||||
|
|
||||||
let (onlineStartedStream, onlineStartedContinuation) = AsyncStream<Void>.makeStream()
|
let (onlineStartedStream, onlineStartedContinuation) = AsyncStream<Void>.makeStream()
|
||||||
let (resumeOnlineSendStream, resumeOnlineSendContinuation) = AsyncStream<Void>.makeStream()
|
let (resumeOnlineSendStream, resumeOnlineSendContinuation) = AsyncStream<Void>.makeStream()
|
||||||
@@ -206,7 +206,7 @@ struct PresenceServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func deinitCancelsPendingSend() async throws {
|
func deinitCancelsPendingSend() async throws {
|
||||||
userSettings.sharePresence = true
|
userSettings.app.sharePresence = true
|
||||||
|
|
||||||
let (onlineStartedStream, onlineStartedContinuation) = AsyncStream.makeStream(of: Void.self)
|
let (onlineStartedStream, onlineStartedContinuation) = AsyncStream.makeStream(of: Void.self)
|
||||||
let (onlineFinishedStream, onlineFinishedContinuation) = AsyncStream.makeStream(of: Void.self)
|
let (onlineFinishedStream, onlineFinishedContinuation) = AsyncStream.makeStream(of: Void.self)
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ final class RoomFlowCoordinatorTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func threadedEventRoutes() async throws {
|
func threadedEventRoutes() async throws {
|
||||||
userSettings.threadsEnabled = true
|
userSettings.app.threadsEnabled = true
|
||||||
setupRoomFlowCoordinator()
|
setupRoomFlowCoordinator()
|
||||||
|
|
||||||
// Navigate directly to the threaded event
|
// Navigate directly to the threaded event
|
||||||
|
|||||||
@@ -148,12 +148,12 @@ final class RoomListFiltersStateTests {
|
|||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private func enableLowPriorityFeature() {
|
private func enableLowPriorityFeature() {
|
||||||
userSettings.lowPriorityFilterEnabled = true
|
userSettings.app.lowPriorityFilterEnabled = true
|
||||||
state = RoomListFiltersState(userSettings: userSettings)
|
state = RoomListFiltersState(userSettings: userSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func enableMentionsFeature() {
|
private func enableMentionsFeature() {
|
||||||
userSettings.mentionsFilterEnabled = true
|
userSettings.app.mentionsFilterEnabled = true
|
||||||
state = RoomListFiltersState(userSettings: userSettings)
|
state = RoomListFiltersState(userSettings: userSettings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ final class RoomScreenViewModelTests {
|
|||||||
@Test
|
@Test
|
||||||
func pinnedEventsBannerThreadedSelection() async throws {
|
func pinnedEventsBannerThreadedSelection() async throws {
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.threadsEnabled = true
|
userSettings.app.threadsEnabled = true
|
||||||
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
||||||
|
|
||||||
let roomProxyMock = JoinedRoomProxyMock(.init())
|
let roomProxyMock = JoinedRoomProxyMock(.init())
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ final class RoomSummaryProviderTests {
|
|||||||
|
|
||||||
private func setup(isLowPriorityFilterEnabled: Bool = false, includesOnlyJoinedRooms: Bool = false, setsRoomList: Bool = true) {
|
private func setup(isLowPriorityFilterEnabled: Bool = false, includesOnlyJoinedRooms: Bool = false, setsRoomList: Bool = true) {
|
||||||
userSettings = UserSettings.mock()
|
userSettings = UserSettings.mock()
|
||||||
userSettings.lowPriorityFilterEnabled = isLowPriorityFilterEnabled
|
userSettings.app.lowPriorityFilterEnabled = isLowPriorityFilterEnabled
|
||||||
|
|
||||||
let stateEventStringBuilder = RoomStateEventStringBuilder(userID: "@me:matrix.org")
|
let stateEventStringBuilder = RoomStateEventStringBuilder(userID: "@me:matrix.org")
|
||||||
let attributedStringBuilder = AttributedStringBuilder(mentionBuilder: MentionBuilder())
|
let attributedStringBuilder = AttributedStringBuilder(mentionBuilder: MentionBuilder())
|
||||||
|
|||||||
@@ -124,44 +124,44 @@ struct SearchScreenViewModelTests {
|
|||||||
context.searchQuery = "Second"
|
context.searchQuery = "Second"
|
||||||
context.send(viewAction: .selectRoom(roomID: "2"))
|
context.send(viewAction: .selectRoom(roomID: "2"))
|
||||||
|
|
||||||
#expect(userSettings.searchBreadcrumbs == [.room(roomID: "2")])
|
#expect(userSettings.account.searchBreadcrumbs == [.room(roomID: "2")])
|
||||||
|
|
||||||
context.send(viewAction: .submitQuery)
|
context.send(viewAction: .submitQuery)
|
||||||
|
|
||||||
#expect(userSettings.searchBreadcrumbs == [.query("Second"), .room(roomID: "2")])
|
#expect(userSettings.account.searchBreadcrumbs == [.query("Second"), .room(roomID: "2")])
|
||||||
|
|
||||||
// Selecting the same room again from the empty state only bumps it back to the top.
|
// Selecting the same room again from the empty state only bumps it back to the top.
|
||||||
context.searchQuery = ""
|
context.searchQuery = ""
|
||||||
context.send(viewAction: .selectRoom(roomID: "2"))
|
context.send(viewAction: .selectRoom(roomID: "2"))
|
||||||
|
|
||||||
#expect(userSettings.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")])
|
#expect(userSettings.account.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")])
|
||||||
|
|
||||||
// Submitting an empty query records nothing.
|
// Submitting an empty query records nothing.
|
||||||
context.send(viewAction: .submitQuery)
|
context.send(viewAction: .submitQuery)
|
||||||
|
|
||||||
#expect(userSettings.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")])
|
#expect(userSettings.account.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func removingBreadcrumbs() {
|
func removingBreadcrumbs() {
|
||||||
userSettings.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
|
userSettings.account.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
|
||||||
|
|
||||||
context.send(viewAction: .removeBreadcrumb(.query("Second")))
|
context.send(viewAction: .removeBreadcrumb(.query("Second")))
|
||||||
|
|
||||||
#expect(userSettings.searchBreadcrumbs == [.room(roomID: "2")])
|
#expect(userSettings.account.searchBreadcrumbs == [.room(roomID: "2")])
|
||||||
|
|
||||||
context.send(viewAction: .removeBreadcrumb(.room(SearchScreenRoom(.mock(id: "2", name: "Second")))))
|
context.send(viewAction: .removeBreadcrumb(.room(SearchScreenRoom(.mock(id: "2", name: "Second")))))
|
||||||
|
|
||||||
#expect(userSettings.searchBreadcrumbs.isEmpty)
|
#expect(userSettings.account.searchBreadcrumbs.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func clearingBreadcrumbs() {
|
func clearingBreadcrumbs() {
|
||||||
userSettings.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
|
userSettings.account.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
|
||||||
|
|
||||||
context.send(viewAction: .clearBreadcrumbs)
|
context.send(viewAction: .clearBreadcrumbs)
|
||||||
|
|
||||||
#expect(userSettings.searchBreadcrumbs.isEmpty)
|
#expect(userSettings.account.searchBreadcrumbs.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -169,7 +169,7 @@ struct SearchScreenViewModelTests {
|
|||||||
staticRoomListSubject.send([.mock(id: "2", name: "Second")])
|
staticRoomListSubject.send([.mock(id: "2", name: "Second")])
|
||||||
|
|
||||||
let deferred = deferFulfillment(context.observe(\.viewState.breadcrumbs)) { $0.count == 2 }
|
let deferred = deferFulfillment(context.observe(\.viewState.breadcrumbs)) { $0.count == 2 }
|
||||||
userSettings.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
|
userSettings.account.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
#expect(context.viewState.breadcrumbs.map(\.id) == ["query-Second", "room-2"])
|
#expect(context.viewState.breadcrumbs.map(\.id) == ["query-Second", "room-2"])
|
||||||
@@ -180,7 +180,7 @@ struct SearchScreenViewModelTests {
|
|||||||
staticRoomListSubject.send([.mock(id: "2", name: "Second"), .mock(id: "left", name: "Left", isJoined: false)])
|
staticRoomListSubject.send([.mock(id: "2", name: "Second"), .mock(id: "left", name: "Left", isJoined: false)])
|
||||||
|
|
||||||
let deferred = deferFulfillment(context.observe(\.viewState.breadcrumbs)) { !$0.isEmpty }
|
let deferred = deferFulfillment(context.observe(\.viewState.breadcrumbs)) { !$0.isEmpty }
|
||||||
userSettings.searchBreadcrumbs = [.room(roomID: "left"), .room(roomID: "2")]
|
userSettings.account.searchBreadcrumbs = [.room(roomID: "left"), .room(roomID: "2")]
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
#expect(context.viewState.breadcrumbs.map(\.id) == ["room-2"])
|
#expect(context.viewState.breadcrumbs.map(\.id) == ["room-2"])
|
||||||
@@ -190,7 +190,7 @@ struct SearchScreenViewModelTests {
|
|||||||
@Test
|
@Test
|
||||||
func roomBreadcrumbsAreDisplayedAfterTheRoomListLoads() async throws {
|
func roomBreadcrumbsAreDisplayedAfterTheRoomListLoads() async throws {
|
||||||
var deferred = deferFulfillment(context.observe(\.viewState.breadcrumbs)) { $0.count == 1 }
|
var deferred = deferFulfillment(context.observe(\.viewState.breadcrumbs)) { $0.count == 1 }
|
||||||
userSettings.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
|
userSettings.account.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")]
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
#expect(context.viewState.breadcrumbs.map(\.id) == ["query-Second"])
|
#expect(context.viewState.breadcrumbs.map(\.id) == ["query-Second"])
|
||||||
|
|||||||
@@ -458,8 +458,8 @@ final class SecurityAndPrivacyScreenViewModelTests {
|
|||||||
joinRule: ElementX.JoinRule,
|
joinRule: ElementX.JoinRule,
|
||||||
forceDisableE2EE: Bool = false) {
|
forceDisableE2EE: Bool = false) {
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.knockingEnabled = true
|
userSettings.app.knockingEnabled = true
|
||||||
userSettings.forceDisableE2EE.applyRemoteValue(forceDisableE2EE)
|
userSettings.app.forceDisableE2EE.applyRemoteValue(forceDisableE2EE)
|
||||||
roomProxy = JoinedRoomProxyMock(.init(isEncrypted: false,
|
roomProxy = JoinedRoomProxyMock(.init(isEncrypted: false,
|
||||||
canonicalAlias: "#room:matrix.org",
|
canonicalAlias: "#room:matrix.org",
|
||||||
members: .allMembersAsCreator,
|
members: .allMembersAsCreator,
|
||||||
|
|||||||
@@ -181,11 +181,11 @@ struct SettingsScreenViewModelTests {
|
|||||||
#expect(!context.viewState.showAddAccountButton)
|
#expect(!context.viewState.showAddAccountButton)
|
||||||
|
|
||||||
var deferred = deferFulfillment(context.observe(\.viewState.showAddAccountButton)) { $0 }
|
var deferred = deferFulfillment(context.observe(\.viewState.showAddAccountButton)) { $0 }
|
||||||
userSettings.multiAccountEnabled = true
|
userSettings.app.multiAccountEnabled = true
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
deferred = deferFulfillment(context.observe(\.viewState.showAddAccountButton)) { !$0 }
|
deferred = deferFulfillment(context.observe(\.viewState.showAddAccountButton)) { !$0 }
|
||||||
userSettings.multiAccountEnabled = false
|
userSettings.app.multiAccountEnabled = false
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -900,14 +900,14 @@ final class TimelineViewModelTests {
|
|||||||
func disablingTheFlagClearsTheSelection() async throws {
|
func disablingTheFlagClearsTheSelection() async throws {
|
||||||
let items = [TextRoomTimelineItem(eventID: "$1")]
|
let items = [TextRoomTimelineItem(eventID: "$1")]
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.messageMultiSelectEnabled = true
|
userSettings.app.messageMultiSelectEnabled = true
|
||||||
let viewModel = makeViewModel(timelineController: TimelineControllerMock(.init(timelineItems: items)), userSettings: userSettings)
|
let viewModel = makeViewModel(timelineController: TimelineControllerMock(.init(timelineItems: items)), userSettings: userSettings)
|
||||||
|
|
||||||
startSelection(items[0].id, in: viewModel)
|
startSelection(items[0].id, in: viewModel)
|
||||||
#expect(viewModel.state.messageSelection.isActive)
|
#expect(viewModel.state.messageSelection.isActive)
|
||||||
|
|
||||||
let deferred = deferFulfillment(viewModel.context.$viewState) { !$0.messageSelection.isEnabled && !$0.messageSelection.isActive }
|
let deferred = deferFulfillment(viewModel.context.$viewState) { !$0.messageSelection.isEnabled && !$0.messageSelection.isActive }
|
||||||
userSettings.messageMultiSelectEnabled = false
|
userSettings.app.messageMultiSelectEnabled = false
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -945,7 +945,7 @@ final class TimelineViewModelTests {
|
|||||||
private func makeSelectionViewModel(timelineController: TimelineControllerProtocol,
|
private func makeSelectionViewModel(timelineController: TimelineControllerProtocol,
|
||||||
userIndicatorController: UserIndicatorControllerProtocol = UserIndicatorControllerMock()) -> TimelineViewModel {
|
userIndicatorController: UserIndicatorControllerProtocol = UserIndicatorControllerMock()) -> TimelineViewModel {
|
||||||
let userSettings = UserSettings.mock()
|
let userSettings = UserSettings.mock()
|
||||||
userSettings.messageMultiSelectEnabled = true
|
userSettings.app.messageMultiSelectEnabled = true
|
||||||
return makeViewModel(timelineController: timelineController,
|
return makeViewModel(timelineController: timelineController,
|
||||||
userIndicatorController: userIndicatorController,
|
userIndicatorController: userIndicatorController,
|
||||||
userSettings: userSettings)
|
userSettings: userSettings)
|
||||||
|
|||||||
@@ -24,31 +24,31 @@ struct UserSettingsTests {
|
|||||||
@Test
|
@Test
|
||||||
func accountSettingsAreScopedToAccount() {
|
func accountSettingsAreScopedToAccount() {
|
||||||
// Given settings for two different accounts.
|
// Given settings for two different accounts.
|
||||||
#expect(aliceSettings.searchBreadcrumbs.isEmpty)
|
#expect(aliceSettings.account.searchBreadcrumbs.isEmpty)
|
||||||
#expect(bobSettings.searchBreadcrumbs.isEmpty)
|
#expect(bobSettings.account.searchBreadcrumbs.isEmpty)
|
||||||
|
|
||||||
// When updating an account setting for Alice.
|
// When updating an account setting for Alice.
|
||||||
aliceSettings.searchBreadcrumbs = [.query("Alice")]
|
aliceSettings.account.searchBreadcrumbs = [.query("Alice")]
|
||||||
|
|
||||||
// Then only Alice should see the change.
|
// Then only Alice should see the change.
|
||||||
#expect(aliceSettings.searchBreadcrumbs == [.query("Alice")])
|
#expect(aliceSettings.account.searchBreadcrumbs == [.query("Alice")])
|
||||||
#expect(bobSettings.searchBreadcrumbs.isEmpty)
|
#expect(bobSettings.account.searchBreadcrumbs.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func appSettingsAreShared() {
|
func appSettingsAreShared() {
|
||||||
// Given settings for two different accounts.
|
// Given settings for two different accounts.
|
||||||
#expect(!appSettings.hasSignedInBefore)
|
#expect(!appSettings.hasSignedInBefore)
|
||||||
#expect(!aliceSettings.hasSignedInBefore)
|
#expect(!aliceSettings.app.hasSignedInBefore)
|
||||||
#expect(!bobSettings.hasSignedInBefore)
|
#expect(!bobSettings.app.hasSignedInBefore)
|
||||||
|
|
||||||
// When updating an app setting through Alice's settings.
|
// When updating an app setting through Alice's settings.
|
||||||
aliceSettings.hasSignedInBefore = true
|
aliceSettings.app.hasSignedInBefore = true
|
||||||
|
|
||||||
// Then the change should be seen by the app and every account.
|
// Then the change should be seen by the app and every account.
|
||||||
#expect(appSettings.hasSignedInBefore)
|
#expect(appSettings.hasSignedInBefore)
|
||||||
#expect(aliceSettings.hasSignedInBefore)
|
#expect(aliceSettings.app.hasSignedInBefore)
|
||||||
#expect(bobSettings.hasSignedInBefore)
|
#expect(bobSettings.app.hasSignedInBefore)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -59,18 +59,18 @@ struct UserSettingsTests {
|
|||||||
try store.set(JSONEncoder().encode([SearchBreadcrumb.query("Alice")]), forKey: "searchBreadcrumbs")
|
try store.set(JSONEncoder().encode([SearchBreadcrumb.query("Alice")]), forKey: "searchBreadcrumbs")
|
||||||
#expect(store.object(forKey: "hasRunIdentityConfirmationOnboarding") != nil)
|
#expect(store.object(forKey: "hasRunIdentityConfirmationOnboarding") != nil)
|
||||||
#expect(store.object(forKey: "searchBreadcrumbs") != nil)
|
#expect(store.object(forKey: "searchBreadcrumbs") != nil)
|
||||||
#expect(!aliceSettings.hasRunIdentityConfirmationOnboarding)
|
#expect(!aliceSettings.account.hasRunIdentityConfirmationOnboarding)
|
||||||
#expect(aliceSettings.searchBreadcrumbs.isEmpty)
|
#expect(aliceSettings.account.searchBreadcrumbs.isEmpty)
|
||||||
|
|
||||||
// When migrating those values into Alice's account settings.
|
// When migrating those values into Alice's account settings.
|
||||||
aliceSettings.migrateAppSettingsValueToAccountSettings(\.hasRunIdentityConfirmationOnboardingKey)
|
aliceSettings.account.migrateAppSettingsValue(\.hasRunIdentityConfirmationOnboardingKey)
|
||||||
aliceSettings.migrateAppSettingsValueToAccountSettings(\.searchBreadcrumbsKey)
|
aliceSettings.account.migrateAppSettingsValue(\.searchBreadcrumbsKey)
|
||||||
|
|
||||||
// Then the values should belong to Alice only.
|
// Then the values should belong to Alice only.
|
||||||
#expect(aliceSettings.hasRunIdentityConfirmationOnboarding)
|
#expect(aliceSettings.account.hasRunIdentityConfirmationOnboarding)
|
||||||
#expect(aliceSettings.searchBreadcrumbs == [.query("Alice")])
|
#expect(aliceSettings.account.searchBreadcrumbs == [.query("Alice")])
|
||||||
#expect(!bobSettings.hasRunIdentityConfirmationOnboarding)
|
#expect(!bobSettings.account.hasRunIdentityConfirmationOnboarding)
|
||||||
#expect(bobSettings.searchBreadcrumbs.isEmpty)
|
#expect(bobSettings.account.searchBreadcrumbs.isEmpty)
|
||||||
#expect(store.object(forKey: "hasRunIdentityConfirmationOnboarding") == nil)
|
#expect(store.object(forKey: "hasRunIdentityConfirmationOnboarding") == nil)
|
||||||
#expect(store.object(forKey: "searchBreadcrumbs") == nil)
|
#expect(store.object(forKey: "searchBreadcrumbs") == nil)
|
||||||
}
|
}
|
||||||
@@ -79,24 +79,24 @@ struct UserSettingsTests {
|
|||||||
func resetSessionSpecificSettings() {
|
func resetSessionSpecificSettings() {
|
||||||
// Given app settings and account settings for two different accounts.
|
// Given app settings and account settings for two different accounts.
|
||||||
appSettings.hasSignedInBefore = true
|
appSettings.hasSignedInBefore = true
|
||||||
aliceSettings.hasRunIdentityConfirmationOnboarding = true
|
aliceSettings.account.hasRunIdentityConfirmationOnboarding = true
|
||||||
aliceSettings.searchBreadcrumbs = [.query("Alice")]
|
aliceSettings.account.searchBreadcrumbs = [.query("Alice")]
|
||||||
bobSettings.hasRunIdentityConfirmationOnboarding = true
|
bobSettings.account.hasRunIdentityConfirmationOnboarding = true
|
||||||
bobSettings.searchBreadcrumbs = [.query("Bob")]
|
bobSettings.account.searchBreadcrumbs = [.query("Bob")]
|
||||||
#expect(appSettings.hasSignedInBefore)
|
#expect(appSettings.hasSignedInBefore)
|
||||||
#expect(aliceSettings.hasRunIdentityConfirmationOnboarding)
|
#expect(aliceSettings.account.hasRunIdentityConfirmationOnboarding)
|
||||||
#expect(aliceSettings.searchBreadcrumbs == [.query("Alice")])
|
#expect(aliceSettings.account.searchBreadcrumbs == [.query("Alice")])
|
||||||
#expect(bobSettings.hasRunIdentityConfirmationOnboarding)
|
#expect(bobSettings.account.hasRunIdentityConfirmationOnboarding)
|
||||||
#expect(bobSettings.searchBreadcrumbs == [.query("Bob")])
|
#expect(bobSettings.account.searchBreadcrumbs == [.query("Bob")])
|
||||||
|
|
||||||
// When resetting Alice's session specific settings.
|
// When resetting Alice's session specific settings.
|
||||||
aliceSettings.resetSessionSpecificSettings()
|
aliceSettings.account.reset()
|
||||||
|
|
||||||
// Then only Alice's account settings should be reset.
|
// Then only Alice's account settings should be reset.
|
||||||
#expect(!aliceSettings.hasRunIdentityConfirmationOnboarding)
|
#expect(!aliceSettings.account.hasRunIdentityConfirmationOnboarding)
|
||||||
#expect(aliceSettings.searchBreadcrumbs.isEmpty)
|
#expect(aliceSettings.account.searchBreadcrumbs.isEmpty)
|
||||||
#expect(bobSettings.hasRunIdentityConfirmationOnboarding)
|
#expect(bobSettings.account.hasRunIdentityConfirmationOnboarding)
|
||||||
#expect(bobSettings.searchBreadcrumbs == [.query("Bob")])
|
#expect(bobSettings.account.searchBreadcrumbs == [.query("Bob")])
|
||||||
#expect(appSettings.hasSignedInBefore)
|
#expect(appSettings.hasSignedInBefore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user