mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
8ddd161625
Unselecting the last message used to end the selection, closing it without the user asking. Selection mode is now its own state: Forward starts it and it only ends with the close button, once the messages are forwarded, when the timeline changes or the feature is turned off. With nothing selected the forward button stays visible but disabled.
132 lines
5.8 KiB
Swift
132 lines
5.8 KiB
Swift
//
|
|
// Copyright 2025 Element Creations Ltd.
|
|
// Copyright 2022-2025 New Vector Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
// Please see LICENSE files in the repository root for full details.
|
|
//
|
|
|
|
import Compound
|
|
import SwiftUI
|
|
|
|
struct PinnedEventsTimelineScreen: View {
|
|
@ObservedObject var context: PinnedEventsTimelineScreenViewModel.Context
|
|
@ObservedObject var timelineContext: TimelineViewModel.Context
|
|
|
|
private var isMessageSelectionActive: Bool {
|
|
timelineContext.viewState.messageSelection.isActive
|
|
}
|
|
|
|
private var title: String {
|
|
let pinnedEventIDs = timelineContext.viewState.pinnedEventIDs
|
|
guard !pinnedEventIDs.isEmpty else {
|
|
return L10n.screenPinnedTimelineScreenTitleEmpty
|
|
}
|
|
return L10n.screenPinnedTimelineScreenTitle(pinnedEventIDs.count)
|
|
}
|
|
|
|
var body: some View {
|
|
content
|
|
.navigationTitle(title)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar { toolbar }
|
|
.safeAreaInset(edge: .bottom, spacing: 0) {
|
|
if isMessageSelectionActive {
|
|
TimelineMessageSelectionActionBar(context: timelineContext)
|
|
}
|
|
}
|
|
.background(.compound.bgCanvasDefault)
|
|
.interactiveDismissDisabled()
|
|
.timelineMediaPreview(viewModel: $context.mediaPreviewViewModel)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var content: some View {
|
|
if timelineContext.viewState.pinnedEventIDs.isEmpty {
|
|
VStack(spacing: 16) {
|
|
BigIcon(icon: \.pin)
|
|
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)
|
|
} else {
|
|
TimelineView(timelineContext: timelineContext)
|
|
}
|
|
}
|
|
|
|
@ToolbarContentBuilder
|
|
private var toolbar: some ToolbarContent {
|
|
if isMessageSelectionActive {
|
|
TimelineMessageSelectionToolbar {
|
|
timelineContext.send(viewAction: .clearMessageSelection)
|
|
}
|
|
} else {
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button(L10n.actionClose) {
|
|
context.send(viewAction: .close)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Previews
|
|
|
|
struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
|
|
static let viewModel = PinnedEventsTimelineScreenViewModel(roomProxy: JoinedRoomProxyMock(.init()),
|
|
userIndicatorController: UserIndicatorControllerMock(),
|
|
userSettings: .volatile(),
|
|
analyticsService: AnalyticsServiceMock(.init()))
|
|
|
|
static let emptyTimelineViewModel = makeTimelineViewModel(timelineItems: [])
|
|
static let selectingTimelineViewModel = makeTimelineViewModel(timelineItems: TimelineFixtures.default, isSelecting: true)
|
|
|
|
static var previews: some View {
|
|
ElementNavigationStack {
|
|
PinnedEventsTimelineScreen(context: viewModel.context, timelineContext: emptyTimelineViewModel.context)
|
|
}
|
|
.previewDisplayName("Empty")
|
|
|
|
ElementNavigationStack {
|
|
PinnedEventsTimelineScreen(context: viewModel.context, timelineContext: selectingTimelineViewModel.context)
|
|
}
|
|
.previewDisplayName("Selecting")
|
|
.snapshotPreferences(expect: selectingTimelineViewModel.context.$viewState.map(\.messageSelection.isActive))
|
|
}
|
|
|
|
static func makeTimelineViewModel(timelineItems: [RoomTimelineItemProtocol], isSelecting: Bool = false) -> TimelineViewModel {
|
|
let eventIDs = timelineItems.compactMap { ($0 as? EventBasedTimelineItemProtocol)?.id.eventID }
|
|
let timelineController = TimelineControllerMock(.init(timelineKind: .pinned, timelineItems: timelineItems))
|
|
|
|
let userSettings = UserSettings.volatile()
|
|
userSettings.messageMultiSelectEnabled = isSelecting
|
|
let userSession = UserSessionMock(.init(userSettings: userSettings))
|
|
|
|
let timelineViewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room", pinnedEventIDs: Set(eventIDs))),
|
|
timelineController: timelineController,
|
|
userSession: userSession,
|
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
|
userIndicatorController: UserIndicatorControllerMock(),
|
|
appMediator: AppMediatorMock(.init()),
|
|
analyticsService: AnalyticsServiceMock(.init()),
|
|
emojiProvider: EmojiProvider(userSettings: .volatile()),
|
|
linkMetadataProvider: LinkMetadataProvider(),
|
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
|
|
|
if isSelecting {
|
|
timelineViewModel.state.messageSelection.isActive = true
|
|
timelineViewModel.state.messageSelection.selectedEventIDs = Set(eventIDs.prefix(2))
|
|
}
|
|
|
|
return timelineViewModel
|
|
}
|
|
}
|