Skip to content

Commit f68708c

Browse files
authored
Merge pull request #493 from mpretty-cyro/fix/animated-image-handling
Animated image handling improvements
2 parents 9e20a46 + f368521 commit f68708c

File tree

10 files changed

+577
-371
lines changed

10 files changed

+577
-371
lines changed

Session.xcodeproj/project.pbxproj

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,6 @@
549549
FD2272DD2C34EFFA004D8A6C /* AppSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272DC2C34EFFA004D8A6C /* AppSetup.swift */; };
550550
FD2272DE2C34F11F004D8A6C /* _001_ThemePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9F828A5F14A003AE748 /* _001_ThemePreferences.swift */; };
551551
FD2272E02C3502BE004D8A6C /* Setting+Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272DF2C3502BE004D8A6C /* Setting+Theme.swift */; };
552-
FD2272E42C35134B004D8A6C /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272E32C35134B004D8A6C /* Data+Utilities.swift */; };
553552
FD2272E62C351378004D8A6C /* SUIKImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272E52C351378004D8A6C /* SUIKImageFormat.swift */; };
554553
FD2272EA2C351CA7004D8A6C /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272E92C351CA7004D8A6C /* Threading.swift */; };
555554
FD2272EC2C352155004D8A6C /* Feature.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272EB2C352155004D8A6C /* Feature.swift */; };
@@ -1911,7 +1910,6 @@
19111910
FD2272D72C34EDE6004D8A6C /* SnodeAPIEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPIEndpoint.swift; sourceTree = "<group>"; };
19121911
FD2272DC2C34EFFA004D8A6C /* AppSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSetup.swift; sourceTree = "<group>"; };
19131912
FD2272DF2C3502BE004D8A6C /* Setting+Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Setting+Theme.swift"; sourceTree = "<group>"; };
1914-
FD2272E32C35134B004D8A6C /* Data+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = "<group>"; };
19151913
FD2272E52C351378004D8A6C /* SUIKImageFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SUIKImageFormat.swift; sourceTree = "<group>"; };
19161914
FD2272E92C351CA7004D8A6C /* Threading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = "<group>"; };
19171915
FD2272EB2C352155004D8A6C /* Feature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Feature.swift; sourceTree = "<group>"; };
@@ -3314,7 +3312,6 @@
33143312
isa = PBXGroup;
33153313
children = (
33163314
94CD962F2E1B88430097754D /* CGRect+Utilities.swift */,
3317-
FD2272E32C35134B004D8A6C /* Data+Utilities.swift */,
33183315
FD848B9728422F1A000E298B /* Date+Utilities.swift */,
33193316
FD8A5B282DC060DD004C689B /* Double+Utilities.swift */,
33203317
94E9BC0C2C7BFBDA006984EA /* Localization+Style.swift */,
@@ -6076,7 +6073,6 @@
60766073
FDE5219C2E08E76C00061B8E /* SessionAsyncImage.swift in Sources */,
60776074
FD09B7E328865FDA00ED0B66 /* HighlightMentionBackgroundView.swift in Sources */,
60786075
FD3FAB632AEB9A1500DC5421 /* ToastController.swift in Sources */,
6079-
FD2272E42C35134B004D8A6C /* Data+Utilities.swift in Sources */,
60806076
C331FFE72558FB0000070591 /* SNTextField.swift in Sources */,
60816077
942256962C23F8DD00C0FDBF /* CompatibleScrollingVStack.swift in Sources */,
60826078
FD71165B28E6DDBC00B47552 /* StyledNavigationController.swift in Sources */,
@@ -8156,7 +8152,7 @@
81568152
CODE_SIGN_IDENTITY = "iPhone Developer";
81578153
COMPILE_LIB_SESSION = "";
81588154
COPY_PHASE_STRIP = NO;
8159-
CURRENT_PROJECT_VERSION = 622;
8155+
CURRENT_PROJECT_VERSION = 623;
81608156
ENABLE_BITCODE = NO;
81618157
ENABLE_STRICT_OBJC_MSGSEND = YES;
81628158
ENABLE_TESTABILITY = YES;
@@ -8196,7 +8192,7 @@
81968192
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
81978193
LIB_SESSION_SOURCE_DIR = "${SRCROOT}/../LibSession-Util";
81988194
LOCALIZED_STRING_SWIFTUI_SUPPORT = NO;
8199-
MARKETING_VERSION = 2.13.2;
8195+
MARKETING_VERSION = 2.14.0;
82008196
ONLY_ACTIVE_ARCH = YES;
82018197
OTHER_CFLAGS = "-Werror=protocol";
82028198
OTHER_SWIFT_FLAGS = "-D DEBUG -Xfrontend -warn-long-expression-type-checking=100";
@@ -8237,7 +8233,7 @@
82378233
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
82388234
CODE_SIGN_IDENTITY = "iPhone Distribution";
82398235
COMPILE_LIB_SESSION = "";
8240-
CURRENT_PROJECT_VERSION = 622;
8236+
CURRENT_PROJECT_VERSION = 623;
82418237
ENABLE_BITCODE = NO;
82428238
ENABLE_MODULE_VERIFIER = YES;
82438239
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -8272,7 +8268,7 @@
82728268
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
82738269
LIB_SESSION_SOURCE_DIR = "${SRCROOT}/../LibSession-Util";
82748270
LOCALIZED_STRING_SWIFTUI_SUPPORT = NO;
8275-
MARKETING_VERSION = 2.13.2;
8271+
MARKETING_VERSION = 2.14.0;
82768272
ONLY_ACTIVE_ARCH = NO;
82778273
OTHER_CFLAGS = (
82788274
"-DNS_BLOCK_ASSERTIONS=1",
@@ -8718,7 +8714,7 @@
87188714
CODE_SIGN_IDENTITY = "iPhone Developer";
87198715
COMPILE_LIB_SESSION = YES;
87208716
COPY_PHASE_STRIP = NO;
8721-
CURRENT_PROJECT_VERSION = 622;
8717+
CURRENT_PROJECT_VERSION = 623;
87228718
ENABLE_BITCODE = NO;
87238719
ENABLE_STRICT_OBJC_MSGSEND = YES;
87248720
ENABLE_TESTABILITY = YES;
@@ -8757,7 +8753,7 @@
87578753
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
87588754
LIB_SESSION_SOURCE_DIR = "${SRCROOT}/../LibSession-Util";
87598755
LOCALIZED_STRING_SWIFTUI_SUPPORT = NO;
8760-
MARKETING_VERSION = 2.13.2;
8756+
MARKETING_VERSION = 2.14.0;
87618757
ONLY_ACTIVE_ARCH = YES;
87628758
OTHER_CFLAGS = (
87638759
"-fobjc-arc-exceptions",
@@ -9305,7 +9301,7 @@
93059301
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
93069302
CODE_SIGN_IDENTITY = "iPhone Distribution";
93079303
COMPILE_LIB_SESSION = YES;
9308-
CURRENT_PROJECT_VERSION = 622;
9304+
CURRENT_PROJECT_VERSION = 623;
93099305
ENABLE_BITCODE = NO;
93109306
ENABLE_STRICT_OBJC_MSGSEND = YES;
93119307
GCC_NO_COMMON_BLOCKS = YES;
@@ -9338,7 +9334,7 @@
93389334
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
93399335
LIB_SESSION_SOURCE_DIR = "${SRCROOT}/../LibSession-Util";
93409336
LOCALIZED_STRING_SWIFTUI_SUPPORT = NO;
9341-
MARKETING_VERSION = 2.13.2;
9337+
MARKETING_VERSION = 2.14.0;
93429338
ONLY_ACTIVE_ARCH = NO;
93439339
OTHER_CFLAGS = (
93449340
"-DNS_BLOCK_ASSERTIONS=1",

Session/Conversations/Message Cells/Content Views/QuoteView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ final class QuoteView: UIView {
131131
}
132132

133133
// Generate the thumbnail if needed
134-
imageView.loadThumbnail(size: .medium, attachment: attachment, using: dependencies) { [weak imageView] success in
134+
imageView.loadThumbnail(size: .small, attachment: attachment, using: dependencies) { [weak imageView] success in
135135
guard success else { return }
136136

137137
imageView?.contentMode = .scaleAspectFill

Session/Conversations/Message Cells/Content Views/SwiftUI/QuoteView_SwiftUI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ struct QuoteView_SwiftUI: View {
8383

8484
SessionAsyncImage(
8585
attachment: attachment,
86-
thumbnailSize: .medium,
86+
thumbnailSize: .small,
8787
using: dependencies
8888
) { image in
8989
image

Session/Media Viewing & Editing/MediaTileViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,7 @@ class GalleryGridCellItem: PhotoGridItem {
893893
var source: ImageDataManager.DataSource {
894894
ImageDataManager.DataSource.thumbnailFrom(
895895
attachment: galleryItem.attachment,
896-
size: .medium,
896+
size: .small,
897897
using: dependencies
898898
) ?? .image("", nil)
899899
}

Session/Media Viewing & Editing/PhotoLibrary.swift

Lines changed: 70 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,15 @@ class PhotoPickerAssetItem: PhotoGridItem {
5252
return .video
5353
}
5454

55-
// TODO show GIF badge?
55+
if asset.utType?.isAnimated == true {
56+
return .animated
57+
}
5658

5759
return .photo
5860
}
5961

6062
var source: ImageDataManager.DataSource {
61-
return .closureThumbnail(self.asset.localIdentifier, size) { [photoCollectionContents, asset, size, pixelDimension] in
63+
return .asyncSource(self.asset.localIdentifier) { [photoCollectionContents, asset, size, pixelDimension] in
6264
await photoCollectionContents.requestThumbnail(
6365
for: asset,
6466
size: size,
@@ -148,44 +150,70 @@ class PhotoCollectionContents {
148150

149151
// MARK: ImageManager
150152

151-
func requestThumbnail(for asset: PHAsset, size: ImageDataManager.ThumbnailSize, thumbnailSize: CGSize) async -> UIImage? {
153+
func requestThumbnail(for asset: PHAsset, size: ImageDataManager.ThumbnailSize, thumbnailSize: CGSize) async -> ImageDataManager.DataSource? {
152154
var hasResumed: Bool = false
153155

154-
return await withCheckedContinuation { [imageManager] continuation in
155-
let options = PHImageRequestOptions()
156-
157-
switch size {
158-
case .small: options.deliveryMode = .opportunistic
159-
case .medium, .large: options.deliveryMode = .highQualityFormat
160-
}
161-
162-
imageManager.requestImage(
163-
for: asset,
164-
targetSize: thumbnailSize,
165-
contentMode: .aspectFill,
166-
options: options
167-
) { image, info in
168-
guard !hasResumed else { return }
169-
guard
170-
info?[PHImageErrorKey] == nil,
171-
(info?[PHImageCancelledKey] as? Bool) != true
172-
else {
173-
hasResumed = true
174-
return continuation.resume(returning: nil)
156+
/// The `requestImage` function will always return a static thumbnail so if it's an animated image then we need custom
157+
/// handling (the default PhotoKit resizing can't resize animated images so we need to return the original file)
158+
switch asset.utType?.isAnimated {
159+
case .some(true):
160+
return await withCheckedContinuation { [imageManager] continuation in
161+
let options = PHImageRequestOptions()
162+
options.deliveryMode = .highQualityFormat
163+
options.isNetworkAccessAllowed = true
164+
165+
imageManager.requestImageDataAndOrientation(for: asset, options: options) { data, uti, orientation, info in
166+
guard !hasResumed else { return }
167+
168+
guard let data = data, info?[PHImageErrorKey] == nil else {
169+
hasResumed = true
170+
continuation.resume(returning: nil)
171+
return
172+
}
173+
174+
// Successfully fetched the data, resume with the animated result
175+
hasResumed = true
176+
continuation.resume(returning: .data(asset.localIdentifier, data))
177+
}
175178
}
176179

177-
switch size {
178-
case .small: break // We want the first image, whether it is degraded or not
179-
case .medium, .large:
180-
// For medium and large thumbnails we want the full image so ignore any
181-
// degraded images
182-
guard (info?[PHImageResultIsDegradedKey] as? Bool) != true else { return }
183-
180+
default:
181+
return await withCheckedContinuation { [imageManager] continuation in
182+
let options = PHImageRequestOptions()
183+
184+
switch size {
185+
case .small: options.deliveryMode = .opportunistic
186+
case .medium, .large: options.deliveryMode = .highQualityFormat
187+
}
188+
189+
imageManager.requestImage(
190+
for: asset,
191+
targetSize: thumbnailSize,
192+
contentMode: .aspectFill,
193+
options: options
194+
) { image, info in
195+
guard !hasResumed else { return }
196+
guard
197+
info?[PHImageErrorKey] == nil,
198+
(info?[PHImageCancelledKey] as? Bool) != true
199+
else {
200+
hasResumed = true
201+
return continuation.resume(returning: nil)
202+
}
203+
204+
switch size {
205+
case .small: break // We want the first image, whether it is degraded or not
206+
case .medium, .large:
207+
// For medium and large thumbnails we want the full image so ignore any
208+
// degraded images
209+
guard (info?[PHImageResultIsDegradedKey] as? Bool) != true else { return }
210+
211+
}
212+
213+
continuation.resume(returning: .image("\(asset.localIdentifier)-\(size)", image))
214+
hasResumed = true
215+
}
184216
}
185-
186-
continuation.resume(returning: image)
187-
hasResumed = true
188-
}
189217
}
190218
}
191219

@@ -482,3 +510,10 @@ class PhotoLibrary: NSObject, PHPhotoLibraryChangeObserver {
482510
return collections
483511
}
484512
}
513+
514+
private extension PHAsset {
515+
var utType: UTType? {
516+
return (value(forKey: "uniformTypeIdentifier") as? String) // stringlint:ignore
517+
.map { UTType($0) }
518+
}
519+
}

Session/Utilities/ImageLoading+Convenience.swift

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -96,34 +96,6 @@ public extension ImageDataManagerType {
9696

9797
load(source, onComplete: onComplete)
9898
}
99-
// TODO: Is this needeed????
100-
func cachedImage(
101-
attachment: Attachment,
102-
using dependencies: Dependencies
103-
) -> UIImage? {
104-
guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.from(
105-
attachment: attachment,
106-
using: dependencies
107-
) else { return nil }
108-
109-
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
110-
var result: ImageDataManager.ProcessedImageData? = nil
111-
112-
load(source) { imageData in
113-
result = imageData
114-
semaphore.signal()
115-
}
116-
117-
/// We don't really want to wait at all but it's async logic so give it a very time timeout so it has the chance
118-
/// to deal with other logic running
119-
_ = semaphore.wait(timeout: .now() + .milliseconds(10))
120-
121-
switch result?.type {
122-
case .staticImage(let image): return image
123-
case .animatedImage(let frames, _): return frames.first
124-
case .none: return nil
125-
}
126-
}
12799
}
128100

129101
// MARK: - SessionImageView Convenience

0 commit comments

Comments
 (0)