mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Navigate to the Pinned events timeline (#3139)
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user