Navigate to the Pinned events timeline (#3139)

This commit is contained in:
Mauro
2024-08-08 18:29:39 +02:00
committed by GitHub
parent 0ad8f3cae8
commit ed7d3b0b13
65 changed files with 655 additions and 156 deletions
@@ -14,8 +14,6 @@
// limitations under the License.
//
// periphery:ignore:all - this is just a encryptionResetPassword remove this comment once generating the final file
import Combine
import SwiftUI
@@ -14,8 +14,6 @@
// limitations under the License.
//
// periphery:ignore:all - this is just a logViewer remove this comment once generating the final file
import Combine
import SwiftUI
@@ -14,8 +14,6 @@
// limitations under the License.
//
// periphery:ignore:all - this is just a identityConfirmation remove this comment once generating the final file
import Combine
import SwiftUI
@@ -14,8 +14,6 @@
// limitations under the License.
//
// periphery:ignore:all - this is just a identityConfirmed remove this comment once generating the final file
import Combine
import SwiftUI
@@ -0,0 +1,61 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
struct PinnedEventsTimelineScreenCoordinatorParameters { }
enum PinnedEventsTimelineScreenCoordinatorAction {
case dismiss
// Consider adding CustomStringConvertible conformance if the actions contain PII
}
final class PinnedEventsTimelineScreenCoordinator: CoordinatorProtocol {
private let parameters: PinnedEventsTimelineScreenCoordinatorParameters
private let viewModel: PinnedEventsTimelineScreenViewModelProtocol
private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<PinnedEventsTimelineScreenCoordinatorAction, Never> = .init()
var actions: AnyPublisher<PinnedEventsTimelineScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: PinnedEventsTimelineScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = PinnedEventsTimelineScreenViewModel()
}
func start() {
viewModel.actionsPublisher.sink { [weak self] action in
MXLog.info("Coordinator: received view model action: \(action)")
guard let self else { return }
switch action {
case .dismiss:
self.actionsSubject.send(.dismiss)
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(PinnedEventsTimelineScreen(context: viewModel.context))
}
}
@@ -0,0 +1,32 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum PinnedEventsTimelineScreenViewModelAction {
case dismiss
}
struct PinnedEventsTimelineScreenViewState: BindableState {
var title: String {
// TODO: Implement the non empty case
L10n.screenPinnedTimelineScreenTitleEmpty
}
}
enum PinnedEventsTimelineScreenViewAction {
case close
}
@@ -0,0 +1,42 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
typealias PinnedEventsTimelineScreenViewModelType = StateStoreViewModel<PinnedEventsTimelineScreenViewState, PinnedEventsTimelineScreenViewAction>
class PinnedEventsTimelineScreenViewModel: PinnedEventsTimelineScreenViewModelType, PinnedEventsTimelineScreenViewModelProtocol {
private let actionsSubject: PassthroughSubject<PinnedEventsTimelineScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<PinnedEventsTimelineScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init() {
super.init(initialViewState: PinnedEventsTimelineScreenViewState())
}
// MARK: - Public
override func process(viewAction: PinnedEventsTimelineScreenViewAction) {
MXLog.info("View model: received view action: \(viewAction)")
switch viewAction {
case .close:
actionsSubject.send(.dismiss)
}
}
}
@@ -0,0 +1,23 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
@MainActor
protocol PinnedEventsTimelineScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<PinnedEventsTimelineScreenViewModelAction, Never> { get }
var context: PinnedEventsTimelineScreenViewModelType.Context { get }
}
@@ -0,0 +1,68 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Compound
import SwiftUI
struct PinnedEventsTimelineScreen: View {
@ObservedObject var context: PinnedEventsTimelineScreenViewModel.Context
var body: some View {
content
.navigationTitle(context.viewState.title)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.background(.compound.bgCanvasDefault)
}
private var content: some View {
// TODO: Implement switching between empty state and timeline
VStack(spacing: 16) {
HeroImage(icon: \.pin, style: .normal)
Text(L10n.screenPinnedTimelineEmptyStateHeadline)
.font(.compound.headingSMSemibold)
.foregroundStyle(.compound.textPrimary)
.multilineTextAlignment(.center)
Text(L10n.screenPinnedTimelineEmptyStateDescription(L10n.actionPin))
.font(.compound.bodyMD)
.foregroundStyle(.compound.textSecondary)
.multilineTextAlignment(.center)
Spacer()
}
.padding(.top, 48)
.padding(.horizontal, 16)
}
@ToolbarContentBuilder
private var toolbar: some ToolbarContent {
ToolbarItem(placement: .confirmationAction) {
Button(L10n.actionClose) {
context.send(viewAction: .close)
}
}
}
}
// MARK: - Previews
struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = PinnedEventsTimelineScreenViewModel()
static var previews: some View {
NavigationStack {
PinnedEventsTimelineScreen(context: viewModel.context)
}
}
}
@@ -14,8 +14,6 @@
// limitations under the License.
//
// periphery:ignore:all - this is just a qRCodeLogin remove this comment once generating the final file
import Combine
import SwiftUI
@@ -14,8 +14,6 @@
// limitations under the License.
//
// periphery:ignore:all - this is just a roomChangePermissions remove this comment once generating the final file
import Combine
import SwiftUI
@@ -14,8 +14,6 @@
// limitations under the License.
//
// periphery:ignore:all - this is just a roomChangeRoles remove this comment once generating the final file
import Combine
import SwiftUI
@@ -25,6 +25,7 @@ struct RoomDetailsScreenCoordinatorParameters {
let userIndicatorController: UserIndicatorControllerProtocol
let notificationSettings: NotificationSettingsProxyProtocol
let attributedStringBuilder: AttributedStringBuilderProtocol
let appMediator: AppMediatorProtocol
}
enum RoomDetailsScreenCoordinatorAction {
@@ -36,6 +37,7 @@ enum RoomDetailsScreenCoordinatorAction {
case presentPollsHistory
case presentRolesAndPermissionsScreen
case presentCall
case presentPinnedEventsTimeline
}
final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
@@ -55,7 +57,9 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
analyticsService: parameters.analyticsService,
userIndicatorController: parameters.userIndicatorController,
notificationSettingsProxy: parameters.notificationSettings,
attributedStringBuilder: parameters.attributedStringBuilder)
attributedStringBuilder: parameters.attributedStringBuilder,
appMediator: parameters.appMediator,
appSettings: ServiceLocator.shared.settings)
}
// MARK: - Public
@@ -82,6 +86,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.presentRolesAndPermissionsScreen)
case .startCall:
actionsSubject.send(.presentCall)
case .displayPinnedEventsTimeline:
actionsSubject.send(.presentPinnedEventsTimeline)
}
}
.store(in: &cancellables)
@@ -30,6 +30,7 @@ enum RoomDetailsScreenViewModelAction {
case requestPollsHistoryPresentation
case requestRolesAndPermissionsPresentation
case startCall
case displayPinnedEventsTimeline
}
// MARK: View
@@ -52,6 +53,8 @@ struct RoomDetailsScreenViewState: BindableState {
var canEditRolesOrPermissions = false
var notificationSettingsState: RoomDetailsNotificationSettingsState = .loading
var canJoinCall = false
var isPinningEnabled = false
var pinnedEventsActionState = RoomDetailsScreenPinnedEventsActionState.loading
var canEdit: Bool {
!isDirect && (canEditRoomName || canEditRoomTopic || canEditRoomAvatar)
@@ -194,6 +197,7 @@ enum RoomDetailsScreenViewAction {
case toggleFavourite(isFavourite: Bool)
case processTapRolesAndPermissions
case processTapCall
case processTapPinnedEvents
}
enum RoomDetailsScreenViewShortcut {
@@ -261,3 +265,26 @@ enum RoomDetailsScreenErrorType: Hashable {
/// Leaving room has failed..
case unknown
}
enum RoomDetailsScreenPinnedEventsActionState {
case loading
case loaded(numberOfItems: Int)
var count: String {
switch self {
case .loading:
return ""
case .loaded(let numberOfItems):
return "\(numberOfItems)"
}
}
var isLoading: Bool {
switch self {
case .loading:
return true
default:
return false
}
}
}
@@ -27,8 +27,26 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
private let userIndicatorController: UserIndicatorControllerProtocol
private let notificationSettingsProxy: NotificationSettingsProxyProtocol
private let attributedStringBuilder: AttributedStringBuilderProtocol
private let appSettings: AppSettings
private var dmRecipient: RoomMemberProxyProtocol?
private var pinnedEventsTimelineProvider: RoomTimelineProviderProtocol? {
didSet {
guard let pinnedEventsTimelineProvider else {
return
}
state.pinnedEventsActionState = .loaded(numberOfItems: pinnedEventsTimelineProvider.itemProxies.filter(\.isEvent).count)
pinnedEventsTimelineProvider.updatePublisher
// When pinning or unpinning an item, the timeline might return empty for a short while, so we need to debounce it to prevent weird UI behaviours like the banner disappearing
.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main)
.sink { [weak self] updatedItems, _ in
self?.state.pinnedEventsActionState = .loaded(numberOfItems: updatedItems.filter(\.isEvent).count)
}
.store(in: &cancellables)
}
}
private var actionsSubject: PassthroughSubject<RoomDetailsScreenViewModelAction, Never> = .init()
@@ -42,7 +60,9 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
analyticsService: AnalyticsService,
userIndicatorController: UserIndicatorControllerProtocol,
notificationSettingsProxy: NotificationSettingsProxyProtocol,
attributedStringBuilder: AttributedStringBuilderProtocol) {
attributedStringBuilder: AttributedStringBuilderProtocol,
appMediator: AppMediatorProtocol,
appSettings: AppSettings) {
self.roomProxy = roomProxy
self.clientProxy = clientProxy
self.mediaProvider = mediaProvider
@@ -50,6 +70,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
self.userIndicatorController = userIndicatorController
self.notificationSettingsProxy = notificationSettingsProxy
self.attributedStringBuilder = attributedStringBuilder
self.appSettings = appSettings
let topic = attributedStringBuilder.fromPlain(roomProxy.topic)
@@ -63,6 +84,19 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
bindings: .init()),
imageProvider: mediaProvider)
appSettings.$pinningEnabled
.weakAssign(to: \.state.isPinningEnabled, on: self)
.store(in: &cancellables)
appSettings.$pinningEnabled
.combineLatest(appMediator.networkMonitor.reachabilityPublisher)
.filter { $0.0 && $0.1 == .reachable }
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.setupPinnedEventsTimelineProviderIfNeeded()
}
.store(in: &cancellables)
Task {
let userID = roomProxy.ownUserID
if case let .success(permission) = await roomProxy.canUserJoinCall(userID: userID) {
@@ -135,6 +169,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
actionsSubject.send(.requestRolesAndPermissionsPresentation)
case .processTapCall:
actionsSubject.send(.startCall)
case .processTapPinnedEvents:
actionsSubject.send(.displayPinnedEventsTimeline)
}
}
@@ -337,6 +373,22 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
}
}
}
private func setupPinnedEventsTimelineProviderIfNeeded() {
guard pinnedEventsTimelineProvider == nil else {
return
}
Task {
guard let timelineProvider = await roomProxy.pinnedEventsTimeline?.timelineProvider else {
return
}
if pinnedEventsTimelineProvider == nil {
pinnedEventsTimelineProvider = timelineProvider
}
}
}
}
private extension AttributedString {
@@ -198,6 +198,16 @@ struct RoomDetailsScreen: View {
context.send(viewAction: .toggleFavourite(isFavourite: newValue))
}
if context.viewState.isPinningEnabled {
ListRow(label: .default(title: L10n.screenRoomDetailsPinnedEventsRowTitle,
icon: \.pin),
details: context.viewState.pinnedEventsActionState.isLoading ? .isWaiting(true) : .title(context.viewState.pinnedEventsActionState.count),
kind: context.viewState.pinnedEventsActionState.isLoading ? .label : .navigationLink(action: {
context.send(viewAction: .processTapPinnedEvents)
}))
.disabled(context.viewState.pinnedEventsActionState.isLoading)
}
if context.viewState.canEditRolesOrPermissions, context.viewState.dmRecipient == nil {
ListRow(label: .default(title: L10n.screenRoomDetailsRolesAndPermissions,
icon: \.admin),
@@ -320,6 +330,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
notificationSettingsProxyMockConfiguration.roomMode.isDefault = false
let notificationSettingsProxy = NotificationSettingsProxyMock(with: notificationSettingsProxyMockConfiguration)
let appSettings = AppSettings()
appSettings.pinningEnabled = true
return RoomDetailsScreenViewModel(roomProxy: roomProxy,
clientProxy: ClientProxyMock(.init()),
@@ -327,7 +338,9 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
analyticsService: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
notificationSettingsProxy: notificationSettingsProxy,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()))
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appMediator: AppMediatorMock.default,
appSettings: appSettings)
}()
static let dmRoomViewModel = {
@@ -345,6 +358,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
members: members))
let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init())
let appSettings = AppSettings()
appSettings.pinningEnabled = true
return RoomDetailsScreenViewModel(roomProxy: roomProxy,
clientProxy: ClientProxyMock(.init()),
@@ -352,7 +366,9 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
analyticsService: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
notificationSettingsProxy: notificationSettingsProxy,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()))
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appMediator: AppMediatorMock.default,
appSettings: appSettings)
}()
static let simpleRoomViewModel = {
@@ -369,6 +385,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
members: members))
let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init())
let appSettings = AppSettings()
appSettings.pinningEnabled = true
return RoomDetailsScreenViewModel(roomProxy: roomProxy,
clientProxy: ClientProxyMock(.init()),
@@ -376,7 +393,9 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
analyticsService: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
notificationSettingsProxy: notificationSettingsProxy,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()))
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appMediator: AppMediatorMock.default,
appSettings: appSettings)
}()
static var previews: some View {
@@ -14,8 +14,6 @@
// limitations under the License.
//
// periphery:ignore:all - this is just a roomRolesAndPermissions remove this comment once generating the final file
import Combine
import SwiftUI
@@ -45,6 +45,7 @@ enum RoomScreenCoordinatorAction {
case presentRoomMemberDetails(userID: String)
case presentMessageForwarding(forwardingItem: MessageForwardingItem)
case presentCallScreen
case presentPinnedEventsTimeline
}
final class RoomScreenCoordinator: CoordinatorProtocol {
@@ -67,7 +68,6 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
mediaPlayerProvider: parameters.mediaPlayerProvider,
voiceMessageMediaManager: parameters.voiceMessageMediaManager,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
networkMonitor: ServiceLocator.shared.networkMonitor,
appMediator: parameters.appMediator,
appSettings: parameters.appSettings,
analyticsService: ServiceLocator.shared.analytics)
@@ -126,6 +126,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
composerViewModel.process(roomAction: action)
case .displayCallScreen:
actionsSubject.send(.presentCallScreen)
case .displayPinnedEventsTimeline:
actionsSubject.send(.presentPinnedEventsTimeline)
}
}
.store(in: &cancellables)
@@ -33,6 +33,7 @@ enum RoomScreenViewModelAction {
case displayLocation(body: String, geoURI: GeoURI, description: String?)
case composer(action: RoomScreenComposerAction)
case displayCallScreen
case displayPinnedEventsTimeline
}
enum RoomScreenComposerMode: Equatable {
@@ -33,7 +33,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
private let timelineController: RoomTimelineControllerProtocol
private let mediaPlayerProvider: MediaPlayerProviderProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let networkMonitor: NetworkMonitorProtocol
private let appMediator: AppMediatorProtocol
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
@@ -50,7 +49,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
private var paginateBackwardsTask: Task<Void, Never>?
private var paginateForwardsTask: Task<Void, Never>?
private var pinnedEventsTimelineProviderSetupTask: Task<Void, Never>?
private var pinnedEventsTimelineProvider: RoomTimelineProviderProtocol? {
didSet {
@@ -77,7 +75,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
mediaPlayerProvider: MediaPlayerProviderProtocol,
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
networkMonitor: NetworkMonitorProtocol,
appMediator: AppMediatorProtocol,
appSettings: AppSettings,
analyticsService: AnalyticsService) {
@@ -88,7 +85,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
self.analyticsService = analyticsService
self.userIndicatorController = userIndicatorController
self.appMediator = appMediator
self.networkMonitor = networkMonitor
pinnedEventStringBuilder = .pinnedEventStringBuilder(userID: roomProxy.ownUserID)
let voiceMessageRecorder = VoiceMessageRecorder(audioRecorder: AudioRecorder(), mediaPlayerProvider: mediaPlayerProvider)
@@ -148,8 +144,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
state.canJoinCall = permission
}
}
setupPinnedEventsTimelineProviderIfNeeded()
}
// MARK: - Public
@@ -230,8 +224,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
}
state.pinnedEventsBannerState.previousPin()
case .viewAllPins:
// TODO: Implement
break
actionsSubject.send(.displayPinnedEventsTimeline)
}
}
@@ -509,12 +502,12 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
}
.store(in: &cancellables)
networkMonitor.reachabilityPublisher
appSettings.$pinningEnabled
.combineLatest(appMediator.networkMonitor.reachabilityPublisher)
.filter { $0.0 && $0.1 == .reachable }
.receive(on: DispatchQueue.main)
.sink { [weak self] networkState in
if networkState == .reachable {
self?.setupPinnedEventsTimelineProviderIfNeeded()
}
.sink { [weak self] _ in
self?.setupPinnedEventsTimelineProviderIfNeeded()
}
.store(in: &cancellables)
}
@@ -534,20 +527,18 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
}
private func setupPinnedEventsTimelineProviderIfNeeded() {
guard pinnedEventsTimelineProvider == nil,
pinnedEventsTimelineProviderSetupTask == nil else {
guard pinnedEventsTimelineProvider == nil else {
return
}
pinnedEventsTimelineProviderSetupTask = Task { [weak self] in
guard let self else { return }
guard let pinnedEventsTimelineProvider = await roomProxy.pinnedEventsTimeline?.timelineProvider else {
pinnedEventsTimelineProviderSetupTask = nil
Task {
guard let timelineProvider = await roomProxy.pinnedEventsTimeline?.timelineProvider else {
return
}
self.pinnedEventsTimelineProvider = pinnedEventsTimelineProvider
if pinnedEventsTimelineProvider == nil {
pinnedEventsTimelineProvider = timelineProvider
}
}
}
@@ -953,7 +944,6 @@ extension RoomScreenViewModel {
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
userIndicatorController: ServiceLocator.shared.userIndicatorController,
networkMonitor: ServiceLocator.shared.networkMonitor,
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics)
@@ -58,7 +58,6 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview {
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
userIndicatorController: UserIndicatorControllerMock(),
networkMonitor: ServiceLocator.shared.networkMonitor,
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics)
@@ -214,7 +214,6 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
userIndicatorController: ServiceLocator.shared.userIndicatorController,
networkMonitor: ServiceLocator.shared.networkMonitor,
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics)
@@ -96,7 +96,6 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
userIndicatorController: ServiceLocator.shared.userIndicatorController,
networkMonitor: ServiceLocator.shared.networkMonitor,
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics)
@@ -101,7 +101,6 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
userIndicatorController: ServiceLocator.shared.userIndicatorController,
networkMonitor: ServiceLocator.shared.networkMonitor,
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics)
@@ -86,7 +86,6 @@ struct TimelineView_Previews: PreviewProvider, TestablePreview {
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
userIndicatorController: ServiceLocator.shared.userIndicatorController,
networkMonitor: ServiceLocator.shared.networkMonitor,
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics)
@@ -19,7 +19,7 @@ import SwiftUI
struct SecureBackupLogoutConfirmationScreenCoordinatorParameters {
let secureBackupController: SecureBackupControllerProtocol
let networkMonitor: NetworkMonitorProtocol
let appMediator: AppMediatorProtocol
}
enum SecureBackupLogoutConfirmationScreenCoordinatorAction {
@@ -39,7 +39,7 @@ final class SecureBackupLogoutConfirmationScreenCoordinator: CoordinatorProtocol
init(parameters: SecureBackupLogoutConfirmationScreenCoordinatorParameters) {
viewModel = SecureBackupLogoutConfirmationScreenViewModel(secureBackupController: parameters.secureBackupController,
networkMonitor: parameters.networkMonitor)
appMediator: parameters.appMediator)
}
func start() {
@@ -21,7 +21,7 @@ typealias SecureBackupLogoutConfirmationScreenViewModelType = StateStoreViewMode
class SecureBackupLogoutConfirmationScreenViewModel: SecureBackupLogoutConfirmationScreenViewModelType, SecureBackupLogoutConfirmationScreenViewModelProtocol {
private let secureBackupController: SecureBackupControllerProtocol
private let networkMonitor: NetworkMonitorProtocol
private let appMediator: AppMediatorProtocol
// periphery:ignore - auto cancels when reassigned
@CancellableTask
@@ -32,13 +32,13 @@ class SecureBackupLogoutConfirmationScreenViewModel: SecureBackupLogoutConfirmat
actionsSubject.eraseToAnyPublisher()
}
init(secureBackupController: SecureBackupControllerProtocol, networkMonitor: NetworkMonitorProtocol) {
init(secureBackupController: SecureBackupControllerProtocol, appMediator: AppMediatorProtocol) {
self.secureBackupController = secureBackupController
self.networkMonitor = networkMonitor
self.appMediator = appMediator
super.init(initialViewState: .init(mode: .saveRecoveryKey))
networkMonitor.reachabilityPublisher
appMediator.networkMonitor.reachabilityPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] reachability in
guard let self,
@@ -75,7 +75,7 @@ class SecureBackupLogoutConfirmationScreenViewModel: SecureBackupLogoutConfirmat
private func attemptLogout() {
if state.mode == .saveRecoveryKey {
state.mode = networkMonitor.reachabilityPublisher.value == .reachable ? .backupOngoing : .offline
state.mode = appMediator.networkMonitor.reachabilityPublisher.value == .reachable ? .backupOngoing : .offline
keyUploadWaitingTask = Task {
var result = await secureBackupController.waitForKeyBackupUpload()
@@ -126,7 +126,10 @@ struct SecureBackupLogoutConfirmationScreen_Previews: PreviewProvider, TestableP
let networkMonitor = NetworkMonitorMock()
networkMonitor.underlyingReachabilityPublisher = CurrentValueSubject<NetworkMonitorReachability, Never>(.reachable).asCurrentValuePublisher()
let appMediator = AppMediatorMock()
appMediator.underlyingNetworkMonitor = networkMonitor
return SecureBackupLogoutConfirmationScreenViewModel(secureBackupController: secureBackupController,
networkMonitor: networkMonitor)
appMediator: appMediator)
}
}