mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-07 04:48:34 +00:00
Add support for copying a caption. (#3563)
* Add timeline item action for copying a media caption. * Fix the action colours when long pressing a timeline item. Nice side effect: Reasonable sized buttons when button shapes are enabled. * Re-arrange the Timeline Item Menu. * Add timeline item menu snapshots for media items.
This commit is contained in:
@@ -36,8 +36,10 @@
|
||||
"action_confirm_password" = "Confirm password";
|
||||
"action_continue" = "Continue";
|
||||
"action_copy" = "Copy";
|
||||
"action_copy_caption" = "Copy caption";
|
||||
"action_copy_link" = "Copy link";
|
||||
"action_copy_link_to_message" = "Copy link to message";
|
||||
"action_copy_text" = "Copy text";
|
||||
"action_create" = "Create";
|
||||
"action_create_a_room" = "Create a room";
|
||||
"action_deactivate" = "Deactivate";
|
||||
@@ -84,6 +86,7 @@
|
||||
"action_reject" = "Reject";
|
||||
"action_remove" = "Remove";
|
||||
"action_remove_caption" = "Remove caption";
|
||||
"action_remove_message" = "Remove message";
|
||||
"action_reply" = "Reply";
|
||||
"action_reply_in_thread" = "Reply in thread";
|
||||
"action_report_bug" = "Report bug";
|
||||
|
||||
@@ -104,10 +104,14 @@ internal enum L10n {
|
||||
internal static var actionContinue: String { return L10n.tr("Localizable", "action_continue") }
|
||||
/// Copy
|
||||
internal static var actionCopy: String { return L10n.tr("Localizable", "action_copy") }
|
||||
/// Copy caption
|
||||
internal static var actionCopyCaption: String { return L10n.tr("Localizable", "action_copy_caption") }
|
||||
/// Copy link
|
||||
internal static var actionCopyLink: String { return L10n.tr("Localizable", "action_copy_link") }
|
||||
/// Copy link to message
|
||||
internal static var actionCopyLinkToMessage: String { return L10n.tr("Localizable", "action_copy_link_to_message") }
|
||||
/// Copy text
|
||||
internal static var actionCopyText: String { return L10n.tr("Localizable", "action_copy_text") }
|
||||
/// Create
|
||||
internal static var actionCreate: String { return L10n.tr("Localizable", "action_create") }
|
||||
/// Create a room
|
||||
@@ -204,6 +208,8 @@ internal enum L10n {
|
||||
internal static var actionRemove: String { return L10n.tr("Localizable", "action_remove") }
|
||||
/// Remove caption
|
||||
internal static var actionRemoveCaption: String { return L10n.tr("Localizable", "action_remove_caption") }
|
||||
/// Remove message
|
||||
internal static var actionRemoveMessage: String { return L10n.tr("Localizable", "action_remove_message") }
|
||||
/// Reply
|
||||
internal static var actionReply: String { return L10n.tr("Localizable", "action_reply") }
|
||||
/// Reply in thread
|
||||
|
||||
@@ -7,14 +7,34 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension LabelStyle where Self == MenuSheetLabelStyle {
|
||||
/// A label style for labels that are within a menu that is being presented as a sheet.
|
||||
static var menuSheet: Self { MenuSheetLabelStyle() }
|
||||
extension ButtonStyle where Self == MenuSheetButtonStyle {
|
||||
/// A button style for buttons that are within a menu that is being presented as a sheet.
|
||||
static var menuSheet: Self { MenuSheetButtonStyle() }
|
||||
}
|
||||
|
||||
/// The style used for labels that are part of a menu that's presented as
|
||||
/// a sheet as `TimelineItemMenu` and `RoomAttachmentPicker`.
|
||||
struct MenuSheetLabelStyle: LabelStyle {
|
||||
/// The style used for buttons that are part of a menu that's presented as
|
||||
/// a sheet such as `TimelineItemMenu`.
|
||||
struct MenuSheetButtonStyle: ButtonStyle {
|
||||
@Environment(\.accessibilityShowButtonShapes) private var accessibilityShowButtonShapes
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.labelStyle(MenuSheetLabelStyle())
|
||||
.foregroundStyle(configuration.role == .destructive ? .compound.textCriticalPrimary : .compound.textActionPrimary)
|
||||
.contentShape(.rect)
|
||||
.opacity(configuration.isPressed ? 0.3 : 1)
|
||||
.background {
|
||||
if accessibilityShowButtonShapes {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color(uiColor: .secondarySystemFill))
|
||||
.opacity(configuration.isPressed ? 0.8 : 1)
|
||||
.padding(4)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct MenuSheetLabelStyle: LabelStyle {
|
||||
var spacing: CGFloat = 16
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
|
||||
@@ -32,7 +32,6 @@ struct RoomAttachmentPicker: View {
|
||||
context.send(viewAction: .enableTextFormatting)
|
||||
} label: {
|
||||
Label(L10n.screenRoomAttachmentTextFormatting, icon: \.textFormatting)
|
||||
.labelStyle(.menuSheet)
|
||||
}
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.attachmentPickerTextFormatting)
|
||||
|
||||
@@ -40,7 +39,6 @@ struct RoomAttachmentPicker: View {
|
||||
context.send(viewAction: .attach(.poll))
|
||||
} label: {
|
||||
Label(L10n.screenRoomAttachmentSourcePoll, icon: \.polls)
|
||||
.labelStyle(.menuSheet)
|
||||
}
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.attachmentPickerPoll)
|
||||
|
||||
@@ -48,7 +46,6 @@ struct RoomAttachmentPicker: View {
|
||||
context.send(viewAction: .attach(.location))
|
||||
} label: {
|
||||
Label(L10n.screenRoomAttachmentSourceLocation, icon: \.locationPin)
|
||||
.labelStyle(.menuSheet)
|
||||
}
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.attachmentPickerLocation)
|
||||
|
||||
@@ -56,7 +53,6 @@ struct RoomAttachmentPicker: View {
|
||||
context.send(viewAction: .attach(.file))
|
||||
} label: {
|
||||
Label(L10n.screenRoomAttachmentSourceFiles, icon: \.attachment)
|
||||
.labelStyle(.menuSheet)
|
||||
}
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.attachmentPickerDocuments)
|
||||
|
||||
@@ -64,7 +60,6 @@ struct RoomAttachmentPicker: View {
|
||||
context.send(viewAction: .attach(.photoLibrary))
|
||||
} label: {
|
||||
Label(L10n.screenRoomAttachmentSourceGallery, icon: \.image)
|
||||
.labelStyle(.menuSheet)
|
||||
}
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.attachmentPickerPhotoLibrary)
|
||||
|
||||
@@ -72,7 +67,6 @@ struct RoomAttachmentPicker: View {
|
||||
context.send(viewAction: .attach(.camera))
|
||||
} label: {
|
||||
Label(L10n.screenRoomAttachmentSourceCamera, icon: \.takePhoto)
|
||||
.labelStyle(.menuSheet)
|
||||
}
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.attachmentPickerCamera)
|
||||
}
|
||||
|
||||
@@ -102,7 +102,13 @@ class TimelineInteractionHandler {
|
||||
case .copy:
|
||||
guard let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol else { return }
|
||||
UIPasteboard.general.string = messageTimelineItem.body
|
||||
case .edit:
|
||||
case .copyCaption:
|
||||
guard let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol,
|
||||
let caption = messageTimelineItem.mediaCaption else {
|
||||
return
|
||||
}
|
||||
UIPasteboard.general.string = caption
|
||||
case .edit, .addCaption, .editCaption, .editPoll:
|
||||
switch timelineItem {
|
||||
case let messageTimelineItem as EventBasedMessageTimelineItemProtocol:
|
||||
processEditMessageEvent(messageTimelineItem)
|
||||
@@ -115,13 +121,6 @@ class TimelineInteractionHandler {
|
||||
default:
|
||||
MXLog.error("Cannot edit item with id: \(timelineItem.id)")
|
||||
}
|
||||
case .addCaption, .editCaption:
|
||||
switch timelineItem {
|
||||
case let messageTimelineItem as EventBasedMessageTimelineItemProtocol:
|
||||
processEditMessageEvent(messageTimelineItem)
|
||||
default:
|
||||
MXLog.error("Cannot add/edit caption on item with id: \(timelineItem.id)")
|
||||
}
|
||||
case .removeCaption:
|
||||
guard case let .event(_, eventOrTransactionID) = timelineItem.id else {
|
||||
MXLog.error("Failed removing caption, missing event ID")
|
||||
|
||||
@@ -51,7 +51,7 @@ struct TimelineItemMacContextMenu: View {
|
||||
}
|
||||
|
||||
Section {
|
||||
ForEach(menuActions.debugActions) { action in
|
||||
ForEach(menuActions.secondaryActions) { action in
|
||||
Button(role: action.isDestructive ? .destructive : nil) {
|
||||
send(action)
|
||||
} label: {
|
||||
|
||||
@@ -47,7 +47,7 @@ struct TimelineItemMenu: View {
|
||||
.background(Color.compound.bgSubtlePrimary)
|
||||
}
|
||||
|
||||
viewsForActions(actions.debugActions)
|
||||
viewsForActions(actions.secondaryActions)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,8 +179,8 @@ struct TimelineItemMenu: View {
|
||||
send(action)
|
||||
} label: {
|
||||
action.label
|
||||
.labelStyle(.menuSheet)
|
||||
}
|
||||
.buttonStyle(.menuSheet)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,22 +261,29 @@ private extension View {
|
||||
// MARK: - Previews
|
||||
|
||||
struct TimelineItemMenu_Previews: PreviewProvider, TestablePreview {
|
||||
enum ItemType { case incomingText, outgoingMedia, outgoingMediaWithCaption }
|
||||
|
||||
static let viewModel = TimelineViewModel.mock
|
||||
static let (item, actions) = makeItem()
|
||||
static let (backupItem, _) = makeItem(authenticity: .notGuaranteed(color: .gray))
|
||||
static let (unsignedItem, _) = makeItem(authenticity: .unsignedDevice(color: .red))
|
||||
static let (unencryptedItem, _) = makeItem(authenticity: .sentInClear(color: .red))
|
||||
static let (unknownFailureItem, _) = makeItem(deliveryStatus: .sendingFailed(.unknown))
|
||||
static let (identityChangedItem, _) = makeItem(deliveryStatus: .sendingFailed(.verifiedUser(.changedIdentity(users: [
|
||||
static let (item, actions) = makeActions()
|
||||
static let (backupItem, _) = makeActions(authenticity: .notGuaranteed(color: .gray))
|
||||
static let (unsignedItem, _) = makeActions(authenticity: .unsignedDevice(color: .red))
|
||||
static let (unencryptedItem, _) = makeActions(authenticity: .sentInClear(color: .red))
|
||||
static let (unknownFailureItem, _) = makeActions(deliveryStatus: .sendingFailed(.unknown))
|
||||
static let (identityChangedItem, _) = makeActions(deliveryStatus: .sendingFailed(.verifiedUser(.changedIdentity(users: [
|
||||
"@alice:matrix.org"
|
||||
]))))
|
||||
static let (unsignedDevicesItem, _) = makeItem(deliveryStatus: .sendingFailed(.verifiedUser(.hasUnsignedDevice(devices: [
|
||||
static let (unsignedDevicesItem, _) = makeActions(deliveryStatus: .sendingFailed(.verifiedUser(.hasUnsignedDevice(devices: [
|
||||
"@alice:matrix.org": ["DEVICE1", "DEVICE2"]
|
||||
]))))
|
||||
static let (ownUnsignedDevicesItem, _) = makeItem(deliveryStatus: .sendingFailed(.verifiedUser(.hasUnsignedDevice(devices: [
|
||||
static let (ownUnsignedDevicesItem, _) = makeActions(deliveryStatus: .sendingFailed(.verifiedUser(.hasUnsignedDevice(devices: [
|
||||
RoomMemberProxyMock.mockMe.userID: ["DEVICE1"]
|
||||
]))))
|
||||
|
||||
// Media
|
||||
|
||||
static let (mediaItem, mediaItemActions) = makeActions(itemType: .outgoingMedia)
|
||||
static let (mediaItemWithCaption, mediaItemWithCaptionActions) = makeActions(itemType: .outgoingMediaWithCaption)
|
||||
|
||||
static var previews: some View {
|
||||
TimelineItemMenu(item: item, actions: actions)
|
||||
.environmentObject(viewModel.context)
|
||||
@@ -314,26 +321,56 @@ struct TimelineItemMenu_Previews: PreviewProvider, TestablePreview {
|
||||
TimelineItemMenu(item: identityChangedItem, actions: actions)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Identity Changed")
|
||||
|
||||
// Media
|
||||
|
||||
TimelineItemMenu(item: mediaItem, actions: mediaItemActions)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Media")
|
||||
|
||||
TimelineItemMenu(item: mediaItemWithCaption, actions: mediaItemWithCaptionActions)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Media with Caption")
|
||||
}
|
||||
|
||||
static func makeItem(authenticity: EncryptionAuthenticity? = nil,
|
||||
deliveryStatus: TimelineItemDeliveryStatus? = nil) -> (TextRoomTimelineItem, TimelineItemMenuActions)! {
|
||||
guard var item = RoomTimelineItemFixtures.singleMessageChunk.first as? TextRoomTimelineItem,
|
||||
let actions = TimelineItemMenuActions(isReactable: true,
|
||||
actions: [.copy, .edit, .reply(isThread: false), .pin, .redact],
|
||||
debugActions: [.viewSource],
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings)) else {
|
||||
return nil
|
||||
}
|
||||
static func makeActions(itemType: ItemType = .incomingText,
|
||||
authenticity: EncryptionAuthenticity? = nil,
|
||||
deliveryStatus: TimelineItemDeliveryStatus? = nil) -> (EventBasedTimelineItemProtocol, TimelineItemMenuActions)! {
|
||||
guard var item = makeItem(itemType: itemType) else { return nil }
|
||||
let provider = TimelineItemMenuActionProvider(timelineItem: item,
|
||||
canCurrentUserRedactSelf: true,
|
||||
canCurrentUserRedactOthers: false,
|
||||
canCurrentUserPin: true,
|
||||
pinnedEventIDs: [],
|
||||
isDM: true,
|
||||
isViewSourceEnabled: true,
|
||||
isCreateMediaCaptionsEnabled: true,
|
||||
isPinnedEventsTimeline: false,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
|
||||
guard let actions = provider.makeActions() else { return nil }
|
||||
|
||||
if let authenticity {
|
||||
item.properties.encryptionAuthenticity = authenticity
|
||||
}
|
||||
if var textItem = item as? TextRoomTimelineItem {
|
||||
if let authenticity {
|
||||
textItem.properties.encryptionAuthenticity = authenticity
|
||||
}
|
||||
|
||||
if let deliveryStatus {
|
||||
item.properties.deliveryStatus = deliveryStatus
|
||||
if let deliveryStatus {
|
||||
textItem.properties.deliveryStatus = deliveryStatus
|
||||
}
|
||||
item = textItem
|
||||
}
|
||||
|
||||
return (item, actions)
|
||||
}
|
||||
|
||||
static func makeItem(itemType: ItemType) -> EventBasedTimelineItemProtocol? {
|
||||
switch itemType {
|
||||
case .incomingText:
|
||||
RoomTimelineItemFixtures.singleMessageChunk.first as? EventBasedTimelineItemProtocol
|
||||
case .outgoingMedia:
|
||||
RoomTimelineItemFixtures.mediaChunk[1] as? EventBasedTimelineItemProtocol
|
||||
case .outgoingMediaWithCaption:
|
||||
RoomTimelineItemFixtures.mediaChunk[5] as? EventBasedTimelineItemProtocol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,18 @@ import SwiftUI
|
||||
struct TimelineItemMenuActions {
|
||||
let reactions: [TimelineItemMenuReaction]
|
||||
let actions: [TimelineItemMenuAction]
|
||||
let debugActions: [TimelineItemMenuAction]
|
||||
let secondaryActions: [TimelineItemMenuAction]
|
||||
|
||||
init?(isReactable: Bool,
|
||||
actions: [TimelineItemMenuAction],
|
||||
debugActions: [TimelineItemMenuAction],
|
||||
secondaryActions: [TimelineItemMenuAction],
|
||||
emojiProvider: EmojiProviderProtocol) {
|
||||
if !isReactable, actions.isEmpty, debugActions.isEmpty {
|
||||
if !isReactable, actions.isEmpty, secondaryActions.isEmpty {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.actions = actions
|
||||
self.debugActions = debugActions
|
||||
self.secondaryActions = secondaryActions
|
||||
|
||||
var frequentlyUsed: OrderedSet<TimelineItemMenuReaction> = [
|
||||
.init(key: "👍️", symbol: .handThumbsup),
|
||||
@@ -56,10 +56,12 @@ struct TimelineItemMenuReaction: Hashable {
|
||||
|
||||
enum TimelineItemMenuAction: Identifiable, Hashable {
|
||||
case copy
|
||||
case copyCaption
|
||||
case edit
|
||||
case addCaption
|
||||
case editCaption
|
||||
case removeCaption
|
||||
case editPoll
|
||||
case copyPermalink
|
||||
case redact
|
||||
case reply(isThread: Bool)
|
||||
@@ -79,7 +81,7 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
|
||||
/// Whether the item should cancel a reply/edit occurring in the composer.
|
||||
var switchToDefaultComposer: Bool {
|
||||
switch self {
|
||||
case .reply, .edit, .addCaption, .editCaption:
|
||||
case .reply, .edit, .addCaption, .editCaption, .editPoll:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
@@ -89,7 +91,7 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
|
||||
/// Whether the action should be shown for an item that failed to send.
|
||||
var canAppearInFailedEcho: Bool {
|
||||
switch self {
|
||||
case .copy, .edit, .redact, .viewSource:
|
||||
case .copy, .edit, .redact, .viewSource, .editPoll:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -130,7 +132,9 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
|
||||
var label: some View {
|
||||
switch self {
|
||||
case .copy:
|
||||
Label(L10n.actionCopy, icon: \.copy)
|
||||
Label(L10n.actionCopyText, icon: \.copy)
|
||||
case .copyCaption:
|
||||
Label(L10n.actionCopyCaption, icon: \.copy)
|
||||
case .edit:
|
||||
Label(L10n.actionEdit, icon: \.edit)
|
||||
case .addCaption:
|
||||
@@ -138,7 +142,9 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
|
||||
case .editCaption:
|
||||
Label(L10n.actionEditCaption, icon: \.edit)
|
||||
case .removeCaption:
|
||||
Label(L10n.actionRemoveCaption, icon: \.delete)
|
||||
Label(L10n.actionRemoveCaption, icon: \.close)
|
||||
case .editPoll:
|
||||
Label(L10n.actionEditPoll, icon: \.edit)
|
||||
case .copyPermalink:
|
||||
Label(L10n.actionCopyLinkToMessage, icon: \.link)
|
||||
case .reply(let isThread):
|
||||
@@ -146,7 +152,7 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
|
||||
case .forward:
|
||||
Label(L10n.actionForward, icon: \.forward)
|
||||
case .redact:
|
||||
Label(L10n.actionRemove, icon: \.delete)
|
||||
Label(L10n.actionRemoveMessage, icon: \.delete)
|
||||
case .viewSource:
|
||||
Label(L10n.actionViewSource, icon: \.code)
|
||||
case .retryDecryption:
|
||||
|
||||
+64
-42
@@ -32,26 +32,12 @@ struct TimelineItemMenuActionProvider {
|
||||
return nil
|
||||
}
|
||||
|
||||
var debugActions: [TimelineItemMenuAction] = []
|
||||
if isViewSourceEnabled {
|
||||
debugActions.append(.viewSource)
|
||||
}
|
||||
|
||||
if let encryptedItem = timelineItem as? EncryptedRoomTimelineItem {
|
||||
switch encryptedItem.encryptionType {
|
||||
case .megolmV1AesSha2(let sessionID, _):
|
||||
debugActions.append(.retryDecryption(sessionID: sessionID))
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return .init(isReactable: false,
|
||||
actions: [.copyPermalink],
|
||||
debugActions: debugActions,
|
||||
emojiProvider: emojiProvider)
|
||||
return makeEncryptedItemActions(encryptedItem)
|
||||
}
|
||||
|
||||
var actions: [TimelineItemMenuAction] = []
|
||||
var secondaryActions: [TimelineItemMenuAction] = []
|
||||
|
||||
if item.canBeRepliedTo {
|
||||
if let messageItem = item as? EventBasedMessageTimelineItemProtocol {
|
||||
@@ -65,58 +51,94 @@ struct TimelineItemMenuActionProvider {
|
||||
actions.append(.forward(itemID: item.id))
|
||||
}
|
||||
|
||||
if item.isEditable {
|
||||
if let messageItem = item as? EventBasedMessageTimelineItemProtocol, messageItem.supportsMediaCaption {
|
||||
if messageItem.hasMediaCaption {
|
||||
actions.append(contentsOf: [.editCaption, .removeCaption])
|
||||
} else if isCreateMediaCaptionsEnabled {
|
||||
actions.append(.addCaption)
|
||||
}
|
||||
} else if !(item is VoiceMessageRoomTimelineItem) {
|
||||
actions.append(.edit)
|
||||
}
|
||||
}
|
||||
|
||||
if canCurrentUserPin, let eventID = item.id.eventID {
|
||||
actions.append(pinnedEventIDs.contains(eventID) ? .unpin : .pin)
|
||||
}
|
||||
|
||||
if item.isCopyable {
|
||||
actions.append(.copy)
|
||||
}
|
||||
|
||||
if item.isRemoteMessage {
|
||||
actions.append(.copyPermalink)
|
||||
}
|
||||
|
||||
if item.isEditable {
|
||||
if item.supportsMediaCaption {
|
||||
if item.hasMediaCaption {
|
||||
actions.append(.editCaption)
|
||||
} else if isCreateMediaCaptionsEnabled {
|
||||
actions.append(.addCaption)
|
||||
}
|
||||
} else if item is PollRoomTimelineItem {
|
||||
actions.append(.editPoll)
|
||||
} else if !(item is VoiceMessageRoomTimelineItem) {
|
||||
actions.append(.edit)
|
||||
}
|
||||
}
|
||||
|
||||
if item.isCopyable {
|
||||
actions.append(.copy)
|
||||
} else if item.hasMediaCaption {
|
||||
actions.append(.copyCaption)
|
||||
}
|
||||
|
||||
if item.hasMediaCaption {
|
||||
actions.append(.removeCaption)
|
||||
}
|
||||
|
||||
if canRedactItem(item), let poll = item.pollIfAvailable, !poll.hasEnded, let eventID = item.id.eventID {
|
||||
actions.append(.endPoll(pollStartID: eventID))
|
||||
}
|
||||
|
||||
if canRedactItem(item) {
|
||||
actions.append(.redact)
|
||||
if isViewSourceEnabled {
|
||||
actions.append(.viewSource)
|
||||
}
|
||||
|
||||
if !item.isOutgoing {
|
||||
actions.append(.report)
|
||||
secondaryActions.append(.report)
|
||||
}
|
||||
|
||||
if item.hasFailedToSend {
|
||||
actions = actions.filter(\.canAppearInFailedEcho)
|
||||
}
|
||||
|
||||
if item.isRedacted {
|
||||
actions = actions.filter(\.canAppearInRedacted)
|
||||
if canRedactItem(item) {
|
||||
secondaryActions.append(.redact)
|
||||
}
|
||||
|
||||
if isPinnedEventsTimeline {
|
||||
actions.insert(.viewInRoomTimeline, at: 0)
|
||||
actions = actions.filter(\.canAppearInPinnedEventsTimeline)
|
||||
secondaryActions = secondaryActions.filter(\.canAppearInPinnedEventsTimeline)
|
||||
}
|
||||
|
||||
if item.hasFailedToSend {
|
||||
actions = actions.filter(\.canAppearInFailedEcho)
|
||||
secondaryActions = secondaryActions.filter(\.canAppearInFailedEcho)
|
||||
}
|
||||
|
||||
if item.isRedacted {
|
||||
actions = actions.filter(\.canAppearInRedacted)
|
||||
secondaryActions = secondaryActions.filter(\.canAppearInRedacted)
|
||||
}
|
||||
|
||||
return .init(isReactable: isPinnedEventsTimeline ? false : item.isReactable,
|
||||
actions: actions,
|
||||
debugActions: debugActions,
|
||||
secondaryActions: secondaryActions,
|
||||
emojiProvider: emojiProvider)
|
||||
}
|
||||
|
||||
private func makeEncryptedItemActions(_ encryptedItem: EncryptedRoomTimelineItem) -> TimelineItemMenuActions? {
|
||||
var actions: [TimelineItemMenuAction] = [.copyPermalink]
|
||||
var secondaryActions: [TimelineItemMenuAction] = []
|
||||
|
||||
if isViewSourceEnabled {
|
||||
actions.append(.viewSource)
|
||||
}
|
||||
|
||||
switch encryptedItem.encryptionType {
|
||||
case .megolmV1AesSha2(let sessionID, _):
|
||||
secondaryActions.append(.retryDecryption(sessionID: sessionID))
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return .init(isReactable: false,
|
||||
actions: actions,
|
||||
secondaryActions: secondaryActions,
|
||||
emojiProvider: emojiProvider)
|
||||
}
|
||||
|
||||
|
||||
@@ -250,28 +250,16 @@ enum RoomTimelineItemFixtures {
|
||||
|
||||
static var mediaChunk: [RoomTimelineItemProtocol] {
|
||||
[
|
||||
VideoRoomTimelineItem(id: .randomEvent,
|
||||
timestamp: "10:47 am",
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: ""),
|
||||
content: .init(filename: "video.mp4",
|
||||
videoInfo: .mockVideo,
|
||||
thumbnailInfo: .mockThumbnail,
|
||||
blurhash: "KtI~70X5V?yss9oyrYs:t6")),
|
||||
ImageRoomTimelineItem(id: .randomEvent,
|
||||
timestamp: "10:47 am",
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: ""),
|
||||
content: .init(filename: "image.jpg",
|
||||
imageInfo: .mockImage,
|
||||
thumbnailInfo: nil,
|
||||
blurhash: "KpE4oyayR5|GbHb];3j@of"))
|
||||
AudioRoomTimelineItem(isOutgoing: false, caption: "Listen to this!"),
|
||||
AudioRoomTimelineItem(isOutgoing: true),
|
||||
FileRoomTimelineItem(isOutgoing: false),
|
||||
FileRoomTimelineItem(isOutgoing: true, caption: "Please check this ASAP!"),
|
||||
ImageRoomTimelineItem(isOutgoing: false),
|
||||
ImageRoomTimelineItem(isOutgoing: true, caption: "Isn't this pretty!"),
|
||||
VideoRoomTimelineItem(isOutgoing: false, caption: "Woah, it was incredible!"),
|
||||
VideoRoomTimelineItem(isOutgoing: true),
|
||||
VoiceMessageRoomTimelineItem(isOutgoing: false),
|
||||
VoiceMessageRoomTimelineItem(isOutgoing: true)
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -294,3 +282,92 @@ private extension TextRoomTimelineItem {
|
||||
return newSelf
|
||||
}
|
||||
}
|
||||
|
||||
private extension AudioRoomTimelineItem {
|
||||
init(isOutgoing: Bool, caption: String? = nil) {
|
||||
self.init(id: .randomEvent,
|
||||
timestamp: "10:47 am",
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: isOutgoing,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
|
||||
content: .init(filename: "audio.mp3",
|
||||
caption: caption,
|
||||
duration: 60,
|
||||
waveform: nil,
|
||||
source: .init(url: .mockMXCAudio, mimeType: nil),
|
||||
fileSize: nil,
|
||||
contentType: .mp3))
|
||||
}
|
||||
}
|
||||
|
||||
private extension FileRoomTimelineItem {
|
||||
init(isOutgoing: Bool, caption: String? = nil) {
|
||||
self.init(id: .randomEvent,
|
||||
timestamp: "10:47 am",
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: isOutgoing,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
|
||||
content: .init(filename: "file.pdf",
|
||||
caption: caption,
|
||||
source: .init(url: .mockMXCFile, mimeType: nil),
|
||||
fileSize: nil,
|
||||
thumbnailSource: nil,
|
||||
contentType: .pdf))
|
||||
}
|
||||
}
|
||||
|
||||
private extension ImageRoomTimelineItem {
|
||||
init(isOutgoing: Bool, caption: String? = nil) {
|
||||
self.init(id: .randomEvent,
|
||||
timestamp: "10:47 am",
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: isOutgoing,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
|
||||
content: .init(filename: "image.jpg",
|
||||
caption: caption,
|
||||
imageInfo: .mockImage,
|
||||
thumbnailInfo: nil,
|
||||
blurhash: "KpE4oyayR5|GbHb];3j@of"))
|
||||
}
|
||||
}
|
||||
|
||||
private extension VideoRoomTimelineItem {
|
||||
init(isOutgoing: Bool, caption: String? = nil) {
|
||||
self.init(id: .randomEvent,
|
||||
timestamp: "10:47 am",
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: isOutgoing,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
|
||||
content: .init(filename: "video.mp4",
|
||||
caption: caption,
|
||||
videoInfo: .mockVideo,
|
||||
thumbnailInfo: .mockThumbnail,
|
||||
blurhash: "KtI~70X5V?yss9oyrYs:t6"))
|
||||
}
|
||||
}
|
||||
|
||||
private extension VoiceMessageRoomTimelineItem {
|
||||
init(isOutgoing: Bool) {
|
||||
self.init(id: .randomEvent,
|
||||
timestamp: "10:47 am",
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: isOutgoing,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
|
||||
content: .init(filename: "message.ogg",
|
||||
duration: 10,
|
||||
waveform: .mockWaveform,
|
||||
source: .init(url: .mockMXCAudio, mimeType: nil),
|
||||
fileSize: nil,
|
||||
contentType: .audio))
|
||||
}
|
||||
}
|
||||
|
||||
+10
-6
@@ -35,18 +35,22 @@ extension EventBasedMessageTimelineItemProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
var hasMediaCaption: Bool {
|
||||
var mediaCaption: String? {
|
||||
switch contentType {
|
||||
case .audio(let content):
|
||||
content.caption != nil
|
||||
content.caption
|
||||
case .file(let content):
|
||||
content.caption != nil
|
||||
content.caption
|
||||
case .image(let content):
|
||||
content.caption != nil
|
||||
content.caption
|
||||
case .video(let content):
|
||||
content.caption != nil
|
||||
content.caption
|
||||
case .emote, .notice, .text, .location, .voice:
|
||||
false
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
var hasMediaCaption: Bool {
|
||||
mediaCaption != nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,4 +99,14 @@ extension EventBasedTimelineItemProtocol {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var supportsMediaCaption: Bool {
|
||||
guard let messageBasedItem = self as? EventBasedMessageTimelineItemProtocol else { return false }
|
||||
return messageBasedItem.supportsMediaCaption
|
||||
}
|
||||
|
||||
var hasMediaCaption: Bool {
|
||||
guard let messageBasedItem = self as? EventBasedMessageTimelineItemProtocol else { return false }
|
||||
return messageBasedItem.hasMediaCaption
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bcc6fdf7c3f7e40f9beedb8f9779fa74b3c8d7020112398059783ff2820fa4ed
|
||||
size 135575
|
||||
oid sha256:42992a72deec6d6cc0a3268f4619db58fb44ba869b6cca0c9937ee70a0effd7e
|
||||
size 147883
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3e337818f2c9ecd0e7abfe35868857394f3b1129e798354266035b65ed9fe9ee
|
||||
size 142532
|
||||
oid sha256:a0868feab75e9ac054da551d7ebcc17d7cb8e2cfb3c4ab540f5d05753a273aea
|
||||
size 154967
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a7ba26595547bdab86bc57097fcccb0062b73c9afcccc5ee2d9481c24a5ee954
|
||||
size 134046
|
||||
oid sha256:a57163af2dbcb6af527279ee9959aaf4ac5830424b14deede615891b8da411c4
|
||||
size 146496
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8dfdb1408f464b7570b1c587e03dec72be0cc02ad28af2475c7f13eabfecfdea
|
||||
size 143529
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a8bad202aa9c390b47af7d1e35f3e65c731a3f3ae659c943c7f1ae63581590ec
|
||||
size 133449
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7f1d6997b3ca132dbf486145371ccc38418ee1ccfbb05ebd670f73f621008c48
|
||||
size 124084
|
||||
oid sha256:ccec869066425d2bcb107a1dae131d0a87bd2f1c8b345edd8fe77edfe3f779a1
|
||||
size 136131
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fbf1b0ba7b26a5bf3040ceffbfcca15cf38445fe4db7a39b81b16826517c6762
|
||||
size 134605
|
||||
oid sha256:e4448552425a8dde78ee792f3915d76721d548d38daa3b59854e56d692b15286
|
||||
size 147056
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bdcb99ed272db7ee82b09574da7e791ee371b35107ca8104a72acd71e99b1156
|
||||
size 129013
|
||||
oid sha256:b9845682cd8bc72244d05d872a5212735890703a5e65400667986f1d158a991d
|
||||
size 141448
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7f1d6997b3ca132dbf486145371ccc38418ee1ccfbb05ebd670f73f621008c48
|
||||
size 124084
|
||||
oid sha256:ccec869066425d2bcb107a1dae131d0a87bd2f1c8b345edd8fe77edfe3f779a1
|
||||
size 136131
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:83ec69d4b69f3a33422b0a472665c65855a365b4f676888edb9eb054ebaf77d4
|
||||
size 133818
|
||||
oid sha256:b2d5f335b38cdde8f4e5e8c40e7c2c95bee523d353d880e026c7c012c0f86284
|
||||
size 146268
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e173a969f56bdfad935308070f5dc1958eaf7781fbcf18e01c81b95799ce3ae1
|
||||
size 132960
|
||||
oid sha256:5bcb1cf3a950c99e05559cb2cf2162e5b96cbccdebbe42e3b09efa2256a5c53c
|
||||
size 145195
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c5a5a1d65cd3f0c569f2ca1fb80258e8179870acade4a1d68b3e9e67a52d8d29
|
||||
size 142541
|
||||
oid sha256:75219b465e5332f12fada6b5c507c9ee6c62c409b06167824310cb7dfbb068f7
|
||||
size 156450
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9fbc2bf481aeecb17f3be814b3bcf7147f2d8d757eb82a15b2a0db10a5ec8f40
|
||||
size 144452
|
||||
oid sha256:e950f852539eccca04f68060e56787413f90a6c2481df251e8ab315cf5b659d1
|
||||
size 158242
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1eac1d2d29c71d9c332f26b311b50c7ef601896a6941a1f617d200e23bb88731
|
||||
size 141641
|
||||
oid sha256:065d14de719ede0234e865a45119a2731da20e7982249db240180db9b7dafe71
|
||||
size 155522
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5c33873265e2e90314f656d95668da81ab86e7461fea51bfbff083eaea512b52
|
||||
size 148202
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:001894f533a0619f577647f55a790769d50f5200d4664d027a6b4337e7c8fc25
|
||||
size 136972
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:39c0cc82049d6e4c12416c457a087d99b52169ab266da1beaa22888aa933bca5
|
||||
size 125934
|
||||
oid sha256:a9bd0e980e02351fdcc69c1e2e71c6c579ee9223b671aedbb8b906d82f0a6da6
|
||||
size 139535
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:eac68c72456505eaafd6c94948857aaa2d54accc4990c3dd3cd1cb31023c89bf
|
||||
size 140645
|
||||
oid sha256:e28d69132bf0d7c6e2380916185df3e5bc0bcf858eda4418cbf561e19b0cfa91
|
||||
size 154495
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:150daec2df86269a675887b754f64e067dcdb856dff9d1c8a36e746495745ab8
|
||||
size 133199
|
||||
oid sha256:b0451dcad4c79972172c171622e32af923db17278bb59d8858ed538ef2e5acb8
|
||||
size 146847
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:39c0cc82049d6e4c12416c457a087d99b52169ab266da1beaa22888aa933bca5
|
||||
size 125934
|
||||
oid sha256:a9bd0e980e02351fdcc69c1e2e71c6c579ee9223b671aedbb8b906d82f0a6da6
|
||||
size 139535
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a1d61ad248aa93c90b680e51cdf4a30d8cfd9568a062b03b559d6f9d57fffddf
|
||||
size 140013
|
||||
oid sha256:dc893c5f8cfe7a247818288ec6e551ff9ee4c02971cde61d6bb5502f7af139d5
|
||||
size 153853
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2c30cb638c2eecebb63ca66a5f0107bb85225aa786cbf32ecbca9974b42cd383
|
||||
size 139069
|
||||
oid sha256:1d8fb938c99c65f5884636f8806bf3fe9a5de5b6877cf0a2899529df29e30679
|
||||
size 152908
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dd17e2229ac0f1c09ef301589f24de7f879364dbc468460d6e387b2393d6b584
|
||||
size 88452
|
||||
oid sha256:914c77c473286e62ff5fabd0d62ce7bd0a71c0755a8d2e99ceabb102bdd7f7f0
|
||||
size 99760
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2e9f207e4656524273989488b2e8f1d9ee2901d500a76b89dbbf01e831a146c7
|
||||
size 91341
|
||||
oid sha256:6224e94342cbe0303473eb91f16f97e85e67e43ab74be0d6b68868e3a428d1ea
|
||||
size 103095
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:46f40c81c046bb1593d0ac0d1b396558e75ab4d9a85ce9f460610c623835711a
|
||||
size 87463
|
||||
oid sha256:db0cc945ae881ff0979376ebe01eda05899100557c0778164a0b43f54f8ae6c5
|
||||
size 98124
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:10701f9bcc6d6906d8b1063b145fd754c0af3928e5d5664cb35b1aa4d79aa999
|
||||
size 95921
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:01dd4d77411e87302bb5b4b152de7e5eb761c7f4b367a84f36a31fc5e141fa9d
|
||||
size 87399
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b1a6d6103056a883e4fa9344b65766867ef28355f359c428c6f8fc7b8a5b1dd4
|
||||
size 78383
|
||||
oid sha256:841d4f5f4f5a4e3eac938f2fcd4b0bb94561eebd6179c91cb84816f3ae7df8b8
|
||||
size 89445
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:12b9450065c9aa924ac7b833d2c98a8ea011ea3ec470438869f08591590a5576
|
||||
size 88346
|
||||
oid sha256:6383d2c6351a4833a6ce83a71c6c57c2297d3a8b19c2a818211e21ce61cb7c96
|
||||
size 99003
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:05ae1dfcf89ec7c5329c570e29b876935d261470a6fc074d961452f75e4ab4e3
|
||||
size 81155
|
||||
oid sha256:b3068613a53a7634432c201ee51c5bd5e066d93a524019ee7f0b7c6b7199842b
|
||||
size 92207
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b1a6d6103056a883e4fa9344b65766867ef28355f359c428c6f8fc7b8a5b1dd4
|
||||
size 78383
|
||||
oid sha256:841d4f5f4f5a4e3eac938f2fcd4b0bb94561eebd6179c91cb84816f3ae7df8b8
|
||||
size 89445
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:100ebc998da4d0625307771a9fda2dc78becccf2fe0e58cb514967cc26b77e46
|
||||
size 86917
|
||||
oid sha256:a0dac8094cdadc60b5bf9fb5057b910a51d34598ca207acedac4af329ebe61b3
|
||||
size 97569
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5d2451f6dfdb5d0342c13d9bba95f035610649f5e0c6c026b6b5c02a3f5ea792
|
||||
size 84457
|
||||
oid sha256:f008b5f6b5015caa5102ee2e3368072b89dc8d369b43d229776b2c4d3a7ec119
|
||||
size 95442
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a7e0833f33a5372ac34cb1ffc904b3113c292ddfac1798f2ec49263f72487b8b
|
||||
size 104147
|
||||
oid sha256:2214c2b47d33f7e71ebe4b113d8c57461977d0e5558b4851e54be67bfc613bc1
|
||||
size 120917
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9dd681b7005cad7f3172e46704ce15f35348741ab4f61c25c7e25dbc976dd47f
|
||||
size 96872
|
||||
oid sha256:c0ca91f7036a9b588ae5cfaa98df46fad4a0c546a815646bcc93b60f5cc04b96
|
||||
size 114129
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:66a515af35f86cc79842515e6f200a8ac9e66393bafa3d08dd73c94b55a0e273
|
||||
size 102463
|
||||
oid sha256:224c0a2a1a180bb0f769690986615b42670eaeba5da36c136a194a9b649392a4
|
||||
size 119359
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3ff91b2679413fe8a068f82306eeaa78de5db89edacc910443fd03a9200ac42c
|
||||
size 111302
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0f083235302db13e888d8c1fdd9cf6746acba7c6b25e6066f225514b78963bc7
|
||||
size 99585
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f9f354421510618dc4c3ebdd4ed3f6aeb274236bd4d680948127a21737882a35
|
||||
size 83970
|
||||
oid sha256:28feeb86b0c09df9a9bc37b3523adbd9145c24372ba3f0d1e46e3b7811d56339
|
||||
size 100926
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:20be265e58a5ce19848a1d9c9221587656efbeb1eefa982bf598021ed91b1976
|
||||
size 104759
|
||||
oid sha256:68ba46257a66431778c6fd648be80e65bd724f451cc4bbd05a92c8dd6eca3ab3
|
||||
size 121585
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bbcb5ce6d51e66aa74d7ebb6cdaf574a80613cf5cbc2a52285f2db5758a260c8
|
||||
size 88609
|
||||
oid sha256:4a3212f9f5ccb198f4bb9770ea232b700cf635a0a3d37ecb7b8297c0496c12f4
|
||||
size 105456
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f9f354421510618dc4c3ebdd4ed3f6aeb274236bd4d680948127a21737882a35
|
||||
size 83970
|
||||
oid sha256:28feeb86b0c09df9a9bc37b3523adbd9145c24372ba3f0d1e46e3b7811d56339
|
||||
size 100926
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cb4cba6c55ed6d5a399729817e3bbcf0109e940022009ec2b994d744a0e26b9e
|
||||
size 100765
|
||||
oid sha256:9e084c9628684fe7b383b72a2f5906e47f9d65ba2de12a374b728ece3be37c68
|
||||
size 117656
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e808c53766810d2cef79e239295f55b151a151cb42a7d7fd24f83ef889964ae1
|
||||
size 95987
|
||||
oid sha256:ec4a30d3db9d84514c116964209d741a6179b11c0992b0e92e9e98a8d5e050eb
|
||||
size 112532
|
||||
|
||||
Reference in New Issue
Block a user