Remove plain style layout (#2980)

This commit is contained in:
Mauro
2024-06-27 16:53:19 +02:00
committed by GitHub
parent 5b58ea30f6
commit 064b1bf224
122 changed files with 17 additions and 759 deletions
@@ -1,307 +0,0 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftUI
struct TimelineItemPlainStylerView<Content: View>: View {
@EnvironmentObject private var context: RoomScreenViewModel.Context
@Environment(\.timelineGroupStyle) private var timelineGroupStyle
@Environment(\.focussedEventID) private var focussedEventID
let timelineItem: EventBasedTimelineItemProtocol
let adjustedDeliveryStatus: TimelineItemDeliveryStatus?
@ViewBuilder let content: () -> Content
private var isFocussed: Bool { focussedEventID != nil && timelineItem.id.eventID == focussedEventID }
var body: some View {
VStack(alignment: .trailing, spacing: 0) {
VStack(alignment: .leading, spacing: 4) {
header
VStack(alignment: .leading, spacing: 4) {
contentWithReply
supplementaryViews
}
}
TimelineItemStatusView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus)
.environmentObject(context)
}
.padding(TimelineStyle.plain.rowInsets)
.highlightedTimelineItem(isFocussed)
}
@ViewBuilder
var contentWithReply: some View {
VStack(alignment: .leading, spacing: 8) {
if let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol {
if messageTimelineItem.isThreaded {
ThreadDecorator()
}
if let replyDetails = messageTimelineItem.replyDetails {
HStack(spacing: 4.0) {
Rectangle()
.foregroundColor(.compound.iconTertiary)
.frame(width: 4.0)
TimelineReplyView(placement: .timeline, timelineItemReplyDetails: replyDetails)
.onTapGesture {
context.send(viewAction: .focusOnEventID(replyDetails.eventID))
}
}
}
}
content()
.layoutPriority(1)
.timelineItemAccessibility(timelineItem) {
context.send(viewAction: .displayTimelineItemMenu(itemID: timelineItem.id))
}
}
.onTapGesture(count: 2) {
context.send(viewAction: .displayEmojiPicker(itemID: timelineItem.id))
}
.onTapGesture {
context.send(viewAction: .itemTapped(itemID: timelineItem.id))
}
// We need a tap gesture before this long one so that it doesn't
// steal away the gestures from the scroll view
.longPressWithFeedback {
context.send(viewAction: .displayTimelineItemMenu(itemID: timelineItem.id))
}
.swipeRightAction {
SwipeToReplyView(timelineItem: timelineItem)
} shouldStartAction: {
context.viewState.timelineItemMenuActionProvider?(timelineItem.id)?.canReply ?? false
} action: {
let isThread = (timelineItem as? EventBasedMessageTimelineItemProtocol)?.isThreaded ?? false
context.send(viewAction: .handleTimelineItemMenuAction(itemID: timelineItem.id, action: .reply(isThread: isThread)))
}
.contextMenu {
TimelineItemMacContextMenu(item: timelineItem,
actionProvider: context.viewState.timelineItemMenuActionProvider) { action in
context.send(viewAction: .handleTimelineItemMenuAction(itemID: timelineItem.id, action: action))
}
}
}
@ViewBuilder
private var header: some View {
if shouldShowSenderDetails {
HStack(spacing: 8) {
HStack(spacing: 8) {
TimelineSenderAvatarView(timelineItem: timelineItem)
Text(timelineItem.sender.displayName ?? timelineItem.sender.id)
.font(.compound.bodyMDSemibold)
.foregroundColor(.compound.decorativeColor(for: timelineItem.sender.id).text)
.lineLimit(1)
}
.onTapGesture {
context.send(viewAction: .displayRoomMemberDetails(userID: timelineItem.sender.id))
}
Spacer()
Text(timelineItem.timestamp)
.foregroundColor(.compound.textSecondary)
.font(.compound.bodyXS)
}
.padding(.top, 16)
// sender info are read inside the `TimelineAccessibilityModifier`
.accessibilityHidden(true)
}
}
@ViewBuilder
private var supplementaryViews: some View {
VStack(spacing: 4) {
if timelineItem.properties.isEdited {
Text(L10n.commonEditedSuffix)
.font(.compound.bodySM)
.foregroundColor(.compound.textSecondary)
}
if !timelineItem.properties.reactions.isEmpty {
TimelineReactionsView(context: context,
itemID: timelineItem.id,
reactions: timelineItem.properties.reactions)
// Workaround to stop the message long press stealing the touch from the reaction buttons
.onTapGesture { }
}
}
}
private var shouldShowSenderDetails: Bool {
timelineGroupStyle.shouldShowSenderDetails
}
}
struct TimelineItemPlainStylerView_Previews: PreviewProvider, TestablePreview {
static let viewModel = RoomScreenViewModel.mock
static var previews: some View {
VStack(alignment: .leading, spacing: 0) {
ForEach(1..<MockRoomTimelineController().timelineItems.count, id: \.self) { index in
let item = MockRoomTimelineController().timelineItems[index]
RoomTimelineItemView(viewState: .init(item: item, groupStyle: .single))
}
}
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
.previewLayout(.sizeThatFits)
threads
.padding()
.environment(\.timelineStyle, .plain)
.previewDisplayName("Threads")
replies
.environment(\.timelineStyle, .plain)
.previewDisplayName("Replies")
}
// These akwats include a reply
static var threads: some View {
ScrollView {
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""),
timestamp: "10:42",
isOutgoing: true,
isEditable: false,
canBeRepliedTo: true,
isThreaded: true,
sender: .init(id: "whoever"),
content: .init(body: "A long message that should be on multiple lines."),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
eventID: "123",
eventContent: .message(.text(.init(body: "Short"))))),
groupStyle: .single))
AudioRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
timestamp: "10:42",
isOutgoing: true,
isEditable: false,
canBeRepliedTo: true,
isThreaded: true,
sender: .init(id: ""),
content: .init(body: "audio.ogg",
duration: 100,
waveform: nil,
source: nil,
contentType: nil),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
eventID: "123",
eventContent: .message(.text(.init(body: "Short"))))))
FileRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
timestamp: "10:42",
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
isThreaded: true,
sender: .init(id: ""),
content: .init(body: "File",
source: nil,
thumbnailSource: nil,
contentType: nil),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
eventID: "123",
eventContent: .message(.text(.init(body: "Short"))))))
ImageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
timestamp: "10:42",
isOutgoing: true,
isEditable: true,
canBeRepliedTo: true,
isThreaded: true,
sender: .init(id: ""),
content: .init(body: "Some image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
eventID: "123",
eventContent: .message(.text(.init(body: "Short"))))))
LocationRoomTimelineView(timelineItem: .init(id: .random,
timestamp: "Now",
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
isThreaded: true,
sender: .init(id: "Bob"),
content: .init(body: "Fallback geo uri description",
geoURI: .init(latitude: 41.902782,
longitude: 12.496366),
description: "Location description description description description description description description description"),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
eventID: "123",
eventContent: .message(.text(.init(body: "Short"))))))
LocationRoomTimelineView(timelineItem: .init(id: .random,
timestamp: "Now",
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
isThreaded: true,
sender: .init(id: "Bob"),
content: .init(body: "Fallback geo uri description",
geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
eventID: "123",
eventContent: .message(.text(.init(body: "Short"))))))
VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
timestamp: "10:42",
isOutgoing: true,
isEditable: false,
canBeRepliedTo: true,
isThreaded: true,
sender: .init(id: ""),
content: .init(body: "audio.ogg",
duration: 100,
waveform: EstimatedWaveform.mockWaveform,
source: nil,
contentType: nil),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
eventID: "123",
eventContent: .message(.text(.init(body: "Short"))))),
playerState: AudioPlayerState(id: .timelineItemIdentifier(.init(timelineID: "")), duration: 10, waveform: EstimatedWaveform.mockWaveform))
}
.environmentObject(viewModel.context)
}
static var replies: some View {
ScrollView {
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""),
timestamp: "10:42",
isOutgoing: true,
isEditable: false,
canBeRepliedTo: true,
isThreaded: false,
sender: .init(id: "whoever"),
content: .init(body: "A long message that should be on multiple lines."),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
eventID: "123",
eventContent: .message(.text(.init(body: "Short"))))),
groupStyle: .single))
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""),
timestamp: "10:42",
isOutgoing: true,
isEditable: false,
canBeRepliedTo: true,
isThreaded: false,
sender: .init(id: "whoever"),
content: .init(body: "Short message"),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
eventID: "123",
eventContent: .message(.text(.init(body: "A long message that should be on more than 2 lines and so will be clipped by the layout."))))),
groupStyle: .single))
}
.environmentObject(viewModel.context)
}
}
@@ -18,14 +18,11 @@ import Foundation
import SwiftUI
enum TimelineStyle: String, CaseIterable, Codable {
case plain
case bubbles
/// List row insets for a timeline
var rowInsets: EdgeInsets {
switch self {
case .plain:
return EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20)
case .bubbles:
return EdgeInsets(top: 1, leading: 8, bottom: 1, trailing: 8)
}
@@ -34,8 +31,6 @@ enum TimelineStyle: String, CaseIterable, Codable {
/// Short hand for `self == .bubbles`
var isBubbles: Bool {
switch self {
case .plain:
return false
case .bubbles:
return true
}
@@ -60,8 +60,6 @@ struct TimelineStyler<Content: View>: View {
@ViewBuilder
var mainContent: some View {
switch style {
case .plain:
TimelineItemPlainStylerView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, content: content)
case .bubbles:
TimelineItemBubbledStylerView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, content: content)
}
@@ -204,11 +202,6 @@ struct TimelineItemStyler_Previews: PreviewProvider, TestablePreview {
.environmentObject(viewModel.context)
.environment(\.timelineStyle, .bubbles)
.previewDisplayName("Bubbles")
testView
.environmentObject(viewModel.context)
.environment(\.timelineStyle, .plain)
.previewDisplayName("Plain")
languagesTestView
.environmentObject(viewModel.context)
@@ -51,13 +51,8 @@ struct TimelineItemStatusView: View {
TimelineDeliveryStatusView(deliveryStatus: .sent)
}
case .sendingFailed:
// Bubbles handle the failure internally, but plain items don't.
if style == .plain {
CompoundIcon(\.error, size: .xSmall, relativeTo: .compound.bodyMD)
.foregroundColor(.compound.iconCriticalPrimary)
.accessibilityLabel(L10n.commonSendingFailed)
.accessibilityHint(L10n.actionTapForOptions)
}
// Bubbles handle the case internally
EmptyView()
}
}
@@ -40,9 +40,6 @@ struct AudioRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body.environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -37,9 +37,6 @@ struct EmoteRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body.environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -62,9 +62,6 @@ struct EncryptedRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body.environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -40,9 +40,6 @@ struct FileRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body.environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -173,8 +173,6 @@ struct FormattedBodyText: View {
struct FormattedBodyText_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body
body
.environment(\.timelineStyle, .plain)
}
@ViewBuilder
@@ -61,9 +61,6 @@ struct ImageRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body
.environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -93,15 +93,6 @@ struct LocationRoomTimelineView_Previews: PreviewProvider, TestablePreview {
}
.environmentObject(viewModel.context)
.previewDisplayName("Bubbles")
ScrollView {
VStack(spacing: 0) {
states
}
}
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
.previewDisplayName("Plain")
}
@ViewBuilder
@@ -49,9 +49,6 @@ struct NoticeRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body.environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -77,40 +77,10 @@ struct PollRoomTimelineView_Previews: PreviewProvider, TestablePreview {
.environment(\.timelineStyle, .bubbles)
.environmentObject(viewModel.context)
.previewDisplayName("Creator, disclosed, Bubble")
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(), isOutgoing: false))
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
.previewDisplayName("Disclosed, Plain")
PollRoomTimelineView(timelineItem: .mock(poll: .undisclosed(), isOutgoing: false))
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
.previewDisplayName("Undisclosed, Plain")
PollRoomTimelineView(timelineItem: .mock(poll: .endedDisclosed))
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
.previewDisplayName("Ended, Disclosed, Plain")
PollRoomTimelineView(timelineItem: .mock(poll: .endedUndisclosed))
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
.previewDisplayName("Ended, Undisclosed, Plain")
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(createdByAccountOwner: true)))
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
.previewDisplayName("Creator, disclosed, Plain")
PollRoomTimelineView(timelineItem: .mock(poll: .emptyDisclosed, isEditable: true))
.environment(\.timelineStyle, .bubbles)
.environmentObject(viewModel.context)
.previewDisplayName("Creator, no votes, Bubble")
PollRoomTimelineView(timelineItem: .mock(poll: .emptyDisclosed, isEditable: true))
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
.previewDisplayName("Creator, no votes, Plain")
}
}
@@ -33,8 +33,6 @@ struct StateRoomTimelineView: View {
struct StateRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body
body
.environment(\.timelineStyle, .plain)
}
static var body: some View {
@@ -52,9 +52,6 @@ struct StickerRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body.environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -42,19 +42,10 @@ struct TextRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body.environmentObject(viewModel.context)
.previewDisplayName("Bubble")
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
.previewDisplayName("Plain")
body
.environmentObject(viewModel.context)
.environment(\.layoutDirection, .rightToLeft)
.previewDisplayName("Bubble RTL")
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
.environment(\.layoutDirection, .rightToLeft)
.previewDisplayName("Plain RTL")
}
static var body: some View {
@@ -44,9 +44,6 @@ struct UnsupportedRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body.environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -72,9 +72,6 @@ struct VideoRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body.environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -41,17 +41,6 @@ struct AdvancedSettingsScreen: View {
}
}
private extension TimelineStyle {
var name: String {
switch self {
case .plain:
return L10n.commonModern
case .bubbles:
return L10n.commonBubbles
}
}
}
private extension AppAppearance {
var name: String {
switch self {
@@ -86,10 +86,6 @@ struct VoiceMessageRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
body.environmentObject(viewModel.context)
.previewDisplayName("Bubble")
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
.previewDisplayName("Plain")
}
static var body: some View {