mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Refactor TimelineItemBubbledStylerView for polls (#1404)
* Refactor bubble bg colors and insets * Improve MapLibreStaticMapView layout * Add BubbleTimestampLayout * Refactor BubbleTimestampLayoutType * Refine .horizontal, .vertical layouts * Rename layout type * Add docs * Restore LocationRoomTimelineItem body * Refactor whitespaces * Fix background color for LocationRoomTimelineView. * Fix UTs * Fix analytics ui tests * Fix bug report tests * Fix settings ui tests * Rename private property * Record room screen tests
This commit is contained in:
@@ -392,7 +392,6 @@
|
||||
8C1A5ECAF895D4CAF8C4D461 /* AppActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F21ED7205048668BEB44A38 /* AppActivityView.swift */; };
|
||||
8C454500B8073E1201F801A9 /* MXLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A34A814CBD56230BC74FFCF4 /* MXLogger.swift */; };
|
||||
8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; };
|
||||
8D0C5BC670D514760CC84E2A /* TextBasedRoomTimelineViewMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A542BC40D6EC2E66BC5659B /* TextBasedRoomTimelineViewMock.swift */; };
|
||||
8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; };
|
||||
8D605456793F243649EC96AA /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = CD6B0C4639E066915B5E6463 /* target.yml */; };
|
||||
8D71E5E53F372202379BECCE /* BugReportScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303FCADE77DF1F3670C086ED /* BugReportScreenViewModel.swift */; };
|
||||
@@ -1017,7 +1016,6 @@
|
||||
49D2C8E66E83EA578A7F318A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = "<group>"; };
|
||||
4A4AD793D50748F8997E5B15 /* TimelineItemMacContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMacContextMenu.swift; sourceTree = "<group>"; };
|
||||
4A542BC40D6EC2E66BC5659B /* TextBasedRoomTimelineViewMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineViewMock.swift; sourceTree = "<group>"; };
|
||||
4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaLoader.swift; sourceTree = "<group>"; };
|
||||
4AD6299F4516797E9BBE14C3 /* AnalyticsLocationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsLocationType.swift; sourceTree = "<group>"; };
|
||||
4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = "<group>"; };
|
||||
@@ -3207,7 +3205,6 @@
|
||||
ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */,
|
||||
612EF972F2A1800682D32C5E /* StickerRoomTimelineView.swift */,
|
||||
B9227F7495DA43324050A863 /* TextBasedRoomTimelineItem.swift */,
|
||||
4A542BC40D6EC2E66BC5659B /* TextBasedRoomTimelineViewMock.swift */,
|
||||
4E47F18A9A077E351CEA10D4 /* TextBasedRoomTimelineViewProtocol.swift */,
|
||||
F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */,
|
||||
F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */,
|
||||
@@ -3670,14 +3667,6 @@
|
||||
path = Timeline;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
"TEMP_8B4A8977-AAD5-44D7-8CB7-F1BC31A64C84" /* element-x-ios */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
41553551C55AD59885840F0E /* secrets.xcconfig */,
|
||||
);
|
||||
path = "element-x-ios";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -4657,7 +4646,6 @@
|
||||
2C4C750D0039AFABDF24236C /* TemplateScreenViewModelProtocol.swift in Sources */,
|
||||
D85D4FA590305180B4A41795 /* Tests.swift in Sources */,
|
||||
8317E1314C00DCCC99D30DA8 /* TextBasedRoomTimelineItem.swift in Sources */,
|
||||
8D0C5BC670D514760CC84E2A /* TextBasedRoomTimelineViewMock.swift in Sources */,
|
||||
A2A5AB2E8B3F5CA769E531FA /* TextBasedRoomTimelineViewProtocol.swift in Sources */,
|
||||
BB784A02BADB03C820617A46 /* TextRoomTimelineItem.swift in Sources */,
|
||||
53F1196F9C69512306A2693F /* TextRoomTimelineItemContent.swift in Sources */,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{
|
||||
"identity" : "compound-ios",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vector-im/compound-ios.git",
|
||||
"location" : "https://github.com/vector-im/compound-ios",
|
||||
"state" : {
|
||||
"revision" : "1f3eb60c4f87249d95addf84ce1aef22c2968763"
|
||||
}
|
||||
@@ -208,10 +208,10 @@
|
||||
{
|
||||
"identity" : "swiftui-introspect",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/siteline/SwiftUI-Introspect.git",
|
||||
"location" : "https://github.com/siteline/SwiftUI-Introspect",
|
||||
"state" : {
|
||||
"revision" : "730ab9e6cdbb3122ad88277b295c4cecd284a311",
|
||||
"version" : "0.9.1"
|
||||
"revision" : "b94da693e57eaf79d16464b8b7c90d09cba4e290",
|
||||
"version" : "0.9.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -48,9 +48,7 @@ struct MapLibreStaticMapView<PinAnnotation: View>: View {
|
||||
AsyncImage(url: url) { phase in
|
||||
switch phase {
|
||||
case .empty:
|
||||
Image("mapBlurred")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
placeholderImage
|
||||
case .success(let image):
|
||||
ZStack {
|
||||
image
|
||||
@@ -64,24 +62,31 @@ struct MapLibreStaticMapView<PinAnnotation: View>: View {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.position(x: geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY)
|
||||
.id(fetchAttempt)
|
||||
} else {
|
||||
Image("mapBlurred")
|
||||
placeholderImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var placeholderImage: some View {
|
||||
Image("mapBlurred")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
}
|
||||
|
||||
private var errorView: some View {
|
||||
Button {
|
||||
fetchAttempt += 1
|
||||
} label: {
|
||||
ZStack {
|
||||
Image("mapBlurred")
|
||||
VStack {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
Text(L10n.actionStaticMapLoad)
|
||||
placeholderImage
|
||||
.overlay {
|
||||
VStack {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
Text(L10n.actionStaticMapLoad)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+99
-46
@@ -26,11 +26,9 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
@ViewBuilder let content: () -> Content
|
||||
|
||||
@ScaledMetric private var senderNameVerticalPadding = 3
|
||||
private let cornerRadius: CGFloat = 12
|
||||
|
||||
@State private var showItemActionMenu = false
|
||||
|
||||
private var isTextItem: Bool { timelineItem is TextBasedRoomTimelineItem }
|
||||
private var isEncryptedOneToOneRoom: Bool { context.viewState.isEncryptedOneToOneRoom }
|
||||
|
||||
/// The base padding applied to bubbles on either side.
|
||||
@@ -65,7 +63,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
messageBubbleWithReactions
|
||||
}
|
||||
.padding(timelineItem.isOutgoing ? .leading : .trailing, 48) // Additional padding to differentiate alignment.
|
||||
|
||||
|
||||
HStack(spacing: 0) {
|
||||
if !timelineItem.isOutgoing {
|
||||
Spacer()
|
||||
@@ -150,50 +148,37 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
|
||||
@ViewBuilder
|
||||
var styledContent: some View {
|
||||
if shouldFillBubble {
|
||||
contentWithTimestamp
|
||||
.bubbleStyle(inset: false,
|
||||
cornerRadius: cornerRadius,
|
||||
corners: roundedCorners)
|
||||
} else {
|
||||
contentWithTimestamp
|
||||
.bubbleStyle(inset: true,
|
||||
color: timelineItem.isOutgoing ? .compound._bgBubbleOutgoing : .compound._bgBubbleIncoming,
|
||||
cornerRadius: cornerRadius,
|
||||
corners: roundedCorners)
|
||||
}
|
||||
contentWithTimestamp
|
||||
.bubbleStyle(insets: timelineItem.bubbleInsets,
|
||||
color: timelineItem.bubbleBackgroundColor,
|
||||
corners: roundedCorners)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var contentWithTimestamp: some View {
|
||||
if isTextItem || shouldFillBubble {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
timelineItem.bubbleSendInfoLayoutType
|
||||
.layout {
|
||||
contentWithReply
|
||||
interactiveLocalizedSendInfo
|
||||
}
|
||||
} else {
|
||||
HStack(alignment: .bottom, spacing: 4) {
|
||||
contentWithReply
|
||||
interactiveLocalizedSendInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var interactiveLocalizedSendInfo: some View {
|
||||
if timelineItem.hasFailedToSend {
|
||||
backgroundedLocalizedSendInfo
|
||||
layoutedLocalizedSendInfo
|
||||
.onTapGesture {
|
||||
context.sendFailedConfirmationDialogInfo = .init(itemID: timelineItem.id)
|
||||
}
|
||||
} else {
|
||||
backgroundedLocalizedSendInfo
|
||||
layoutedLocalizedSendInfo
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var backgroundedLocalizedSendInfo: some View {
|
||||
if shouldFillBubble {
|
||||
var layoutedLocalizedSendInfo: some View {
|
||||
switch timelineItem.bubbleSendInfoLayoutType {
|
||||
case .overlay(capsuleStyle: true):
|
||||
localizedSendInfo
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 2)
|
||||
@@ -201,9 +186,20 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
.cornerRadius(10)
|
||||
.padding(.trailing, 4)
|
||||
.padding(.bottom, 4)
|
||||
|
||||
} else {
|
||||
case .overlay(capsuleStyle: false):
|
||||
localizedSendInfo
|
||||
.padding(.bottom, -4)
|
||||
case .horizontal:
|
||||
localizedSendInfo
|
||||
.padding(.bottom, 4)
|
||||
.padding(.trailing, 4)
|
||||
case .vertical:
|
||||
GridRow {
|
||||
localizedSendInfo
|
||||
.padding(.bottom, 4)
|
||||
.padding(.trailing, 4)
|
||||
.gridColumnAlignment(.trailing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +218,6 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
}
|
||||
.font(.compound.bodyXS)
|
||||
.foregroundColor(timelineItem.hasFailedToSend ? .compound.textCriticalPrimary : .compound.textSecondary)
|
||||
.padding(.bottom, shouldFillBubble ? 0 : -4)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -257,19 +252,6 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
guard timelineItem.isOutgoing || isEncryptedOneToOneRoom else { return 0 }
|
||||
return timelineGroupStyle == .single || timelineGroupStyle == .first ? 8 : 0
|
||||
}
|
||||
|
||||
private var shouldFillBubble: Bool {
|
||||
switch timelineItem {
|
||||
case is ImageRoomTimelineItem,
|
||||
is VideoRoomTimelineItem,
|
||||
is StickerRoomTimelineItem:
|
||||
return true
|
||||
case let locationTimelineItem as LocationRoomTimelineItem:
|
||||
return locationTimelineItem.content.geoURI != nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private var alignment: HorizontalAlignment {
|
||||
timelineItem.isOutgoing ? .trailing : .leading
|
||||
@@ -302,13 +284,84 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
}
|
||||
|
||||
private extension View {
|
||||
func bubbleStyle(inset: Bool, color: Color? = nil, cornerRadius: CGFloat, corners: UIRectCorner) -> some View {
|
||||
padding(inset ? 8 : 0)
|
||||
.background(inset ? color : nil)
|
||||
func bubbleStyle(insets: CGFloat, color: Color? = nil, cornerRadius: CGFloat = 12, corners: UIRectCorner) -> some View {
|
||||
padding(insets)
|
||||
.background(color)
|
||||
.cornerRadius(cornerRadius, corners: corners)
|
||||
}
|
||||
}
|
||||
|
||||
// Describes how the content and the send info should be arranged inside a bubble
|
||||
private enum BubbleSendInfoLayoutType {
|
||||
case horizontal
|
||||
case vertical
|
||||
case overlay(capsuleStyle: Bool)
|
||||
|
||||
var layout: AnyLayout {
|
||||
let layout: any Layout
|
||||
|
||||
switch self {
|
||||
case .horizontal:
|
||||
layout = HStackLayout(alignment: .bottom, spacing: 4)
|
||||
case .vertical:
|
||||
layout = GridLayout(alignment: .leading, verticalSpacing: 4)
|
||||
case .overlay:
|
||||
layout = ZStackLayout(alignment: .bottomTrailing)
|
||||
}
|
||||
|
||||
return AnyLayout(layout)
|
||||
}
|
||||
}
|
||||
|
||||
private extension EventBasedTimelineItemProtocol {
|
||||
var bubbleBackgroundColor: Color? {
|
||||
let defaultColor: Color = isOutgoing ? .compound._bgBubbleOutgoing : .compound._bgBubbleIncoming
|
||||
|
||||
switch self {
|
||||
case is ImageRoomTimelineItem,
|
||||
is VideoRoomTimelineItem,
|
||||
is StickerRoomTimelineItem:
|
||||
return nil
|
||||
default:
|
||||
return defaultColor
|
||||
}
|
||||
}
|
||||
|
||||
// The insets for the full bubble content.
|
||||
// Padding affecting just the "send info" should be added inside `layoutedLocalizedSendInfo`
|
||||
var bubbleInsets: CGFloat {
|
||||
let defaultPadding: CGFloat = 8
|
||||
|
||||
switch self {
|
||||
case is ImageRoomTimelineItem,
|
||||
is VideoRoomTimelineItem,
|
||||
is StickerRoomTimelineItem:
|
||||
return 0
|
||||
case let locationTimelineItem as LocationRoomTimelineItem:
|
||||
return locationTimelineItem.content.geoURI == nil ? defaultPadding : 0
|
||||
default:
|
||||
return defaultPadding
|
||||
}
|
||||
}
|
||||
|
||||
var bubbleSendInfoLayoutType: BubbleSendInfoLayoutType {
|
||||
let defaultTimestampLayout: BubbleSendInfoLayoutType = .horizontal
|
||||
|
||||
switch self {
|
||||
case is TextBasedRoomTimelineItem:
|
||||
return .overlay(capsuleStyle: false)
|
||||
case is ImageRoomTimelineItem,
|
||||
is VideoRoomTimelineItem,
|
||||
is StickerRoomTimelineItem:
|
||||
return .overlay(capsuleStyle: true)
|
||||
case let locationTimelineItem as LocationRoomTimelineItem:
|
||||
return .overlay(capsuleStyle: locationTimelineItem.content.geoURI != nil)
|
||||
default:
|
||||
return defaultTimestampLayout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TimelineItemBubbledStylerView_Previews: PreviewProvider {
|
||||
static let viewModel = RoomScreenViewModel.mock
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ struct EmoteRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
||||
var body: some View {
|
||||
TimelineStyler(timelineItem: timelineItem) {
|
||||
if let attributedString = timelineItem.content.formattedBody {
|
||||
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: additionalWhitespaces)
|
||||
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
|
||||
} else {
|
||||
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: additionalWhitespaces)
|
||||
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +34,8 @@ struct LocationRoomTimelineView: View {
|
||||
.aspectRatio(mapAspectRatio, contentMode: .fit)
|
||||
.clipped()
|
||||
}
|
||||
.background(backgroundView)
|
||||
} else {
|
||||
FormattedBodyText(text: timelineItem.body)
|
||||
FormattedBodyText(text: timelineItem.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,16 +51,6 @@ struct LocationRoomTimelineView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var backgroundView: some View {
|
||||
switch timelineStyle {
|
||||
case .bubbles:
|
||||
timelineItem.isOutgoing ? Color.compound._bgBubbleOutgoing : Color.compound._bgBubbleIncoming
|
||||
case .plain:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
private let mapAspectRatio: Double = 3 / 2
|
||||
private let mapMaxHeight: Double = 300
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ struct NoticeRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
||||
.foregroundColor(.compound.iconSecondary)
|
||||
|
||||
if let attributedString = timelineItem.content.formattedBody {
|
||||
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: additionalWhitespaces)
|
||||
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
|
||||
} else {
|
||||
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: additionalWhitespaces)
|
||||
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
|
||||
}
|
||||
}
|
||||
.padding(.leading, 4) // Trailing padding is provided by FormattedBodyText
|
||||
|
||||
@@ -17,15 +17,3 @@
|
||||
import Foundation
|
||||
|
||||
protocol TextBasedRoomTimelineItem: EventBasedMessageTimelineItemProtocol { }
|
||||
|
||||
extension TextBasedRoomTimelineItem {
|
||||
/// contains the timestamp and an optional edited localised prefix
|
||||
/// example: (edited) 12:17 PM
|
||||
var localizedSendInfo: String {
|
||||
var start = ""
|
||||
if properties.isEdited {
|
||||
start = "\(L10n.commonEditedSuffix) "
|
||||
}
|
||||
return start + timestamp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
//
|
||||
// Copyright 2023 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
|
||||
|
||||
// generated with auto mockable and customised to support the generic
|
||||
class TextBasedRoomTimelineViewMock<TimelineItemType: TextBasedRoomTimelineItem>: TextBasedRoomTimelineViewProtocol {
|
||||
var timelineItem: TimelineItemType {
|
||||
get { underlyingTimelineItem }
|
||||
set(value) { underlyingTimelineItem = value }
|
||||
}
|
||||
|
||||
var underlyingTimelineItem: TimelineItemType!
|
||||
var timelineStyle: TimelineStyle {
|
||||
get { underlyingTimelineStyle }
|
||||
set(value) { underlyingTimelineStyle = value }
|
||||
}
|
||||
|
||||
var underlyingTimelineStyle: TimelineStyle!
|
||||
}
|
||||
-19
@@ -20,22 +20,3 @@ protocol TextBasedRoomTimelineViewProtocol {
|
||||
var timelineItem: TimelineItemType { get }
|
||||
var timelineStyle: TimelineStyle { get }
|
||||
}
|
||||
|
||||
extension TextBasedRoomTimelineViewProtocol {
|
||||
var additionalWhitespaces: Int {
|
||||
guard timelineStyle == .bubbles else {
|
||||
return 0
|
||||
}
|
||||
var whiteSpaces = 1
|
||||
timelineItem.localizedSendInfo.forEach { _ in
|
||||
whiteSpaces += 1
|
||||
}
|
||||
|
||||
// To account for the extra spacing created by the alert icon
|
||||
if timelineItem.hasFailedToSend {
|
||||
whiteSpaces += 3
|
||||
}
|
||||
|
||||
return whiteSpaces
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ struct TextRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
||||
TimelineStyler(timelineItem: timelineItem) {
|
||||
if let attributedString = timelineItem.content.formattedBody {
|
||||
FormattedBodyText(attributedString: attributedString,
|
||||
additionalWhitespacesCount: additionalWhitespaces,
|
||||
additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle),
|
||||
boostEmojiSize: true)
|
||||
} else {
|
||||
FormattedBodyText(text: timelineItem.body,
|
||||
additionalWhitespacesCount: additionalWhitespaces,
|
||||
additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle),
|
||||
boostEmojiSize: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,4 +53,31 @@ extension EventBasedTimelineItemProtocol {
|
||||
var hasFailedDecryption: Bool {
|
||||
self is EncryptedRoomTimelineItem
|
||||
}
|
||||
|
||||
func additionalWhitespaces(timelineStyle: TimelineStyle) -> Int {
|
||||
guard timelineStyle == .bubbles else {
|
||||
return 0
|
||||
}
|
||||
var whiteSpaces = 1
|
||||
localizedSendInfo.forEach { _ in
|
||||
whiteSpaces += 1
|
||||
}
|
||||
|
||||
// To account for the extra spacing created by the alert icon
|
||||
if hasFailedToSend {
|
||||
whiteSpaces += 3
|
||||
}
|
||||
|
||||
return whiteSpaces
|
||||
}
|
||||
|
||||
/// contains the timestamp and an optional edited localised prefix
|
||||
/// example: (edited) 12:17 PM
|
||||
var localizedSendInfo: String {
|
||||
var start = ""
|
||||
if properties.isEdited {
|
||||
start = "\(L10n.commonEditedSuffix) "
|
||||
}
|
||||
return start + timestamp
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ struct LocationRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatabl
|
||||
let content: LocationRoomTimelineItemContent
|
||||
|
||||
var body: String {
|
||||
L10n.commonSharedLocation
|
||||
content.body
|
||||
}
|
||||
|
||||
var replyDetails: TimelineItemReplyDetails?
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b4a6596df37c3a0cc56eb999e8aab2aa0589b0ffebded25fbaa7fc0f128ce2f6
|
||||
size 350997
|
||||
oid sha256:06b4d1de59e18e7e0cdb58b49b6de0c2b7ec21063c11a823e2f4e238d1640f2d
|
||||
size 296741
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6de5d464a4b1f8981e6006ddaea668d01a2366034dc2565531e6b9669eb24d71
|
||||
size 326455
|
||||
oid sha256:9c006fbb509e4114b934197aa1e40e2e906a9eee1e043d74a96528862d2edf43
|
||||
size 301910
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3b0d03fc5e88ebddbffba3545514ab05c835b5f67ce067897252982093a4e104
|
||||
size 345733
|
||||
oid sha256:644447cf6dd5c8e28cc28386d2228eaddaf520f653221c510401927a4db068ce
|
||||
size 298163
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:143706144c6c2c9772dfa984512559510e88b073164eb2c7b8e19d1300c0d86d
|
||||
size 358851
|
||||
oid sha256:35978e28c5167b57d1b6d9bb5699fec969acb13d6001f09303ea50629b7a05ad
|
||||
size 301134
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4ef2bf1bddbac71de4fb5952bdb1a58c4f8107ca68323ad8ac4ff830ccf86066
|
||||
size 299747
|
||||
oid sha256:380e2a9d6e32d16ab8bb572d963cc9357baedf90f1e5ffc6180d30921a40a7e2
|
||||
size 300010
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5cd496003bb58476c26d03323ce364ea4c8e881e113781a81372f243a4191727
|
||||
size 430163
|
||||
oid sha256:5cf469ffe0a81e64c3e88b188f8f54c8835e9912085fea0fbeec6231d246e592
|
||||
size 304213
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6610a02c243f8aa0b494a06fef42861ab18d6d4e4d8c886a3f2e0258540cc6bd
|
||||
size 408366
|
||||
oid sha256:1729d0b3d1637ac4d9717f7821421e30afdd784b7dfc798bfac934eed6d9376b
|
||||
size 290271
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dccbc12f8cd5476d6c3f10dc44c85a6505b05290653e5ee9fd2ed31a3432cb8f
|
||||
size 424765
|
||||
oid sha256:65b14f7218113f703c462c53f31ccf8fd20b05551ee17efc8f69717d35cd6a9c
|
||||
size 325570
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f941a49c42577fc470859a69c5a56a83177e3515863e5f283e2e73f3d664a0c8
|
||||
size 435321
|
||||
oid sha256:9acbe442057ee125325c8ea8d159ec328172b7cd0852459667fc6a7d240143d5
|
||||
size 309313
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:08c2810e0aa77e6d421c760c2eb386a2d4d50eec77de0f5e5f42e615d5cc5a46
|
||||
size 306051
|
||||
oid sha256:42dddaf4b8dd23c8018d9fa7c487ce8346e6adfbe10434889090f7202daf39a9
|
||||
size 307188
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fa6c3dc4e0eabbc0a8e633de76cdd1d523d39d356590130f3cd1798e3bee0e83
|
||||
size 351481
|
||||
oid sha256:3c434451e78882347dd741c295cf6fa6c5b842201a6fe87e4a24c13b27dd02e8
|
||||
size 297230
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:650e4a9a861ca1f6b1bb892ab0e01f76dc884aebf8ea351d67fb2acfea28c75f
|
||||
size 330385
|
||||
oid sha256:55d2c24fc253e862c962bb9dfab9967be096b16b8199da60eb18d195ba06a4b6
|
||||
size 300572
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:25d6c8939a3f2c7dddcbcff9be019ab89ac0b24ecb8b3477e8bfaaa7d73ef38d
|
||||
size 346218
|
||||
oid sha256:4eba5196eb855dbbc5e914b103ffcd552f5009e6348c54085a61521f73fd593c
|
||||
size 298641
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b33e22d3248e639fd6169bd49fcf6eabc250bc0b346c0ddfa71ce1eb833487f8
|
||||
size 356377
|
||||
oid sha256:8486ab8e9cd82e5b6e7454bd5839c4024b2d657fc2a084f43f9ce130b9c54c6a
|
||||
size 301747
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7c5697f227d2e6df0787a0e6d4a5c188e20a8d2c3029cde6602b601363905a37
|
||||
size 300190
|
||||
oid sha256:7e185789c83db3017fe3070424fb8f2aa27b959d62e6eca6ba2a4dd96b21f4e7
|
||||
size 300452
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e350b7d9688e3b2bf8022b631ba9bcd68cdf55b1542d9b57a11c09382cf73e6d
|
||||
size 108191
|
||||
oid sha256:a81e4fbd557c2220fc1b75086b757ca4edfcf8384dd0bdaa40f52ebae959e115
|
||||
size 108213
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0a56b27e07517a4492fde3e838ee817f511b05d47c40f98ca9eecc2134735817
|
||||
size 235551
|
||||
oid sha256:074800a57b21edd7d9b54e1d01a85a9cb29b3e96045ef999a66ac386fe366220
|
||||
size 235638
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5ded0d89aaaa438e2f7627e01ba763fcce9c5c50323625367f3c1c6807287564
|
||||
size 220367
|
||||
oid sha256:7602c21904a1b961e3841bbf881a0e972fcfe075d68502c95583a3e4a42a9860
|
||||
size 227310
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e93c55f85674a2698d8d8569dc86b49c2b7a474b4398b480d0188789984b06fb
|
||||
size 225752
|
||||
oid sha256:d1039489d9d16dcc6367118caf0621aacd96994eb27251f2b05998824fca45d5
|
||||
size 234278
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1bb16327656727348d7b4d03354843153a12fd68d6a5e836051ad00e15a7be85
|
||||
size 430749
|
||||
oid sha256:3df719c0282253bd6374dfe5780a8e089929b37f8f9b750c0184fa9be8edcbb7
|
||||
size 304809
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bce5567d7ba91090b14eada0d25c8f20b24404b9a62e8c73180d9d63f8a9148b
|
||||
size 409038
|
||||
oid sha256:436486516ce97f7eb691471f779361cfb99c6419d4b668e6abd2fe412909c05d
|
||||
size 290954
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f6a84ad7d884a59519a91d29e50bf733133be895ccec94bae469f59966e7561f
|
||||
size 425342
|
||||
oid sha256:ca282a2905f782a2a02525ad1898fa80903ce9a00099ac3a7fa99f34412aae56
|
||||
size 326148
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e08810c191b8acc377ba450e004d555c0b2411027746130dcdac72ac88199d0c
|
||||
size 435939
|
||||
oid sha256:37c26c86225d648072c5b7668745dab809f26c643fb28074005aed04c302c50a
|
||||
size 309980
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e917a63402fc03d3a844724c68d10093009f5da8cf0340ef2e16cfc99ae09772
|
||||
size 306563
|
||||
oid sha256:2c5c89904577779c4fd7b92485d147ea0ccbaa706dee162d34b4afc42ad6c2b3
|
||||
size 307707
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9e8a396e6552c2c3574e8879e758ea190ca7ca095d8b132d80bfc9152179ff2b
|
||||
size 165232
|
||||
oid sha256:5292689319bfed500832759b2ecf924eacbd6e3c413f520b52f6fe1e2e1c9900
|
||||
size 164822
|
||||
|
||||
@@ -21,30 +21,18 @@ final class TextBasedRoomTimelineTests: XCTestCase {
|
||||
func testTextRoomTimelineItemWhitespaceEnd() {
|
||||
let timestamp = "Now"
|
||||
let timelineItem = TextRoomTimelineItem(id: .random, timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||
view.underlyingTimelineItem = timelineItem
|
||||
view.timelineStyle = .bubbles
|
||||
|
||||
XCTAssertEqual(view.additionalWhitespaces, timestamp.count + 1)
|
||||
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .bubbles), timestamp.count + 1)
|
||||
}
|
||||
|
||||
func testTextRoomTimelineItemWhitespaceEndLonger() {
|
||||
let timestamp = "10:00 AM"
|
||||
let timelineItem = TextRoomTimelineItem(id: .random, timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||
view.underlyingTimelineItem = timelineItem
|
||||
view.timelineStyle = .bubbles
|
||||
|
||||
XCTAssertEqual(view.additionalWhitespaces, timestamp.count + 1)
|
||||
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .bubbles), timestamp.count + 1)
|
||||
}
|
||||
|
||||
func testTextRoomTimelineItemWhitespaceEndPlain() {
|
||||
let timelineItem = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||
view.underlyingTimelineItem = timelineItem
|
||||
view.timelineStyle = .plain
|
||||
|
||||
XCTAssertEqual(view.additionalWhitespaces, 0)
|
||||
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .plain), 0)
|
||||
}
|
||||
|
||||
func testTextRoomTimelineItemWhitespaceEndWithEdit() {
|
||||
@@ -52,11 +40,7 @@ final class TextBasedRoomTimelineTests: XCTestCase {
|
||||
var timelineItem = TextRoomTimelineItem(id: .random, timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||
timelineItem.properties.isEdited = true
|
||||
let editedCount = L10n.commonEditedSuffix.count
|
||||
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||
view.underlyingTimelineItem = timelineItem
|
||||
view.timelineStyle = .bubbles
|
||||
|
||||
XCTAssertEqual(view.additionalWhitespaces, timestamp.count + editedCount + 2)
|
||||
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .bubbles), timestamp.count + editedCount + 2)
|
||||
}
|
||||
|
||||
func testTextRoomTimelineItemWhitespaceEndWithEditAndAlert() {
|
||||
@@ -65,10 +49,6 @@ final class TextBasedRoomTimelineTests: XCTestCase {
|
||||
timelineItem.properties.isEdited = true
|
||||
timelineItem.properties.deliveryStatus = .sendingFailed
|
||||
let editedCount = L10n.commonEditedSuffix.count
|
||||
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||
view.underlyingTimelineItem = timelineItem
|
||||
view.timelineStyle = .bubbles
|
||||
|
||||
XCTAssertEqual(view.additionalWhitespaces, timestamp.count + editedCount + 5)
|
||||
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .bubbles), timestamp.count + editedCount + 5)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user