diff options
Diffstat (limited to 'modules/expo-bluesky-gif-view/ios/GifView.swift')
-rw-r--r-- | modules/expo-bluesky-gif-view/ios/GifView.swift | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/modules/expo-bluesky-gif-view/ios/GifView.swift b/modules/expo-bluesky-gif-view/ios/GifView.swift new file mode 100644 index 000000000..de722d7a6 --- /dev/null +++ b/modules/expo-bluesky-gif-view/ios/GifView.swift @@ -0,0 +1,185 @@ +import ExpoModulesCore +import SDWebImage +import SDWebImageWebPCoder + +typealias SDWebImageContext = [SDWebImageContextOption: Any] + +public class GifView: ExpoView, AVPlayerViewControllerDelegate { + // Events + private let onPlayerStateChange = EventDispatcher() + + // SDWebImage + private let imageView = SDAnimatedImageView(frame: .zero) + private let imageManager = SDWebImageManager( + cache: SDImageCache.shared, + loader: SDImageLoadersManager.shared + ) + private var isPlaying = true + private var isLoaded = false + + // Requests + private var webpOperation: SDWebImageCombinedOperation? + private var placeholderOperation: SDWebImageCombinedOperation? + + // Props + var source: String? = nil + var placeholderSource: String? = nil + var autoplay = true { + didSet { + if !autoplay { + self.pause() + } else { + self.play() + } + } + } + + // MARK: - Lifecycle + + public required init(appContext: AppContext? = nil) { + super.init(appContext: appContext) + self.clipsToBounds = true + + self.imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.imageView.layer.masksToBounds = false + self.imageView.backgroundColor = .clear + self.imageView.contentMode = .scaleToFill + + // We have to explicitly set this to false. If we don't, every time + // the view comes into the viewport, it will start animating again + self.imageView.autoPlayAnimatedImage = false + + self.addSubview(self.imageView) + } + + public override func willMove(toWindow newWindow: UIWindow?) { + if newWindow == nil { + // Don't cancel the placeholder operation, because we really want that to complete for + // when we scroll back up + self.webpOperation?.cancel() + self.placeholderOperation?.cancel() + } else if self.imageView.image == nil { + self.load() + } + } + + // MARK: - Loading + + private func load() { + guard let source = self.source, let placeholderSource = self.placeholderSource else { + return + } + + self.webpOperation?.cancel() + self.placeholderOperation?.cancel() + + // We only need to start an operation for the placeholder if it doesn't exist + // in the cache already. Cache key is by default the absolute URL of the image. + // See: + // https://github.com/SDWebImage/SDWebImage/blob/master/Docs/HowToUse.md#using-asynchronous-image-caching-independently + if !SDImageCache.shared.diskImageDataExists(withKey: source), + let url = URL(string: placeholderSource) + { + self.placeholderOperation = imageManager.loadImage( + with: url, + options: [.retryFailed], + context: Util.createContext(), + progress: onProgress(_:_:_:), + completed: onLoaded(_:_:_:_:_:_:) + ) + } + + if let url = URL(string: source) { + self.webpOperation = imageManager.loadImage( + with: url, + options: [.retryFailed], + context: Util.createContext(), + progress: onProgress(_:_:_:), + completed: onLoaded(_:_:_:_:_:_:) + ) + } + } + + private func setImage(_ image: UIImage) { + if self.imageView.image == nil || image.sd_isAnimated { + self.imageView.image = image + } + + if image.sd_isAnimated { + self.firePlayerStateChange() + if isPlaying { + self.imageView.startAnimating() + } + } + } + + // MARK: - Loading blocks + + private func onProgress(_ receivedSize: Int, _ expectedSize: Int, _ imageUrl: URL?) {} + + private func onLoaded( + _ image: UIImage?, + _ data: Data?, + _ error: Error?, + _ cacheType: SDImageCacheType, + _ finished: Bool, + _ imageUrl: URL? + ) { + guard finished else { + return + } + + if let placeholderSource = self.placeholderSource, + imageUrl?.absoluteString == placeholderSource, + self.imageView.image == nil, + let image = image + { + self.setImage(image) + return + } + + if let source = self.source, + imageUrl?.absoluteString == source, + // UIImage perf suckssss if the image is animated + let data = data, + let animatedImage = SDAnimatedImage(data: data) + { + self.placeholderOperation?.cancel() + self.isPlaying = self.autoplay + self.isLoaded = true + self.setImage(animatedImage) + self.firePlayerStateChange() + } + } + + // MARK: - Playback Controls + + func play() { + self.imageView.startAnimating() + self.isPlaying = true + self.firePlayerStateChange() + } + + func pause() { + self.imageView.stopAnimating() + self.isPlaying = false + self.firePlayerStateChange() + } + + func toggle() { + if self.isPlaying { + self.pause() + } else { + self.play() + } + } + + // MARK: - Util + + private func firePlayerStateChange() { + onPlayerStateChange([ + "isPlaying": self.isPlaying, + "isLoaded": self.isLoaded + ]) + } +} |