diff --git a/Tusker/Views/GIFImageView.swift b/Packages/TuskerComponents/Sources/TuskerComponents/GIFImageView.swift similarity index 58% rename from Tusker/Views/GIFImageView.swift rename to Packages/TuskerComponents/Sources/TuskerComponents/GIFImageView.swift index 2b43f816..294e8492 100644 --- a/Tusker/Views/GIFImageView.swift +++ b/Packages/TuskerComponents/Sources/TuskerComponents/GIFImageView.swift @@ -1,6 +1,6 @@ // // GIFImageView.swift -// Tusker +// TuskerComponents // // Created by Shadowfacts on 11/11/21. // Copyright © 2021 Shadowfacts. All rights reserved. @@ -8,36 +8,36 @@ import UIKit -class GIFImageView: UIImageView { +open class GIFImageView: UIImageView { - fileprivate(set) var gifController: GIFController? = nil - var isAnimatingGIF: Bool { gifController?.state == .playing } + public fileprivate(set) var gifController: GIFController? = nil + public var isAnimatingGIF: Bool { gifController?.state == .playing } /// Detaches the current GIF controller from this view. /// If this view is the GIF controller's only one, it will stop itself. - func detachGIFController() { + public func detachGIFController() { gifController?.detach(from: self) } } /// A `GIFController` controls the animation of one or more `GIFImageView`s. -class GIFController { +public class GIFController { // GIFImageView strongly holds the controller so that when the last view detaches, the controller is freed private var imageViews = WeakArray() - private(set) var gifData: Data + public let gifData: Data private(set) var state: State = .stopped - private(set) var lastFrame: (image: UIImage, index: Int)? = nil + public private(set) var lastFrame: (image: UIImage, index: Int)? = nil - init(gifData: Data) { + public init(gifData: Data) { self.gifData = gifData } /// Attaches another view to this controller, letting it play back alongside the others. /// Immediately brings it into sync with the others, setting the last frame if there was one. - func attach(to view: GIFImageView) { + public func attach(to view: GIFImageView) { imageViews.append(view) view.gifController = self @@ -49,13 +49,13 @@ class GIFController { /// Detaches the given view from this controller. /// If no views attached views remain, the last strong reference to this controller is nilled out /// and image animation will stop at the next CGAnimateImageDataWithBlock callback. - func detach(from view: GIFImageView) { + public func detach(from view: GIFImageView) { // todo: does === work the way i want here imageViews.removeAll(where: { $0 === view }) view.gifController = nil } - func startAnimating() { + public func startAnimating() { guard state.shouldStop else { return } state = .playing @@ -74,7 +74,7 @@ class GIFController { } } - func stopAnimating() { + public func stopAnimating() { guard state == .playing else { return } state = .stopping @@ -89,3 +89,47 @@ class GIFController { } } + +private class WeakHolder { + weak var object: T? + + init(_ object: T?) { + self.object = object + } +} + +private struct WeakArray: MutableCollection, RangeReplaceableCollection { + private var array: [WeakHolder] + + var startIndex: Int { array.startIndex } + var endIndex: Int { array.endIndex } + + init() { + array = [] + } + + init(_ elements: [Element]) { + array = elements.map { WeakHolder($0) } + } + + init(_ elements: [Element?]) { + array = elements.map { WeakHolder($0) } + } + + subscript(position: Int) -> Element? { + get { + array[position].object + } + set(newValue) { + array[position] = WeakHolder(newValue) + } + } + + func index(after i: Int) -> Int { + return array.index(after: i) + } + + mutating func replaceSubrange(_ subrange: Range, with newElements: C) where C : Collection, Self.Element == C.Element { + array.replaceSubrange(subrange, with: newElements.map { WeakHolder($0) }) + } +} diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index aa6674c7..376e2314 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -332,7 +332,6 @@ D6D9498D298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */; }; D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLable.swift */; }; D6DD2A3F273C1F4900386A6C /* ComposeAttachmentImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD2A3E273C1F4900386A6C /* ComposeAttachmentImage.swift */; }; - D6DD2A45273D6C5700386A6C /* GIFImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD2A44273D6C5700386A6C /* GIFImageView.swift */; }; D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */; }; D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */; }; D6DD8FFD298495A8002AD3FD /* LogoutService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD8FFC298495A8002AD3FD /* LogoutService.swift */; }; @@ -758,7 +757,6 @@ D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = ""; }; D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = ""; }; D6DD2A3E273C1F4900386A6C /* ComposeAttachmentImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAttachmentImage.swift; sourceTree = ""; }; - D6DD2A44273D6C5700386A6C /* GIFImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GIFImageView.swift; sourceTree = ""; }; D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWarningCopyMode.swift; sourceTree = ""; }; D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preferences+Notification.swift"; sourceTree = ""; }; D6DD8FFC298495A8002AD3FD /* LogoutService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutService.swift; sourceTree = ""; }; @@ -1459,7 +1457,6 @@ D61F75B8293C15A000C0B37F /* ZeroHeightCollectionViewCell.swift */, D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */, D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */, - D6DD2A44273D6C5700386A6C /* GIFImageView.swift */, D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */, D620483323D3801D008A63EF /* LinkTextView.swift */, D61A45E728DF477D002BE511 /* LoadingCollectionViewCell.swift */, @@ -2145,7 +2142,6 @@ D6C143DA253510F4007DC240 /* ComposeEmojiTextField.swift in Sources */, D6DD2A3F273C1F4900386A6C /* ComposeAttachmentImage.swift in Sources */, D65B4B6229771A3F00DABDFB /* FetchStatusService.swift in Sources */, - D6DD2A45273D6C5700386A6C /* GIFImageView.swift in Sources */, D61F759029353B4300C0B37F /* FileManager+Size.swift in Sources */, 0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */, D6945C2F23AC47C3005C403C /* SavedDataManager.swift in Sources */, diff --git a/Tusker/Screens/Attachment Gallery/AttachmentPreviewViewController.swift b/Tusker/Screens/Attachment Gallery/AttachmentPreviewViewController.swift index bf0f7cc2..92e5f504 100644 --- a/Tusker/Screens/Attachment Gallery/AttachmentPreviewViewController.swift +++ b/Tusker/Screens/Attachment Gallery/AttachmentPreviewViewController.swift @@ -8,6 +8,7 @@ import UIKit import Pachyderm +import TuskerComponents class AttachmentPreviewViewController: UIViewController { diff --git a/Tusker/Screens/Compose/ComposeAttachmentImage.swift b/Tusker/Screens/Compose/ComposeAttachmentImage.swift index a0ffd2c6..dd439d68 100644 --- a/Tusker/Screens/Compose/ComposeAttachmentImage.swift +++ b/Tusker/Screens/Compose/ComposeAttachmentImage.swift @@ -8,6 +8,7 @@ import SwiftUI import Photos +import TuskerComponents struct ComposeAttachmentImage: View { let attachment: CompositionAttachment diff --git a/Tusker/Screens/Large Image/LargeImageContentView.swift b/Tusker/Screens/Large Image/LargeImageContentView.swift index 197d2bc8..f36515ce 100644 --- a/Tusker/Screens/Large Image/LargeImageContentView.swift +++ b/Tusker/Screens/Large Image/LargeImageContentView.swift @@ -10,6 +10,7 @@ import UIKit import Pachyderm @preconcurrency import AVFoundation @preconcurrency import VisionKit +import TuskerComponents protocol LargeImageContentView: UIView { var animationImage: UIImage? { get } diff --git a/Tusker/Screens/Large Image/LoadingLargeImageViewController.swift b/Tusker/Screens/Large Image/LoadingLargeImageViewController.swift index 0aa8397a..86da2597 100644 --- a/Tusker/Screens/Large Image/LoadingLargeImageViewController.swift +++ b/Tusker/Screens/Large Image/LoadingLargeImageViewController.swift @@ -7,6 +7,7 @@ import UIKit import Pachyderm +import TuskerComponents class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableViewController { diff --git a/Tusker/Screens/Large Image/Transitions/LargeImageExpandAnimationController.swift b/Tusker/Screens/Large Image/Transitions/LargeImageExpandAnimationController.swift index f59689c8..20d0a793 100644 --- a/Tusker/Screens/Large Image/Transitions/LargeImageExpandAnimationController.swift +++ b/Tusker/Screens/Large Image/Transitions/LargeImageExpandAnimationController.swift @@ -7,6 +7,7 @@ // import UIKit +import TuskerComponents protocol LargeImageAnimatableViewController: UIViewController { var animationSourceView: UIImageView? { get } diff --git a/Tusker/Screens/Large Image/Transitions/LargeImageShrinkAnimationController.swift b/Tusker/Screens/Large Image/Transitions/LargeImageShrinkAnimationController.swift index dab88290..e7366beb 100644 --- a/Tusker/Screens/Large Image/Transitions/LargeImageShrinkAnimationController.swift +++ b/Tusker/Screens/Large Image/Transitions/LargeImageShrinkAnimationController.swift @@ -7,6 +7,7 @@ // import UIKit +import TuskerComponents class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTransitioning { diff --git a/Tusker/Views/Attachments/AttachmentView.swift b/Tusker/Views/Attachments/AttachmentView.swift index ec81c0aa..75e727a2 100644 --- a/Tusker/Views/Attachments/AttachmentView.swift +++ b/Tusker/Views/Attachments/AttachmentView.swift @@ -9,6 +9,7 @@ import UIKit import Pachyderm import AVFoundation +import TuskerComponents protocol AttachmentViewDelegate: AnyObject { func attachmentViewGallery(startingAt index: Int) -> GalleryViewController?