Replace AppSettings with UserSettings from the UserSessionFlow and below.

Remove some redundant injections when passed alongside a user session too.

# Conflicts:
#	ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift
#	ElementX/Sources/Screens/Timeline/TimelineViewModel.swift
This commit is contained in:
Doug
2026-09-25 13:51:33 +01:00
committed by Stefan Ceriu
parent 54cc1cba60
commit fccad5fc30
117 changed files with 554 additions and 594 deletions
@@ -40,7 +40,7 @@ class TimelineInteractionHandler {
private let voiceMessageRecorder: VoiceMessageRecorderProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let appMediator: AppMediatorProtocol
private let appSettings: AppSettings
private let userSettings: UserSettings
private let analyticsService: AnalyticsServiceProtocol
private let emojiProvider: EmojiProviderProtocol
private let linkMetadataProvider: LinkMetadataProviderProtocol
@@ -78,7 +78,6 @@ class TimelineInteractionHandler {
voiceMessageRecorder: VoiceMessageRecorderProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
appMediator: AppMediatorProtocol,
appSettings: AppSettings,
analyticsService: AnalyticsServiceProtocol,
emojiProvider: EmojiProviderProtocol,
linkMetadataProvider: LinkMetadataProviderProtocol,
@@ -90,7 +89,7 @@ class TimelineInteractionHandler {
self.voiceMessageRecorder = voiceMessageRecorder
self.userIndicatorController = userIndicatorController
self.appMediator = appMediator
self.appSettings = appSettings
userSettings = userSession.userSettings
self.analyticsService = analyticsService
self.emojiProvider = emojiProvider
self.linkMetadataProvider = linkMetadataProvider
@@ -456,8 +455,8 @@ class TimelineInteractionHandler {
// MARK: Audio Playback
func changePlaybackSpeed(for itemID: TimelineItemIdentifier) {
let nextSpeed = appSettings.voiceMessagePlaybackSpeed.next
appSettings.voiceMessagePlaybackSpeed = nextSpeed
let nextSpeed = userSettings.voiceMessagePlaybackSpeed.next
userSettings.voiceMessagePlaybackSpeed = nextSpeed
audioPlayerState(for: itemID)?.setPlaybackSpeed(nextSpeed)
}
@@ -589,8 +588,8 @@ class TimelineInteractionHandler {
title: L10n.commonVoiceMessage,
duration: voiceMessageRoomTimelineItem.content.duration,
waveform: voiceMessageRoomTimelineItem.content.waveform,
playbackSpeed: appSettings.voiceMessagePlaybackSpeed,
playbackSpeedPublisher: appSettings.voiceMessagePlaybackSpeedPublisher)
playbackSpeed: userSettings.voiceMessagePlaybackSpeed,
playbackSpeedPublisher: userSettings.voiceMessagePlaybackSpeedPublisher)
mediaPlayerProvider.register(audioPlayerState: playerState)
return playerState
}
@@ -711,7 +710,6 @@ class TimelineInteractionHandler {
mediaPlayerProvider: mediaPlayerProvider,
userIndicatorController: userIndicatorController,
appMediator: appMediator,
appSettings: appSettings,
analyticsService: analyticsService,
emojiProvider: emojiProvider,
linkMetadataProvider: linkMetadataProvider,
@@ -29,7 +29,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
private let mediaPlayerProvider: MediaPlayerProviderProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let appMediator: AppMediatorProtocol
private let appSettings: AppSettings
private let userSettings: UserSettings
private let analyticsService: AnalyticsServiceProtocol
private let emojiProvider: EmojiProviderProtocol
@@ -54,7 +54,6 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
mediaPlayerProvider: MediaPlayerProviderProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
appMediator: AppMediatorProtocol,
appSettings: AppSettings,
analyticsService: AnalyticsServiceProtocol,
emojiProvider: EmojiProviderProtocol,
linkMetadataProvider: LinkMetadataProviderProtocol,
@@ -63,7 +62,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
self.timelineController = timelineController
self.userSession = userSession
self.mediaPlayerProvider = mediaPlayerProvider
self.appSettings = appSettings
userSettings = userSession.userSettings
self.analyticsService = analyticsService
self.userIndicatorController = userIndicatorController
self.appMediator = appMediator
@@ -78,7 +77,6 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
voiceMessageRecorder: voiceMessageRecorder,
userIndicatorController: userIndicatorController,
appMediator: appMediator,
appSettings: appSettings,
analyticsService: analyticsService,
emojiProvider: emojiProvider,
linkMetadataProvider: linkMetadataProvider,
@@ -99,16 +97,16 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
timelineState: TimelineState(focussedEvent: focussedEventID.map { .init(eventID: $0, appearance: .immediate) }),
ownUserID: roomProxy.ownUserID,
hideTimelineMedia: hideTimelineMedia,
isViewSourceEnabled: appSettings.viewSourceEnabled,
areThreadsEnabled: appSettings.threadsEnabled,
linkPreviewsEnabled: appSettings.linkPreviewsEnabled,
jumpToReadMarkerEnabled: appSettings.jumpToReadMarkerEnabled,
messageSelection: .init(isEnabled: appSettings.messageMultiSelectEnabled),
isViewSourceEnabled: userSettings.viewSourceEnabled,
areThreadsEnabled: userSettings.threadsEnabled,
linkPreviewsEnabled: userSettings.linkPreviewsEnabled,
jumpToReadMarkerEnabled: userSettings.jumpToReadMarkerEnabled,
messageSelection: .init(isEnabled: userSettings.messageMultiSelectEnabled),
hasPredecessor: roomProxy.predecessorRoom != nil,
pinnedEventIDs: roomProxy.infoPublisher.value.pinnedEventIDs,
emojiProvider: emojiProvider,
linkMetadataProvider: hideTimelineMedia ? nil : linkMetadataProvider,
mapTilerConfiguration: appSettings.mapTilerConfiguration.publisher.value,
mapTilerConfiguration: userSettings.mapTilerConfiguration.publisher.value,
bindings: .init(reactionsCollapsed: [:])),
mediaProvider: userSession.mediaProvider,
contentScannerService: userSession.contentScannerService)
@@ -274,7 +272,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
case .voiceMessage(let voiceMessageAction):
processVoiceMessageAction(voiceMessageAction)
case .contentChanged(let isEmpty):
guard appSettings.sharePresence else {
guard userSettings.sharePresence else {
return
}
@@ -501,7 +499,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
}
.store(in: &cancellables)
setupAppSettingsSubscriptions()
setupUserSettingsSubscriptions()
roomProxy.membersPublisher
.receive(on: DispatchQueue.main)
@@ -510,7 +508,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
roomProxy.typingMembersPublisher
.receive(on: DispatchQueue.main)
.filter { [weak self] _ in self?.appSettings.sharePresence ?? false }
.filter { [weak self] _ in self?.userSettings.sharePresence ?? false }
.weakAssign(to: \.state.typingMembers, on: self)
.store(in: &cancellables)
@@ -562,7 +560,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
func viewInRoomTimeline(eventID: String) async {
switch await roomProxy.loadOrFetchEventDetails(for: eventID) {
case .success(let event):
let threadRootEventID: String? = if appSettings.threadsEnabled {
let threadRootEventID: String? = if userSettings.threadsEnabled {
event.threadRootEventId()
} else {
nil
@@ -573,20 +571,20 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
}
}
private func setupAppSettingsSubscriptions() {
appSettings.sharePresencePublisher
private func setupUserSettingsSubscriptions() {
userSettings.sharePresencePublisher
.weakAssign(to: \.state.showReadReceipts, on: self)
.store(in: &cancellables)
appSettings.viewSourceEnabledPublisher
userSettings.viewSourceEnabledPublisher
.weakAssign(to: \.state.isViewSourceEnabled, on: self)
.store(in: &cancellables)
appSettings.threadsEnabledPublisher
userSettings.threadsEnabledPublisher
.weakAssign(to: \.state.areThreadsEnabled, on: self)
.store(in: &cancellables)
appSettings.jumpToReadMarkerEnabledPublisher
userSettings.jumpToReadMarkerEnabledPublisher
.weakAssign(to: \.state.jumpToReadMarkerEnabled, on: self)
.store(in: &cancellables)
@@ -1148,7 +1146,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
primaryButton: .init(title: L10n.actionOk, action: nil),
secondaryButton: .init(title: L10n.actionLearnMore) { [weak self] in
guard let self else { return }
appMediator.open(appSettings.historySharingDetailsURL)
appMediator.open(userSettings.historySharingDetailsURL)
})
case .inviteAgain:
state.bindings.alertInfo = .init(id: .inviteAgain,
@@ -1199,7 +1197,7 @@ extension TimelineViewModel {
extension TimelineViewModel {
private func setupSelectionSubscriptions() {
appSettings.messageMultiSelectEnabledPublisher
userSettings.messageMultiSelectEnabledPublisher
.sink { [weak self] isEnabled in
self?.state.messageSelection.isEnabled = isEnabled
if !isEnabled {
@@ -1300,8 +1298,6 @@ extension TimelineViewModel {
clientProxyMock.roomSummaryForIdentifierReturnValue = .mock(id: "!room:matrix.org", name: "Room", canonicalAlias: "#room:matrix.org")
let roomProxy = JoinedRoomProxyMock(.init(name: "Preview room", predecessor: hasPredecessor ? .init(roomId: UUID().uuidString) : nil))
let appSettings = AppSettings.volatile()
return TimelineViewModel(roomProxy: roomProxy,
focussedEventID: nil,
timelineController: timelineController ?? TimelineControllerMock(.init(timelineKind: timelineKind)),
@@ -1309,9 +1305,8 @@ extension TimelineViewModel {
mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock(.init()),
appSettings: appSettings,
analyticsService: AnalyticsServiceMock(.init()),
emojiProvider: EmojiProvider(appSettings: appSettings),
emojiProvider: EmojiProvider(userSettings: .volatile()),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
}
@@ -340,7 +340,7 @@ struct TimelineItemMenu_Previews: PreviewProvider, TestablePreview {
isViewSourceEnabled: true,
areThreadsEnabled: true,
timelineKind: .live,
emojiProvider: EmojiProvider(appSettings: .volatile()))
emojiProvider: EmojiProvider(userSettings: .volatile()))
guard let actions = provider.makeActions() else { return nil }
if var textItem = item as? TextRoomTimelineItem {
@@ -45,17 +45,14 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview {
]
let roomProxyMock = JoinedRoomProxyMock(.init(name: "Room", members: members))
let appSettings = AppSettings.volatile()
return TimelineViewModel(roomProxy: roomProxyMock,
timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock(.init()),
appSettings: appSettings,
analyticsService: AnalyticsServiceMock(.init()),
emojiProvider: EmojiProvider(appSettings: appSettings),
emojiProvider: EmojiProvider(userSettings: .volatile()),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
}()
@@ -421,39 +421,39 @@ private extension TimelineItemKeyForwarder {
struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview {
static let viewModel: TimelineViewModel = {
let appSettings = AppSettings.volatile()
appSettings.threadsEnabled = true
let userSettings = UserSettings.volatile()
userSettings.threadsEnabled = true
let userSession = UserSessionMock(.init(userSettings: userSettings))
let roomProxy = JoinedRoomProxyMock(.init())
return TimelineViewModel(roomProxy: roomProxy,
focussedEventID: nil,
timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()),
userSession: userSession,
mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock(.init()),
appSettings: appSettings,
analyticsService: AnalyticsServiceMock(.init()),
emojiProvider: EmojiProvider(appSettings: appSettings),
emojiProvider: EmojiProvider(userSettings: userSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
}()
static let viewModelWithPins: TimelineViewModel = {
let appSettings = AppSettings.volatile()
appSettings.threadsEnabled = true
let userSettings = UserSettings.volatile()
userSettings.threadsEnabled = true
let userSession = UserSessionMock(.init(userSettings: userSettings))
let roomProxy = JoinedRoomProxyMock(.init(name: "Preview Room", pinnedEventIDs: ["pinned"]))
return TimelineViewModel(roomProxy: roomProxy,
focussedEventID: nil,
timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()),
userSession: userSession,
mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock(.init()),
appSettings: appSettings,
analyticsService: AnalyticsServiceMock(.init()),
emojiProvider: EmojiProvider(appSettings: appSettings),
emojiProvider: EmojiProvider(userSettings: userSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
}()
@@ -461,19 +461,19 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
static let unsafeViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: false)))
static let selectingViewModel: TimelineViewModel = {
let appSettings = AppSettings.volatile()
appSettings.messageMultiSelectEnabled = true
let userSettings = UserSettings.volatile()
userSettings.messageMultiSelectEnabled = true
let userSession = UserSessionMock(.init(userSettings: userSettings))
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init()),
focussedEventID: nil,
timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()),
userSession: userSession,
mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock(.init()),
appSettings: appSettings,
analyticsService: AnalyticsServiceMock(.init()),
emojiProvider: EmojiProvider(appSettings: appSettings),
emojiProvider: EmojiProvider(userSettings: userSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
viewModel.state.messageSelection.selectedEventIDs = ["selected"]
@@ -79,21 +79,16 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
.mockMe
]
static let viewModel = {
let appSettings = AppSettings.volatile()
return TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Test", members: members)),
timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock(.init()),
appSettings: appSettings,
analyticsService: AnalyticsServiceMock(.init()),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
}()
static let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Test", members: members)),
timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock(.init()),
analyticsService: AnalyticsServiceMock(.init()),
emojiProvider: EmojiProvider(userSettings: .volatile()),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
static let singleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now")]
static let doubleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now"),
@@ -89,22 +89,17 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
static let focussedEventID = "TimelineFixtures.default.5"
static let composerViewModel = ComposerToolbarViewModel.mock()
static let timelineViewModel = {
let appSettings = AppSettings.volatile()
return TimelineViewModel(roomProxy: roomProxyMock,
focussedEventID: focussedEventID,
timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock(.init()),
appSettings: appSettings,
analyticsService: AnalyticsServiceMock(.init()),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
}()
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
focussedEventID: focussedEventID,
timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock(.init()),
analyticsService: AnalyticsServiceMock(.init()),
emojiProvider: EmojiProvider(userSettings: .volatile()),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
static var previews: some View {
ElementNavigationStack {
@@ -155,21 +155,16 @@ struct TimelineView_Previews: PreviewProvider { // Not testable as this preview
name: "Preview room"))
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
static let composerViewModel = ComposerToolbarViewModel.mock()
static let timelineViewModel = {
let appSettings = AppSettings.volatile()
return TimelineViewModel(roomProxy: roomProxyMock,
timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock(.init()),
appSettings: appSettings,
analyticsService: AnalyticsServiceMock(.init()),
emojiProvider: EmojiProvider(appSettings: appSettings),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
}()
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
timelineController: TimelineControllerMock(.init()),
userSession: UserSessionMock(.init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
userIndicatorController: UserIndicatorControllerMock(),
appMediator: AppMediatorMock(.init()),
analyticsService: AnalyticsServiceMock(.init()),
emojiProvider: EmojiProvider(userSettings: .volatile()),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
static var previews: some View {
ElementNavigationStack {