Files
element-x-ios/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift
T
Doug 93825c01ad Initial implementation of AccountSettings. (#6275)
* Make the UserPreference macro also generate an accessor for the key name.

* Initial implementation of AccountSettings.

- Add an AccountUserDefaults storage with a userID prefix on the key.
- Migrate the 2 values reset by AppSettings.resetSessionSpecificSettings to AccountSettings.

* Rename UserSettings.volatile() to UserSettings.mock()

Given it now has a userID parameter with a default value..
2026-10-06 17:52:24 +01:00

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: .mock(),
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.mock()
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: .mock()),
linkMetadataProvider: LinkMetadataProvider(),
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
if isSelecting {
timelineViewModel.state.messageSelection.isActive = true
timelineViewModel.state.messageSelection.selectedEventIDs = Set(eventIDs.prefix(2))
}
return timelineViewModel
}
}