forked from shadowfacts/Tusker
parent
be977dbea9
commit
cf71fc3f98
|
@ -7,13 +7,11 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0411610022B442870030A9B7 /* LoadingLargeImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 041160FE22B442870030A9B7 /* LoadingLargeImageViewController.swift */; };
|
||||
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0427033722B30F5F000D31B6 /* BehaviorPrefsView.swift */; };
|
||||
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0427033922B31269000D31B6 /* AdvancedPrefsView.swift */; };
|
||||
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450531E22B0097E00100BA2 /* Timline+UI.swift */; };
|
||||
04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04586B4022B2FFB10021BD04 /* PreferencesView.swift */; };
|
||||
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04586B4222B301470021BD04 /* AppearancePrefsView.swift */; };
|
||||
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04D14BAE22B34A2800642648 /* GalleryViewController.swift */; };
|
||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
|
||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
||||
|
@ -87,7 +85,6 @@
|
|||
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943123A5466600D38C68 /* SelectableTableViewCell.swift */; };
|
||||
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */; };
|
||||
D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944E23A9C99800D38C68 /* EditListAccountsViewController.swift */; };
|
||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
|
||||
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.swift */; };
|
||||
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */; };
|
||||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2425217ABF63005076CC /* UserActivityType.swift */; };
|
||||
|
@ -113,9 +110,6 @@
|
|||
D6412B0D24B0D4CF00F5412E /* ProfileHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6412B0C24B0D4CF00F5412E /* ProfileHeaderView.swift */; };
|
||||
D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */; };
|
||||
D642E83A2BA75F4C004BFD6A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D642E8392BA75F4C004BFD6A /* PrivacyInfo.xcprivacy */; };
|
||||
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */; };
|
||||
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; };
|
||||
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */; };
|
||||
D646DCAC2A06C8840059ECEB /* ProfileFieldValueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646DCAB2A06C8840059ECEB /* ProfileFieldValueView.swift */; };
|
||||
D646DCAE2A06C8C90059ECEB /* ProfileFieldVerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646DCAD2A06C8C90059ECEB /* ProfileFieldVerificationView.swift */; };
|
||||
D646DCD22A06F2510059ECEB /* NotificationsCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646DCD12A06F2510059ECEB /* NotificationsCollectionViewController.swift */; };
|
||||
|
@ -124,7 +118,6 @@
|
|||
D646DCD82A07F3500059ECEB /* FollowRequestNotificationCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646DCD72A07F3500059ECEB /* FollowRequestNotificationCollectionViewCell.swift */; };
|
||||
D646DCDA2A081A2C0059ECEB /* PollFinishedNotificationCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646DCD92A081A2C0059ECEB /* PollFinishedNotificationCollectionViewCell.swift */; };
|
||||
D646DCDC2A081CF10059ECEB /* StatusUpdatedNotificationCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646DCDB2A081CF10059ECEB /* StatusUpdatedNotificationCollectionViewCell.swift */; };
|
||||
D647D92824257BEB0005044F /* AttachmentPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */; };
|
||||
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9026C80DC600FC57FB /* ToastView.swift */; };
|
||||
D64AAE9526C88C5000FC57FB /* ToastableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */; };
|
||||
D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9626C88DC400FC57FB /* ToastConfiguration.swift */; };
|
||||
|
@ -132,7 +125,6 @@
|
|||
D64D8CA92463B494006B0BAA /* MultiThreadDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */; };
|
||||
D651C5B42915B00400236EF6 /* ProfileFieldsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D651C5B32915B00400236EF6 /* ProfileFieldsView.swift */; };
|
||||
D6531DEE246B81C9000F9538 /* GifvPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DED246B81C9000F9538 /* GifvPlayerView.swift */; };
|
||||
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */; };
|
||||
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */; };
|
||||
D6552367289870790048A653 /* ScreenCorners in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = D6552366289870790048A653 /* ScreenCorners */; };
|
||||
D659F35E2953A212002D944A /* TTTKit in Frameworks */ = {isa = PBXBuildFile; productRef = D659F35D2953A212002D944A /* TTTKit */; };
|
||||
|
@ -154,7 +146,6 @@
|
|||
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; };
|
||||
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; };
|
||||
D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */ = {isa = PBXBuildFile; productRef = D6676CA427A8D0020052936B /* WebURLFoundationExtras */; };
|
||||
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F02134D5050057A976 /* UIViewController+Delegates.swift */; };
|
||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */; };
|
||||
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */; };
|
||||
D66C900B28DAB7FD00217BF2 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */; };
|
||||
|
@ -166,7 +157,6 @@
|
|||
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */; };
|
||||
D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */; };
|
||||
D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68015412401A74600D6103B /* MediaPrefsView.swift */; };
|
||||
D681A29A249AD62D0085E54E /* LargeImageContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681A299249AD62D0085E54E /* LargeImageContentView.swift */; };
|
||||
D681E4D7246E32290053414F /* StatusActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681E4D6246E32290053414F /* StatusActivityItemSource.swift */; };
|
||||
D681E4D9246E346E0053414F /* AccountActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681E4D8246E346E0053414F /* AccountActivityItemSource.swift */; };
|
||||
D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */; };
|
||||
|
@ -204,6 +194,8 @@
|
|||
D6934F382BA8E2B7002B1C8D /* GifvController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F372BA8E2B7002B1C8D /* GifvController.swift */; };
|
||||
D6934F3A2BA8F3D7002B1C8D /* FallbackGalleryContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F392BA8F3D7002B1C8D /* FallbackGalleryContentViewController.swift */; };
|
||||
D6934F3C2BAA0F80002B1C8D /* VideoGalleryContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F3B2BAA0F80002B1C8D /* VideoGalleryContentViewController.swift */; };
|
||||
D6934F3E2BAA19D5002B1C8D /* ImageActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F3D2BAA19D5002B1C8D /* ImageActivityItemSource.swift */; };
|
||||
D6934F402BAA19EC002B1C8D /* GifvActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6934F3F2BAA19EC002B1C8D /* GifvActivityItemSource.swift */; };
|
||||
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */; };
|
||||
D693A72F25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */; };
|
||||
D693A73025CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */; };
|
||||
|
@ -287,10 +279,8 @@
|
|||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */; };
|
||||
D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */; };
|
||||
D6C82B4125C5BB7E0017F1E6 /* ExploreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C82B4025C5BB7E0017F1E6 /* ExploreViewController.swift */; };
|
||||
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C94D862139E62700CB5196 /* LargeImageViewController.swift */; };
|
||||
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C94D882139E6EC00CB5196 /* AttachmentView.swift */; };
|
||||
D6CA6A92249FAD8900AD45C1 /* AudioSessionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */; };
|
||||
D6CA6A94249FADE700AD45C1 /* GalleryPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA6A93249FADE700AD45C1 /* GalleryPlayerViewController.swift */; };
|
||||
D6CA6ED229EF6091003EC5DF /* TuskerPreferences in Frameworks */ = {isa = PBXBuildFile; productRef = D6CA6ED129EF6091003EC5DF /* TuskerPreferences */; };
|
||||
D6CA8CDA2962231F0050C433 /* ArrayUniqueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA8CD92962231F0050C433 /* ArrayUniqueTests.swift */; };
|
||||
D6CA8CDE296387310050C433 /* SaveToPhotosActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA8CDD296387310050C433 /* SaveToPhotosActivity.swift */; };
|
||||
|
@ -317,7 +307,6 @@
|
|||
D6D79F532A0FFE3200AB2315 /* ToggleableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */; };
|
||||
D6D79F572A1160B800AB2315 /* AccountDisplayNameLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */; };
|
||||
D6D79F592A13293200AB2315 /* BackgroundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F582A13293200AB2315 /* BackgroundManager.swift */; };
|
||||
D6D79F5B2A13D22B00AB2315 /* GalleryFallbackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F5A2A13D22B00AB2315 /* GalleryFallbackViewController.swift */; };
|
||||
D6D94955298963A900C59229 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D94954298963A900C59229 /* Colors.swift */; };
|
||||
D6D9498D298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */; };
|
||||
D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLable.swift */; };
|
||||
|
@ -418,13 +407,11 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
041160FE22B442870030A9B7 /* LoadingLargeImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingLargeImageViewController.swift; sourceTree = "<group>"; };
|
||||
0427033722B30F5F000D31B6 /* BehaviorPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BehaviorPrefsView.swift; sourceTree = "<group>"; };
|
||||
0427033922B31269000D31B6 /* AdvancedPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedPrefsView.swift; sourceTree = "<group>"; };
|
||||
0450531E22B0097E00100BA2 /* Timline+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timline+UI.swift"; sourceTree = "<group>"; };
|
||||
04586B4022B2FFB10021BD04 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
||||
04586B4222B301470021BD04 /* AppearancePrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePrefsView.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -497,7 +484,6 @@
|
|||
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTimelineViewController.swift; sourceTree = "<group>"; };
|
||||
D627944E23A9C99800D38C68 /* EditListAccountsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditListAccountsViewController.swift; sourceTree = "<group>"; };
|
||||
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; };
|
||||
D62D2421217AA7E1005076CC /* UserActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityManager.swift; sourceTree = "<group>"; };
|
||||
D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserActivity+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D62D2425217ABF63005076CC /* UserActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityType.swift; sourceTree = "<group>"; };
|
||||
|
@ -523,9 +509,6 @@
|
|||
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineTextAttachment.swift; sourceTree = "<group>"; };
|
||||
D642E8392BA75F4C004BFD6A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
D642E83D2BA7AD0F004BFD6A /* GalleryVC */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = GalleryVC; sourceTree = "<group>"; };
|
||||
D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageExpandAnimationController.swift; sourceTree = "<group>"; };
|
||||
D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageShrinkAnimationController.swift; sourceTree = "<group>"; };
|
||||
D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = "<group>"; };
|
||||
D646DCAB2A06C8840059ECEB /* ProfileFieldValueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldValueView.swift; sourceTree = "<group>"; };
|
||||
D646DCAD2A06C8C90059ECEB /* ProfileFieldVerificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldVerificationView.swift; sourceTree = "<group>"; };
|
||||
D646DCD12A06F2510059ECEB /* NotificationsCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsCollectionViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -534,7 +517,6 @@
|
|||
D646DCD72A07F3500059ECEB /* FollowRequestNotificationCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowRequestNotificationCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D646DCD92A081A2C0059ECEB /* PollFinishedNotificationCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFinishedNotificationCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D646DCDB2A081CF10059ECEB /* StatusUpdatedNotificationCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusUpdatedNotificationCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentPreviewViewController.swift; sourceTree = "<group>"; };
|
||||
D64AAE9026C80DC600FC57FB /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
|
||||
D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastableViewController.swift; sourceTree = "<group>"; };
|
||||
D64AAE9626C88DC400FC57FB /* ToastConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastConfiguration.swift; sourceTree = "<group>"; };
|
||||
|
@ -542,7 +524,6 @@
|
|||
D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiThreadDictionary.swift; sourceTree = "<group>"; };
|
||||
D651C5B32915B00400236EF6 /* ProfileFieldsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldsView.swift; sourceTree = "<group>"; };
|
||||
D6531DED246B81C9000F9538 /* GifvPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvPlayerView.swift; sourceTree = "<group>"; };
|
||||
D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvAttachmentViewController.swift; sourceTree = "<group>"; };
|
||||
D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewSwipeActionProvider.swift; sourceTree = "<group>"; };
|
||||
D659F36129541065002D944A /* TTTView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TTTView.swift; sourceTree = "<group>"; };
|
||||
D65B4B532971F71D00DABDFB /* EditedReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditedReport.swift; sourceTree = "<group>"; };
|
||||
|
@ -565,7 +546,6 @@
|
|||
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusStateResolver.swift; sourceTree = "<group>"; };
|
||||
D663626B21361C6700C9CBA2 /* Account+Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Preferences.swift"; sourceTree = "<group>"; };
|
||||
D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppShortcutItems.swift; sourceTree = "<group>"; };
|
||||
D667E5F02134D5050057A976 /* UIViewController+Delegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Delegates.swift"; sourceTree = "<group>"; };
|
||||
D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Equatable.swift"; sourceTree = "<group>"; };
|
||||
D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Traits.swift"; sourceTree = "<group>"; };
|
||||
D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -578,7 +558,6 @@
|
|||
D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Uniques.swift"; sourceTree = "<group>"; };
|
||||
D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposingPrefsView.swift; sourceTree = "<group>"; };
|
||||
D68015412401A74600D6103B /* MediaPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPrefsView.swift; sourceTree = "<group>"; };
|
||||
D681A299249AD62D0085E54E /* LargeImageContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageContentView.swift; sourceTree = "<group>"; };
|
||||
D681E4D6246E32290053414F /* StatusActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActivityItemSource.swift; sourceTree = "<group>"; };
|
||||
D681E4D8246E346E0053414F /* AccountActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountActivityItemSource.swift; sourceTree = "<group>"; };
|
||||
D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeDrawingViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -616,6 +595,8 @@
|
|||
D6934F372BA8E2B7002B1C8D /* GifvController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvController.swift; sourceTree = "<group>"; };
|
||||
D6934F392BA8F3D7002B1C8D /* FallbackGalleryContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FallbackGalleryContentViewController.swift; sourceTree = "<group>"; };
|
||||
D6934F3B2BAA0F80002B1C8D /* VideoGalleryContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoGalleryContentViewController.swift; sourceTree = "<group>"; };
|
||||
D6934F3D2BAA19D5002B1C8D /* ImageActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageActivityItemSource.swift; sourceTree = "<group>"; };
|
||||
D6934F3F2BAA19EC002B1C8D /* GifvActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvActivityItemSource.swift; sourceTree = "<group>"; };
|
||||
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagsViewController.swift; sourceTree = "<group>"; };
|
||||
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedProfileCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FeaturedProfileCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||
|
@ -699,10 +680,8 @@
|
|||
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; };
|
||||
D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentsContainerView.swift; sourceTree = "<group>"; };
|
||||
D6C82B4025C5BB7E0017F1E6 /* ExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreViewController.swift; sourceTree = "<group>"; };
|
||||
D6C94D862139E62700CB5196 /* LargeImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageViewController.swift; sourceTree = "<group>"; };
|
||||
D6C94D882139E6EC00CB5196 /* AttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentView.swift; sourceTree = "<group>"; };
|
||||
D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSessionHelper.swift; sourceTree = "<group>"; };
|
||||
D6CA6A93249FADE700AD45C1 /* GalleryPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryPlayerViewController.swift; sourceTree = "<group>"; };
|
||||
D6CA6ED029EF6060003EC5DF /* TuskerPreferences */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = TuskerPreferences; sourceTree = "<group>"; };
|
||||
D6CA8CD92962231F0050C433 /* ArrayUniqueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayUniqueTests.swift; sourceTree = "<group>"; };
|
||||
D6CA8CDD296387310050C433 /* SaveToPhotosActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveToPhotosActivity.swift; sourceTree = "<group>"; };
|
||||
|
@ -736,7 +715,6 @@
|
|||
D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleableButton.swift; sourceTree = "<group>"; };
|
||||
D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDisplayNameLabel.swift; sourceTree = "<group>"; };
|
||||
D6D79F582A13293200AB2315 /* BackgroundManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundManager.swift; sourceTree = "<group>"; };
|
||||
D6D79F5A2A13D22B00AB2315 /* GalleryFallbackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryFallbackViewController.swift; sourceTree = "<group>"; };
|
||||
D6D94954298963A900C59229 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
|
||||
D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = "<group>"; };
|
||||
|
@ -842,13 +820,9 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
0411610522B457290030A9B7 /* Attachment Gallery */ = {
|
||||
0411610522B457290030A9B7 /* Gallery */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
04D14BAE22B34A2800642648 /* GalleryViewController.swift */,
|
||||
D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */,
|
||||
D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */,
|
||||
D6CA6A93249FADE700AD45C1 /* GalleryPlayerViewController.swift */,
|
||||
D6934F2D2BA7AEF9002B1C8D /* StatusAttachmentsGalleryDataSource.swift */,
|
||||
D6934F332BA8D65A002B1C8D /* ImageGalleryDataSource.swift */,
|
||||
D6934F312BA7E43E002B1C8D /* LoadingGalleryContentViewController.swift */,
|
||||
|
@ -857,7 +831,7 @@
|
|||
D6934F3B2BAA0F80002B1C8D /* VideoGalleryContentViewController.swift */,
|
||||
D6934F392BA8F3D7002B1C8D /* FallbackGalleryContentViewController.swift */,
|
||||
);
|
||||
path = "Attachment Gallery";
|
||||
path = Gallery;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D60089172981FEA4005B4D00 /* Tip Jar */ = {
|
||||
|
@ -1017,14 +991,13 @@
|
|||
children = (
|
||||
D65B4B89297879DE00DABDFB /* Account Follows */,
|
||||
D6A3BC822321F69400FD64D5 /* Account List */,
|
||||
0411610522B457290030A9B7 /* Attachment Gallery */,
|
||||
D641C787213DD862004B4513 /* Compose */,
|
||||
D641C785213DD83B004B4513 /* Conversation */,
|
||||
D6F2E960249E772F005846BB /* Crash Reporter */,
|
||||
D61F759729384D4200C0B37F /* Customize Timelines */,
|
||||
D627943C23A5635D00D38C68 /* Explore */,
|
||||
D6A4DCC92553666600D9DE31 /* Fast Account Switcher */,
|
||||
D641C788213DD86D004B4513 /* Large Image */,
|
||||
0411610522B457290030A9B7 /* Gallery */,
|
||||
D627944B23A9A02400D38C68 /* Lists */,
|
||||
D627944823A6AD5100D38C68 /* Local Predicate Statuses List */,
|
||||
D641C782213DD7F0004B4513 /* Main */,
|
||||
|
@ -1127,19 +1100,6 @@
|
|||
path = Compose;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D641C788213DD86D004B4513 /* Large Image */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D646C954213B364600269FB5 /* Transitions */,
|
||||
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */,
|
||||
D6C94D862139E62700CB5196 /* LargeImageViewController.swift */,
|
||||
D681A299249AD62D0085E54E /* LargeImageContentView.swift */,
|
||||
041160FE22B442870030A9B7 /* LoadingLargeImageViewController.swift */,
|
||||
D6D79F5A2A13D22B00AB2315 /* GalleryFallbackViewController.swift */,
|
||||
);
|
||||
path = "Large Image";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D641C789213DD87E004B4513 /* Preferences */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1208,16 +1168,6 @@
|
|||
path = Packages;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D646C954213B364600269FB5 /* Transitions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */,
|
||||
D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */,
|
||||
D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */,
|
||||
);
|
||||
path = Transitions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D64AAE8F26C80DB600FC57FB /* Toast */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1272,7 +1222,6 @@
|
|||
D667E5F62135C2ED0057A976 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D667E5F02134D5050057A976 /* UIViewController+Delegates.swift */,
|
||||
D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */,
|
||||
D663626B21361C6700C9CBA2 /* Account+Preferences.swift */,
|
||||
D6F4D79329ECB0AF00351B87 /* UIBackgroundConfiguration+AppColors.swift */,
|
||||
|
@ -1398,6 +1347,8 @@
|
|||
children = (
|
||||
D681E4D6246E32290053414F /* StatusActivityItemSource.swift */,
|
||||
D681E4D8246E346E0053414F /* AccountActivityItemSource.swift */,
|
||||
D6934F3D2BAA19D5002B1C8D /* ImageActivityItemSource.swift */,
|
||||
D6934F3F2BAA19EC002B1C8D /* GifvActivityItemSource.swift */,
|
||||
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */,
|
||||
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */,
|
||||
D6CA8CDD296387310050C433 /* SaveToPhotosActivity.swift */,
|
||||
|
@ -1902,7 +1853,6 @@
|
|||
D6E57FA325C26FAB00341037 /* Localizable.stringsdict in Resources */,
|
||||
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */,
|
||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
|
||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
||||
D601FA84297EEC3F00A8E8B5 /* SuggestedProfileCardCollectionViewCell.xib in Resources */,
|
||||
D6F2E966249E8BFD005846BB /* IssueReporterViewController.xib in Resources */,
|
||||
D6A4DCCD2553667800D9DE31 /* FastAccountSwitcherViewController.xib in Resources */,
|
||||
|
@ -1996,7 +1946,6 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */,
|
||||
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
||||
D6C3F4F5298ED0890009FCFF /* LocalPredicateStatusesViewController.swift in Sources */,
|
||||
D691771729A710520054D7EF /* ProfileNoContentCollectionViewCell.swift in Sources */,
|
||||
|
@ -2036,7 +1985,6 @@
|
|||
D6B22A0F2560D52D004D82EF /* TabbedPageViewController.swift in Sources */,
|
||||
D6895DC228D65274006341DA /* CustomAlertController.swift in Sources */,
|
||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
||||
0411610022B442870030A9B7 /* LoadingLargeImageViewController.swift in Sources */,
|
||||
D6A4DCE525537C7A00D9DE31 /* FastSwitchingAccountView.swift in Sources */,
|
||||
D6A4DCCC2553667800D9DE31 /* FastAccountSwitcherViewController.swift in Sources */,
|
||||
D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */,
|
||||
|
@ -2055,7 +2003,6 @@
|
|||
D651C5B42915B00400236EF6 /* ProfileFieldsView.swift in Sources */,
|
||||
D646DCD82A07F3500059ECEB /* FollowRequestNotificationCollectionViewCell.swift in Sources */,
|
||||
D6F2E965249E8BFD005846BB /* IssueReporterViewController.swift in Sources */,
|
||||
D6CA6A94249FADE700AD45C1 /* GalleryPlayerViewController.swift in Sources */,
|
||||
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */,
|
||||
D6D12B2F2925D66500D528E1 /* TimelineGapCollectionViewCell.swift in Sources */,
|
||||
D6D94955298963A900C59229 /* Colors.swift in Sources */,
|
||||
|
@ -2079,10 +2026,8 @@
|
|||
D6A6C10F25B62D2400298D0F /* DiskCache.swift in Sources */,
|
||||
D6C3F4F9298EDBF20009FCFF /* ConversationTree.swift in Sources */,
|
||||
D6B81F3C2560365300F6E31D /* RefreshableViewController.swift in Sources */,
|
||||
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */,
|
||||
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */,
|
||||
D6DD8FFF2984D327002AD3FD /* BookmarksViewController.swift in Sources */,
|
||||
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */,
|
||||
D646DCD22A06F2510059ECEB /* NotificationsCollectionViewController.swift in Sources */,
|
||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
||||
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameView.swift in Sources */,
|
||||
|
@ -2162,7 +2107,6 @@
|
|||
D6945C2F23AC47C3005C403C /* SavedDataManager.swift in Sources */,
|
||||
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
||||
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
||||
D6D79F592A13293200AB2315 /* BackgroundManager.swift in Sources */,
|
||||
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */,
|
||||
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */,
|
||||
|
@ -2190,7 +2134,6 @@
|
|||
D646DCAC2A06C8840059ECEB /* ProfileFieldValueView.swift in Sources */,
|
||||
D6F4D79429ECB0AF00351B87 /* UIBackgroundConfiguration+AppColors.swift in Sources */,
|
||||
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,
|
||||
D681A29A249AD62D0085E54E /* LargeImageContentView.swift in Sources */,
|
||||
D6C041C42AED77730094D32D /* EditListSettingsService.swift in Sources */,
|
||||
D6DFC69E242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift in Sources */,
|
||||
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */,
|
||||
|
@ -2210,6 +2153,7 @@
|
|||
D61A45EA28DF51EE002BE511 /* TimelineLikeCollectionViewController.swift in Sources */,
|
||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
|
||||
D6934F302BA7AF91002B1C8D /* ImageGalleryContentViewController.swift in Sources */,
|
||||
D6934F3E2BAA19D5002B1C8D /* ImageActivityItemSource.swift in Sources */,
|
||||
D67C1795266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift in Sources */,
|
||||
D61F75962937037800C0B37F /* ToggleFollowHashtagService.swift in Sources */,
|
||||
D60089192981FEBA005B4D00 /* ConfettiView.swift in Sources */,
|
||||
|
@ -2221,7 +2165,6 @@
|
|||
D6E77D0B286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift in Sources */,
|
||||
D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */,
|
||||
D65B4B58297203A700DABDFB /* ReportSelectRulesView.swift in Sources */,
|
||||
D6D79F5B2A13D22B00AB2315 /* GalleryFallbackViewController.swift in Sources */,
|
||||
D6C3F4FB299035650009FCFF /* TrendsViewController.swift in Sources */,
|
||||
D6B81F442560390300F6E31D /* MenuController.swift in Sources */,
|
||||
D691771129A2B76A0054D7EF /* MainActor+Unsafe.swift in Sources */,
|
||||
|
@ -2231,14 +2174,12 @@
|
|||
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */,
|
||||
D68A76F129539116001DA1B3 /* FlipView.swift in Sources */,
|
||||
D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */,
|
||||
D647D92824257BEB0005044F /* AttachmentPreviewViewController.swift in Sources */,
|
||||
D61F75B5293BD97400C0B37F /* DeleteFilterService.swift in Sources */,
|
||||
D6DFC6A0242C4CCC00ACC392 /* Weak.swift in Sources */,
|
||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */,
|
||||
D61ABEF828EFC3F900B29151 /* ProfileStatusesViewController.swift in Sources */,
|
||||
D6ADB6EA28E91C30009924AB /* TimelineStatusCollectionViewCell.swift in Sources */,
|
||||
D681E4D7246E32290053414F /* StatusActivityItemSource.swift in Sources */,
|
||||
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */,
|
||||
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */,
|
||||
D621733328F1D5ED004C7DB1 /* ReblogService.swift in Sources */,
|
||||
D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */,
|
||||
|
@ -2254,7 +2195,6 @@
|
|||
D6895DC428D65342006341DA /* ConfirmReblogStatusPreviewView.swift in Sources */,
|
||||
D62E9987279D094F00C26176 /* StatusMetaIndicatorsView.swift in Sources */,
|
||||
D6BEA247291A0F2D002F4D01 /* Duckable+Root.swift in Sources */,
|
||||
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */,
|
||||
D6C1B2082545D1EC00DAAA66 /* StatusCardView.swift in Sources */,
|
||||
D64D8CA92463B494006B0BAA /* MultiThreadDictionary.swift in Sources */,
|
||||
D6F6A554291F0D9600F496A8 /* DeleteListService.swift in Sources */,
|
||||
|
@ -2267,11 +2207,11 @@
|
|||
D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */,
|
||||
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */,
|
||||
D6E77D09286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift in Sources */,
|
||||
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
|
||||
D6D79F292A0D596B00AB2315 /* StatusEditHistoryViewController.swift in Sources */,
|
||||
D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */,
|
||||
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */,
|
||||
D61F75B3293BD89C00C0B37F /* UpdateFilterService.swift in Sources */,
|
||||
D6934F402BAA19EC002B1C8D /* GifvActivityItemSource.swift in Sources */,
|
||||
D6C3F5172991C1A00009FCFF /* View+AppListStyle.swift in Sources */,
|
||||
D65B4B6A297777D900DABDFB /* StatusNotFoundView.swift in Sources */,
|
||||
D6412B0924B0291E00F5412E /* MyProfileViewController.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// GifvActivityItemSource.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 3/19/24.
|
||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
import AVFoundation
|
||||
|
||||
class GifvActivityItemSource: NSObject, UIActivityItemSource {
|
||||
let asset: AVAsset
|
||||
let url: URL
|
||||
|
||||
init(asset: AVAsset, url: URL) {
|
||||
self.asset = asset
|
||||
self.url = url
|
||||
}
|
||||
|
||||
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
||||
return url
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: UIActivity.ActivityType?, suggestedSize size: CGSize) -> UIImage? {
|
||||
#if os(visionOS)
|
||||
#warning("Use async AVAssetImageGenerator.image(at:)")
|
||||
return nil
|
||||
#else
|
||||
let generator = AVAssetImageGenerator(asset: self.asset)
|
||||
generator.appliesPreferredTrackTransform = true
|
||||
if let image = try? generator.copyCGImage(at: .zero, actualTime: nil) {
|
||||
return UIImage(cgImage: image)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
||||
do {
|
||||
let data = try Data(contentsOf: url)
|
||||
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent)
|
||||
try data.write(to: tempURL)
|
||||
return tempURL
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: UIActivity.ActivityType?) -> String {
|
||||
return (UTType(filenameExtension: url.pathExtension) ?? .video).identifier
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// ImageActivityItemSource.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 3/19/24.
|
||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
class ImageActivityItemSource: NSObject, UIActivityItemSource {
|
||||
let data: Data
|
||||
let url: URL
|
||||
let image: UIImage?
|
||||
|
||||
init(data: Data, url: URL, image: UIImage?) {
|
||||
self.data = data
|
||||
self.url = url
|
||||
self.image = image
|
||||
}
|
||||
|
||||
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
||||
return url
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: UIActivity.ActivityType?, suggestedSize size: CGSize) -> UIImage? {
|
||||
return image
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
||||
do {
|
||||
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent)
|
||||
try data.write(to: tempURL)
|
||||
return tempURL
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: UIActivity.ActivityType?) -> String {
|
||||
return (UTType(filenameExtension: url.pathExtension) ?? .image).identifier
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
//
|
||||
// UIViewController+Delegate.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 8/27/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
#if !os(visionOS)
|
||||
import UIKit
|
||||
|
||||
extension UIViewController: UIViewControllerTransitioningDelegate {
|
||||
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
if let presented = presented as? LargeImageAnimatableViewController,
|
||||
presented.animationImage != nil {
|
||||
return LargeImageExpandAnimationController()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
if let dismissed = dismissed as? LargeImageAnimatableViewController,
|
||||
dismissed.animationImage != nil {
|
||||
return LargeImageShrinkAnimationController(interactionController: dismissed.dismissInteractionController)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
|
||||
if let animator = animator as? LargeImageShrinkAnimationController,
|
||||
let interactionController = animator.interactionController,
|
||||
interactionController.inProgress {
|
||||
return interactionController
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,50 +0,0 @@
|
|||
//
|
||||
// AttachmentPreviewViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 3/20/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import TuskerComponents
|
||||
|
||||
class AttachmentPreviewViewController: UIViewController {
|
||||
|
||||
private let attachment: Attachment
|
||||
private let sourceView: AttachmentView
|
||||
|
||||
init(sourceView: AttachmentView) {
|
||||
self.attachment = sourceView.attachment
|
||||
self.sourceView = sourceView
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
if let data = ImageCache.attachments.getData(attachment.url),
|
||||
let image = UIImage(data: data) {
|
||||
let imageView: UIImageView
|
||||
if attachment.url.pathExtension == "gif" {
|
||||
let gifView = GIFImageView(image: image)
|
||||
let controller = sourceView.gifController ?? GIFController(gifData: data)
|
||||
controller.attach(to: gifView)
|
||||
controller.startAnimating()
|
||||
imageView = gifView
|
||||
} else {
|
||||
imageView = UIImageView(image: image)
|
||||
}
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.backgroundColor = .black
|
||||
view = imageView
|
||||
preferredContentSize = image.size
|
||||
} else {
|
||||
view = UIActivityIndicatorView(style: .large)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
//
|
||||
// GalleryPlayerViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 6/21/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import AVKit
|
||||
import Pachyderm
|
||||
|
||||
class GalleryPlayerViewController: UIViewController {
|
||||
|
||||
let playerVC = AVPlayerViewController()
|
||||
|
||||
var attachment: Attachment!
|
||||
|
||||
private var isFirstAppearance = true
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
view.backgroundColor = .black
|
||||
|
||||
playerVC.allowsPictureInPicturePlayback = true
|
||||
|
||||
playerVC.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
addChild(playerVC)
|
||||
playerVC.didMove(toParent: self)
|
||||
view.addSubview(playerVC.view)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
playerVC.view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
|
||||
playerVC.view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
|
||||
playerVC.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
||||
playerVC.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
// starting while audio is already playing from another app often takes nearly a second,
|
||||
// so do it on a background thread as to not block the UI
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
AudioSessionHelper.enable()
|
||||
AudioSessionHelper.setVideoPlayback()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if self.isFirstAppearance {
|
||||
self.isFirstAppearance = false
|
||||
self.playerVC.player?.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
// starting often takes around half a second,
|
||||
// so do it on a background thread as to not block the UI
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
AudioSessionHelper.setDefault()
|
||||
AudioSessionHelper.disable()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
// GalleryViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 6/13/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import AVFoundation
|
||||
import AVKit
|
||||
|
||||
class GalleryViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, LargeImageAnimatableViewController {
|
||||
|
||||
weak var avPlayerViewControllerDelegate: AVPlayerViewControllerDelegate?
|
||||
|
||||
let attachments: [Attachment]
|
||||
let sourceViews: WeakArray<UIImageView>
|
||||
let startIndex: Int
|
||||
|
||||
var pages: [UIViewController]!
|
||||
|
||||
var currentIndex: Int {
|
||||
guard let vc = viewControllers?.first,
|
||||
let index = pages.firstIndex(of: vc) else {
|
||||
fatalError()
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
var animationSourceView: UIImageView? { sourceViews[currentIndex] }
|
||||
var largeImageController: LargeImageViewController? {
|
||||
// use protocol because page controllers may be loading or non-loading VCs
|
||||
(pages[currentIndex] as? LargeImageAnimatableViewController)?.largeImageController
|
||||
}
|
||||
var animationImage: UIImage? {
|
||||
if let page = pages[currentIndex] as? LargeImageAnimatableViewController,
|
||||
let image = page.animationImage {
|
||||
return image
|
||||
} else {
|
||||
return animationSourceView?.image
|
||||
}
|
||||
}
|
||||
var dismissInteractionController: LargeImageInteractionController?
|
||||
|
||||
var isInteractivelyAnimatingDismissal: Bool = false {
|
||||
didSet {
|
||||
#if !os(visionOS)
|
||||
setNeedsStatusBarAppearanceUpdate()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
override var prefersStatusBarHidden: Bool {
|
||||
return !isInteractivelyAnimatingDismissal
|
||||
}
|
||||
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
||||
return .none
|
||||
}
|
||||
override var childForHomeIndicatorAutoHidden: UIViewController? {
|
||||
return viewControllers?.first
|
||||
}
|
||||
|
||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||
return .allButUpsideDown
|
||||
} else {
|
||||
return .all
|
||||
}
|
||||
}
|
||||
|
||||
init(attachments: [Attachment], sourceViews: [UIImageView?], startIndex: Int) {
|
||||
self.attachments = attachments
|
||||
self.sourceViews = WeakArray(sourceViews)
|
||||
self.startIndex = startIndex
|
||||
|
||||
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal)
|
||||
|
||||
modalPresentationStyle = .fullScreen
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.pages = attachments.enumerated().map { (index, attachment) in
|
||||
switch attachment.kind {
|
||||
case .image:
|
||||
let vc = LoadingLargeImageViewController(attachment: attachment)
|
||||
vc.shrinkGestureEnabled = false
|
||||
vc.animationSourceView = sourceViews[index]
|
||||
return vc
|
||||
case .video, .audio:
|
||||
let vc = GalleryPlayerViewController()
|
||||
vc.playerVC.player = AVPlayer(url: attachment.url)
|
||||
vc.playerVC.delegate = avPlayerViewControllerDelegate
|
||||
vc.attachment = attachment
|
||||
return vc
|
||||
case .gifv:
|
||||
// Passing the source view to the LargeImageGifvContentView is a crappy workaround for not
|
||||
// having the current frame to use as the animationImage. This will break when there
|
||||
// are more than 4 attachments and there is a gifv at index >= 3 (the More... button will show
|
||||
// in place of the fourth attachment, so there aren't source views for the attachments at index >= 3).
|
||||
// This isn't a priority as only Mastodon converts gifs to gifvs, and Mastodon (in its default configuration,
|
||||
// I don't know about forks) doesn't allow more than four attachments, meaning there will always be a source view.
|
||||
let gifvContentView = LargeImageGifvContentView(attachment: attachment, source: sourceViews[index]!)
|
||||
let vc = LargeImageViewController(contentView: gifvContentView, description: attachment.description, sourceView: nil)
|
||||
vc.shrinkGestureEnabled = false
|
||||
return vc
|
||||
default:
|
||||
return UINavigationController(rootViewController: GalleryFallbackViewController(attachment: attachment))
|
||||
}
|
||||
}
|
||||
|
||||
setViewControllers([pages[startIndex]], direction: .forward, animated: false)
|
||||
|
||||
self.dataSource = self
|
||||
self.delegate = self
|
||||
|
||||
overrideUserInterfaceStyle = .dark
|
||||
|
||||
dismissInteractionController = LargeImageInteractionController(viewController: self)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if let vc = pages[currentIndex] as? AVPlayerViewController {
|
||||
// when the gallery is first shown, after the transition finishes, the controls for the player controller appear semi-transparent
|
||||
// hiding the controls and then immediately reshowing them makes sure they're visible when the gallery is presented
|
||||
vc.showsPlaybackControls = false
|
||||
vc.showsPlaybackControls = true
|
||||
|
||||
// begin playing the video as soon as we appear
|
||||
vc.player?.play()
|
||||
}
|
||||
}
|
||||
|
||||
override func accessibilityPerformEscape() -> Bool {
|
||||
dismiss(animated: true)
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: - Page View Controller Data Source
|
||||
|
||||
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
|
||||
guard let index = pages.firstIndex(of: viewController),
|
||||
index > 0 else {
|
||||
return nil
|
||||
}
|
||||
return pages[index - 1]
|
||||
}
|
||||
|
||||
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
|
||||
guard let index = pages.firstIndex(of: viewController),
|
||||
index < pages.count - 1 else {
|
||||
return nil
|
||||
}
|
||||
return pages[index + 1]
|
||||
}
|
||||
|
||||
// MARK: - Page View Controller Delegate
|
||||
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
|
||||
if let pending = pendingViewControllers.first as? LoadingLargeImageViewController,
|
||||
let current = viewControllers!.first as? LoadingLargeImageViewController {
|
||||
pending.controlsVisible = current.controlsVisible
|
||||
}
|
||||
|
||||
if let pending = pendingViewControllers.first as? AVPlayerViewController {
|
||||
// show controls and begin playing when the player page becomes visible
|
||||
pending.showsPlaybackControls = true
|
||||
pending.player?.play()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// GifvAttachmentViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 5/12/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import AVFoundation
|
||||
|
||||
class GifvAttachmentViewController: UIViewController {
|
||||
|
||||
private let attachment: Attachment
|
||||
|
||||
init(attachment: Attachment) {
|
||||
precondition(attachment.kind == .gifv)
|
||||
self.attachment = attachment
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
let asset = AVURLAsset(url: attachment.url)
|
||||
let controller = GifvController(asset: asset)
|
||||
self.view = GifvPlayerView(controller: controller, gravity: .resizeAspect)
|
||||
}
|
||||
|
||||
}
|
|
@ -41,9 +41,12 @@ class GifvGalleryContentViewController: UIViewController, GalleryContentViewCont
|
|||
])
|
||||
|
||||
presentationSizeCancellable = controller.presentationSizeSubject
|
||||
.sink { [unowned self] _ in
|
||||
.sink { [unowned self] size in
|
||||
self.preferredContentSize = size
|
||||
self.container?.galleryContentChanged()
|
||||
}
|
||||
|
||||
preferredContentSize = controller.item.presentationSize
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
|
@ -26,6 +26,8 @@ class ImageGalleryContentViewController: UIViewController, GalleryContentViewCon
|
|||
self.gifController = gifController
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
preferredContentSize = image.size
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
|
@ -14,7 +14,7 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
|||
private let url: URL
|
||||
let caption: String?
|
||||
private let item: AVPlayerItem
|
||||
private let player: AVPlayer
|
||||
let player: AVPlayer
|
||||
|
||||
private var presentationSizeObservation: NSKeyValueObservation?
|
||||
private var statusObservation: NSKeyValueObservation?
|
||||
|
@ -50,8 +50,9 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
|||
playerView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
])
|
||||
|
||||
presentationSizeObservation = item.observe(\.presentationSize, changeHandler: { [unowned self] _, _ in
|
||||
presentationSizeObservation = item.observe(\.presentationSize, changeHandler: { [unowned self] item, _ in
|
||||
MainActor.runUnsafely {
|
||||
self.preferredContentSize = item.presentationSize
|
||||
self.container?.galleryContentChanged()
|
||||
}
|
||||
})
|
||||
|
@ -63,6 +64,8 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
preferredContentSize = item.presentationSize
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
|
@ -1,73 +0,0 @@
|
|||
//
|
||||
// GalleryFallbackViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 5/16/23.
|
||||
// Copyright © 2023 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import QuickLook
|
||||
import Pachyderm
|
||||
|
||||
class GalleryFallbackViewController: QLPreviewController {
|
||||
|
||||
private let attachment: Attachment
|
||||
private let previewItem: GalleryPreviewItem
|
||||
|
||||
private var loadAttachmentTask: Task<Void, Never>?
|
||||
|
||||
deinit {
|
||||
loadAttachmentTask?.cancel()
|
||||
}
|
||||
|
||||
init(attachment: Attachment) {
|
||||
self.attachment = attachment
|
||||
self.previewItem = GalleryPreviewItem()
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
dataSource = self
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if previewItem.previewItemURL == nil,
|
||||
loadAttachmentTask == nil {
|
||||
loadAttachmentTask = Task {
|
||||
do {
|
||||
let (data, _) = try await URLSession.shared.data(from: attachment.url)
|
||||
let url = FileManager.default.temporaryDirectory.appendingPathComponent(attachment.url.lastPathComponent)
|
||||
try data.write(to: url)
|
||||
try Task.checkCancellation()
|
||||
previewItem.previewItemURL = url
|
||||
refreshCurrentPreviewItem()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension GalleryFallbackViewController: QLPreviewControllerDataSource {
|
||||
nonisolated func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
nonisolated func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
|
||||
return MainActor.runUnsafely {
|
||||
previewItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class GalleryPreviewItem: NSObject, QLPreviewItem {
|
||||
var previewItemURL: URL? = nil
|
||||
}
|
|
@ -1,277 +0,0 @@
|
|||
//
|
||||
// LargeImageContentView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 6/17/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
@preconcurrency import AVFoundation
|
||||
@preconcurrency import VisionKit
|
||||
import TuskerComponents
|
||||
|
||||
@MainActor
|
||||
protocol LargeImageContentView: UIView {
|
||||
var animationImage: UIImage? { get }
|
||||
var activityItemsForSharing: [Any] { get }
|
||||
var owner: LargeImageViewController? { get set }
|
||||
func setControlsVisible(_ controlsVisible: Bool, animated: Bool)
|
||||
func grayscaleStateChanged()
|
||||
}
|
||||
|
||||
class LargeImageImageContentView: UIImageView, LargeImageContentView {
|
||||
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
@available(iOS 16.0, *)
|
||||
private static let analyzer = ImageAnalyzer()
|
||||
private var _analysisInteraction: AnyObject?
|
||||
@available(iOS 16.0, *)
|
||||
private var analysisInteraction: ImageAnalysisInteraction? { _analysisInteraction as? ImageAnalysisInteraction }
|
||||
#endif
|
||||
|
||||
var animationImage: UIImage? { image! }
|
||||
var activityItemsForSharing: [Any] {
|
||||
guard let data else {
|
||||
return []
|
||||
}
|
||||
return [ImageActivityItemSource(data: data, url: url, image: image)]
|
||||
}
|
||||
weak var owner: LargeImageViewController?
|
||||
|
||||
private let url: URL
|
||||
private let data: Data?
|
||||
|
||||
init(url: URL, data: Data?, image: UIImage) {
|
||||
self.url = url
|
||||
self.data = data
|
||||
|
||||
super.init(image: image)
|
||||
|
||||
contentMode = .scaleAspectFit
|
||||
isUserInteractionEnabled = true
|
||||
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
if #available(iOS 16.0, *),
|
||||
ImageAnalyzer.isSupported {
|
||||
let interaction = ImageAnalysisInteraction()
|
||||
self._analysisInteraction = interaction
|
||||
interaction.delegate = self
|
||||
interaction.preferredInteractionTypes = .automatic
|
||||
addInteraction(interaction)
|
||||
Task {
|
||||
do {
|
||||
let result = try await LargeImageImageContentView.analyzer.analyze(image, configuration: ImageAnalyzer.Configuration([.text, .machineReadableCode]))
|
||||
interaction.analysis = result
|
||||
} catch {
|
||||
// if analysis fails, we just don't show anything
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setControlsVisible(_ controlsVisible: Bool, animated: Bool) {
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
if #available(iOS 16.0, *),
|
||||
let analysisInteraction {
|
||||
analysisInteraction.setSupplementaryInterfaceHidden(!controlsVisible, animated: animated)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func grayscaleStateChanged() {
|
||||
guard let data else {
|
||||
return
|
||||
}
|
||||
|
||||
let image: UIImage?
|
||||
if Preferences.shared.grayscaleImages {
|
||||
image = ImageGrayscalifier.convert(url: nil, data: data)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
}
|
||||
|
||||
if let image = image {
|
||||
self.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
@available(iOS 16.0, *)
|
||||
extension LargeImageImageContentView: ImageAnalysisInteractionDelegate {
|
||||
func presentingViewController(for interaction: ImageAnalysisInteraction) -> UIViewController? {
|
||||
return owner
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
class LargeImageGifContentView: GIFImageView, LargeImageContentView {
|
||||
var animationImage: UIImage? { image }
|
||||
var activityItemsForSharing: [Any] {
|
||||
[ImageActivityItemSource(data: gifController!.gifData, url: url, image: image)]
|
||||
}
|
||||
weak var owner: LargeImageViewController?
|
||||
|
||||
private let url: URL
|
||||
|
||||
init(url: URL, gifController: GIFController) {
|
||||
self.url = url
|
||||
|
||||
super.init(image: gifController.lastFrame?.image)
|
||||
|
||||
contentMode = .scaleAspectFit
|
||||
|
||||
gifController.attach(to: self)
|
||||
// todo: doing this in the init feels wrong
|
||||
gifController.startAnimating()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setControlsVisible(_ controlsVisible: Bool, animated: Bool) {
|
||||
}
|
||||
|
||||
func grayscaleStateChanged() {
|
||||
// todo
|
||||
}
|
||||
}
|
||||
|
||||
class LargeImageGifvContentView: GifvPlayerView, LargeImageContentView {
|
||||
private(set) var animationImage: UIImage?
|
||||
var activityItemsForSharing: [Any] {
|
||||
[GifvActivityItemSource(asset: asset, attachment: attachment)]
|
||||
}
|
||||
weak var owner: LargeImageViewController?
|
||||
|
||||
private let attachment: Attachment
|
||||
private let asset: AVURLAsset
|
||||
|
||||
private var videoSize: CGSize?
|
||||
override var intrinsicContentSize: CGSize {
|
||||
videoSize ?? CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
|
||||
}
|
||||
|
||||
init(attachment: Attachment, source: UIImageView) {
|
||||
precondition(attachment.kind == .gifv)
|
||||
|
||||
self.attachment = attachment
|
||||
self.asset = AVURLAsset(url: attachment.url)
|
||||
|
||||
let controller = GifvController(asset: asset)
|
||||
super.init(controller: controller, gravity: .resizeAspect)
|
||||
|
||||
self.animationImage = source.image
|
||||
|
||||
controller.play()
|
||||
|
||||
Task {
|
||||
do {
|
||||
if let track = try await asset.loadTracks(withMediaType: .video).first {
|
||||
let (size, transform) = try await track.load(.naturalSize, .preferredTransform)
|
||||
self.videoSize = size.applying(transform)
|
||||
self.invalidateIntrinsicContentSize()
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setControlsVisible(_ controlsVisible: Bool, animated: Bool) {
|
||||
}
|
||||
|
||||
func grayscaleStateChanged() {
|
||||
// no-op, GifvPlayerView observes the grayscale state itself
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ImageActivityItemSource: NSObject, UIActivityItemSource {
|
||||
let data: Data
|
||||
let url: URL
|
||||
let image: UIImage?
|
||||
|
||||
init(data: Data, url: URL, image: UIImage?) {
|
||||
self.data = data
|
||||
self.url = url
|
||||
self.image = image
|
||||
}
|
||||
|
||||
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
||||
return url
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: UIActivity.ActivityType?, suggestedSize size: CGSize) -> UIImage? {
|
||||
return image
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
||||
do {
|
||||
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent)
|
||||
try data.write(to: tempURL)
|
||||
return tempURL
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: UIActivity.ActivityType?) -> String {
|
||||
return (UTType(filenameExtension: url.pathExtension) ?? .image).identifier
|
||||
}
|
||||
}
|
||||
|
||||
class GifvActivityItemSource: NSObject, UIActivityItemSource {
|
||||
let asset: AVAsset
|
||||
let attachment: Attachment
|
||||
|
||||
init(asset: AVAsset, attachment: Attachment) {
|
||||
self.asset = asset
|
||||
self.attachment = attachment
|
||||
}
|
||||
|
||||
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
||||
return attachment.url
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: UIActivity.ActivityType?, suggestedSize size: CGSize) -> UIImage? {
|
||||
#if os(visionOS)
|
||||
#warning("Use async AVAssetImageGenerator.image(at:)")
|
||||
return nil
|
||||
#else
|
||||
let generator = AVAssetImageGenerator(asset: self.asset)
|
||||
generator.appliesPreferredTrackTransform = true
|
||||
if let image = try? generator.copyCGImage(at: .zero, actualTime: nil) {
|
||||
return UIImage(cgImage: image)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
||||
do {
|
||||
let data = try Data(contentsOf: attachment.url)
|
||||
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(attachment.url.lastPathComponent)
|
||||
try data.write(to: tempURL)
|
||||
return tempURL
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: UIActivity.ActivityType?) -> String {
|
||||
return (UTType(filenameExtension: attachment.url.pathExtension) ?? .video).identifier
|
||||
}
|
||||
}
|
|
@ -1,397 +0,0 @@
|
|||
//
|
||||
// LargeImageViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 8/31/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeImageAnimatableViewController {
|
||||
|
||||
weak var animationSourceView: UIImageView?
|
||||
var largeImageController: LargeImageViewController? { self }
|
||||
var animationImage: UIImage? { contentView.animationImage }
|
||||
var dismissInteractionController: LargeImageInteractionController?
|
||||
|
||||
@IBOutlet weak var scrollView: UIScrollView!
|
||||
@IBOutlet weak var topControlsView: UIView!
|
||||
@IBOutlet weak var descriptionTextView: UITextView!
|
||||
|
||||
private var shareContainer: UIView!
|
||||
private var closeContainer: UIView!
|
||||
private var shareImage: UIImageView!
|
||||
private var shareButtonTopConstraint: NSLayoutConstraint!
|
||||
private var shareButtonLeadingConstraint: NSLayoutConstraint!
|
||||
private var closeButtonTopConstraint: NSLayoutConstraint!
|
||||
private var closeButtonTrailingConstraint: NSLayoutConstraint!
|
||||
|
||||
var contentView: LargeImageContentView {
|
||||
didSet {
|
||||
oldValue.removeFromSuperview()
|
||||
setupContentView()
|
||||
}
|
||||
}
|
||||
var contentViewLeadingConstraint: NSLayoutConstraint!
|
||||
var contentViewTopConstraint: NSLayoutConstraint!
|
||||
|
||||
var imageDescription: String?
|
||||
|
||||
var initialControlsVisible = true
|
||||
private(set) var controlsVisible = true {
|
||||
didSet {
|
||||
setNeedsUpdateOfHomeIndicatorAutoHidden()
|
||||
}
|
||||
}
|
||||
var shrinkGestureEnabled = true
|
||||
|
||||
private var isInitialAppearance = true
|
||||
private var skipUpdatingControlsWhileZooming = false
|
||||
private var prevZoomScale: CGFloat?
|
||||
private var isGrayscale = false
|
||||
private var contentViewSizeObservation: NSKeyValueObservation?
|
||||
|
||||
var isInteractivelyAnimatingDismissal: Bool = false {
|
||||
didSet {
|
||||
#if !os(visionOS)
|
||||
setNeedsStatusBarAppearanceUpdate()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
override var prefersStatusBarHidden: Bool {
|
||||
return !isInteractivelyAnimatingDismissal
|
||||
}
|
||||
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
||||
return .none
|
||||
}
|
||||
|
||||
override var prefersHomeIndicatorAutoHidden: Bool {
|
||||
return !controlsVisible
|
||||
}
|
||||
|
||||
init(contentView: LargeImageContentView, description: String?, sourceView: UIImageView?) {
|
||||
self.imageDescription = description
|
||||
self.animationSourceView = sourceView
|
||||
|
||||
self.contentView = contentView
|
||||
|
||||
super.init(nibName: "LargeImageViewController", bundle: nil)
|
||||
|
||||
modalPresentationStyle = .fullScreen
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupContentView()
|
||||
setupControls()
|
||||
|
||||
setControlsVisible(initialControlsVisible, animated: false)
|
||||
if contentView.activityItemsForSharing.isEmpty {
|
||||
shareContainer.isUserInteractionEnabled = false
|
||||
shareImage.tintColor = .systemGray
|
||||
}
|
||||
|
||||
scrollView.delegate = self
|
||||
|
||||
if let imageDescription = imageDescription,
|
||||
!imageDescription.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
descriptionTextView.text = imageDescription.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
descriptionTextView.textContainerInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
|
||||
// i'm not sure why .automatic doesn't work for this
|
||||
descriptionTextView.contentInsetAdjustmentBehavior = .always
|
||||
let height = min(150, descriptionTextView.contentSize.height)
|
||||
descriptionTextView.topAnchor.constraint(equalTo: descriptionTextView.safeAreaLayoutGuide.bottomAnchor, constant: -(height + 16)).isActive = true
|
||||
} else {
|
||||
descriptionTextView.isHidden = true
|
||||
}
|
||||
|
||||
if shrinkGestureEnabled {
|
||||
dismissInteractionController = LargeImageInteractionController(viewController: self)
|
||||
}
|
||||
|
||||
let singleTap = UITapGestureRecognizer(target: self, action: #selector(scrollViewPressed(_:)))
|
||||
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(scrollViewDoubleTapped(_:)))
|
||||
doubleTap.numberOfTapsRequired = 2
|
||||
// this requirement is needed to make sure the double tap is ever recognized
|
||||
singleTap.require(toFail: doubleTap)
|
||||
view.addGestureRecognizer(singleTap)
|
||||
view.addGestureRecognizer(doubleTap)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||
|
||||
accessibilityElements = [
|
||||
topControlsView!,
|
||||
contentView,
|
||||
descriptionTextView!,
|
||||
]
|
||||
}
|
||||
|
||||
private func setupContentView() {
|
||||
contentView.owner = self
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.addSubview(contentView)
|
||||
contentViewLeadingConstraint = contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor)
|
||||
contentViewTopConstraint = contentView.topAnchor.constraint(equalTo: scrollView.topAnchor)
|
||||
NSLayoutConstraint.activate([
|
||||
contentViewLeadingConstraint,
|
||||
contentViewTopConstraint,
|
||||
])
|
||||
contentViewSizeObservation = (contentView as UIView).observe(\.bounds, changeHandler: { [unowned self] _, _ in
|
||||
MainActor.runUnsafely {
|
||||
self.centerImage()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func setupControls() {
|
||||
shareContainer = UIView()
|
||||
shareContainer.isAccessibilityElement = true
|
||||
shareContainer.accessibilityTraits = .button
|
||||
shareContainer.accessibilityLabel = "Share"
|
||||
shareContainer.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(sharePressed)))
|
||||
shareContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
topControlsView.addSubview(shareContainer)
|
||||
shareImage = UIImageView(image: UIImage(systemName: "square.and.arrow.up"))
|
||||
shareImage.tintColor = .white
|
||||
shareImage.contentMode = .scaleAspectFit
|
||||
shareImage.translatesAutoresizingMaskIntoConstraints = false
|
||||
shareContainer.addSubview(shareImage)
|
||||
shareButtonTopConstraint = shareImage.topAnchor.constraint(greaterThanOrEqualTo: shareContainer.topAnchor)
|
||||
shareButtonLeadingConstraint = shareImage.leadingAnchor.constraint(greaterThanOrEqualTo: shareContainer.leadingAnchor)
|
||||
NSLayoutConstraint.activate([
|
||||
shareContainer.topAnchor.constraint(equalTo: topControlsView.topAnchor),
|
||||
shareContainer.leadingAnchor.constraint(equalTo: topControlsView.leadingAnchor),
|
||||
shareContainer.bottomAnchor.constraint(equalTo: topControlsView.bottomAnchor),
|
||||
shareContainer.widthAnchor.constraint(greaterThanOrEqualToConstant: 50),
|
||||
shareContainer.heightAnchor.constraint(greaterThanOrEqualToConstant: 50),
|
||||
|
||||
shareImage.centerXAnchor.constraint(equalTo: shareContainer.centerXAnchor),
|
||||
shareImage.centerYAnchor.constraint(equalTo: shareContainer.centerYAnchor),
|
||||
shareButtonTopConstraint,
|
||||
shareButtonLeadingConstraint,
|
||||
|
||||
shareImage.widthAnchor.constraint(equalToConstant: 24),
|
||||
shareImage.heightAnchor.constraint(equalToConstant: 24),
|
||||
])
|
||||
|
||||
closeContainer = UIView()
|
||||
closeContainer.isAccessibilityElement = true
|
||||
closeContainer.accessibilityTraits = .button
|
||||
closeContainer.accessibilityLabel = "Close"
|
||||
closeContainer.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(closeButtonPressed)))
|
||||
closeContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
topControlsView.addSubview(closeContainer)
|
||||
let closeImage = UIImageView(image: UIImage(systemName: "xmark"))
|
||||
closeImage.tintColor = .white
|
||||
closeImage.contentMode = .scaleAspectFit
|
||||
closeImage.translatesAutoresizingMaskIntoConstraints = false
|
||||
closeContainer.addSubview(closeImage)
|
||||
closeButtonTopConstraint = closeImage.topAnchor.constraint(greaterThanOrEqualTo: closeContainer.topAnchor)
|
||||
closeButtonTrailingConstraint = closeContainer.trailingAnchor.constraint(greaterThanOrEqualTo: closeImage.trailingAnchor)
|
||||
NSLayoutConstraint.activate([
|
||||
closeContainer.topAnchor.constraint(equalTo: topControlsView.topAnchor),
|
||||
closeContainer.trailingAnchor.constraint(equalTo: topControlsView.trailingAnchor),
|
||||
closeContainer.bottomAnchor.constraint(equalTo: closeContainer.bottomAnchor),
|
||||
closeContainer.widthAnchor.constraint(greaterThanOrEqualToConstant: 50),
|
||||
closeContainer.heightAnchor.constraint(greaterThanOrEqualToConstant: 50),
|
||||
|
||||
closeImage.centerXAnchor.constraint(equalTo: closeContainer.centerXAnchor),
|
||||
closeImage.centerYAnchor.constraint(equalTo: closeContainer.centerYAnchor),
|
||||
closeButtonTopConstraint,
|
||||
closeButtonTrailingConstraint,
|
||||
|
||||
closeImage.widthAnchor.constraint(equalToConstant: 24),
|
||||
closeImage.heightAnchor.constraint(equalToConstant: 24),
|
||||
])
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
// limit the image height to the safe area height, so the image doesn't overlap the top controls
|
||||
// while zoomed all the way out
|
||||
let maxHeight = view.bounds.height - view.safeAreaInsets.top - view.safeAreaInsets.bottom
|
||||
let heightScale = maxHeight / contentView.intrinsicContentSize.height
|
||||
let widthScale = view.bounds.width / contentView.intrinsicContentSize.width
|
||||
let minScale = min(widthScale, heightScale)
|
||||
skipUpdatingControlsWhileZooming = true
|
||||
scrollView.minimumZoomScale = minScale
|
||||
scrollView.zoomScale = minScale
|
||||
scrollView.maximumZoomScale = minScale >= 1 ? minScale + 2 : 2
|
||||
skipUpdatingControlsWhileZooming = false
|
||||
|
||||
centerImage()
|
||||
|
||||
let notchedDeviceTopInsets: [CGFloat] = [
|
||||
44, // iPhone X, Xs, Xs Max, 11 Pro, 11 Pro Max
|
||||
48, // iPhone XR, 11
|
||||
47, // iPhone 12, 12 Pro, 12 Pro Max, 13, 13 Pro, 13 Pro Max, 14, 14 Plus
|
||||
50, // iPhone 12 mini, 13 mini
|
||||
]
|
||||
let pillDeviceTopInsets: [CGFloat] = [
|
||||
59, // iPhone 14 Pro, 14 Pro Max
|
||||
]
|
||||
if notchedDeviceTopInsets.contains(view.safeAreaInsets.top) {
|
||||
// the notch width is not the same for the iPhones 13,
|
||||
// but what we actually want is the same offset from the edges
|
||||
// since the corner radius didn't change
|
||||
let notchWidth: CGFloat = 210
|
||||
let earWidth = (view.bounds.width - notchWidth) / 2
|
||||
let offset = (earWidth - shareImage.bounds.width) / 2
|
||||
shareButtonLeadingConstraint.constant = offset
|
||||
closeButtonTrailingConstraint.constant = offset
|
||||
} else if pillDeviceTopInsets.contains(view.safeAreaInsets.top) {
|
||||
shareButtonLeadingConstraint.constant = 24
|
||||
shareButtonTopConstraint.constant = 24
|
||||
closeButtonTrailingConstraint.constant = 24
|
||||
closeButtonTopConstraint.constant = 24
|
||||
}
|
||||
}
|
||||
|
||||
override func viewSafeAreaInsetsDidChange() {
|
||||
super.viewSafeAreaInsetsDidChange()
|
||||
// the controls view transforms take the safe area insets into account, so they need to be updated
|
||||
updateControlsView()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
// on the first appearance, the text view flashes its own scroll indicators automatically
|
||||
// so we only need to do it on subsequent appearances
|
||||
if isInitialAppearance {
|
||||
isInitialAppearance = false
|
||||
} else {
|
||||
if animated && controlsVisible && !descriptionTextView.isHidden {
|
||||
descriptionTextView.flashScrollIndicators()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func preferencesChanged() {
|
||||
if isGrayscale != Preferences.shared.grayscaleImages {
|
||||
isGrayscale = Preferences.shared.grayscaleImages
|
||||
contentView.grayscaleStateChanged()
|
||||
}
|
||||
}
|
||||
|
||||
func setControlsVisible(_ controlsVisible: Bool, animated: Bool) {
|
||||
self.controlsVisible = controlsVisible
|
||||
if animated {
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
// note: the value of animated: is passed to ImageAnalysisInteractino.setSupplementaryInterfaceHidden which (as of iOS 17.0.2 (21A350)):
|
||||
// - does not animate with animated:false when wrapped in a UIView.animate block
|
||||
// - does not animate with animated:true unless also wrapped in a UIView.animate block
|
||||
self.contentView.setControlsVisible(controlsVisible, animated: true)
|
||||
self.updateControlsView()
|
||||
}
|
||||
if controlsVisible && !descriptionTextView.isHidden {
|
||||
descriptionTextView.flashScrollIndicators()
|
||||
}
|
||||
} else {
|
||||
contentView.setControlsVisible(controlsVisible, animated: false)
|
||||
updateControlsView()
|
||||
}
|
||||
}
|
||||
|
||||
func updateControlsView() {
|
||||
let topOffset = self.controlsVisible ? 0 : -(self.topControlsView.bounds.height + self.view.safeAreaInsets.top)
|
||||
self.topControlsView.transform = CGAffineTransform(translationX: 0, y: topOffset)
|
||||
if self.imageDescription != nil {
|
||||
let bottomOffset = self.controlsVisible ? 0 : self.descriptionTextView.bounds.height + self.view.safeAreaInsets.bottom
|
||||
self.descriptionTextView.transform = CGAffineTransform(translationX: 0, y: bottomOffset)
|
||||
}
|
||||
}
|
||||
|
||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||
return contentView
|
||||
}
|
||||
|
||||
func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
||||
let prevZoomScale = self.prevZoomScale ?? scrollView.minimumZoomScale
|
||||
if !skipUpdatingControlsWhileZooming {
|
||||
if scrollView.zoomScale <= scrollView.minimumZoomScale {
|
||||
setControlsVisible(true, animated: true)
|
||||
} else if scrollView.zoomScale > prevZoomScale {
|
||||
setControlsVisible(false, animated: true)
|
||||
}
|
||||
}
|
||||
self.prevZoomScale = scrollView.zoomScale
|
||||
}
|
||||
|
||||
func centerImage() {
|
||||
let yOffset = max(0, (view.bounds.size.height - contentView.frame.height) / 2)
|
||||
contentViewTopConstraint.constant = yOffset
|
||||
|
||||
let xOffset = max(0, (view.bounds.size.width - contentView.frame.width) / 2)
|
||||
contentViewLeadingConstraint.constant = xOffset
|
||||
}
|
||||
|
||||
func zoomRectFor(scale: CGFloat, center: CGPoint) -> CGRect {
|
||||
var zoomRect = CGRect.zero
|
||||
zoomRect.size.width = contentView.frame.width / scale
|
||||
zoomRect.size.height = contentView.frame.height / scale
|
||||
let newCenter = scrollView.convert(center, to: contentView)
|
||||
zoomRect.origin.x = newCenter.x - (zoomRect.width / 2)
|
||||
zoomRect.origin.y = newCenter.y - (zoomRect.height / 2)
|
||||
return zoomRect
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
|
||||
func animateZoomOut() {
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
self.scrollView.zoomScale = self.scrollView.minimumZoomScale
|
||||
self.view.layoutIfNeeded()
|
||||
})
|
||||
}
|
||||
|
||||
@objc func scrollViewPressed(_ sender: UITapGestureRecognizer) {
|
||||
if scrollView.zoomScale > scrollView.minimumZoomScale {
|
||||
animateZoomOut()
|
||||
} else {
|
||||
setControlsVisible(!controlsVisible, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func scrollViewDoubleTapped(_ recognizer: UITapGestureRecognizer) {
|
||||
if scrollView.zoomScale <= scrollView.minimumZoomScale {
|
||||
let point = recognizer.location(in: recognizer.view)
|
||||
let scale: CGFloat
|
||||
if scrollView.minimumZoomScale < 1 {
|
||||
if 1 - scrollView.zoomScale <= 0.5 {
|
||||
scale = scrollView.zoomScale + 1
|
||||
} else {
|
||||
scale = 1
|
||||
}
|
||||
} else {
|
||||
scale = scrollView.maximumZoomScale
|
||||
}
|
||||
let rect = zoomRectFor(scale: scale, center: point)
|
||||
UIView.animate(withDuration: 0.3) {
|
||||
self.scrollView.zoom(to: rect, animated: false)
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
} else {
|
||||
animateZoomOut()
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func closeButtonPressed(_ sender: Any) {
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
@IBAction func sharePressed(_ sender: Any) {
|
||||
let activityVC = UIActivityViewController(activityItems: contentView.activityItemsForSharing, applicationActivities: [SaveToPhotosActivity()])
|
||||
activityVC.popoverPresentationController?.sourceView = shareImage
|
||||
present(activityVC, animated: true)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<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="LargeImageViewController" customModule="Tusker" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="descriptionTextView" destination="JZk-BO-2Vh" id="cby-Hc-ezg"/>
|
||||
<outlet property="scrollView" destination="Skj-xq-AgQ" id="TFb-zF-m1b"/>
|
||||
<outlet property="topControlsView" destination="kHo-B9-R7a" id="8sJ-xQ-7ix"/>
|
||||
<outlet property="view" destination="BJw-5C-9nT" id="1C2-VA-mNf"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="BJw-5C-9nT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" minimumZoomScale="0.25" maximumZoomScale="2" translatesAutoresizingMaskIntoConstraints="NO" id="Skj-xq-AgQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<gestureRecognizers/>
|
||||
</scrollView>
|
||||
<view contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kHo-B9-R7a">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="36"/>
|
||||
</view>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JZk-BO-2Vh">
|
||||
<rect key="frame" x="0.0" y="517" width="375" height="150"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.5" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="150" placeholder="YES" id="YfV-kQ-0RT"/>
|
||||
</constraints>
|
||||
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="w1g-VC-Ll9"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstItem="Skj-xq-AgQ" firstAttribute="centerY" secondItem="BJw-5C-9nT" secondAttribute="centerY" id="0Xb-ib-2hg"/>
|
||||
<constraint firstAttribute="bottom" secondItem="JZk-BO-2Vh" secondAttribute="bottom" id="7Z2-gW-sPj"/>
|
||||
<constraint firstItem="kHo-B9-R7a" firstAttribute="leading" secondItem="w1g-VC-Ll9" secondAttribute="leading" id="IvH-gU-Kie"/>
|
||||
<constraint firstAttribute="trailing" secondItem="JZk-BO-2Vh" secondAttribute="trailing" id="JgV-jy-qjS"/>
|
||||
<constraint firstItem="Skj-xq-AgQ" firstAttribute="centerX" secondItem="BJw-5C-9nT" secondAttribute="centerX" id="KMe-Zc-NZq"/>
|
||||
<constraint firstItem="Skj-xq-AgQ" firstAttribute="width" secondItem="BJw-5C-9nT" secondAttribute="width" id="Onj-l9-fBu"/>
|
||||
<constraint firstItem="w1g-VC-Ll9" firstAttribute="trailing" secondItem="kHo-B9-R7a" secondAttribute="trailing" id="Uh0-ub-R9V"/>
|
||||
<constraint firstItem="Skj-xq-AgQ" firstAttribute="height" secondItem="BJw-5C-9nT" secondAttribute="height" id="jvz-QW-n9c"/>
|
||||
<constraint firstItem="JZk-BO-2Vh" firstAttribute="leading" secondItem="BJw-5C-9nT" secondAttribute="leading" id="kkj-O9-1rE"/>
|
||||
<constraint firstItem="kHo-B9-R7a" firstAttribute="top" secondItem="BJw-5C-9nT" secondAttribute="top" id="n1O-C3-yQR"/>
|
||||
</constraints>
|
||||
<point key="canvasLocation" x="-164" y="475.41229385307349"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
|
@ -1,176 +0,0 @@
|
|||
// LoadingLargeImageViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 6/14/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import TuskerComponents
|
||||
|
||||
class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableViewController {
|
||||
|
||||
private var attachment: Attachment?
|
||||
let url: URL
|
||||
let cache: ImageCache
|
||||
let imageDescription: String?
|
||||
|
||||
private(set) var loaded = false
|
||||
private(set) var largeImageVC: LargeImageViewController?
|
||||
private var loadingVC: LoadingViewController?
|
||||
|
||||
private var imageRequest: ImageCache.Request?
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var shrinkGestureEnabled = true
|
||||
|
||||
weak var animationSourceView: UIImageView?
|
||||
var largeImageController: LargeImageViewController? { largeImageVC }
|
||||
var animationImage: UIImage? { largeImageVC?.animationImage ?? animationSourceView?.image }
|
||||
var dismissInteractionController: LargeImageInteractionController?
|
||||
|
||||
var isInteractivelyAnimatingDismissal: Bool = false {
|
||||
didSet {
|
||||
#if !os(visionOS)
|
||||
setNeedsStatusBarAppearanceUpdate()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
override var prefersStatusBarHidden: Bool {
|
||||
return !isInteractivelyAnimatingDismissal
|
||||
}
|
||||
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
||||
return .none
|
||||
}
|
||||
override var childForHomeIndicatorAutoHidden: UIViewController? {
|
||||
return largeImageVC
|
||||
}
|
||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||
return .allButUpsideDown
|
||||
} else {
|
||||
return .all
|
||||
}
|
||||
}
|
||||
|
||||
init(url: URL, cache: ImageCache, imageDescription: String?) {
|
||||
self.url = url
|
||||
self.cache = cache
|
||||
self.imageDescription = imageDescription
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
modalPresentationStyle = .fullScreen
|
||||
}
|
||||
|
||||
convenience init(attachment: Attachment) {
|
||||
self.init(url: attachment.url, cache: .attachments, imageDescription: attachment.description)
|
||||
self.attachment = attachment
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
overrideUserInterfaceStyle = .dark
|
||||
view.backgroundColor = .black
|
||||
|
||||
// always load full resolution from disk for large image, in case the cache is scaled
|
||||
if let entry = cache.get(url, loadOriginal: true) {
|
||||
// todo: if load original is true, is there any way entry.data could be nil?
|
||||
// feels like the data param of createLargeImage shouldn't be optional
|
||||
createLargeImage(data: entry.data, image: entry.image, url: url)
|
||||
} else {
|
||||
createPreview()
|
||||
|
||||
loadingVC = LoadingViewController()
|
||||
embedChild(loadingVC!)
|
||||
imageRequest = cache.get(url, loadOriginal: true) { [weak self] (data, image) in
|
||||
guard let self = self, let image = image else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.imageRequest = nil
|
||||
self.loadingVC?.removeViewAndController()
|
||||
self.createLargeImage(data: data, image: image, url: self.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if shrinkGestureEnabled {
|
||||
dismissInteractionController = LargeImageInteractionController(viewController: self)
|
||||
}
|
||||
}
|
||||
|
||||
override func didMove(toParent parent: UIViewController?) {
|
||||
super.didMove(toParent: parent)
|
||||
|
||||
if parent == nil {
|
||||
imageRequest?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
private func createLargeImage(data: Data?, image: UIImage, url: URL) {
|
||||
guard !loaded else { return }
|
||||
loaded = true
|
||||
|
||||
let content: LargeImageContentView
|
||||
|
||||
// todo: p sure grayscaling gifs has never worked
|
||||
if url.pathExtension == "gif", let data = data {
|
||||
// todo: pulling the gif controller out of the source view feels icky
|
||||
// is it possible for the source view's gif controller to have different data than we just got?
|
||||
// should this be a property set by the animation controller instead?
|
||||
let gifController = (animationSourceView as? GIFImageView)?.gifController ?? GIFController(gifData: data)
|
||||
content = LargeImageGifContentView(url: url, gifController: gifController)
|
||||
} else {
|
||||
if let transformedImage = ImageGrayscalifier.convertIfNecessary(url: url, image: image) {
|
||||
content = LargeImageImageContentView(url: url, data: data, image: transformedImage)
|
||||
} else {
|
||||
content = LargeImageImageContentView(url: url, data: data, image: image)
|
||||
}
|
||||
}
|
||||
|
||||
setContent(content)
|
||||
}
|
||||
|
||||
private func setContent(_ content: LargeImageContentView) {
|
||||
if let existing = largeImageVC {
|
||||
existing.contentView = content
|
||||
} else {
|
||||
largeImageVC = LargeImageViewController(contentView: content, description: imageDescription, sourceView: animationSourceView)
|
||||
largeImageVC!.initialControlsVisible = initialControlsVisible
|
||||
largeImageVC!.shrinkGestureEnabled = false
|
||||
embedChild(largeImageVC!)
|
||||
}
|
||||
}
|
||||
|
||||
private func createPreview() {
|
||||
guard !self.loaded,
|
||||
var image = animationSourceView?.image else { return }
|
||||
|
||||
if Preferences.shared.grayscaleImages,
|
||||
let source = image.cgImage,
|
||||
let grayscale = ImageGrayscalifier.convert(url: nil, cgImage: source) {
|
||||
image = grayscale
|
||||
}
|
||||
setContent(LargeImageImageContentView(url: url, data: nil, image: image))
|
||||
}
|
||||
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
//
|
||||
// LargeImageExpandAnimationController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 9/1/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TuskerComponents
|
||||
|
||||
@MainActor
|
||||
protocol LargeImageAnimatableViewController: UIViewController {
|
||||
var animationSourceView: UIImageView? { get }
|
||||
var largeImageController: LargeImageViewController? { get }
|
||||
var animationImage: UIImage? { get }
|
||||
var dismissInteractionController: LargeImageInteractionController? { get }
|
||||
var isInteractivelyAnimatingDismissal: Bool { get set }
|
||||
}
|
||||
|
||||
extension LargeImageAnimatableViewController {
|
||||
func sourceViewFrame(in coordinateSpace: UIView) -> CGRect? {
|
||||
guard let sourceView = animationSourceView else { return nil }
|
||||
|
||||
var sourceFrame = sourceView.convert(sourceView.bounds, to: coordinateSpace)
|
||||
if let scrollView = coordinateSpace 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)
|
||||
}
|
||||
|
||||
return sourceFrame
|
||||
}
|
||||
}
|
||||
|
||||
class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
if UIAccessibility.prefersCrossFadeTransitions {
|
||||
return 0.2
|
||||
} else {
|
||||
return 0.4
|
||||
}
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard let fromVC = transitionContext.viewController(forKey: .from),
|
||||
let toVC = transitionContext.viewController(forKey: .to) as? LargeImageAnimatableViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
if UIAccessibility.prefersCrossFadeTransitions {
|
||||
animateCrossFadeTransition(using: transitionContext)
|
||||
return
|
||||
}
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
containerView.addSubview(toVC.view)
|
||||
|
||||
let finalVCFrame = transitionContext.finalFrame(for: toVC)
|
||||
guard let sourceView = toVC.animationSourceView,
|
||||
let sourceFrame = toVC.sourceViewFrame(in: fromVC.view),
|
||||
let image = toVC.animationImage else {
|
||||
toVC.view.frame = finalVCFrame
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
return
|
||||
}
|
||||
|
||||
// use alpha, because isHidden makes stack views re-layout
|
||||
sourceView.alpha = 0
|
||||
toVC.view.alpha = 0
|
||||
toVC.largeImageController?.contentView.isHidden = true
|
||||
toVC.largeImageController?.setControlsVisible(false, animated: false)
|
||||
|
||||
var finalFrameSize = finalVCFrame.inset(by: toVC.view.safeAreaInsets).size
|
||||
let newWidth = finalFrameSize.width / image.size.width
|
||||
let newHeight = finalFrameSize.height / image.size.height
|
||||
if newHeight < newWidth {
|
||||
finalFrameSize.width = newHeight * image.size.width
|
||||
} else {
|
||||
finalFrameSize.height = newWidth * image.size.height
|
||||
}
|
||||
let finalFrame = CGRect(origin: CGPoint(x: finalVCFrame.midX - finalFrameSize.width / 2, y: finalVCFrame.midY - finalFrameSize.height / 2), size: finalFrameSize)
|
||||
|
||||
let imageView = GIFImageView(frame: sourceFrame)
|
||||
imageView.image = image
|
||||
if let gifController = (sourceView as? GIFImageView)?.gifController {
|
||||
gifController.attach(to: imageView)
|
||||
}
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.layer.cornerRadius = sourceView.layer.cornerRadius
|
||||
imageView.layer.maskedCorners = sourceView.layer.maskedCorners
|
||||
imageView.layer.masksToBounds = true
|
||||
|
||||
containerView.addSubview(imageView)
|
||||
|
||||
let duration = transitionDuration(using: transitionContext)
|
||||
let velocity = 1 / CGFloat(duration)
|
||||
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.75, initialSpringVelocity: velocity, options: []) {
|
||||
imageView.frame = finalFrame
|
||||
imageView.layer.cornerRadius = 0
|
||||
toVC.view.alpha = 1
|
||||
toVC.largeImageController?.setControlsVisible(true, animated: false)
|
||||
} completion: { (_) in
|
||||
// This shouldn't be necessary. I believe it's a workaround for using a XIB
|
||||
// for the large image VC. Without this, the final frame of the large image VC
|
||||
// is not set to the propper rect (it uses the frame of the preview device
|
||||
// in the XIB). When using a storyboard, the final frame is automatically set
|
||||
// (or UIKit does layout differently when loading the view) and this is not necessary.
|
||||
toVC.view.frame = finalVCFrame
|
||||
|
||||
toVC.largeImageController?.contentView.isHidden = false
|
||||
fromVC.view.isHidden = false
|
||||
imageView.removeFromSuperview()
|
||||
|
||||
sourceView.alpha = 1
|
||||
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
}
|
||||
}
|
||||
|
||||
func animateCrossFadeTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard let toVC = transitionContext.viewController(forKey: .to) as? LargeImageAnimatableViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
transitionContext.containerView.addSubview(toVC.view)
|
||||
toVC.view.alpha = 0
|
||||
|
||||
let duration = transitionDuration(using: transitionContext)
|
||||
UIView.animate(withDuration: duration) {
|
||||
toVC.view.alpha = 1
|
||||
} completion: { (_) in
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
//
|
||||
// LargeImageInteractionController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 9/1/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class LargeImageInteractionController: UIPercentDrivenInteractiveTransition {
|
||||
|
||||
var inProgress = false
|
||||
var direction: CGFloat?
|
||||
|
||||
var shouldCompleteTransition = false
|
||||
private weak var viewController: LargeImageAnimatableViewController!
|
||||
|
||||
init(viewController: LargeImageAnimatableViewController) {
|
||||
super.init()
|
||||
self.viewController = viewController
|
||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:)))
|
||||
panRecognizer.allowedScrollTypesMask = .continuous
|
||||
viewController.view.addGestureRecognizer(panRecognizer)
|
||||
}
|
||||
|
||||
@objc func handleGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
guard let recognizerSuperview = recognizer.view?.superview else {
|
||||
// Assume the gesture has ended b/c we don't have a view/superview anymore.
|
||||
inProgress = false
|
||||
direction = nil
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
let translation = recognizer.translation(in: recognizerSuperview)
|
||||
var progress = translation.y / 200
|
||||
if let direction = direction {
|
||||
progress *= direction
|
||||
} else if abs(progress) > 0.01 {
|
||||
// if the progress is less than +/- 1%, don't set the direction because the translation may be random jitter in the user's finger
|
||||
direction = progress > 0 ? 1 : -1
|
||||
progress = abs(progress)
|
||||
}
|
||||
progress = min(max(progress, 0), 1)
|
||||
let velocity = abs(recognizer.velocity(in: recognizer.view!.superview!).y)
|
||||
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
inProgress = true
|
||||
viewController.dismiss(animated: true)
|
||||
case .changed:
|
||||
shouldCompleteTransition = progress > 0.5 || velocity > 1000
|
||||
viewController.isInteractivelyAnimatingDismissal = progress > 0.1
|
||||
update(progress)
|
||||
case .cancelled:
|
||||
inProgress = false
|
||||
cancel()
|
||||
case .ended:
|
||||
inProgress = false
|
||||
direction = nil
|
||||
if shouldCompleteTransition {
|
||||
finish()
|
||||
} else {
|
||||
cancel()
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
super.cancel()
|
||||
viewController?.isInteractivelyAnimatingDismissal = false
|
||||
}
|
||||
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
//
|
||||
// LargeImageShrinkAnimationController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 9/1/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TuskerComponents
|
||||
|
||||
class LargeImageShrinkAnimationController: 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? LargeImageAnimatableViewController,
|
||||
let toVC = transitionContext.viewController(forKey: .to) else {
|
||||
return
|
||||
}
|
||||
|
||||
if UIAccessibility.prefersCrossFadeTransitions && !transitionContext.isInteractive {
|
||||
animateCrossFadeTransition(using: transitionContext)
|
||||
return
|
||||
}
|
||||
|
||||
guard let sourceView = fromVC.animationSourceView,
|
||||
let sourceFrame = fromVC.sourceViewFrame(in: toVC.view),
|
||||
let image = fromVC.animationImage else {
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
return
|
||||
}
|
||||
|
||||
// use alpha, becaus isHidden makes stack views re-layout
|
||||
sourceView.alpha = 0
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
|
||||
let originalVCFrame = fromVC.view.frame
|
||||
var originalFrameSize = originalVCFrame.inset(by: fromVC.view.safeAreaInsets).size
|
||||
let newWidth = originalFrameSize.width / image.size.width
|
||||
let newHeight = originalFrameSize.height / image.size.height
|
||||
if newHeight < newWidth {
|
||||
originalFrameSize.width = newHeight * image.size.width
|
||||
} else {
|
||||
originalFrameSize.height = newWidth * image.size.height
|
||||
}
|
||||
let originalFrame = CGRect(origin: CGPoint(x: originalVCFrame.midX - originalFrameSize.width / 2, y: originalVCFrame.midY - originalFrameSize.height / 2), size: originalFrameSize)
|
||||
|
||||
let imageView = GIFImageView(frame: originalFrame)
|
||||
imageView.image = image
|
||||
if let gifController = (sourceView as? GIFImageView)?.gifController {
|
||||
gifController.attach(to: imageView)
|
||||
// todo: this might not be necessary, the large image content view should have started it
|
||||
gifController.startAnimating()
|
||||
}
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.layer.cornerRadius = 0
|
||||
imageView.layer.masksToBounds = true
|
||||
|
||||
let blackView = UIView(frame: originalVCFrame)
|
||||
blackView.backgroundColor = .black
|
||||
blackView.alpha = 1
|
||||
|
||||
// Save the old superview of toVC so we can move it back after the animation.
|
||||
// It would be better to just not move toVC and instead remove from fromVC from the
|
||||
// containerView so that toVC is visible behind it, but that doens't work for some reason
|
||||
// (the entire screen just goes black, not even the contents of the containerView appear).
|
||||
let oldSuperview = toVC.view.superview
|
||||
|
||||
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 = sourceView.layer.cornerRadius
|
||||
imageView.layer.maskedCorners = sourceView.layer.maskedCorners
|
||||
blackView.alpha = 0
|
||||
}, completion: { _ in
|
||||
blackView.removeFromSuperview()
|
||||
imageView.removeFromSuperview()
|
||||
if transitionContext.transitionWasCancelled {
|
||||
toVC.view.removeFromSuperview()
|
||||
}
|
||||
|
||||
sourceView.alpha = 1
|
||||
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
|
||||
// move the toVC back to the view that it was in before
|
||||
oldSuperview?.addSubview(toVC.view)
|
||||
})
|
||||
}
|
||||
|
||||
func animateCrossFadeTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard let fromVC = transitionContext.viewController(forKey: .from) as? LargeImageAnimatableViewController,
|
||||
let toVC = transitionContext.viewController(forKey: .to) else {
|
||||
return
|
||||
}
|
||||
|
||||
transitionContext.containerView.addSubview(toVC.view)
|
||||
transitionContext.containerView.addSubview(fromVC.view)
|
||||
|
||||
let duration = transitionDuration(using: transitionContext)
|
||||
UIView.animate(withDuration: duration) {
|
||||
fromVC.view.alpha = 0
|
||||
} completion: { (_) in
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -474,7 +474,6 @@ class CustomAlertActionButton: UIControl {
|
|||
}
|
||||
}
|
||||
|
||||
#if os(visionOS)
|
||||
extension CustomAlertController: UIViewControllerTransitioningDelegate {
|
||||
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return CustomAlertPresentationAnimation()
|
||||
|
@ -484,17 +483,6 @@ extension CustomAlertController: UIViewControllerTransitioningDelegate {
|
|||
return CustomAlertDismissAnimation()
|
||||
}
|
||||
}
|
||||
#else
|
||||
extension CustomAlertController {
|
||||
override func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return CustomAlertPresentationAnimation()
|
||||
}
|
||||
|
||||
override func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return CustomAlertDismissAnimation()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
class CustomAlertPresentationAnimation: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
|
|
|
@ -651,18 +651,6 @@ struct MenuPreviewHelper {
|
|||
}
|
||||
}
|
||||
|
||||
extension LargeImageViewController: CustomPreviewPresenting {
|
||||
func presentFromPreview(presenter: UIViewController) {
|
||||
presenter.present(self, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension GalleryViewController: CustomPreviewPresenting {
|
||||
func presentFromPreview(presenter: UIViewController) {
|
||||
presenter.present(self, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension SFSafariViewController: CustomPreviewPresenting {
|
||||
func presentFromPreview(presenter: UIViewController) {
|
||||
presenter.present(self, animated: true)
|
||||
|
|
|
@ -20,7 +20,7 @@ protocol TuskerNavigationDelegate: UIViewController, ToastableViewController {
|
|||
extension TuskerNavigationDelegate {
|
||||
|
||||
func show(_ vc: UIViewController) {
|
||||
if vc is LargeImageViewController || vc is GalleryViewController || vc is SFSafariViewController {
|
||||
if vc is SFSafariViewController {
|
||||
present(vc, animated: true)
|
||||
} else {
|
||||
show(vc, sender: self)
|
||||
|
|
|
@ -443,11 +443,15 @@ fileprivate extension AttachmentView {
|
|||
extension AttachmentView: UIContextMenuInteractionDelegate {
|
||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
||||
return UIContextMenuConfiguration { [unowned self] () -> UIViewController? in
|
||||
if self.attachment.kind == .image {
|
||||
return AttachmentPreviewViewController(sourceView: self)
|
||||
} else if self.attachment.kind == .gifv {
|
||||
let vc = GifvAttachmentViewController(attachment: self.attachment)
|
||||
vc.preferredContentSize = self.image?.size ?? .zero
|
||||
if self.attachment.kind == .image,
|
||||
let image {
|
||||
return ImageGalleryContentViewController(url: self.attachment.url, caption: nil, originalData: nil, image: image, gifController: self.gifController)
|
||||
} else if self.attachment.kind == .gifv,
|
||||
let gifvView {
|
||||
return GifvGalleryContentViewController(controller: gifvView.controller, caption: nil)
|
||||
} else if self.attachment.kind == .video || self.attachment.kind == .audio {
|
||||
let vc = VideoGalleryContentViewController(url: self.attachment.url, caption: nil)
|
||||
vc.player.isMuted = true
|
||||
return vc
|
||||
} else {
|
||||
return self.delegate?.attachmentViewGallery(startingAt: self.index)
|
||||
|
@ -475,7 +479,7 @@ extension AttachmentView: UIContextMenuInteractionDelegate {
|
|||
itemData = Task { data }
|
||||
}
|
||||
} else if self.attachment.kind == .gifv {
|
||||
itemSource = GifvActivityItemSource(asset: AVAsset(url: self.attachment.url), attachment: self.attachment)
|
||||
itemSource = GifvActivityItemSource(asset: AVAsset(url: self.attachment.url), url: self.attachment.url)
|
||||
itemData = Task {
|
||||
try? await URLSession.shared.data(from: self.attachment.url).0
|
||||
}
|
||||
|
@ -509,11 +513,7 @@ extension AttachmentView: UIContextMenuInteractionDelegate {
|
|||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
||||
animator.addCompletion {
|
||||
animator.preferredCommitStyle = .pop
|
||||
if let gallery = animator.previewViewController as? GalleryViewController {
|
||||
self.delegate?.attachmentViewPresent(gallery, animated: true)
|
||||
} else {
|
||||
self.showGallery()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue