Update MediaGallery to tolerate undownloaded attachments
This commit is contained in:
parent
2abb4f2508
commit
5e0bfaadbf
@ -519,20 +519,14 @@ extension AudioAttachment {
|
||||
case .attachmentStream(let stream, _):
|
||||
return ByteCountFormatter().string(for: stream.attachmentStream.unencryptedByteCount) ?? ""
|
||||
case .attachmentPointer:
|
||||
owsFailDebug("Shouldn't get here - undownloaded media not implemented")
|
||||
// TODO: [Media Gallery]: Source byte information for undownloaded attachment
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
var dateString: String {
|
||||
switch state {
|
||||
case .attachmentStream:
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.setLocalizedDateFormatFromTemplate("Mdyy")
|
||||
return dateFormatter.string(from: receivedAtDate)
|
||||
case .attachmentPointer:
|
||||
owsFailDebug("Shouldn't get here - undownloaded media not implemented")
|
||||
return ""
|
||||
}
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.setLocalizedDateFormatFromTemplate("Mdyy")
|
||||
return dateFormatter.string(from: receivedAtDate)
|
||||
}
|
||||
}
|
||||
|
||||
@ -437,21 +437,25 @@ class CVComponentBodyMedia: CVComponentBase, CVComponent {
|
||||
componentDelegate.didCancelDownload(message, attachmentId: pointer.attachment.id)
|
||||
return true
|
||||
}
|
||||
case .stream(let stream, isUploading: _, imageMetadata: _):
|
||||
case .stream(let referencedAttachmentStream, isUploading: _, imageMetadata: _):
|
||||
let itemViewModel = CVItemViewModelImpl(renderItem: renderItem)
|
||||
if let item = items.first(where: { $0.attachment.attachment.attachment.id == stream.attachment.id }), item.isBroken {
|
||||
if let item = items.first(where: { $0.attachment.attachment.attachment.id == referencedAttachmentStream.attachment.id }), item.isBroken {
|
||||
componentDelegate.didTapBrokenVideo()
|
||||
return true
|
||||
}
|
||||
componentDelegate.didTapBodyMedia(
|
||||
itemViewModel: itemViewModel,
|
||||
attachmentStream: stream,
|
||||
attachment: referencedAttachmentStream,
|
||||
imageView: mediaView,
|
||||
)
|
||||
return true
|
||||
case .backupThumbnail:
|
||||
// Download the fullsize attachment
|
||||
componentDelegate.didTapSkippedDownloads(message)
|
||||
case .backupThumbnail(let thumbnail):
|
||||
let itemViewModel = CVItemViewModelImpl(renderItem: renderItem)
|
||||
componentDelegate.didTapBodyMedia(
|
||||
itemViewModel: itemViewModel,
|
||||
attachment: thumbnail,
|
||||
imageView: mediaView,
|
||||
)
|
||||
return true
|
||||
case .undownloadable:
|
||||
componentDelegate.didTapUndownloadableMedia()
|
||||
|
||||
@ -134,7 +134,7 @@ public protocol CVComponentDelegate: AnyObject, AudioMessageViewDelegate, CVPoll
|
||||
|
||||
func didTapBodyMedia(
|
||||
itemViewModel: CVItemViewModelImpl,
|
||||
attachmentStream: ReferencedAttachmentStream,
|
||||
attachment: ReferencedAttachment,
|
||||
imageView: UIView,
|
||||
)
|
||||
|
||||
|
||||
@ -483,7 +483,7 @@ extension ConversationViewController: CVComponentDelegate {
|
||||
|
||||
public func didTapBodyMedia(
|
||||
itemViewModel: CVItemViewModelImpl,
|
||||
attachmentStream: ReferencedAttachmentStream,
|
||||
attachment: ReferencedAttachment,
|
||||
imageView: UIView,
|
||||
) {
|
||||
AssertIsOnMainThread()
|
||||
@ -492,7 +492,7 @@ extension ConversationViewController: CVComponentDelegate {
|
||||
|
||||
guard
|
||||
let pageVC = MediaPageViewController(
|
||||
initialMediaAttachment: attachmentStream,
|
||||
initialMediaAttachment: attachment,
|
||||
thread: self.thread,
|
||||
spoilerState: self.viewState.spoilerState,
|
||||
)
|
||||
|
||||
@ -333,7 +333,7 @@ extension ConversationViewController: MediaPresentationContextProvider {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let mediaView = messageCell.albumItemView(forAttachment: galleryItem.attachmentStream) else {
|
||||
guard let mediaView = messageCell.albumItemView(forAttachment: galleryItem.referencedAttachment) else {
|
||||
owsFailDebug("itemView was unexpectedly nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -404,7 +404,7 @@ extension EditHistoryTableSheetViewController: CVComponentDelegate {
|
||||
|
||||
func didTapBodyMedia(
|
||||
itemViewModel: CVItemViewModelImpl,
|
||||
attachmentStream: ReferencedAttachmentStream,
|
||||
attachment: ReferencedAttachment,
|
||||
imageView: UIView,
|
||||
) {}
|
||||
|
||||
|
||||
@ -14,16 +14,9 @@ class AudioCell: MediaTileListModeCell {
|
||||
|
||||
private var audioItem: MediaGalleryCellItemAudio? {
|
||||
didSet {
|
||||
guard let audioItem else {
|
||||
if audioItem == nil {
|
||||
audioAttachment = nil
|
||||
return
|
||||
}
|
||||
audioAttachment = AudioAttachment(
|
||||
attachmentStream: audioItem.attachmentStream,
|
||||
owningMessage: audioItem.message,
|
||||
metadata: audioItem.metadata,
|
||||
receivedAtDate: audioItem.receivedAtDate,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +38,7 @@ class AudioCell: MediaTileListModeCell {
|
||||
|
||||
let currentContentSizeCategory = UITraitCollection.current.preferredContentSizeCategory
|
||||
let displaysTopLabel = AudioAllMediaPresenter.hasAttachmentLabel(
|
||||
attachment: audioItem.attachmentStream.attachment,
|
||||
attachment: audioItem.referencedAttachment.attachment,
|
||||
isVoiceMessage: audioItem.isVoiceMessage,
|
||||
)
|
||||
|
||||
@ -60,8 +53,9 @@ class AudioCell: MediaTileListModeCell {
|
||||
}
|
||||
|
||||
guard
|
||||
let stream = audioItem.referencedAttachment.asReferencedStream,
|
||||
let audioAttachment = AudioAttachment(
|
||||
attachmentStream: audioItem.attachmentStream,
|
||||
attachmentStream: stream,
|
||||
owningMessage: audioItem.message,
|
||||
metadata: audioItem.metadata,
|
||||
receivedAtDate: audioItem.receivedAtDate,
|
||||
@ -266,7 +260,7 @@ class AudioCell: MediaTileListModeCell {
|
||||
let cvAudioPlayer = AppEnvironment.shared.cvAudioPlayerRef
|
||||
cvAudioPlayer.setPlaybackProgress(
|
||||
progress: scrubbedTime,
|
||||
forAttachment: audioItem.attachmentStream.attachment,
|
||||
forAttachment: audioItem.referencedAttachment.attachment,
|
||||
)
|
||||
case .possible, .failed, .cancelled:
|
||||
audioMessageView.clearOverrideProgress(animated: false)
|
||||
@ -283,6 +277,7 @@ class AudioCell: MediaTileListModeCell {
|
||||
guard let itemModel, let audioMessageView, let audioItem, let audioAttachment else {
|
||||
return
|
||||
}
|
||||
guard case .attachmentStream = audioAttachment.state else { return }
|
||||
if audioMessageView.handleTap(sender: sender, itemModel: itemModel) {
|
||||
return
|
||||
}
|
||||
@ -317,7 +312,6 @@ class AudioCell: MediaTileListModeCell {
|
||||
owsFailDebug("Unexpected item type")
|
||||
return
|
||||
}
|
||||
self.audioItem = audioItem
|
||||
self.spoilerState = spoilerState
|
||||
|
||||
if let audioMessageView {
|
||||
@ -325,7 +319,24 @@ class AudioCell: MediaTileListModeCell {
|
||||
self.audioMessageView = nil
|
||||
}
|
||||
|
||||
self.audioItem = audioItem
|
||||
SSKEnvironment.shared.databaseStorageRef.read { transaction in
|
||||
if let referencedStream = audioItem.referencedAttachment.asReferencedStream {
|
||||
audioAttachment = AudioAttachment(
|
||||
attachmentStream: referencedStream,
|
||||
owningMessage: audioItem.message,
|
||||
metadata: audioItem.metadata,
|
||||
receivedAtDate: audioItem.receivedAtDate,
|
||||
)
|
||||
} else if let pointer = audioItem.referencedAttachment.asReferencedAnyPointer {
|
||||
audioAttachment = AudioAttachment(
|
||||
attachmentPointer: pointer,
|
||||
owningMessage: audioItem.message,
|
||||
metadata: audioItem.metadata,
|
||||
receivedAtDate: audioItem.receivedAtDate,
|
||||
downloadState: pointer.attachmentPointer.downloadState(tx: transaction),
|
||||
)
|
||||
}
|
||||
createAudioMessageView(transaction: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ class MediaGalleryFileCell: MediaTileListModeCell {
|
||||
|
||||
static let reuseIdentifier = "MediaGalleryFileCell"
|
||||
|
||||
private var attachment: ReferencedAttachmentStream?
|
||||
private var referencedAttachment: ReferencedAttachment?
|
||||
private var receivedAtDate: Date?
|
||||
private var owningMessage: TSMessage?
|
||||
private var mediaMetadata: MediaMetadata?
|
||||
@ -20,13 +20,13 @@ class MediaGalleryFileCell: MediaTileListModeCell {
|
||||
private var fileItem: MediaGalleryCellItemOtherFile? {
|
||||
didSet {
|
||||
guard let fileItem else {
|
||||
attachment = nil
|
||||
referencedAttachment = nil
|
||||
receivedAtDate = nil
|
||||
owningMessage = nil
|
||||
mediaMetadata = nil
|
||||
return
|
||||
}
|
||||
attachment = fileItem.attachmentStream
|
||||
referencedAttachment = fileItem.referencedAttachment
|
||||
receivedAtDate = fileItem.receivedAtDate
|
||||
owningMessage = fileItem.message
|
||||
mediaMetadata = fileItem.metadata
|
||||
@ -58,7 +58,7 @@ class MediaGalleryFileCell: MediaTileListModeCell {
|
||||
return cellHeight
|
||||
}
|
||||
|
||||
guard let attachment = item.attachmentStream else {
|
||||
guard let attachment = item.referencedAttachment?.asReferencedStream else {
|
||||
return defaultCellHeight
|
||||
}
|
||||
let genericAttachment = CVComponentState.GenericAttachment(
|
||||
@ -86,6 +86,7 @@ class MediaGalleryFileCell: MediaTileListModeCell {
|
||||
private var itemModel: CVItemModel?
|
||||
|
||||
private var genericAttachmentView: CVComponentView?
|
||||
private var attachmentType: CVAttachment?
|
||||
|
||||
private let genericAttachmentContainerView: UIView = {
|
||||
let view = UIView.container()
|
||||
@ -157,11 +158,21 @@ class MediaGalleryFileCell: MediaTileListModeCell {
|
||||
itemViewState: itemViewState.build(),
|
||||
coreState: coreState,
|
||||
)
|
||||
let genericAttachment = CVComponentState.GenericAttachment(attachment: .stream(
|
||||
fileItem.attachmentStream,
|
||||
isUploading: false,
|
||||
imageMetadata: nil,
|
||||
))
|
||||
|
||||
self.attachmentType = {
|
||||
if let stream = fileItem.referencedAttachment.asReferencedStream {
|
||||
return .stream(stream, isUploading: false, imageMetadata: nil)
|
||||
} else if let backupPointer = fileItem.referencedAttachment.asReferencedBackupThumbnail {
|
||||
return .backupThumbnail(backupPointer)
|
||||
} else if let pointer = fileItem.referencedAttachment.asReferencedAnyPointer {
|
||||
let downloadState = pointer.attachmentPointer.downloadState(tx: transaction)
|
||||
return .pointer(pointer, downloadState: downloadState)
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
guard let attachmentType else { return }
|
||||
|
||||
let genericAttachment = CVComponentState.GenericAttachment(attachment: attachmentType)
|
||||
let component = CVComponentGenericAttachment(
|
||||
itemModel: itemModel,
|
||||
genericAttachment: genericAttachment,
|
||||
@ -222,16 +233,15 @@ class MediaGalleryFileCell: MediaTileListModeCell {
|
||||
|
||||
@objc
|
||||
private func handleTapGesture(_ sender: UITapGestureRecognizer) {
|
||||
guard let fileItem, let itemModel else {
|
||||
guard
|
||||
let itemModel,
|
||||
let attachmentType
|
||||
else {
|
||||
return
|
||||
}
|
||||
let genericAttachment = CVComponentGenericAttachment(
|
||||
itemModel: itemModel,
|
||||
genericAttachment: .init(attachment: .stream(
|
||||
fileItem.attachmentStream,
|
||||
isUploading: false,
|
||||
imageMetadata: nil,
|
||||
)),
|
||||
genericAttachment: .init(attachment: attachmentType),
|
||||
)
|
||||
if
|
||||
PKAddPassesViewController.canAddPasses(),
|
||||
@ -419,7 +429,7 @@ extension MediaGalleryFileCell: CVComponentDelegate {
|
||||
|
||||
func didTapBodyMedia(
|
||||
itemViewModel: CVItemViewModelImpl,
|
||||
attachmentStream: ReferencedAttachmentStream,
|
||||
attachment: ReferencedAttachment,
|
||||
imageView: UIView,
|
||||
) {}
|
||||
|
||||
|
||||
@ -40,10 +40,10 @@ struct MediaGalleryItem: Equatable, Hashable, MediaGallerySectionItem {
|
||||
|
||||
let message: TSMessage
|
||||
let sender: Sender?
|
||||
let attachmentStream: ReferencedAttachmentStream
|
||||
let referencedAttachment: ReferencedAttachment
|
||||
let receivedAtDate: Date
|
||||
|
||||
var renderingFlag: AttachmentReference.RenderingFlag { attachmentStream.reference.renderingFlag }
|
||||
var renderingFlag: AttachmentReference.RenderingFlag { referencedAttachment.reference.renderingFlag }
|
||||
|
||||
let galleryDate: GalleryDate
|
||||
let captionForDisplay: MediaCaptionView.Content?
|
||||
@ -54,7 +54,7 @@ struct MediaGalleryItem: Equatable, Hashable, MediaGallerySectionItem {
|
||||
init(
|
||||
message: TSMessage,
|
||||
sender: Sender?,
|
||||
attachmentStream: ReferencedAttachmentStream,
|
||||
referencedAttachment: ReferencedAttachment,
|
||||
albumIndex: Int,
|
||||
numItemsInAlbum: Int,
|
||||
spoilerState: SpoilerRenderState,
|
||||
@ -62,13 +62,13 @@ struct MediaGalleryItem: Equatable, Hashable, MediaGallerySectionItem {
|
||||
) {
|
||||
self.message = message
|
||||
self.sender = sender
|
||||
self.attachmentStream = attachmentStream
|
||||
self.referencedAttachment = referencedAttachment
|
||||
self.receivedAtDate = message.receivedAtDate
|
||||
self.galleryDate = GalleryDate(message: message)
|
||||
self.albumIndex = albumIndex
|
||||
self.numItemsInAlbum = numItemsInAlbum
|
||||
self.orderingKey = MediaGalleryItemOrderingKey(messageSortKey: message.sortId, attachmentSortKey: albumIndex)
|
||||
if let captionText = attachmentStream.reference.legacyMessageCaption?.filterForDisplay {
|
||||
if let captionText = referencedAttachment.reference.legacyMessageCaption?.filterForDisplay {
|
||||
self.captionForDisplay = .attachmentStreamCaption(captionText)
|
||||
} else if let body = message.body {
|
||||
let hydratedMessageBody = MessageBody(
|
||||
@ -83,11 +83,15 @@ struct MediaGalleryItem: Equatable, Hashable, MediaGallerySectionItem {
|
||||
}
|
||||
}
|
||||
|
||||
private var mimeType: String { attachmentStream.attachmentStream.mimeType }
|
||||
private var mimeType: String { referencedAttachment.attachment.mimeType }
|
||||
|
||||
var isVideo: Bool {
|
||||
switch attachmentStream.attachmentStream.contentType {
|
||||
switch referencedAttachment.attachment.contentType {
|
||||
case .video:
|
||||
// For now, if the video isn't a stream, don't treat it as a video.
|
||||
if referencedAttachment.asReferencedStream == nil {
|
||||
return false
|
||||
}
|
||||
return renderingFlag != .shouldLoop
|
||||
case .file, .image, .audio:
|
||||
return false
|
||||
@ -95,7 +99,7 @@ struct MediaGalleryItem: Equatable, Hashable, MediaGallerySectionItem {
|
||||
}
|
||||
|
||||
var isAnimated: Bool {
|
||||
switch attachmentStream.attachmentStream.contentType {
|
||||
switch referencedAttachment.attachment.contentType {
|
||||
case .video:
|
||||
return renderingFlag == .shouldLoop
|
||||
case .file, .image, .audio:
|
||||
@ -104,7 +108,7 @@ struct MediaGalleryItem: Equatable, Hashable, MediaGallerySectionItem {
|
||||
}
|
||||
|
||||
var isImage: Bool {
|
||||
switch attachmentStream.attachmentStream.contentType {
|
||||
switch referencedAttachment.attachment.contentType {
|
||||
case .image:
|
||||
return true
|
||||
case .file, .video, .audio:
|
||||
@ -112,33 +116,33 @@ struct MediaGalleryItem: Equatable, Hashable, MediaGallerySectionItem {
|
||||
}
|
||||
}
|
||||
|
||||
var attachmentId: AttachmentReferenceId { attachmentStream.reference.referenceId }
|
||||
var attachmentId: AttachmentReferenceId { referencedAttachment.reference.referenceId }
|
||||
|
||||
typealias AsyncThumbnailBlock = @MainActor (UIImage) -> Void
|
||||
func thumbnailImage(completion: @escaping AsyncThumbnailBlock) {
|
||||
Task { [attachmentStream] in
|
||||
if let image = await attachmentStream.attachmentStream.thumbnailImage(quality: .small) {
|
||||
Task { [referencedAttachment] in
|
||||
if let image = await referencedAttachment.getThumbnailImage(quality: .small) {
|
||||
await completion(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func thumbnailImageSync() -> UIImage? {
|
||||
return attachmentStream.attachmentStream.thumbnailImageSync(quality: .small)
|
||||
referencedAttachment.getThumbnailImageSync(quality: .small)
|
||||
}
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
static func ==(lhs: MediaGalleryItem, rhs: MediaGalleryItem) -> Bool {
|
||||
return lhs.attachmentStream.attachmentStream.id == rhs.attachmentStream.attachmentStream.id
|
||||
&& lhs.attachmentStream.reference.hasSameOwner(as: rhs.attachmentStream.reference)
|
||||
return lhs.referencedAttachment.attachment.id == rhs.referencedAttachment.attachment.id
|
||||
&& lhs.referencedAttachment.reference.hasSameOwner(as: rhs.referencedAttachment.reference)
|
||||
}
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(attachmentStream.attachmentStream.id)
|
||||
let attachmentReference = attachmentStream.reference
|
||||
hasher.combine(referencedAttachment.attachment.id)
|
||||
let attachmentReference = referencedAttachment.reference
|
||||
hasher.combine(attachmentReference.owner.id)
|
||||
}
|
||||
|
||||
@ -484,10 +488,6 @@ class MediaGallery {
|
||||
spoilerState: SpoilerRenderState,
|
||||
transaction: DBReadTransaction,
|
||||
) -> MediaGalleryItem? {
|
||||
guard let attachmentStream = attachment.attachment.asStream() else {
|
||||
owsFailDebug("gallery doesn't yet support showing undownloaded attachments")
|
||||
return nil
|
||||
}
|
||||
|
||||
let message: TSMessage
|
||||
switch attachment.reference.owner {
|
||||
@ -560,7 +560,7 @@ class MediaGallery {
|
||||
return MediaGalleryItem(
|
||||
message: message,
|
||||
sender: sender,
|
||||
attachmentStream: .init(reference: attachment.reference, attachmentStream: attachmentStream),
|
||||
referencedAttachment: .init(reference: attachment.reference, attachment: attachment.attachment),
|
||||
albumIndex: Int(albumIndex),
|
||||
numItemsInAlbum: itemsInAlbum.count,
|
||||
spoilerState: spoilerState,
|
||||
@ -674,7 +674,7 @@ class MediaGallery {
|
||||
shouldLoadAlbumRemainder: Bool,
|
||||
) {
|
||||
guard let path = indexPath(for: item) else {
|
||||
owsFailDebug("showing detail view for an item that hasn't been loaded: \(item.attachmentStream)")
|
||||
owsFailDebug("showing detail view for an item that hasn't been loaded: \(item.referencedAttachment)")
|
||||
return
|
||||
}
|
||||
|
||||
@ -702,7 +702,7 @@ class MediaGallery {
|
||||
|
||||
guard
|
||||
let itemId = mediaGalleryFinder.galleryItemId(
|
||||
of: focusedItem.attachmentStream,
|
||||
of: focusedItem.referencedAttachment,
|
||||
in: focusedItem.galleryDate.interval,
|
||||
excluding: deletedAttachmentIds,
|
||||
tx: transaction,
|
||||
@ -839,13 +839,13 @@ class MediaGallery {
|
||||
return
|
||||
}
|
||||
|
||||
Logger.info("with items: \(items.map { ($0.attachmentStream, $0.message.timestamp) })")
|
||||
Logger.info("with items: \(items.map { ($0.referencedAttachment, $0.message.timestamp) })")
|
||||
|
||||
deletedGalleryItems.formUnion(items)
|
||||
delegates.forEach { $0.mediaGallery(self, willDelete: items, initiatedBy: initiatedBy) }
|
||||
|
||||
deletedAttachmentIds.formUnion(items.lazy.map {
|
||||
$0.attachmentStream.reference.referenceId
|
||||
$0.referencedAttachment.reference.referenceId
|
||||
})
|
||||
|
||||
Task {
|
||||
@ -858,7 +858,7 @@ class MediaGallery {
|
||||
|
||||
for item in items {
|
||||
let message = item.message
|
||||
let referencedAttachment: ReferencedAttachment = item.attachmentStream
|
||||
let referencedAttachment: ReferencedAttachment = item.referencedAttachment
|
||||
|
||||
attachmentStore.removeReference(
|
||||
reference: referencedAttachment.reference,
|
||||
|
||||
@ -22,14 +22,14 @@ enum MediaGalleryCellItem {
|
||||
case audio(MediaGalleryCellItemAudio)
|
||||
case otherFile(MediaGalleryCellItemOtherFile)
|
||||
|
||||
var attachmentStream: ReferencedAttachmentStream? {
|
||||
var referencedAttachment: ReferencedAttachment? {
|
||||
switch self {
|
||||
case .photoVideo(let item):
|
||||
return item.galleryItem.attachmentStream
|
||||
return item.galleryItem.referencedAttachment
|
||||
case .audio(let audioItem):
|
||||
return audioItem.attachmentStream
|
||||
return audioItem.referencedAttachment
|
||||
case .otherFile(let fileItem):
|
||||
return fileItem.attachmentStream
|
||||
return fileItem.referencedAttachment
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -40,9 +40,9 @@ extension MediaGalleryCellItem: Equatable {
|
||||
case let (.photoVideo(lvalue), .photoVideo(rvalue)):
|
||||
return lvalue === rvalue
|
||||
case let (.audio(lvalue), .audio(rvalue)):
|
||||
return lvalue.attachmentStream.reference.attachmentRowId == rvalue.attachmentStream.reference.attachmentRowId
|
||||
return lvalue.referencedAttachment.reference.attachmentRowId == rvalue.referencedAttachment.reference.attachmentRowId
|
||||
case let (.otherFile(lvalue), .otherFile(rvalue)):
|
||||
return lvalue.attachmentStream.reference.attachmentRowId == rvalue.attachmentStream.reference.attachmentRowId
|
||||
return lvalue.referencedAttachment.reference.attachmentRowId == rvalue.referencedAttachment.reference.attachmentRowId
|
||||
case (.photoVideo, _), (.audio, _), (.otherFile, _):
|
||||
return false
|
||||
}
|
||||
@ -53,7 +53,7 @@ struct MediaGalleryCellItemAudio {
|
||||
var message: TSMessage
|
||||
var interaction: TSInteraction
|
||||
var thread: TSThread
|
||||
var attachmentStream: ReferencedAttachmentStream
|
||||
var referencedAttachment: ReferencedAttachment
|
||||
var receivedAtDate: Date
|
||||
var isVoiceMessage: Bool
|
||||
var mediaCache: CVMediaCache
|
||||
@ -78,13 +78,13 @@ struct MediaGalleryCellItemOtherFile {
|
||||
var message: TSMessage
|
||||
var interaction: TSInteraction
|
||||
var thread: TSThread
|
||||
var attachmentStream: ReferencedAttachmentStream
|
||||
var referencedAttachment: ReferencedAttachment
|
||||
var receivedAtDate: Date
|
||||
var mediaCache: CVMediaCache
|
||||
var metadata: MediaMetadata
|
||||
|
||||
var size: UInt {
|
||||
UInt(attachmentStream.attachmentStream.unencryptedByteCount)
|
||||
var size: UInt64 {
|
||||
referencedAttachment.unencryptedByteCount() ?? 0
|
||||
}
|
||||
|
||||
var localizedString: String {
|
||||
@ -105,7 +105,7 @@ class MediaGalleryCellItemPhotoVideo: PhotoGridItem {
|
||||
var type: PhotoGridItemType {
|
||||
if galleryItem.isVideo {
|
||||
return .video(
|
||||
duration: galleryItem.attachmentStream.attachmentStream.cachedVideoDuration,
|
||||
duration: galleryItem.referencedAttachment.asReferencedStream?.attachmentStream.cachedVideoDuration,
|
||||
)
|
||||
} else if galleryItem.isAnimated {
|
||||
return .animated
|
||||
@ -128,7 +128,7 @@ extension MediaGalleryItem {
|
||||
return MediaMetadata(
|
||||
sender: sender?.name ?? "",
|
||||
abbreviatedSender: sender?.abbreviatedName ?? "",
|
||||
byteSize: Int(attachmentStream.attachmentStream.unencryptedByteCount),
|
||||
byteSize: Int(clamping: referencedAttachment.unencryptedByteCount() ?? 0),
|
||||
creationDate: receivedAtDate,
|
||||
)
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ class MediaItemViewController: OWSViewController, VideoPlaybackStatusProvider {
|
||||
|
||||
super.init()
|
||||
|
||||
image = attachmentStream.thumbnailImageSync(quality: .large)
|
||||
image = galleryItem.referencedAttachment.getThumbnailImageSync(quality: .large)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -116,37 +116,43 @@ class MediaItemViewController: OWSViewController, VideoPlaybackStatusProvider {
|
||||
guard mediaView == nil else { return }
|
||||
|
||||
let view: UIView
|
||||
if attachmentStream.contentType.isVideo, galleryItem.renderingFlag == .shouldLoop {
|
||||
if let loopingVideoPlayerView = buildLoopingVideoPlayerView() {
|
||||
let referencedAttachmentStream = galleryItem.referencedAttachment.asReferencedStream
|
||||
if
|
||||
let referencedAttachmentStream,
|
||||
referencedAttachmentStream.attachmentStream.contentType.isVideo,
|
||||
galleryItem.renderingFlag == .shouldLoop
|
||||
{
|
||||
if let loopingVideoPlayerView = buildLoopingVideoPlayerView(attachmentStream: referencedAttachmentStream.attachmentStream) {
|
||||
loopingVideoPlayerView.delegate = self
|
||||
view = loopingVideoPlayerView
|
||||
} else {
|
||||
view = buildPlaceholderView()
|
||||
}
|
||||
} else if
|
||||
let imageMetadata = attachmentStream.imageMetadata(),
|
||||
let referencedAttachmentStream,
|
||||
let imageMetadata = referencedAttachmentStream.attachmentStream.imageMetadata(),
|
||||
imageMetadata.isAnimated
|
||||
{
|
||||
if let animatedGif = try? attachmentStream.decryptedSDAnimatedImage() {
|
||||
if let animatedGif = try? referencedAttachmentStream.attachmentStream.decryptedSDAnimatedImage() {
|
||||
view = SDAnimatedImageView(image: animatedGif)
|
||||
} else {
|
||||
view = buildPlaceholderView()
|
||||
}
|
||||
} else if image == nil {
|
||||
// Still loading thumbnail.
|
||||
view = buildPlaceholderView()
|
||||
} else if isVideo {
|
||||
if attachmentStream.contentType.isVideo, let videoPlayerView = buildVideoPlayerView() {
|
||||
videoPlayerView.delegate = self
|
||||
videoPlayerView.videoPlayer?.delegate = self
|
||||
} else if
|
||||
let referencedAttachmentStream,
|
||||
isVideo, // TODO: Separate isVideo from isVideoPlayable
|
||||
let videoPlayerView = buildVideoPlayerView(referencedAttachmentStream: referencedAttachmentStream)
|
||||
{
|
||||
videoPlayerView.delegate = self
|
||||
videoPlayerView.videoPlayer?.delegate = self
|
||||
|
||||
view = videoPlayerView
|
||||
} else {
|
||||
view = buildPlaceholderView()
|
||||
}
|
||||
} else {
|
||||
view = videoPlayerView
|
||||
} else if let image {
|
||||
// Present the static image using standard UIImageView
|
||||
view = UIImageView(image: image)
|
||||
} else {
|
||||
// Still loading thumbnail.
|
||||
view = buildPlaceholderView()
|
||||
}
|
||||
|
||||
mediaView = view
|
||||
@ -158,7 +164,7 @@ class MediaItemViewController: OWSViewController, VideoPlaybackStatusProvider {
|
||||
return view
|
||||
}
|
||||
|
||||
private func buildLoopingVideoPlayerView() -> LoopingVideoView? {
|
||||
private func buildLoopingVideoPlayerView(attachmentStream: AttachmentStream) -> LoopingVideoView? {
|
||||
guard let loopingVideo = LoopingVideo(attachmentStream) else {
|
||||
owsFailBeta("Invalid looping video")
|
||||
return nil
|
||||
@ -168,8 +174,8 @@ class MediaItemViewController: OWSViewController, VideoPlaybackStatusProvider {
|
||||
return videoView
|
||||
}
|
||||
|
||||
private func buildVideoPlayerView() -> VideoPlayerView? {
|
||||
guard let videoPlayer = try? VideoPlayer(attachment: galleryItem.attachmentStream) else {
|
||||
private func buildVideoPlayerView(referencedAttachmentStream: ReferencedAttachmentStream) -> VideoPlayerView? {
|
||||
guard let videoPlayer = try? VideoPlayer(attachment: referencedAttachmentStream) else {
|
||||
owsFailBeta("Invalid attachment")
|
||||
return nil
|
||||
}
|
||||
@ -224,7 +230,7 @@ class MediaItemViewController: OWSViewController, VideoPlaybackStatusProvider {
|
||||
}
|
||||
|
||||
let timestamp = Date().ows_millisecondsSince1970
|
||||
let attachmentId = galleryItem.attachmentStream.attachment.id
|
||||
let attachmentId = galleryItem.referencedAttachment.attachment.id
|
||||
Task {
|
||||
await DependenciesBridge.shared.db.awaitableWrite { tx in
|
||||
DependenciesBridge.shared.attachmentStore.markViewedFullscreen(
|
||||
@ -246,8 +252,6 @@ class MediaItemViewController: OWSViewController, VideoPlaybackStatusProvider {
|
||||
|
||||
private var image: UIImage?
|
||||
|
||||
private var attachmentStream: AttachmentStream { galleryItem.attachmentStream.attachmentStream }
|
||||
|
||||
// MARK: - Video Playback
|
||||
|
||||
var shouldAutoPlayVideo: Bool = false
|
||||
|
||||
@ -547,8 +547,13 @@ class MediaPageViewController: UIPageViewController {
|
||||
|
||||
private func shareCurrentMedia(fromNavigationBar: Bool) {
|
||||
guard let currentViewController else { return }
|
||||
guard let stream = currentViewController.galleryItem.referencedAttachment.asReferencedStream else {
|
||||
// TODO: [MediaGallery]: Handle undownloaded media
|
||||
owsFailDebug("Cannot share undownloaded media")
|
||||
return
|
||||
}
|
||||
guard
|
||||
let attachmentStream = (try? [currentViewController.galleryItem.attachmentStream].asShareableAttachments())?.first
|
||||
let attachmentStream = (try? [stream].asShareableAttachments())?.first
|
||||
else {
|
||||
return
|
||||
}
|
||||
@ -560,9 +565,13 @@ class MediaPageViewController: UIPageViewController {
|
||||
|
||||
private func saveCurrentMediaToPhotos() {
|
||||
guard let mediaItem = currentItem else { return }
|
||||
|
||||
guard let stream = mediaItem.referencedAttachment.asReferencedStream else {
|
||||
// TODO: [MediaGallery]: Handle undownloaded media
|
||||
owsFailDebug("Cannot share undownloaded media")
|
||||
return
|
||||
}
|
||||
AttachmentSaving.saveToPhotoLibrary(
|
||||
referencedAttachmentStreams: [mediaItem.attachmentStream],
|
||||
referencedAttachmentStreams: [stream],
|
||||
)
|
||||
}
|
||||
|
||||
@ -835,7 +844,7 @@ extension MediaPageViewController: MediaGalleryDelegate {
|
||||
}
|
||||
|
||||
func didReloadAllSectionsInMediaGallery(_ mediaGallery: MediaGallery) {
|
||||
let attachment = currentItem.attachmentStream
|
||||
let attachment = currentItem.referencedAttachment
|
||||
guard let reloadedItem = mediaGallery.ensureLoadedForDetailView(focusedAttachment: attachment) else {
|
||||
// Assume the item was deleted.
|
||||
dismissSelf(animated: true)
|
||||
|
||||
@ -613,7 +613,7 @@ class MediaTileViewController: UICollectionViewController, MediaGalleryDelegate,
|
||||
|
||||
guard
|
||||
let pageVC = MediaPageViewController(
|
||||
initialMediaAttachment: galleryItem.attachmentStream,
|
||||
initialMediaAttachment: galleryItem.referencedAttachment,
|
||||
mediaGallery: mediaGallery,
|
||||
spoilerState: spoilerState,
|
||||
)
|
||||
@ -804,7 +804,7 @@ class MediaTileViewController: UICollectionViewController, MediaGalleryDelegate,
|
||||
message: galleryItem.message,
|
||||
interaction: galleryItem.message,
|
||||
thread: thread,
|
||||
attachmentStream: galleryItem.attachmentStream,
|
||||
referencedAttachment: galleryItem.referencedAttachment,
|
||||
receivedAtDate: galleryItem.receivedAtDate,
|
||||
isVoiceMessage: galleryItem.renderingFlag == .voiceMessage,
|
||||
mediaCache: mediaCache,
|
||||
@ -815,7 +815,7 @@ class MediaTileViewController: UICollectionViewController, MediaGalleryDelegate,
|
||||
message: galleryItem.message,
|
||||
interaction: galleryItem.message,
|
||||
thread: thread,
|
||||
attachmentStream: galleryItem.attachmentStream,
|
||||
referencedAttachment: galleryItem.referencedAttachment,
|
||||
receivedAtDate: galleryItem.receivedAtDate,
|
||||
mediaCache: mediaCache,
|
||||
metadata: galleryItem.mediaMetadata!,
|
||||
@ -1521,7 +1521,7 @@ extension MediaTileViewController: MediaGalleryPrimaryViewController {
|
||||
}
|
||||
|
||||
let totalSize = items.reduce(UInt64(0), { result, item in
|
||||
result + UInt64(safeCast: item.attachmentStream.attachmentStream.unencryptedByteCount)
|
||||
result + (item.referencedAttachment.unencryptedByteCount() ?? 0)
|
||||
})
|
||||
return (items.count, totalSize)
|
||||
}
|
||||
@ -1737,8 +1737,12 @@ extension MediaTileViewController: MediaGalleryPrimaryViewController {
|
||||
return
|
||||
}
|
||||
|
||||
let attachments = indexPaths.compactMap {
|
||||
self.galleryItem(at: $0)?.attachmentStream
|
||||
let attachments: [ReferencedAttachmentStream] = indexPaths.compactMap {
|
||||
self.galleryItem(at: $0)?.referencedAttachment.asReferencedStream
|
||||
}
|
||||
if attachments.count < indexPaths.count {
|
||||
// TODO: [MediaGallery] Better handling of undownloaded attachmets
|
||||
owsFailDebug("Attempting to share undownloaded attachments")
|
||||
}
|
||||
let items: [ShareableAttachment] = (try? attachments.asShareableAttachments()) ?? []
|
||||
guard items.count == indexPaths.count else {
|
||||
|
||||
@ -12,7 +12,7 @@ enum Media {
|
||||
var image: UIImage? {
|
||||
switch self {
|
||||
case let .gallery(item):
|
||||
return try? item.attachmentStream.attachmentStream.decryptedImage()
|
||||
return item.referencedAttachment.getBestAvailableLocalImage()
|
||||
case let .image(image):
|
||||
return image
|
||||
}
|
||||
|
||||
@ -173,11 +173,10 @@ class VideoPlaybackControlView: UIView {
|
||||
weak var delegate: VideoPlaybackControlViewDelegate?
|
||||
|
||||
func updateWithMediaItem(_ mediaItem: MediaGalleryItem) {
|
||||
let attachmentStream = mediaItem.attachmentStream.attachmentStream
|
||||
switch attachmentStream.contentType {
|
||||
switch mediaItem.referencedAttachment.attachment.contentType {
|
||||
case .video:
|
||||
if
|
||||
let videoDuration = attachmentStream.cachedVideoDuration,
|
||||
let videoDuration = mediaItem.referencedAttachment.asReferencedStream?.attachmentStream.cachedVideoDuration,
|
||||
videoDuration > 30
|
||||
{
|
||||
showRewindAndFastForward = true
|
||||
|
||||
@ -685,7 +685,7 @@ extension MemberLabelViewController: CVComponentDelegate {
|
||||
|
||||
func didTapBodyMedia(
|
||||
itemViewModel: CVItemViewModelImpl,
|
||||
attachmentStream: ReferencedAttachmentStream,
|
||||
attachment: ReferencedAttachment,
|
||||
imageView: UIView,
|
||||
) {}
|
||||
|
||||
|
||||
@ -820,7 +820,7 @@ extension MessageDetailViewController: MediaPresentationContextProvider {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let mediaView = cellView.albumItemView(forAttachment: galleryItem.attachmentStream) else {
|
||||
guard let mediaView = cellView.albumItemView(forAttachment: galleryItem.referencedAttachment) else {
|
||||
owsFailDebug("itemView was unexpectedly nil")
|
||||
return nil
|
||||
}
|
||||
@ -1160,7 +1160,7 @@ extension MessageDetailViewController: CVComponentDelegate {
|
||||
|
||||
func didTapBodyMedia(
|
||||
itemViewModel: CVItemViewModelImpl,
|
||||
attachmentStream: ReferencedAttachmentStream,
|
||||
attachment: ReferencedAttachment,
|
||||
imageView: UIView,
|
||||
) {
|
||||
guard let thread else {
|
||||
@ -1169,7 +1169,7 @@ extension MessageDetailViewController: CVComponentDelegate {
|
||||
}
|
||||
guard
|
||||
let mediaPageVC = MediaPageViewController(
|
||||
initialMediaAttachment: attachmentStream,
|
||||
initialMediaAttachment: attachment,
|
||||
thread: thread,
|
||||
spoilerState: self.spoilerState,
|
||||
showingSingleMessage: true,
|
||||
|
||||
@ -530,7 +530,7 @@ extension PinnedMessagesDetailsViewController: CVComponentDelegate {
|
||||
|
||||
func didTapBodyMedia(
|
||||
itemViewModel: CVItemViewModelImpl,
|
||||
attachmentStream: ReferencedAttachmentStream,
|
||||
attachment: ReferencedAttachment,
|
||||
imageView: UIView,
|
||||
) {}
|
||||
|
||||
|
||||
@ -275,9 +275,9 @@ extension ConversationSettingsViewController {
|
||||
let availableWidth = self.view.width - ((Self.cellHInnerMargin * 2) + self.cellOuterInsets.totalWidth + self.view.safeAreaInsets.totalWidth)
|
||||
let imageWidth = (availableWidth - totalSpacerSize) / CGFloat(self.maximumRecentMedia)
|
||||
|
||||
for (attachmentStream, imageView) in self.recentMedia.orderedValues {
|
||||
for (referencedAttachment, imageView) in self.recentMedia.orderedValues {
|
||||
let button = OWSButton { [weak self] in
|
||||
self?.showMediaPageView(for: attachmentStream)
|
||||
self?.showMediaPageView(for: referencedAttachment)
|
||||
}
|
||||
stackView.addArrangedSubview(button)
|
||||
button.autoSetDimensions(to: CGSize(square: imageWidth))
|
||||
|
||||
@ -860,10 +860,10 @@ class ConversationSettingsViewController: OWSTableViewController2, BadgeCollecti
|
||||
navigationController?.pushViewController(tileVC, animated: true)
|
||||
}
|
||||
|
||||
func showMediaPageView(for attachmentStream: ReferencedAttachmentStream) {
|
||||
func showMediaPageView(for referencedAttachment: ReferencedAttachment) {
|
||||
guard
|
||||
let vc = MediaPageViewController(
|
||||
initialMediaAttachment: attachmentStream,
|
||||
initialMediaAttachment: referencedAttachment,
|
||||
thread: thread,
|
||||
spoilerState: spoilerState,
|
||||
)
|
||||
@ -879,7 +879,7 @@ class ConversationSettingsViewController: OWSTableViewController2, BadgeCollecti
|
||||
let maximumRecentMedia = 4
|
||||
private(set) var recentMedia = OrderedDictionary<
|
||||
AttachmentReferenceId,
|
||||
(attachment: ReferencedAttachmentStream, imageView: UIImageView),
|
||||
(attachment: ReferencedAttachment, imageView: UIImageView),
|
||||
>() {
|
||||
didSet { AssertIsOnMainThread() }
|
||||
}
|
||||
@ -894,10 +894,6 @@ class ConversationSettingsViewController: OWSTableViewController2, BadgeCollecti
|
||||
mediaGalleryFinder.recentMediaAttachments(limit: maximumRecentMedia, tx: transaction)
|
||||
}
|
||||
recentMedia = recentAttachments.reduce(into: OrderedDictionary(), { result, attachment in
|
||||
guard let attachmentStream = attachment.asReferencedStream else {
|
||||
return owsFailDebug("Unexpected type of attachment")
|
||||
}
|
||||
|
||||
let imageView = UIImageView()
|
||||
imageView.clipsToBounds = true
|
||||
if #available(iOS 26, *) {
|
||||
@ -907,14 +903,11 @@ class ConversationSettingsViewController: OWSTableViewController2, BadgeCollecti
|
||||
imageView.layer.cornerRadius = 4
|
||||
}
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
|
||||
Task {
|
||||
imageView.image = await attachmentStream.attachmentStream.thumbnailImage(quality: .small)
|
||||
}
|
||||
imageView.image = attachment.getThumbnailImageSync(quality: .small)
|
||||
|
||||
result.append(
|
||||
key: attachmentStream.reference.referenceId,
|
||||
value: (attachmentStream, imageView),
|
||||
key: attachment.reference.referenceId,
|
||||
value: (attachment, imageView),
|
||||
)
|
||||
})
|
||||
shouldRefreshAttachmentsOnReappear = false
|
||||
@ -1093,7 +1086,7 @@ extension ConversationSettingsViewController: MediaPresentationContextProvider {
|
||||
let mediaViewShape: MediaViewShape
|
||||
switch item {
|
||||
case .gallery(let galleryItem):
|
||||
guard let imageView = recentMedia[galleryItem.attachmentStream.reference.referenceId]?.imageView else { return nil }
|
||||
guard let imageView = recentMedia[galleryItem.referencedAttachment.reference.referenceId]?.imageView else { return nil }
|
||||
mediaView = imageView
|
||||
mediaViewShape = .rectangle(imageView.layer.cornerRadius)
|
||||
case .image:
|
||||
|
||||
@ -422,7 +422,7 @@ extension MockConversationView: CVComponentDelegate {
|
||||
|
||||
func didTapBodyMedia(
|
||||
itemViewModel: CVItemViewModelImpl,
|
||||
attachmentStream: ReferencedAttachmentStream,
|
||||
attachment: ReferencedAttachment,
|
||||
imageView: UIView,
|
||||
) {}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import Foundation
|
||||
|
||||
/// Just a simple structure holding an attachment and a reference to it,
|
||||
/// since that's something we need to do very often.
|
||||
public class ReferencedAttachment {
|
||||
public class ReferencedAttachment: CustomDebugStringConvertible {
|
||||
public let reference: AttachmentReference
|
||||
public let attachment: Attachment
|
||||
|
||||
@ -54,26 +54,32 @@ public class ReferencedAttachment {
|
||||
public func getThumbnailImage(quality: AttachmentThumbnailQuality) async -> UIImage? {
|
||||
if let stream = asReferencedStream?.attachmentStream {
|
||||
return await stream.thumbnailImage(quality: quality)
|
||||
} else if let backupThumbnail = AttachmentBackupThumbnail(attachment: attachment) {
|
||||
return backupThumbnail.image
|
||||
}
|
||||
return nil
|
||||
return getPlaceholderImage()
|
||||
}
|
||||
|
||||
public func getThumbnailImageSync(quality: AttachmentThumbnailQuality) -> UIImage? {
|
||||
if let stream = asReferencedStream?.attachmentStream {
|
||||
return stream.thumbnailImageSync(quality: quality)
|
||||
} else if let backupThumbnail = AttachmentBackupThumbnail(attachment: attachment) {
|
||||
return backupThumbnail.image
|
||||
}
|
||||
return nil
|
||||
return getPlaceholderImage()
|
||||
}
|
||||
|
||||
public func getBestAvailableLocalImage() -> UIImage? {
|
||||
if let stream = asReferencedStream?.attachmentStream {
|
||||
return try? stream.decryptedImage()
|
||||
} else if let backupThumbnail = AttachmentBackupThumbnail(attachment: attachment) {
|
||||
}
|
||||
return getPlaceholderImage()
|
||||
}
|
||||
|
||||
private func getPlaceholderImage() -> UIImage? {
|
||||
if let backupThumbnail = AttachmentBackupThumbnail(attachment: attachment) {
|
||||
return backupThumbnail.image
|
||||
} else if
|
||||
let blurHash = attachment.blurHash?.nilIfEmpty,
|
||||
let blurHashImage = BlurHash.image(for: blurHash)
|
||||
{
|
||||
return blurHashImage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -86,6 +92,11 @@ public class ReferencedAttachment {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public var debugDescription: String {
|
||||
let isStream = self as? ReferencedAttachmentStream != nil
|
||||
return "ReferencedAttachment(reference: \(reference.owner.id), attachment: \(attachment.id), downloaded: \(isStream))"
|
||||
}
|
||||
}
|
||||
|
||||
public class ReferencedAttachmentStream: ReferencedAttachment {
|
||||
|
||||
@ -241,7 +241,7 @@ public struct MediaGalleryAttachmentFinder {
|
||||
|
||||
// Disregards filter.
|
||||
public func galleryItemId(
|
||||
of attachment: ReferencedAttachmentStream,
|
||||
of attachment: ReferencedAttachment,
|
||||
in interval: DateInterval,
|
||||
excluding deletedAttachmentIds: Set<AttachmentReferenceId>,
|
||||
tx: DBReadTransaction,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user