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
@@ -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 {