mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Refactor TimelineItemSendInfo out of the styler. (#3100)
* Reorder some properties. No code changes. * Refactor TimelineItemSendInfo out of the styler. * Add (unused) examples of encryption shields. * Rename file.
This commit is contained in:
parent
0fab3a8afc
commit
a2ea9c4a06
@ -377,6 +377,7 @@
|
||||
5710AAB27D5D866292C1FE06 /* SessionVerificationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF848B41DAF1066F3054D4A1 /* SessionVerificationScreenModels.swift */; };
|
||||
5732395A4F71F51F9C754C5A /* ElementCallService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33AE897D86784CCA5E4E9227 /* ElementCallService.swift */; };
|
||||
5780E444F405AA1304E1C23E /* DeveloperOptionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E521D6C2BF8DF0DFB35146 /* DeveloperOptionsScreen.swift */; };
|
||||
57B9562E6FE788FC172D4AAF /* TimelineItemSendInfoLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D15447A39D91D2EF536C74DD /* TimelineItemSendInfoLabel.swift */; };
|
||||
57E115A8C33E599DE564F8C3 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDEB27575FEBCF414D4DEE31 /* TimelineView.swift */; };
|
||||
588411C8FD72B2A2DFE5F7DE /* XCUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E992D7B8BE54B2AB454613AF /* XCUIElement.swift */; };
|
||||
5894C2514400A4FBC9327632 /* ServerConfirmationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03277E40D0E0DE0712021A71 /* ServerConfirmationScreenCoordinator.swift */; };
|
||||
@ -2014,6 +2015,7 @@
|
||||
D09A267106B9585D3D0CFC0D /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = "<group>"; };
|
||||
D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
D0C2D52E36AD614B3C003EF6 /* RoomTimelineItemViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemViewState.swift; sourceTree = "<group>"; };
|
||||
D15447A39D91D2EF536C74DD /* TimelineItemSendInfoLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemSendInfoLabel.swift; sourceTree = "<group>"; };
|
||||
D162B2280A15ACAF35360554 /* HighlightedTimelineItemModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightedTimelineItemModifier.swift; sourceTree = "<group>"; };
|
||||
D1896F6288D80E1F3EFB3DF8 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ka; path = ka.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
D196116D2DD3F2757D45FCB7 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/SAS.strings; sourceTree = "<group>"; };
|
||||
@ -4368,6 +4370,7 @@
|
||||
E2DCA495ED42D2463DDAA94D /* TimelineBubbleLayout.swift */,
|
||||
74C6F3DAD167F972702C8893 /* TimelineItemAccessibilityModifier.swift */,
|
||||
98A2932515EA11D3DD8A3506 /* TimelineItemBubbledStylerView.swift */,
|
||||
D15447A39D91D2EF536C74DD /* TimelineItemSendInfoLabel.swift */,
|
||||
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */,
|
||||
892E29C98C4E8182C9037F84 /* TimelineStyler.swift */,
|
||||
);
|
||||
@ -6640,6 +6643,7 @@
|
||||
1C815DD79B401DEBA2914773 /* TimelineItemMock.swift in Sources */,
|
||||
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */,
|
||||
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */,
|
||||
57B9562E6FE788FC172D4AAF /* TimelineItemSendInfoLabel.swift in Sources */,
|
||||
1B88BB631F7FC45A213BB554 /* TimelineItemSender.swift in Sources */,
|
||||
A680F54935A6ADEA4ED6C38F /* TimelineItemStatusView.swift in Sources */,
|
||||
562EFB9AB62B38830D9AA778 /* TimelineMediaFrame.swift in Sources */,
|
||||
|
@ -4,6 +4,10 @@
|
||||
/* Used for testing */
|
||||
"untranslated" = "Untranslated";
|
||||
|
||||
// MARK: - Shields
|
||||
|
||||
"send_info_not_encrypted" = "Not encrypted";
|
||||
|
||||
// MARK: - Soft logout
|
||||
|
||||
"soft_logout_signin_title" = "Sign in";
|
||||
|
@ -10,6 +10,8 @@ import Foundation
|
||||
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
|
||||
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
internal enum UntranslatedL10n {
|
||||
/// Not encrypted
|
||||
internal static var sendInfoNotEncrypted: String { return UntranslatedL10n.tr("Untranslated", "send_info_not_encrypted") }
|
||||
/// Clear all data currently stored on this device?
|
||||
/// Sign in again to access your account data and messages.
|
||||
internal static var softLogoutClearDataDialogContent: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_clear_data_dialog_content") }
|
||||
|
@ -14,10 +14,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
||||
@ -34,9 +32,9 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
/// The base padding applied to bubbles on either side.
|
||||
///
|
||||
/// **Note:** This is on top of the insets applied to the cells by the table view.
|
||||
let bubbleHorizontalPadding: CGFloat = 8
|
||||
private let bubbleHorizontalPadding: CGFloat = 8
|
||||
/// Additional padding applied to outgoing bubbles when the avatar is shown
|
||||
var bubbleAvatarPadding: CGFloat {
|
||||
private var bubbleAvatarPadding: CGFloat {
|
||||
guard !timelineItem.isOutgoing, !isEncryptedOneToOneRoom else { return 0 }
|
||||
return 8
|
||||
}
|
||||
@ -108,7 +106,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
private var messageBubbleWithReactions: some View {
|
||||
// Figma overlaps reactions by 3
|
||||
VStack(alignment: alignment, spacing: -3) {
|
||||
messageBubble
|
||||
messageBubbleWithActions
|
||||
.timelineItemAccessibility(timelineItem) {
|
||||
context.send(viewAction: .displayTimelineItemMenu(itemID: timelineItem.id))
|
||||
}
|
||||
@ -124,8 +122,8 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
}
|
||||
}
|
||||
|
||||
var messageBubble: some View {
|
||||
styledContent
|
||||
var messageBubbleWithActions: some View {
|
||||
messageBubble
|
||||
.onTapGesture {
|
||||
context.send(viewAction: .itemTapped(itemID: timelineItem.id))
|
||||
}
|
||||
@ -156,59 +154,14 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
}
|
||||
.padding(.top, messageBubbleTopPadding)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var styledContent: some View {
|
||||
contentWithTimestamp
|
||||
|
||||
var messageBubble: some View {
|
||||
contentWithReply
|
||||
.timelineItemSendInfo(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus)
|
||||
.bubbleStyle(insets: timelineItem.bubbleInsets,
|
||||
color: timelineItem.bubbleBackgroundColor,
|
||||
corners: roundedCorners)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var contentWithTimestamp: some View {
|
||||
timelineItem.bubbleSendInfoLayoutType
|
||||
.layout {
|
||||
contentWithReply
|
||||
layoutedLocalizedSendInfo
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var layoutedLocalizedSendInfo: some View {
|
||||
switch timelineItem.bubbleSendInfoLayoutType {
|
||||
case .overlay(capsuleStyle: true):
|
||||
localizedSendInfo
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 2)
|
||||
.background(Color.compound.bgSubtleSecondary)
|
||||
.cornerRadius(10)
|
||||
.padding(.trailing, 4)
|
||||
.padding(.bottom, 4)
|
||||
case .horizontal, .overlay(capsuleStyle: false):
|
||||
localizedSendInfo
|
||||
.padding(.bottom, -4)
|
||||
case .vertical:
|
||||
GridRow {
|
||||
localizedSendInfo
|
||||
.gridColumnAlignment(.trailing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var localizedSendInfo: some View {
|
||||
HStack(spacing: 4) {
|
||||
Text(timelineItem.localizedSendInfo)
|
||||
|
||||
if adjustedDeliveryStatus == .sendingFailed {
|
||||
CompoundIcon(\.error, size: .xSmall, relativeTo: .compound.bodyXS)
|
||||
.accessibilityLabel(L10n.commonSendingFailed)
|
||||
}
|
||||
}
|
||||
.font(.compound.bodyXS)
|
||||
.foregroundColor(adjustedDeliveryStatus == .sendingFailed ? .compound.textCriticalPrimary : .compound.textSecondary)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var contentWithReply: some View {
|
||||
@ -293,28 +246,6 @@ private extension View {
|
||||
}
|
||||
}
|
||||
|
||||
// Describes how the content and the send info should be arranged inside a bubble
|
||||
private enum BubbleSendInfoLayoutType {
|
||||
case horizontal(spacing: CGFloat = 4)
|
||||
case vertical(spacing: CGFloat = 4)
|
||||
case overlay(capsuleStyle: Bool)
|
||||
|
||||
var layout: AnyLayout {
|
||||
let layout: any Layout
|
||||
|
||||
switch self {
|
||||
case .horizontal(let spacing):
|
||||
layout = HStackLayout(alignment: .bottom, spacing: spacing)
|
||||
case .vertical(let spacing):
|
||||
layout = GridLayout(alignment: .leading, verticalSpacing: spacing)
|
||||
case .overlay:
|
||||
layout = ZStackLayout(alignment: .bottomTrailing)
|
||||
}
|
||||
|
||||
return AnyLayout(layout)
|
||||
}
|
||||
}
|
||||
|
||||
private extension EventBasedTimelineItemProtocol {
|
||||
var bubbleBackgroundColor: Color? {
|
||||
let defaultColor: Color = isOutgoing ? .compound._bgBubbleOutgoing : .compound._bgBubbleIncoming
|
||||
@ -335,8 +266,8 @@ private extension EventBasedTimelineItemProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
// The insets for the full bubble content.
|
||||
// Padding affecting just the "send info" should be added inside `layoutedLocalizedSendInfo`
|
||||
/// The insets for the full bubble content.
|
||||
/// Padding affecting just the "send info" should be added inside `TimelineItemSendInfoView`
|
||||
var bubbleInsets: EdgeInsets {
|
||||
let defaultInsets: EdgeInsets = .init(around: 8)
|
||||
|
||||
@ -363,25 +294,6 @@ private extension EventBasedTimelineItemProtocol {
|
||||
return defaultInsets
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
case is PollRoomTimelineItem:
|
||||
return .vertical(spacing: 16)
|
||||
default:
|
||||
return defaultTimestampLayout
|
||||
}
|
||||
}
|
||||
|
||||
var contentCornerRadius: CGFloat {
|
||||
guard let message = self as? EventBasedMessageTimelineItemProtocol else { return .zero }
|
||||
@ -395,6 +307,16 @@ private extension EventBasedTimelineItemProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private extension EdgeInsets {
|
||||
init(around: CGFloat) {
|
||||
self.init(top: around, leading: around, bottom: around, trailing: around)
|
||||
}
|
||||
|
||||
static var zero: Self = .init(around: 0)
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = RoomScreenViewModel.mock
|
||||
|
||||
@ -556,11 +478,3 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
||||
.environmentObject(viewModel.context)
|
||||
}
|
||||
}
|
||||
|
||||
private extension EdgeInsets {
|
||||
init(around: CGFloat) {
|
||||
self.init(top: around, leading: around, bottom: around, trailing: around)
|
||||
}
|
||||
|
||||
static var zero: Self = .init(around: 0)
|
||||
}
|
||||
|
@ -0,0 +1,185 @@
|
||||
//
|
||||
// Copyright 2024 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 Compound
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
/// Adds the send info (timestamp along indicators for edits and delivery/encryption issues) for the given timeline item to this view.
|
||||
func timelineItemSendInfo(timelineItem: EventBasedTimelineItemProtocol,
|
||||
adjustedDeliveryStatus: TimelineItemDeliveryStatus?) -> some View {
|
||||
modifier(TimelineItemSendInfoModifier(sendInfo: .init(timelineItem: timelineItem,
|
||||
adjustedDeliveryStatus: adjustedDeliveryStatus)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the send info to a view with the correct layout.
|
||||
private struct TimelineItemSendInfoModifier: ViewModifier {
|
||||
let sendInfo: TimelineItemSendInfo
|
||||
|
||||
var layout: AnyLayout {
|
||||
switch sendInfo.layoutType {
|
||||
case .horizontal(let spacing):
|
||||
AnyLayout(HStackLayout(alignment: .bottom, spacing: spacing))
|
||||
case .vertical(let spacing):
|
||||
AnyLayout(GridLayout(alignment: .leading, verticalSpacing: spacing))
|
||||
case .overlay:
|
||||
AnyLayout(ZStackLayout(alignment: .bottomTrailing))
|
||||
}
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
layout {
|
||||
content
|
||||
TimelineItemSendInfoLabel(sendInfo: sendInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The label shown for a timeline item with info about it's timestamp and various other indicators.
|
||||
private struct TimelineItemSendInfoLabel: View {
|
||||
let sendInfo: TimelineItemSendInfo
|
||||
|
||||
var statusIcon: KeyPath<CompoundIcons, Image>? {
|
||||
switch sendInfo.status {
|
||||
case .sendingFailed: \.error
|
||||
case .unverifiedSession, .authenticityUnknown: \.admin
|
||||
case .unencrypted: \.keyOff
|
||||
case .none: nil
|
||||
}
|
||||
}
|
||||
|
||||
var statusIconAccessibilityLabel: String? {
|
||||
switch sendInfo.status {
|
||||
case .sendingFailed: L10n.commonSendingFailed
|
||||
case .none: nil
|
||||
// Temporary testing strings.
|
||||
case .unverifiedSession: L10n.eventShieldReasonUnsignedDevice
|
||||
case .authenticityUnknown: L10n.eventShieldReasonAuthenticityNotGuaranteed
|
||||
case .unencrypted: UntranslatedL10n.sendInfoNotEncrypted
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
switch sendInfo.layoutType {
|
||||
case .overlay(capsuleStyle: true):
|
||||
content
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 2)
|
||||
.background(Color.compound.bgSubtleSecondary)
|
||||
.cornerRadius(10)
|
||||
.padding(.trailing, 4)
|
||||
.padding(.bottom, 4)
|
||||
case .horizontal, .overlay(capsuleStyle: false):
|
||||
content
|
||||
.padding(.bottom, -4)
|
||||
case .vertical:
|
||||
GridRow {
|
||||
content
|
||||
.gridColumnAlignment(.trailing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var content: some View {
|
||||
HStack(spacing: 4) {
|
||||
Text(sendInfo.localizedString)
|
||||
|
||||
if let statusIcon, let statusIconAccessibilityLabel {
|
||||
CompoundIcon(statusIcon, size: .xSmall, relativeTo: .compound.bodyXS)
|
||||
.accessibilityLabel(statusIconAccessibilityLabel)
|
||||
}
|
||||
}
|
||||
.font(.compound.bodyXS)
|
||||
.foregroundStyle(sendInfo.foregroundStyle)
|
||||
}
|
||||
}
|
||||
|
||||
/// All the data needed to render a timeline item's send info label.
|
||||
private struct TimelineItemSendInfo {
|
||||
enum Status { case sendingFailed, unverifiedSession, authenticityUnknown, unencrypted }
|
||||
|
||||
/// Describes how the content and the send info should be arranged inside a bubble
|
||||
enum LayoutType {
|
||||
case horizontal(spacing: CGFloat = 4)
|
||||
case vertical(spacing: CGFloat = 4)
|
||||
case overlay(capsuleStyle: Bool)
|
||||
}
|
||||
|
||||
let localizedString: String
|
||||
var status: Status?
|
||||
let layoutType: LayoutType
|
||||
|
||||
var foregroundStyle: Color {
|
||||
switch status {
|
||||
case .sendingFailed, .unverifiedSession:
|
||||
.compound.textCriticalPrimary
|
||||
case .authenticityUnknown, .unencrypted, .none:
|
||||
.compound.textSecondary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension TimelineItemSendInfo {
|
||||
init(timelineItem: EventBasedTimelineItemProtocol, adjustedDeliveryStatus: TimelineItemDeliveryStatus?) {
|
||||
localizedString = timelineItem.localizedSendInfo
|
||||
|
||||
status = if adjustedDeliveryStatus == .sendingFailed {
|
||||
.sendingFailed
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
|
||||
layoutType = switch timelineItem {
|
||||
case is TextBasedRoomTimelineItem:
|
||||
.overlay(capsuleStyle: false)
|
||||
case is ImageRoomTimelineItem,
|
||||
is VideoRoomTimelineItem,
|
||||
is StickerRoomTimelineItem:
|
||||
.overlay(capsuleStyle: true)
|
||||
case let locationTimelineItem as LocationRoomTimelineItem:
|
||||
.overlay(capsuleStyle: locationTimelineItem.content.geoURI != nil)
|
||||
case is PollRoomTimelineItem:
|
||||
.vertical(spacing: 16)
|
||||
default:
|
||||
.horizontal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct TimelineItemSendInfoLabel_Previews: PreviewProvider, TestablePreview {
|
||||
static var previews: some View {
|
||||
VStack(spacing: 16) {
|
||||
TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
||||
layoutType: .horizontal()))
|
||||
TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
||||
status: .sendingFailed,
|
||||
layoutType: .horizontal()))
|
||||
TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
||||
status: .unverifiedSession,
|
||||
layoutType: .horizontal()))
|
||||
TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
||||
status: .authenticityUnknown,
|
||||
layoutType: .horizontal()))
|
||||
TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
||||
status: .unencrypted,
|
||||
layoutType: .horizontal()))
|
||||
}
|
||||
}
|
||||
}
|
@ -42,17 +42,17 @@ extension EventBasedTimelineItemProtocol {
|
||||
var isRemoteMessage: Bool {
|
||||
id.eventID != nil
|
||||
}
|
||||
|
||||
var hasFailedToSend: Bool {
|
||||
properties.deliveryStatus == .sendingFailed
|
||||
|
||||
var isRedacted: Bool {
|
||||
self is RedactedRoomTimelineItem
|
||||
}
|
||||
|
||||
|
||||
var pollIfAvailable: Poll? {
|
||||
(self as? PollRoomTimelineItem)?.poll
|
||||
}
|
||||
|
||||
var isRedacted: Bool {
|
||||
self is RedactedRoomTimelineItem
|
||||
var hasFailedToSend: Bool {
|
||||
properties.deliveryStatus == .sendingFailed
|
||||
}
|
||||
|
||||
var hasFailedDecryption: Bool {
|
||||
|
BIN
PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPad-en-GB.1.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPad-en-GB.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPad-pseudo.1.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPad-pseudo.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPhone-15-en-GB.1.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPhone-15-en-GB.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPhone-15-pseudo.1.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPhone-15-pseudo.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user