mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-01 01:03:01 +00:00
93825c01ad
* 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..
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: .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
|
|
}
|
|
}
|