Files
letro-ios/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift
Aaron Thornburgh b4d6fe43c3 Update Send button bg color (#5170)
* Update Send button bg color

Change the gradient bg to accent/rest.

* Tidy-up ComposerToolbar to match iOS 18 Figma.

Also simplifies the tests a bit.

* Add a .glassEffect to Compound's SendButton.

* Add a border to TimelineReplyView.

Also use the same sizes in both the message bubbles and the composer.

* Change icon size and container in message bubbles

- Container size = 36x36px
- Icon size = 24x24px

* Update icon of reply contents to be 24x24

* Update the VoiceMessageButton to match the designs.

* Adopt Liquid Glass in the ComposerToolbar.

* Generate and fix snapshots.

---------

Co-authored-by: Doug <douglase@element.io>
2026-03-25 17:42:10 +00:00

150 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 FileRoomTimelineView: View {
@Environment(\.timelineContext) private var context
let timelineItem: FileRoomTimelineItem
var body: some View {
TimelineStyler(timelineItem: timelineItem) {
MediaFileRoomTimelineContent(filename: timelineItem.content.filename,
fileSize: timelineItem.content.fileSize,
caption: timelineItem.content.caption,
formattedCaption: timelineItem.content.formattedCaption,
additionalWhitespaces: timelineItem.additionalWhitespaces(),
shouldBoost: timelineItem.shouldBoost) {
context?.send(viewAction: .mediaTapped(itemID: timelineItem.id))
}
.accessibilityLabel(L10n.commonFile)
}
}
}
// MARK: Content
struct MediaFileRoomTimelineContent: View {
let filename: String
let fileSize: UInt?
let caption: String?
let formattedCaption: AttributedString?
let additionalWhitespaces: Int
var shouldBoost = false
var isAudioFile = false
private var fileDescription: String {
var fileDescription = "\(filename.validatedFileExtension.uppercased())"
if let fileSize {
fileDescription += " (\(fileSize.formatted(.byteCount(style: .file))))"
}
return fileDescription
}
var onMediaTap: (() -> Void)?
private var icon: KeyPath<CompoundIcons, Image> {
isAudioFile ? \.audio : \.attachment
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
if let onMediaTap {
filePreview
.onTapGesture {
onMediaTap()
}
} else {
filePreview
}
if let formattedCaption {
FormattedBodyText(attributedString: formattedCaption,
additionalWhitespacesCount: additionalWhitespaces,
boostFontSize: shouldBoost)
} else if let caption {
FormattedBodyText(text: caption,
additionalWhitespacesCount: additionalWhitespaces,
boostFontSize: shouldBoost)
}
}
}
var filePreview: some View {
Label {
VStack(alignment: .leading, spacing: 0) {
Text(filename)
.foregroundStyle(.compound.textPrimary)
.font(.compound.bodyLG)
Text(fileDescription)
.font(.compound.bodySM)
.foregroundStyle(.compound.textSecondary)
}
.font(.compound.bodyLG)
.foregroundStyle(.compound.textPrimary)
.lineLimit(2)
} icon: {
CompoundIcon(icon, size: .medium, relativeTo: .body)
.foregroundColor(.compound.iconPrimary)
.scaledPadding(6)
.background(.compound.iconOnSolidPrimary,
in: RoundedRectangle(cornerRadius: 4, style: .continuous))
}
.labelStyle(.custom(spacing: 8, alignment: .center))
.padding(.horizontal, 4) // Add to the styler's padding of 8, as we use the default insets for the caption.
}
}
// MARK: - Previews
struct FileRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static let viewModel = TimelineViewModel.mock
static var previews: some View {
VStack(spacing: 20.0) {
FileRoomTimelineView(timelineItem: makeItem(filename: "document.pdf"))
FileRoomTimelineView(timelineItem: makeItem(filename: "document.pdf",
fileSize: 3 * 1024 * 1024))
FileRoomTimelineView(timelineItem: makeItem(filename: "very very very very long named document.pdf",
fileSize: 3 * 1024 * 1024))
FileRoomTimelineView(timelineItem: makeItem(filename: "spreadsheet.xlsx",
fileSize: 17 * 1024,
caption: "The important figures you asked me to send over."))
FileRoomTimelineView(timelineItem: makeItem(filename: "document.txt",
fileSize: 456,
caption: "Plain caption",
formattedCaption: "Formatted caption"))
}
.environmentObject(viewModel.context)
}
static func makeItem(filename: String,
fileSize: UInt? = nil,
caption: String? = nil,
formattedCaption: AttributedString? = nil) -> FileRoomTimelineItem {
.init(id: .randomEvent,
timestamp: .mock,
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
sender: .init(id: "Bob"),
content: .init(filename: filename,
caption: caption,
formattedCaption: formattedCaption,
source: nil,
fileSize: fileSize,
thumbnailSource: nil,
contentType: nil))
}
}