parent
0abc28900c
commit
e6c6293c10
|
@ -7,10 +7,15 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
0411610022B442870030A9B7 /* AttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 041160FE22B442870030A9B7 /* AttachmentViewController.swift */; };
|
||||||
|
0411610122B442870030A9B7 /* AttachmentViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 041160FF22B442870030A9B7 /* AttachmentViewController.xib */; };
|
||||||
04496BD721625361001F1B23 /* ContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04496BD621625361001F1B23 /* ContentLabel.swift */; };
|
04496BD721625361001F1B23 /* ContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04496BD621625361001F1B23 /* ContentLabel.swift */; };
|
||||||
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450531E22B0097E00100BA2 /* Timline+UI.swift */; };
|
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450531E22B0097E00100BA2 /* Timline+UI.swift */; };
|
||||||
|
0454DDAF22B462EF00B8BB8E /* GalleryExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0454DDAE22B462EF00B8BB8E /* GalleryExpandAnimationController.swift */; };
|
||||||
|
0454DDB122B467AA00B8BB8E /* GalleryShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0454DDB022B467AA00B8BB8E /* GalleryShrinkAnimationController.swift */; };
|
||||||
0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0461A38F2163CBAE00C0A807 /* Cache.framework */; };
|
0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0461A38F2163CBAE00C0A807 /* Cache.framework */; };
|
||||||
0461A3912163CBAE00C0A807 /* Cache.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0461A38F2163CBAE00C0A807 /* Cache.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
0461A3912163CBAE00C0A807 /* Cache.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0461A38F2163CBAE00C0A807 /* Cache.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04D14BAE22B34A2800642648 /* GalleryViewController.swift */; };
|
||||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
|
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
||||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
||||||
|
@ -235,9 +240,14 @@
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
041160FE22B442870030A9B7 /* AttachmentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentViewController.swift; sourceTree = "<group>"; };
|
||||||
|
041160FF22B442870030A9B7 /* AttachmentViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AttachmentViewController.xib; sourceTree = "<group>"; };
|
||||||
04496BD621625361001F1B23 /* ContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLabel.swift; sourceTree = "<group>"; };
|
04496BD621625361001F1B23 /* ContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLabel.swift; sourceTree = "<group>"; };
|
||||||
0450531E22B0097E00100BA2 /* Timline+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timline+UI.swift"; sourceTree = "<group>"; };
|
0450531E22B0097E00100BA2 /* Timline+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timline+UI.swift"; sourceTree = "<group>"; };
|
||||||
|
0454DDAE22B462EF00B8BB8E /* GalleryExpandAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryExpandAnimationController.swift; sourceTree = "<group>"; };
|
||||||
|
0454DDB022B467AA00B8BB8E /* GalleryShrinkAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryShrinkAnimationController.swift; sourceTree = "<group>"; };
|
||||||
0461A38F2163CBAE00C0A807 /* Cache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Cache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
0461A38F2163CBAE00C0A807 /* Cache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Cache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
04D14BAE22B34A2800642648 /* GalleryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryViewController.swift; sourceTree = "<group>"; };
|
||||||
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
|
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
|
||||||
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
||||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -455,6 +465,33 @@
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
0411610422B4571E0030A9B7 /* Attachment */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
041160FE22B442870030A9B7 /* AttachmentViewController.swift */,
|
||||||
|
041160FF22B442870030A9B7 /* AttachmentViewController.xib */,
|
||||||
|
);
|
||||||
|
path = Attachment;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
0411610522B457290030A9B7 /* Gallery */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0411610622B457360030A9B7 /* Transitions */,
|
||||||
|
04D14BAE22B34A2800642648 /* GalleryViewController.swift */,
|
||||||
|
);
|
||||||
|
path = Gallery;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
0411610622B457360030A9B7 /* Transitions */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0454DDAE22B462EF00B8BB8E /* GalleryExpandAnimationController.swift */,
|
||||||
|
0454DDB022B467AA00B8BB8E /* GalleryShrinkAnimationController.swift */,
|
||||||
|
);
|
||||||
|
path = Transitions;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D60A548C21ED515800F1F87C /* GMImagePicker */ = {
|
D60A548C21ED515800F1F87C /* GMImagePicker */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -617,6 +654,8 @@
|
||||||
D641C786213DD852004B4513 /* Notifications */,
|
D641C786213DD852004B4513 /* Notifications */,
|
||||||
D641C787213DD862004B4513 /* Compose */,
|
D641C787213DD862004B4513 /* Compose */,
|
||||||
D641C788213DD86D004B4513 /* Large Image */,
|
D641C788213DD86D004B4513 /* Large Image */,
|
||||||
|
0411610422B4571E0030A9B7 /* Attachment */,
|
||||||
|
0411610522B457290030A9B7 /* Gallery */,
|
||||||
D641C789213DD87E004B4513 /* Preferences */,
|
D641C789213DD87E004B4513 /* Preferences */,
|
||||||
);
|
);
|
||||||
path = Screens;
|
path = Screens;
|
||||||
|
@ -1261,6 +1300,7 @@
|
||||||
D663626621360DD700C9CBA2 /* Preferences.storyboard in Resources */,
|
D663626621360DD700C9CBA2 /* Preferences.storyboard in Resources */,
|
||||||
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
||||||
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */,
|
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */,
|
||||||
|
0411610122B442870030A9B7 /* AttachmentViewController.xib in Resources */,
|
||||||
D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */,
|
D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */,
|
||||||
D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */,
|
D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */,
|
||||||
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */,
|
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */,
|
||||||
|
@ -1357,10 +1397,12 @@
|
||||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
||||||
D6C693F92162E4DB007D6A6D /* StatusContentLabel.swift in Sources */,
|
D6C693F92162E4DB007D6A6D /* StatusContentLabel.swift in Sources */,
|
||||||
D6D58DF922074B74009C8DD9 /* LinkLabel.swift in Sources */,
|
D6D58DF922074B74009C8DD9 /* LinkLabel.swift in Sources */,
|
||||||
|
0454DDAF22B462EF00B8BB8E /* GalleryExpandAnimationController.swift in Sources */,
|
||||||
D6285B5121EA6E6E00FE4B39 /* AdvancedTableViewController.swift in Sources */,
|
D6285B5121EA6E6E00FE4B39 /* AdvancedTableViewController.swift in Sources */,
|
||||||
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */,
|
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */,
|
||||||
D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */,
|
D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */,
|
||||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
||||||
|
0411610022B442870030A9B7 /* AttachmentViewController.swift in Sources */,
|
||||||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
||||||
D66362712136338600C9CBA2 /* ComposeViewController.swift in Sources */,
|
D66362712136338600C9CBA2 /* ComposeViewController.swift in Sources */,
|
||||||
D627FF81217FE8F400CC0648 /* BehaviorTableViewController.swift in Sources */,
|
D627FF81217FE8F400CC0648 /* BehaviorTableViewController.swift in Sources */,
|
||||||
|
@ -1369,6 +1411,7 @@
|
||||||
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */,
|
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */,
|
||||||
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */,
|
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */,
|
||||||
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */,
|
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */,
|
||||||
|
0454DDB122B467AA00B8BB8E /* GalleryShrinkAnimationController.swift in Sources */,
|
||||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
||||||
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */,
|
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */,
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
||||||
|
@ -1409,6 +1452,7 @@
|
||||||
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */,
|
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */,
|
||||||
D663626A2136163000C9CBA2 /* PreferencesAdaptive.swift in Sources */,
|
D663626A2136163000C9CBA2 /* PreferencesAdaptive.swift in Sources */,
|
||||||
D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */,
|
D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */,
|
||||||
|
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */,
|
||||||
D641C77D213CB024004B4513 /* FollowNotificationTableViewCell.swift in Sources */,
|
D641C77D213CB024004B4513 /* FollowNotificationTableViewCell.swift in Sources */,
|
||||||
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */,
|
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */,
|
||||||
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */,
|
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */,
|
||||||
|
|
|
@ -12,27 +12,31 @@ extension UIViewController: UIViewControllerTransitioningDelegate {
|
||||||
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||||
if presented is LargeImageViewController {
|
if presented is LargeImageViewController {
|
||||||
return LargeImageExpandAnimationController()
|
return LargeImageExpandAnimationController()
|
||||||
} else {
|
} else if presented is GalleryViewController {
|
||||||
return nil
|
return GalleryExpandAnimationController()
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||||
if let dismissed = dismissed as? LargeImageViewController {
|
if let dismissed = dismissed as? LargeImageViewController {
|
||||||
return LargeImageShrinkAnimationController(interactionController: dismissed.dismissInteractionController)
|
return LargeImageShrinkAnimationController(interactionController: dismissed.dismissInteractionController)
|
||||||
} else {
|
} else if let dismissed = dismissed as? GalleryViewController {
|
||||||
return nil
|
return GalleryShrinkAnimationController(interactionController: dismissed.dismissInteractionController)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
|
public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
|
||||||
if let animator = animator as? LargeImageShrinkAnimationController,
|
if let animator = animator as? LargeImageShrinkAnimationController,
|
||||||
let interactionController = animator.interactionController,
|
let interactionController = animator.interactionController,
|
||||||
interactionController.inProgress {
|
interactionController.inProgress {
|
||||||
|
|
||||||
return interactionController
|
return interactionController
|
||||||
} else {
|
} else if let animator = animator as? GalleryShrinkAnimationController,
|
||||||
return nil
|
let interactionController = animator.interactionController,
|
||||||
|
interactionController.inProgress {
|
||||||
|
return interactionController
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
// AttachmentViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/14/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class AttachmentViewController: UIViewController {
|
||||||
|
|
||||||
|
let attachment: Attachment
|
||||||
|
|
||||||
|
var largeImageVC: LargeImageViewController?
|
||||||
|
var loadingVC: LoadingViewController?
|
||||||
|
|
||||||
|
private var initialControlsVisible: Bool = true
|
||||||
|
var controlsVisible: Bool {
|
||||||
|
get {
|
||||||
|
return largeImageVC?.controlsVisible ?? initialControlsVisible
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if let largeImageVC = largeImageVC {
|
||||||
|
largeImageVC.setControlsVisible(newValue, animated: false)
|
||||||
|
} else {
|
||||||
|
initialControlsVisible = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var childForHomeIndicatorAutoHidden: UIViewController? {
|
||||||
|
return largeImageVC
|
||||||
|
}
|
||||||
|
|
||||||
|
init(attachment: Attachment) {
|
||||||
|
self.attachment = attachment
|
||||||
|
|
||||||
|
super.init(nibName: "AttachmentViewController", bundle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
if let data = ImageCache.attachments.get(attachment.url) {
|
||||||
|
createLargeImage(data: data)
|
||||||
|
} else {
|
||||||
|
loadingVC = LoadingViewController()
|
||||||
|
embedChild(loadingVC!)
|
||||||
|
ImageCache.attachments.get(attachment.url) { [weak self] (data) in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.loadingVC?.removeViewAndController()
|
||||||
|
self?.createLargeImage(data: data!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createLargeImage(data: Data) {
|
||||||
|
guard let image = UIImage(data: data) else { return }
|
||||||
|
largeImageVC = LargeImageViewController(image: image, description: attachment.description, sourceFrame: nil, sourceCornerRadius: nil)
|
||||||
|
largeImageVC!.initialControlsVisible = initialControlsVisible
|
||||||
|
largeImageVC!.shrinkGestureEnabled = false
|
||||||
|
if attachment.url.pathExtension == "gif" {
|
||||||
|
largeImageVC!.gifData = data
|
||||||
|
}
|
||||||
|
embedChild(largeImageVC!)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14810.11" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14766.13"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="AttachmentViewController" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<connections>
|
||||||
|
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
|
||||||
|
</connections>
|
||||||
|
</placeholder>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
|
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
|
||||||
|
<point key="canvasLocation" x="139" y="3"/>
|
||||||
|
</view>
|
||||||
|
</objects>
|
||||||
|
</document>
|
|
@ -0,0 +1,92 @@
|
||||||
|
// GalleryViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/13/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class GalleryViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
|
||||||
|
|
||||||
|
var dismissInteractionController: LargeImageInteractionController?
|
||||||
|
|
||||||
|
let attachments: [Attachment]
|
||||||
|
let sourcesInfo: [(CGRect, CGFloat)]
|
||||||
|
let startIndex: Int
|
||||||
|
|
||||||
|
let pages: [AttachmentViewController]
|
||||||
|
|
||||||
|
var currentIndex: Int {
|
||||||
|
guard let vc = viewControllers?.first as? AttachmentViewController,
|
||||||
|
let index = pages.firstIndex(of: vc) else {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
override var prefersStatusBarHidden: Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
override var childForHomeIndicatorAutoHidden: UIViewController? {
|
||||||
|
return
|
||||||
|
viewControllers?.first
|
||||||
|
}
|
||||||
|
|
||||||
|
init(attachments: [Attachment], sourcesInfo: [(CGRect, CGFloat)], startIndex: Int) {
|
||||||
|
self.attachments = attachments
|
||||||
|
self.sourcesInfo = sourcesInfo
|
||||||
|
self.startIndex = startIndex
|
||||||
|
|
||||||
|
self.pages = attachments.map(AttachmentViewController.init)
|
||||||
|
|
||||||
|
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal)
|
||||||
|
|
||||||
|
setViewControllers([pages[startIndex]], direction: .forward, animated: false)
|
||||||
|
|
||||||
|
modalPresentationStyle = .fullScreen
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
self.dataSource = self
|
||||||
|
self.delegate = self
|
||||||
|
|
||||||
|
dismissInteractionController = LargeImageInteractionController(viewController: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Page View Controller Data Source
|
||||||
|
|
||||||
|
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
|
||||||
|
guard let attachment = viewController as? AttachmentViewController,
|
||||||
|
let index = pages.firstIndex(of: attachment),
|
||||||
|
index > 0 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return pages[index - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
|
||||||
|
guard let attachment = viewController as? AttachmentViewController,
|
||||||
|
let index = pages.firstIndex(of: attachment),
|
||||||
|
index < pages.count - 1 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return pages[index + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Page View Controller Delegate
|
||||||
|
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
|
||||||
|
let pending = pendingViewControllers.first as! AttachmentViewController
|
||||||
|
let current = viewControllers!.first as! AttachmentViewController
|
||||||
|
pending.controlsVisible = current.controlsVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
// GalleryExpandAnimationController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/14/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Gifu
|
||||||
|
|
||||||
|
class GalleryExpandAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||||
|
|
||||||
|
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||||
|
return 0.2
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||||
|
guard let fromVC = transitionContext.viewController(forKey: .from),
|
||||||
|
let toVC = transitionContext.viewController(forKey: .to) as? GalleryViewController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let attachment = toVC.attachments[toVC.startIndex]
|
||||||
|
let (sourceFrame, sourceCornerRadius) = toVC.sourcesInfo[toVC.startIndex]
|
||||||
|
|
||||||
|
let finalVCFrame = transitionContext.finalFrame(for: toVC)
|
||||||
|
|
||||||
|
guard let data = ImageCache.attachments.get(attachment.url), let image = UIImage(data: data) else {
|
||||||
|
toVC.view.frame = finalVCFrame
|
||||||
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let ratio = image.size.width / image.size.height
|
||||||
|
var width = finalVCFrame.width
|
||||||
|
var height = width / ratio
|
||||||
|
let maxHeight = fromVC.view.bounds.height - fromVC.view.safeAreaInsets.top - fromVC.view.safeAreaInsets.bottom
|
||||||
|
if height > maxHeight {
|
||||||
|
let scaleFactor = maxHeight / height
|
||||||
|
width *= scaleFactor
|
||||||
|
height = maxHeight
|
||||||
|
}
|
||||||
|
let finalFrame = CGRect(x: finalVCFrame.midX - width / 2, y: finalVCFrame.midY - height / 2, width: width, height: height)
|
||||||
|
|
||||||
|
let containerView = transitionContext.containerView
|
||||||
|
|
||||||
|
let imageView = GIFImageView(frame: sourceFrame)
|
||||||
|
imageView.image = image
|
||||||
|
if attachment.url.pathExtension == "gif" {
|
||||||
|
imageView.animate(withGIFData: data)
|
||||||
|
}
|
||||||
|
imageView.contentMode = .scaleAspectFill
|
||||||
|
imageView.layer.cornerRadius = sourceCornerRadius
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
|
||||||
|
let blackView = UIView(frame: finalVCFrame)
|
||||||
|
blackView.backgroundColor = .black
|
||||||
|
blackView.alpha = 0
|
||||||
|
|
||||||
|
containerView.addSubview(toVC.view)
|
||||||
|
containerView.addSubview(blackView)
|
||||||
|
containerView.addSubview(imageView)
|
||||||
|
|
||||||
|
toVC.view.isHidden = true
|
||||||
|
|
||||||
|
let duration = transitionDuration(using: transitionContext)
|
||||||
|
UIView.animate(withDuration: duration, animations: {
|
||||||
|
imageView.frame = finalFrame
|
||||||
|
imageView.layer.cornerRadius = 0
|
||||||
|
blackView.alpha = 1
|
||||||
|
}, completion: { _ in
|
||||||
|
toVC.view.frame = finalVCFrame
|
||||||
|
|
||||||
|
toVC.view.isHidden = false
|
||||||
|
fromVC.view.isHidden = false
|
||||||
|
blackView.removeFromSuperview()
|
||||||
|
imageView.removeFromSuperview()
|
||||||
|
|
||||||
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
// GalleryShrinkAnimationController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/14/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Gifu
|
||||||
|
|
||||||
|
class GalleryShrinkAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||||
|
|
||||||
|
let interactionController: LargeImageInteractionController?
|
||||||
|
|
||||||
|
init(interactionController: LargeImageInteractionController?) {
|
||||||
|
self.interactionController = interactionController
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||||
|
return 0.2
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||||
|
guard let fromVC = transitionContext.viewController(forKey: .from) as? GalleryViewController,
|
||||||
|
let toVC = transitionContext.viewController(forKey: .to) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let (sourceFrame, sourceCornerRadius) = fromVC.sourcesInfo[fromVC.currentIndex]
|
||||||
|
let originalVCFrame = fromVC.view.frame
|
||||||
|
|
||||||
|
let attachment = fromVC.attachments[fromVC.currentIndex]
|
||||||
|
|
||||||
|
guard let data = ImageCache.attachments.get(attachment.url),
|
||||||
|
let image = UIImage(data: data) else {
|
||||||
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let ratio = image.size.width / image.size.height
|
||||||
|
var width = originalVCFrame.width
|
||||||
|
var height = width / ratio
|
||||||
|
let maxHeight = fromVC.view.bounds.height - fromVC.view.safeAreaInsets.top - fromVC.view.safeAreaInsets.bottom
|
||||||
|
if height > maxHeight {
|
||||||
|
let scaleFactor = maxHeight / height
|
||||||
|
width *= scaleFactor
|
||||||
|
height = maxHeight
|
||||||
|
}
|
||||||
|
let originalFrame = CGRect(x: originalVCFrame.midX - width / 2, y: originalVCFrame.midY - height / 2, width: width, height: height)
|
||||||
|
|
||||||
|
let imageView = GIFImageView(frame: originalFrame)
|
||||||
|
imageView.image = image
|
||||||
|
if attachment.url.pathExtension == "gif" {
|
||||||
|
imageView.animate(withGIFData: data)
|
||||||
|
}
|
||||||
|
imageView.contentMode = .scaleAspectFill
|
||||||
|
imageView.layer.cornerRadius = 0
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
|
||||||
|
let blackView = UIView(frame: originalVCFrame)
|
||||||
|
blackView.backgroundColor = .black
|
||||||
|
blackView.alpha = 1
|
||||||
|
|
||||||
|
let containerView = transitionContext.containerView
|
||||||
|
containerView.addSubview(toVC.view)
|
||||||
|
containerView.addSubview(blackView)
|
||||||
|
containerView.addSubview(imageView)
|
||||||
|
|
||||||
|
let duration = transitionDuration(using: transitionContext)
|
||||||
|
UIView.animate(withDuration: duration, animations: {
|
||||||
|
imageView.frame = sourceFrame
|
||||||
|
imageView.layer.cornerRadius = sourceCornerRadius
|
||||||
|
blackView.alpha = 0
|
||||||
|
}, completion: { _ in
|
||||||
|
blackView.removeFromSuperview()
|
||||||
|
imageView.removeFromSuperview()
|
||||||
|
|
||||||
|
if transitionContext.transitionWasCancelled {
|
||||||
|
toVC.view.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
import Photos
|
import Photos
|
||||||
import Gifu
|
import Gifu
|
||||||
|
|
||||||
|
@ -41,20 +42,13 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||||
var gifData: Data?
|
var gifData: Data?
|
||||||
var imageDescription: String?
|
var imageDescription: String?
|
||||||
|
|
||||||
var controlsVisible = true {
|
var initialControlsVisible = true
|
||||||
|
private(set) var controlsVisible = true {
|
||||||
didSet {
|
didSet {
|
||||||
setNeedsUpdateOfHomeIndicatorAutoHidden()
|
setNeedsUpdateOfHomeIndicatorAutoHidden()
|
||||||
|
|
||||||
UIView.animate(withDuration: 0.2) {
|
|
||||||
let topOffset = self.controlsVisible ? 0 : -self.topControlsView.bounds.height
|
|
||||||
self.topControlsView.transform = CGAffineTransform(translationX: 0, y: topOffset)
|
|
||||||
if self.imageDescription != nil {
|
|
||||||
let bottomOffset = self.controlsVisible ? 0 : self.bottomControlsView.bounds.height + self.view.safeAreaInsets.bottom
|
|
||||||
self.bottomControlsView.transform = CGAffineTransform(translationX: 0, y: bottomOffset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var shrinkGestureEnabled = true
|
||||||
|
|
||||||
var prevZoomScale: CGFloat?
|
var prevZoomScale: CGFloat?
|
||||||
|
|
||||||
|
@ -73,6 +67,8 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||||
self.originCornerRadius = sourceCornerRadius
|
self.originCornerRadius = sourceCornerRadius
|
||||||
|
|
||||||
super.init(nibName: "LargeImageViewController", bundle: nil)
|
super.init(nibName: "LargeImageViewController", bundle: nil)
|
||||||
|
|
||||||
|
modalPresentationStyle = .fullScreen
|
||||||
}
|
}
|
||||||
|
|
||||||
// init(gifData: Data?, description: String?, sourceFrame: CGRect, sourceCornerRadius: CGFloat, router: AppRouter) {
|
// init(gifData: Data?, description: String?, sourceFrame: CGRect, sourceCornerRadius: CGFloat, router: AppRouter) {
|
||||||
|
@ -92,6 +88,8 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
setControlsVisible(initialControlsVisible, animated: false)
|
||||||
|
|
||||||
imageView.image = image
|
imageView.image = image
|
||||||
if let gifData = gifData {
|
if let gifData = gifData {
|
||||||
imageView.animate(withGIFData: gifData)
|
imageView.animate(withGIFData: gifData)
|
||||||
|
@ -106,7 +104,9 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||||
bottomControlsView.isHidden = true
|
bottomControlsView.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissInteractionController = LargeImageInteractionController(viewController: self)
|
if shrinkGestureEnabled {
|
||||||
|
dismissInteractionController = LargeImageInteractionController(viewController: self)
|
||||||
|
}
|
||||||
|
|
||||||
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(scrollViewPressed(_:))))
|
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(scrollViewPressed(_:))))
|
||||||
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(scrollViewDoubleTapped(_:)))
|
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(scrollViewDoubleTapped(_:)))
|
||||||
|
@ -142,6 +142,26 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setControlsVisible(_ controlsVisible: Bool, animated: Bool) {
|
||||||
|
self.controlsVisible = controlsVisible
|
||||||
|
if animated {
|
||||||
|
UIView.animate(withDuration: 0.2) {
|
||||||
|
self.updateControlsView()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateControlsView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateControlsView() {
|
||||||
|
let topOffset = self.controlsVisible ? 0 : -self.topControlsView.bounds.height
|
||||||
|
self.topControlsView.transform = CGAffineTransform(translationX: 0, y: topOffset)
|
||||||
|
if self.imageDescription != nil {
|
||||||
|
let bottomOffset = self.controlsVisible ? 0 : self.bottomControlsView.bounds.height + self.view.safeAreaInsets.bottom
|
||||||
|
self.bottomControlsView.transform = CGAffineTransform(translationX: 0, y: bottomOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||||
return imageView
|
return imageView
|
||||||
|
@ -190,7 +210,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||||
if scrollView.zoomScale > scrollView.minimumZoomScale {
|
if scrollView.zoomScale > scrollView.minimumZoomScale {
|
||||||
animateZoomOut()
|
animateZoomOut()
|
||||||
} else {
|
} else {
|
||||||
controlsVisible = !controlsVisible
|
setControlsVisible(!controlsVisible, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ class ProfileTableViewController: EnhancedTableViewController, PreferencesAdapti
|
||||||
updateAccountUI()
|
updateAccountUI()
|
||||||
} else {
|
} else {
|
||||||
loadingVC = LoadingViewController()
|
loadingVC = LoadingViewController()
|
||||||
add(loadingVC!)
|
embedChild(loadingVC!)
|
||||||
MastodonCache.account(for: accountID) { (account) in
|
MastodonCache.account(for: accountID) { (account) in
|
||||||
guard account != nil else {
|
guard account != nil else {
|
||||||
let alert = UIAlertController(title: "Something Went Wrong", message: "Couldn't load the selected account", preferredStyle: .alert)
|
let alert = UIAlertController(title: "Something Went Wrong", message: "Couldn't load the selected account", preferredStyle: .alert)
|
||||||
|
@ -86,7 +86,7 @@ class ProfileTableViewController: EnhancedTableViewController, PreferencesAdapti
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
loadingVC = LoadingViewController()
|
loadingVC = LoadingViewController()
|
||||||
add(loadingVC!)
|
embedChild(loadingVC!)
|
||||||
shouldLoadOnAccountIDSet = true
|
shouldLoadOnAccountIDSet = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ class ProfileTableViewController: EnhancedTableViewController, PreferencesAdapti
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateAccountUI() {
|
func updateAccountUI() {
|
||||||
loadingVC?.remove()
|
loadingVC?.removeViewAndController()
|
||||||
|
|
||||||
updateUIForPreferences()
|
updateUIForPreferences()
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,10 @@ class LoadingViewController: UIViewController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
view.backgroundColor = .systemBackground
|
||||||
|
|
||||||
activityIndicator = UIActivityIndicatorView(style: .large)
|
activityIndicator = UIActivityIndicatorView(style: .large)
|
||||||
activityIndicator.color = .darkGray
|
activityIndicator.color = .secondaryLabel
|
||||||
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
|
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
view.addSubview(activityIndicator)
|
view.addSubview(activityIndicator)
|
||||||
|
|
|
@ -8,18 +8,80 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
// Based on MVCTodo by Dave DeLong: https://github.com/davedelong/MVCTodo/blob/841649dd6aa31bacda3ad7ef9a9a836f66281e50/MVCTodo/Extensions/UIViewController.swift
|
||||||
extension UIViewController {
|
extension UIViewController {
|
||||||
func add(_ child: UIViewController) {
|
func embedChild(_ newChild: UIViewController, in container: UIView? = nil) {
|
||||||
addChild(child)
|
// if the view controller is already a child of something else, remove it
|
||||||
view.addSubview(child.view)
|
if let oldParent = newChild.parent, oldParent != self {
|
||||||
child.didMove(toParent: self)
|
newChild.beginAppearanceTransition(false, animated: false)
|
||||||
|
newChild.willMove(toParent: nil)
|
||||||
|
newChild.removeFromParent()
|
||||||
|
|
||||||
|
if newChild.viewIfLoaded?.superview != nil {
|
||||||
|
newChild.viewIfLoaded?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
newChild.endAppearanceTransition()
|
||||||
|
}
|
||||||
|
|
||||||
|
// since .view returns an IUO, by default the type of this is "UIView?"
|
||||||
|
// explicitly type the variable because We Know Better™
|
||||||
|
var targetContainer: UIView = container ?? self.view
|
||||||
|
if !targetContainer.isContainedWithin(view) {
|
||||||
|
targetContainer = view
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the view controller as a child
|
||||||
|
if newChild.parent != self {
|
||||||
|
newChild.beginAppearanceTransition(true, animated: false)
|
||||||
|
addChild(newChild)
|
||||||
|
newChild.didMove(toParent: self)
|
||||||
|
targetContainer.embedSubview(newChild.view)
|
||||||
|
newChild.endAppearanceTransition()
|
||||||
|
} else {
|
||||||
|
// the view controller is already a child
|
||||||
|
// make sure it's in the right view
|
||||||
|
|
||||||
|
// we don't do the appearance transition stuff here,
|
||||||
|
// because the vc is already a child, so *presumably*
|
||||||
|
// that transition stuff has already appened
|
||||||
|
targetContainer.embedSubview(newChild.view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove() {
|
func removeViewAndController() {
|
||||||
guard parent != nil else { return }
|
|
||||||
|
|
||||||
willMove(toParent: nil)
|
|
||||||
removeFromParent()
|
|
||||||
view.removeFromSuperview()
|
view.removeFromSuperview()
|
||||||
|
removeFromParent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Based on MVCTodo by Dave DeLong: https://github.com/davedelong/MVCTodo/blob/841649dd6aa31bacda3ad7ef9a9a836f66281e50/MVCTodo/Extensions/UIView.swift
|
||||||
|
extension UIView {
|
||||||
|
func embedSubview(_ subview: UIView) {
|
||||||
|
if subview.superview == self { return }
|
||||||
|
|
||||||
|
if subview.superview != nil {
|
||||||
|
subview.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
subview.frame = bounds
|
||||||
|
addSubview(subview)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
subview.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
subview.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
subview.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
subview.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func isContainedWithin(_ other: UIView) -> Bool {
|
||||||
|
var current: UIView? = self
|
||||||
|
while let proposedView = current {
|
||||||
|
if proposedView == other { return true }
|
||||||
|
current = proposedView.superview
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,10 @@ protocol TuskerNavigationDelegate {
|
||||||
|
|
||||||
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIView)
|
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIView)
|
||||||
|
|
||||||
|
func gallery(attachments: [Attachment], sourceViews: [UIView], startIndex: Int) -> GalleryViewController
|
||||||
|
|
||||||
|
func showGallery(attachments: [Attachment], sourceViews: [UIView], startIndex: Int)
|
||||||
|
|
||||||
func showMoreOptions(forStatus statusID: String)
|
func showMoreOptions(forStatus statusID: String)
|
||||||
|
|
||||||
func showMoreOptions(forURL url: URL)
|
func showMoreOptions(forURL url: URL)
|
||||||
|
@ -95,7 +99,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
||||||
present(vc, animated: true)
|
present(vc, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func largeImage(_ image: UIImage, description: String?, sourceView: UIView) -> LargeImageViewController {
|
private func sourceViewInfo(_ sourceView: UIView) -> (CGRect, CGFloat) {
|
||||||
var sourceFrame = sourceView.convert(sourceView.bounds, to: view)
|
var sourceFrame = sourceView.convert(sourceView.bounds, to: view)
|
||||||
if let scrollView = view as? UIScrollView {
|
if let scrollView = view as? UIScrollView {
|
||||||
let scale = scrollView.zoomScale
|
let scale = scrollView.zoomScale
|
||||||
|
@ -105,28 +109,21 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
||||||
let y = sourceFrame.minY * scale - scrollView.contentOffset.y + scrollView.frame.minY
|
let y = sourceFrame.minY * scale - scrollView.contentOffset.y + scrollView.frame.minY
|
||||||
sourceFrame = CGRect(x: x, y: y, width: width, height: height)
|
sourceFrame = CGRect(x: x, y: y, width: width, height: height)
|
||||||
}
|
}
|
||||||
let sourceCornerRadius = sourceView.layer.cornerRadius
|
return (sourceFrame, sourceView.layer.cornerRadius)
|
||||||
|
}
|
||||||
|
|
||||||
|
func largeImage(_ image: UIImage, description: String?, sourceView: UIView) -> LargeImageViewController {
|
||||||
|
let (sourceFrame, sourceCornerRadius) = sourceViewInfo(sourceView)
|
||||||
let vc = LargeImageViewController(image: image, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius)
|
let vc = LargeImageViewController(image: image, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius)
|
||||||
vc.transitioningDelegate = self
|
vc.transitioningDelegate = self
|
||||||
vc.modalPresentationStyle = .fullScreen
|
|
||||||
return vc
|
return vc
|
||||||
}
|
}
|
||||||
|
|
||||||
func largeImage(gifData: Data, description: String?, sourceView: UIView) -> LargeImageViewController {
|
func largeImage(gifData: Data, description: String?, sourceView: UIView) -> LargeImageViewController {
|
||||||
var sourceFrame = sourceView.convert(sourceView.bounds, to: view)
|
let (sourceFrame, sourceCornerRadius) = sourceViewInfo(sourceView)
|
||||||
if let scrollView = view as? UIScrollView {
|
|
||||||
let scale = scrollView.zoomScale
|
|
||||||
let width = sourceFrame.width * scale
|
|
||||||
let height = sourceFrame.height * scale
|
|
||||||
let x = sourceFrame.minX * scale - scrollView.contentOffset.x + scrollView.frame.minX
|
|
||||||
let y = sourceFrame.minY * scale - scrollView.contentOffset.y + scrollView.frame.minY
|
|
||||||
sourceFrame = CGRect(x: x, y: y, width: width, height: height)
|
|
||||||
}
|
|
||||||
let sourceCornerRadius = sourceView.layer.cornerRadius
|
|
||||||
let vc = LargeImageViewController(image: UIImage(data: gifData)!, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius)
|
let vc = LargeImageViewController(image: UIImage(data: gifData)!, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius)
|
||||||
vc.transitioningDelegate = self
|
vc.transitioningDelegate = self
|
||||||
vc.gifData = gifData
|
vc.gifData = gifData
|
||||||
vc.modalPresentationStyle = .fullScreen
|
|
||||||
return vc
|
return vc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +135,17 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
||||||
present(largeImage(gifData: gifData, description: description, sourceView: sourceView), animated: true)
|
present(largeImage(gifData: gifData, description: description, sourceView: sourceView), animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gallery(attachments: [Attachment], sourceViews: [UIView], startIndex: Int) -> GalleryViewController {
|
||||||
|
let sourcesInfo = sourceViews.map(sourceViewInfo)
|
||||||
|
let vc = GalleryViewController(attachments: attachments, sourcesInfo: sourcesInfo, startIndex: startIndex)
|
||||||
|
vc.transitioningDelegate = self
|
||||||
|
return vc
|
||||||
|
}
|
||||||
|
|
||||||
|
func showGallery(attachments: [Attachment], sourceViews: [UIView], startIndex: Int) {
|
||||||
|
present(gallery(attachments: attachments, sourceViews: sourceViews, startIndex: startIndex), animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
private func moreOptions(forURL url: URL) -> UIAlertController {
|
private func moreOptions(forURL url: URL) -> UIAlertController {
|
||||||
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||||
alert.title = url.absoluteString
|
alert.title = url.absoluteString
|
||||||
|
|
|
@ -65,9 +65,7 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func imagePressed() {
|
@objc func imagePressed() {
|
||||||
if image != nil {
|
delegate?.showLargeAttachment(for: self)
|
||||||
delegate?.showLargeAttachment(for: self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -340,11 +340,12 @@ extension StatusTableViewCell: TableViewSwipeActionProvider {
|
||||||
|
|
||||||
extension StatusTableViewCell: AttachmentViewDelegate {
|
extension StatusTableViewCell: AttachmentViewDelegate {
|
||||||
func showLargeAttachment(for attachmentView: AttachmentView) {
|
func showLargeAttachment(for attachmentView: AttachmentView) {
|
||||||
if let gifData = attachmentView.gifData {
|
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||||
delegate?.showLargeImage(gifData: gifData, description: attachmentView.attachment.description, animatingFrom: attachmentView)
|
let startIndex = status.attachments.firstIndex { $0.id == attachmentView.attachment.id } ?? 0
|
||||||
} else {
|
let sourceViews = status.attachments.map { attachment in
|
||||||
delegate?.showLargeImage(attachmentView.image!, description: attachmentView.attachment.description, animatingFrom: attachmentView)
|
attachmentsView.subviews.first { ($0 as! AttachmentView).attachment.id == attachment.id }!
|
||||||
}
|
}
|
||||||
|
delegate?.showGallery(attachments: status.attachments, sourceViews: sourceViews, startIndex: startIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue