Compare commits
33 Commits
b623e348c2
...
0e5aab75df
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 0e5aab75df | |
Shadowfacts | c715d11fc2 | |
Shadowfacts | 8010e86711 | |
Shadowfacts | a41d27f18c | |
Shadowfacts | 083add273b | |
Shadowfacts | 64365bdf2b | |
Shadowfacts | 6adcad63b3 | |
Shadowfacts | 393a134648 | |
Shadowfacts | ba3e9e7491 | |
Shadowfacts | 920f926b48 | |
Shadowfacts | 6e27399e10 | |
Shadowfacts | c3c19b1994 | |
Shadowfacts | 1f40cc9928 | |
Shadowfacts | 66020b7847 | |
Shadowfacts | 00bf99334f | |
Shadowfacts | 3aef7d4d93 | |
Shadowfacts | a901af6be9 | |
Shadowfacts | 056346cee9 | |
Shadowfacts | 30c04b49e7 | |
Shadowfacts | 848022ec6e | |
Shadowfacts | 39e847bda8 | |
Shadowfacts | 5d751cd994 | |
Shadowfacts | d27bddb2ca | |
Shadowfacts | 36326e4469 | |
Shadowfacts | 6b7904ed52 | |
Shadowfacts | 61c6d63c67 | |
Shadowfacts | c0316f55ef | |
Shadowfacts | 803ba50f53 | |
Shadowfacts | 5d0c59e863 | |
Shadowfacts | c7b4d00da7 | |
Shadowfacts | f2a8b91769 | |
Shadowfacts | ce464dfb9f | |
Shadowfacts | d4bf289716 |
|
@ -0,0 +1,59 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 2020.1 (6)
|
||||||
|
This is the pre-WWDC update with lots of bugfixes and some small features. There will likely be another build this week to fix any pressing issues that arise from iOS 14.
|
||||||
|
|
||||||
|
Features/Improvements:
|
||||||
|
- Add mute/unmute conversation status action
|
||||||
|
- iPadOS: Add pointer interactions to remove attachment button, gallery view share/dismiss buttons
|
||||||
|
- Disable reblog button for direct/followers-only posts
|
||||||
|
- On Pleroma, the reblog button is still enabled for your own followers-only posts to match Pleroma's "Boost to original audience" feature.
|
||||||
|
- Add preference to always display status visibilities below account avatars
|
||||||
|
- Add preference to show reply indicators for statuses in timelines
|
||||||
|
- Show share/dismiss controls and image description for gifv attachments
|
||||||
|
- 'Share' is currently disabled for gifv attachments, it will be enabled in a future build
|
||||||
|
- Add crash report helper
|
||||||
|
- If the app detects that it crashed the last time it was running, it will allow you to review the crash report and email it to me
|
||||||
|
- Add Recognize Text context menu option for images on the Compose screen
|
||||||
|
- This uses iOS' builtin Vision framework to perform on-device OCR and generate an image description from the recognized text
|
||||||
|
- Tweak attachment previews to always have a 16:9 aspect ratio
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Fix account/status More actions not working
|
||||||
|
- Improve share sheet loading speed
|
||||||
|
- Fix crash when loading bookmarks
|
||||||
|
- Prompt for Photos access before showing photo picker. Prevents empty sheet displaying.
|
||||||
|
- Fix profile fields not displaying and improve layout
|
||||||
|
- Fix profile header image not displaying the first time an account is loaded
|
||||||
|
- Don't show Follow action for your own account
|
||||||
|
- Fix attachments on the Compose screen being cut-off above the home indicator on iPhone X-style devices
|
||||||
|
- Fix audio being played by other apps pausing when displaying a gifv attachment on Mastodon
|
||||||
|
|
||||||
|
|
||||||
|
## 2020.1 (5)
|
||||||
|
The main focus of this update has been switching to using CoreData internally to cache/synchronize the most up-to-date versions of all statuses. Currently, this does not provide any new functionality, however, it lays the groundwork for several significant features coming in the future, including multiple window support on iPadOS and state restoration/persistence between launches.
|
||||||
|
|
||||||
|
Even though there aren't a huge number of new features in this build, a great deal has changed under the hood. As such, this build may suffer somewhat in the stability department. Please bear with me and report any issues you encounter; you can send me a message on the fediverse, email me at me@shadowfacts.net, or file an issue on the project issue tracker at https://git.shadowfacts.net/shadowfacts/Tusker/issues. Thank you!
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- iPadOS: Add pointer interactions to status action buttons and profile header button
|
||||||
|
- iPadOS: Allow scrolling w/ trackpad/magic mouse to dismiss attachment gallery
|
||||||
|
- iPadOS: Enable interactive push gesture with trackpad/magic mouse
|
||||||
|
- Add drawing attachments using PencilKit
|
||||||
|
- Long-press to open context menu on the 'Add Attachment' button on the Compose screen, select 'Draw Something'
|
||||||
|
- Supports Apple Pencil on iPad, including tilt and pressure sensitivity
|
||||||
|
- Add avatar and instance domain in accounts switcher in Preferences
|
||||||
|
- Show gifv attachments on Mastodon
|
||||||
|
- Currently doesn't show attachment description or share/close buttons
|
||||||
|
- Add 'Clear Cache' option to Preferences -> Advanced for debugging
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Fix size of attachment previews in context menu
|
||||||
|
- Fix previewing audio/video attachments
|
||||||
|
- Fix incorrect image size during attachment expand/shrink animation
|
||||||
|
- Prevent avatars in grouped action notification from overflowing the cell and hiding the timestamp
|
||||||
|
- Fix text in conversation main statuses not being de-selectable
|
||||||
|
- Fix scroll-to-top sometimes not scrolling all the way to the top
|
||||||
|
- Fix account profile descriptions being squashed in the follow notification account list
|
||||||
|
|
||||||
|
|
|
@ -80,28 +80,28 @@ public final class Status: /*StatusProtocol,*/ Decodable {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/unfavourite")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/unfavourite")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func pin(_ status: Status) -> Request<Status> {
|
public static func pin(_ statusID: String) -> Request<Status> {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/pin")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/pin")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func unpin(_ status: Status) -> Request<Status> {
|
public static func unpin(_ statusID: String) -> Request<Status> {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/unpin")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/unpin")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func bookmark(_ status: Status) -> Request<Status> {
|
public static func bookmark(_ statusID: String) -> Request<Status> {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/bookmark")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/bookmark")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func unbookmark(_ statusID: String) -> Request<Status> {
|
public static func unbookmark(_ statusID: String) -> Request<Status> {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/unbookmark")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/unbookmark")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func muteConversation(_ status: Status) -> Request<Status> {
|
public static func muteConversation(_ statusID: String) -> Request<Status> {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/mute")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/mute")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func unmuteConversation(_ status: Status) -> Request<Status> {
|
public static func unmuteConversation(_ statusID: String) -> Request<Status> {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/unmute")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/unmute")
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
|
|
@ -168,6 +168,11 @@
|
||||||
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */; };
|
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */; };
|
||||||
D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */; };
|
D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */; };
|
||||||
D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68015412401A74600D6103B /* MediaPrefsView.swift */; };
|
D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68015412401A74600D6103B /* MediaPrefsView.swift */; };
|
||||||
|
D681A29A249AD62D0085E54E /* LargeImageContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681A299249AD62D0085E54E /* LargeImageContentView.swift */; };
|
||||||
|
D681E4D3246E2AFF0053414F /* MuteConversationActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681E4D2246E2AFF0053414F /* MuteConversationActivity.swift */; };
|
||||||
|
D681E4D5246E2BC30053414F /* UnmuteConversationActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681E4D4246E2BC30053414F /* UnmuteConversationActivity.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 */; };
|
D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */; };
|
||||||
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; };
|
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; };
|
||||||
D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */; };
|
D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */; };
|
||||||
|
@ -180,6 +185,7 @@
|
||||||
D6969E9E240C81B9002843CE /* NSTextAttachment+Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */; };
|
D6969E9E240C81B9002843CE /* NSTextAttachment+Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */; };
|
||||||
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9F240C8384002843CE /* EmojiLabel.swift */; };
|
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9F240C8384002843CE /* EmojiLabel.swift */; };
|
||||||
D6969EA4240DD28D002843CE /* UnknownNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6969EA2240DD28D002843CE /* UnknownNotificationTableViewCell.xib */; };
|
D6969EA4240DD28D002843CE /* UnknownNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6969EA2240DD28D002843CE /* UnknownNotificationTableViewCell.xib */; };
|
||||||
|
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = D69CCBBE249E6EFD000AF167 /* CrashReporter */; };
|
||||||
D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */; };
|
D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */; };
|
||||||
D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */; };
|
D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */; };
|
||||||
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */; };
|
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */; };
|
||||||
|
@ -228,6 +234,8 @@
|
||||||
D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */; };
|
D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */; };
|
||||||
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C94D862139E62700CB5196 /* LargeImageViewController.swift */; };
|
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C94D862139E62700CB5196 /* LargeImageViewController.swift */; };
|
||||||
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C94D882139E6EC00CB5196 /* AttachmentView.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 */; };
|
||||||
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */; };
|
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */; };
|
||||||
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6D4DDD6212518A200E1C4BB /* Assets.xcassets */; };
|
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6D4DDD6212518A200E1C4BB /* Assets.xcassets */; };
|
||||||
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */; };
|
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */; };
|
||||||
|
@ -243,6 +251,8 @@
|
||||||
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */; };
|
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */; };
|
||||||
D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */; };
|
D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */; };
|
||||||
D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F1F84C2193B56E00F5FE67 /* Cache.swift */; };
|
D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F1F84C2193B56E00F5FE67 /* Cache.swift */; };
|
||||||
|
D6F2E965249E8BFD005846BB /* CrashReporterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F2E963249E8BFD005846BB /* CrashReporterViewController.swift */; };
|
||||||
|
D6F2E966249E8BFD005846BB /* CrashReporterViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */; };
|
||||||
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */; };
|
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */; };
|
||||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; };
|
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
@ -467,6 +477,11 @@
|
||||||
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; sourceTree = "<group>"; };
|
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; sourceTree = "<group>"; };
|
||||||
D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposingPrefsView.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>"; };
|
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>"; };
|
||||||
|
D681E4D2246E2AFF0053414F /* MuteConversationActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuteConversationActivity.swift; sourceTree = "<group>"; };
|
||||||
|
D681E4D4246E2BC30053414F /* UnmuteConversationActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnmuteConversationActivity.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>"; };
|
D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeDrawingViewController.swift; sourceTree = "<group>"; };
|
||||||
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = "<group>"; };
|
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = "<group>"; };
|
||||||
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedNavigationViewController.swift; sourceTree = "<group>"; };
|
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedNavigationViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -523,6 +538,8 @@
|
||||||
D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentsContainerView.swift; sourceTree = "<group>"; };
|
D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentsContainerView.swift; sourceTree = "<group>"; };
|
||||||
D6C94D862139E62700CB5196 /* LargeImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageViewController.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>"; };
|
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>"; };
|
||||||
D6D4DDCC212518A000E1C4BB /* Tusker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tusker.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
D6D4DDCC212518A000E1C4BB /* Tusker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tusker.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
D6D4DDD6212518A200E1C4BB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
D6D4DDD6212518A200E1C4BB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
@ -544,6 +561,8 @@
|
||||||
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Scenes.swift"; sourceTree = "<group>"; };
|
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Scenes.swift"; sourceTree = "<group>"; };
|
||||||
D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISceneSession+MastodonController.swift"; sourceTree = "<group>"; };
|
D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISceneSession+MastodonController.swift"; sourceTree = "<group>"; };
|
||||||
D6F1F84C2193B56E00F5FE67 /* Cache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = "<group>"; };
|
D6F1F84C2193B56E00F5FE67 /* Cache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = "<group>"; };
|
||||||
|
D6F2E963249E8BFD005846BB /* CrashReporterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporterViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CrashReporterViewController.xib; sourceTree = "<group>"; };
|
||||||
D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewController.swift; sourceTree = "<group>"; };
|
D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewController.swift; sourceTree = "<group>"; };
|
||||||
D6F953EF21251A2900CF0F2B /* MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonController.swift; sourceTree = "<group>"; };
|
D6F953EF21251A2900CF0F2B /* MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonController.swift; sourceTree = "<group>"; };
|
||||||
D6F98BD523AE951F008A4DAC /* Swifter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Swifter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D6F98BD523AE951F008A4DAC /* Swifter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Swifter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -572,6 +591,7 @@
|
||||||
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
||||||
D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */,
|
D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */,
|
||||||
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
|
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
|
||||||
|
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */,
|
||||||
D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */,
|
D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */,
|
||||||
0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */,
|
0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
|
@ -602,6 +622,7 @@
|
||||||
04D14BAE22B34A2800642648 /* GalleryViewController.swift */,
|
04D14BAE22B34A2800642648 /* GalleryViewController.swift */,
|
||||||
D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */,
|
D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */,
|
||||||
D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */,
|
D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */,
|
||||||
|
D6CA6A93249FADE700AD45C1 /* GalleryPlayerViewController.swift */,
|
||||||
);
|
);
|
||||||
path = "Attachment Gallery";
|
path = "Attachment Gallery";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -773,6 +794,8 @@
|
||||||
D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */,
|
D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */,
|
||||||
D64BC18723C1640A000D0238 /* PinStatusActivity.swift */,
|
D64BC18723C1640A000D0238 /* PinStatusActivity.swift */,
|
||||||
D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */,
|
D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */,
|
||||||
|
D681E4D2246E2AFF0053414F /* MuteConversationActivity.swift */,
|
||||||
|
D681E4D4246E2BC30053414F /* UnmuteConversationActivity.swift */,
|
||||||
);
|
);
|
||||||
path = "Status Activities";
|
path = "Status Activities";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -839,6 +862,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D6C693FA2162FE5D007D6A6D /* Utilities */,
|
D6C693FA2162FE5D007D6A6D /* Utilities */,
|
||||||
|
D6F2E960249E772F005846BB /* Crash Reporter */,
|
||||||
D641C782213DD7F0004B4513 /* Main */,
|
D641C782213DD7F0004B4513 /* Main */,
|
||||||
D641C783213DD7FE004B4513 /* Onboarding */,
|
D641C783213DD7FE004B4513 /* Onboarding */,
|
||||||
D641C781213DD7DD004B4513 /* Timeline */,
|
D641C781213DD7DD004B4513 /* Timeline */,
|
||||||
|
@ -932,6 +956,7 @@
|
||||||
D646C954213B364600269FB5 /* Transitions */,
|
D646C954213B364600269FB5 /* Transitions */,
|
||||||
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */,
|
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */,
|
||||||
D6C94D862139E62700CB5196 /* LargeImageViewController.swift */,
|
D6C94D862139E62700CB5196 /* LargeImageViewController.swift */,
|
||||||
|
D681A299249AD62D0085E54E /* LargeImageContentView.swift */,
|
||||||
041160FE22B442870030A9B7 /* LoadingLargeImageViewController.swift */,
|
041160FE22B442870030A9B7 /* LoadingLargeImageViewController.swift */,
|
||||||
);
|
);
|
||||||
path = "Large Image";
|
path = "Large Image";
|
||||||
|
@ -1131,6 +1156,8 @@
|
||||||
D6AEBB3F2321640F00E5038B /* Activities */ = {
|
D6AEBB3F2321640F00E5038B /* Activities */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D681E4D6246E32290053414F /* StatusActivityItemSource.swift */,
|
||||||
|
D681E4D8246E346E0053414F /* AccountActivityItemSource.swift */,
|
||||||
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */,
|
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */,
|
||||||
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */,
|
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */,
|
||||||
D64BC19123C271D9000D0238 /* MastodonActivity.swift */,
|
D64BC19123C271D9000D0238 /* MastodonActivity.swift */,
|
||||||
|
@ -1267,6 +1294,7 @@
|
||||||
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */,
|
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */,
|
||||||
D64D8CA82463B494006B0BAA /* CachedDictionary.swift */,
|
D64D8CA82463B494006B0BAA /* CachedDictionary.swift */,
|
||||||
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */,
|
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */,
|
||||||
|
D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */,
|
||||||
D6F1F84E2193B9BE00F5FE67 /* Caching */,
|
D6F1F84E2193B9BE00F5FE67 /* Caching */,
|
||||||
D6757A7A2157E00100721E32 /* XCallbackURL */,
|
D6757A7A2157E00100721E32 /* XCallbackURL */,
|
||||||
D62D241E217AA46B005076CC /* Shortcuts */,
|
D62D241E217AA46B005076CC /* Shortcuts */,
|
||||||
|
@ -1318,6 +1346,15 @@
|
||||||
path = Caching;
|
path = Caching;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D6F2E960249E772F005846BB /* Crash Reporter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D6F2E963249E8BFD005846BB /* CrashReporterViewController.swift */,
|
||||||
|
D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */,
|
||||||
|
);
|
||||||
|
path = "Crash Reporter";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D6F953F121251A2F00CF0F2B /* Controllers */ = {
|
D6F953F121251A2F00CF0F2B /* Controllers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1395,6 +1432,7 @@
|
||||||
name = Tusker;
|
name = Tusker;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
D6B0539E23BD2BA300A066FA /* SheetController */,
|
D6B0539E23BD2BA300A066FA /* SheetController */,
|
||||||
|
D69CCBBE249E6EFD000AF167 /* CrashReporter */,
|
||||||
);
|
);
|
||||||
productName = Tusker;
|
productName = Tusker;
|
||||||
productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */;
|
productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */;
|
||||||
|
@ -1490,6 +1528,7 @@
|
||||||
mainGroup = D6D4DDC3212518A000E1C4BB;
|
mainGroup = D6D4DDC3212518A000E1C4BB;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */,
|
D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */,
|
||||||
|
D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */,
|
||||||
);
|
);
|
||||||
productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */;
|
productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
@ -1548,6 +1587,7 @@
|
||||||
D63F9C66241C4CC3004C03CF /* AddAttachmentTableViewCell.xib in Resources */,
|
D63F9C66241C4CC3004C03CF /* AddAttachmentTableViewCell.xib in Resources */,
|
||||||
D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */,
|
D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */,
|
||||||
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */,
|
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */,
|
||||||
|
D6F2E966249E8BFD005846BB /* CrashReporterViewController.xib in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -1690,11 +1730,14 @@
|
||||||
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */,
|
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */,
|
||||||
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */,
|
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */,
|
||||||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */,
|
D620483623D38075008A63EF /* ContentTextView.swift in Sources */,
|
||||||
|
D6F2E965249E8BFD005846BB /* CrashReporterViewController.swift in Sources */,
|
||||||
|
D6CA6A94249FADE700AD45C1 /* GalleryPlayerViewController.swift in Sources */,
|
||||||
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */,
|
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */,
|
||||||
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */,
|
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */,
|
||||||
D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */,
|
D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */,
|
||||||
D60309B52419D4F100A465FF /* ComposeAttachmentsViewController.swift in Sources */,
|
D60309B52419D4F100A465FF /* ComposeAttachmentsViewController.swift in Sources */,
|
||||||
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */,
|
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */,
|
||||||
|
D6CA6A92249FAD8900AD45C1 /* AudioSessionHelper.swift in Sources */,
|
||||||
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */,
|
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */,
|
||||||
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */,
|
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */,
|
||||||
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */,
|
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */,
|
||||||
|
@ -1705,6 +1748,7 @@
|
||||||
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */,
|
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */,
|
||||||
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */,
|
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */,
|
||||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
||||||
|
D681E4D5246E2BC30053414F /* UnmuteConversationActivity.swift in Sources */,
|
||||||
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */,
|
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */,
|
||||||
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */,
|
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */,
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
||||||
|
@ -1744,6 +1788,7 @@
|
||||||
D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */,
|
D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */,
|
||||||
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */,
|
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */,
|
||||||
D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */,
|
D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */,
|
||||||
|
D681E4D3246E2AFF0053414F /* MuteConversationActivity.swift in Sources */,
|
||||||
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */,
|
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */,
|
||||||
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */,
|
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */,
|
||||||
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
|
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
|
||||||
|
@ -1756,6 +1801,7 @@
|
||||||
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
|
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
|
||||||
D67895C0246870DE00D4CD9E /* LocalAccountAvatarView.swift in Sources */,
|
D67895C0246870DE00D4CD9E /* LocalAccountAvatarView.swift in Sources */,
|
||||||
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,
|
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,
|
||||||
|
D681A29A249AD62D0085E54E /* LargeImageContentView.swift in Sources */,
|
||||||
D6DFC69E242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift in Sources */,
|
D6DFC69E242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift in Sources */,
|
||||||
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */,
|
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */,
|
||||||
D63F9C68241C4F79004C03CF /* AddAttachmentTableViewCell.swift in Sources */,
|
D63F9C68241C4F79004C03CF /* AddAttachmentTableViewCell.swift in Sources */,
|
||||||
|
@ -1773,6 +1819,7 @@
|
||||||
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */,
|
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */,
|
||||||
D6DFC6A0242C4CCC00ACC392 /* WeakArray.swift in Sources */,
|
D6DFC6A0242C4CCC00ACC392 /* WeakArray.swift in Sources */,
|
||||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */,
|
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */,
|
||||||
|
D681E4D7246E32290053414F /* StatusActivityItemSource.swift in Sources */,
|
||||||
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */,
|
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */,
|
||||||
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */,
|
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */,
|
||||||
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */,
|
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */,
|
||||||
|
@ -1792,6 +1839,7 @@
|
||||||
0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */,
|
0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */,
|
||||||
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */,
|
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */,
|
||||||
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */,
|
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */,
|
||||||
|
D681E4D9246E346E0053414F /* AccountActivityItemSource.swift in Sources */,
|
||||||
D626493F23C101C500612E6E /* AlbumAssetCollectionViewController.swift in Sources */,
|
D626493F23C101C500612E6E /* AlbumAssetCollectionViewController.swift in Sources */,
|
||||||
D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */,
|
D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */,
|
||||||
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */,
|
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */,
|
||||||
|
@ -2094,7 +2142,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 5;
|
CURRENT_PROJECT_VERSION = 6;
|
||||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||||
INFOPLIST_FILE = Tusker/Info.plist;
|
INFOPLIST_FILE = Tusker/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
@ -2119,7 +2167,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 5;
|
CURRENT_PROJECT_VERSION = 6;
|
||||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||||
INFOPLIST_FILE = Tusker/Info.plist;
|
INFOPLIST_FILE = Tusker/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
@ -2278,6 +2326,14 @@
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
|
D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/microsoft/plcrashreporter";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMinorVersion;
|
||||||
|
minimumVersion = 1.7.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */ = {
|
D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://git.shadowfacts.net/shadowfacts/SheetController.git";
|
repositoryURL = "https://git.shadowfacts.net/shadowfacts/SheetController.git";
|
||||||
|
@ -2289,6 +2345,11 @@
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
D69CCBBE249E6EFD000AF167 /* CrashReporter */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */;
|
||||||
|
productName = CrashReporter;
|
||||||
|
};
|
||||||
D6B0539E23BD2BA300A066FA /* SheetController */ = {
|
D6B0539E23BD2BA300A066FA /* SheetController */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */;
|
package = D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */;
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
"object": {
|
"object": {
|
||||||
"pins": [
|
"pins": [
|
||||||
{
|
{
|
||||||
"package": "SheetController",
|
"package": "PLCrashReporter",
|
||||||
"repositoryURL": "https://git.shadowfacts.net/shadowfacts/SheetController.git",
|
"repositoryURL": "https://github.com/microsoft/plcrashreporter",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": "master",
|
"branch": null,
|
||||||
"revision": "6926446c4e15eb7f4513c4c00df9279553b330be",
|
"revision": "4637a7854de2cc5c354d46fb931d74bdbc2c043e",
|
||||||
"version": null
|
"version": "1.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
|
||||||
|
|
||||||
class AccountActivity: MastodonActivity {
|
class AccountActivity: MastodonActivity {
|
||||||
|
|
||||||
|
@ -15,17 +14,17 @@ class AccountActivity: MastodonActivity {
|
||||||
return .action
|
return .action
|
||||||
}
|
}
|
||||||
|
|
||||||
var account: Account?
|
var account: AccountMO?
|
||||||
|
|
||||||
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
|
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
|
||||||
for case is Account in activityItems {
|
for case is AccountMO in activityItems {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(withActivityItems activityItems: [Any]) {
|
override func prepare(withActivityItems activityItems: [Any]) {
|
||||||
for case let account as Account in activityItems {
|
for case let account as AccountMO in activityItems {
|
||||||
self.account = account
|
self.account = account
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// AccountActivityItemSource.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 5/14/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import LinkPresentation
|
||||||
|
|
||||||
|
class AccountActivityItemSource: NSObject, UIActivityItemSource {
|
||||||
|
let account: AccountMO
|
||||||
|
|
||||||
|
init(_ account: AccountMO) {
|
||||||
|
self.account = account
|
||||||
|
}
|
||||||
|
|
||||||
|
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
||||||
|
return account
|
||||||
|
}
|
||||||
|
|
||||||
|
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
||||||
|
return account
|
||||||
|
}
|
||||||
|
|
||||||
|
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
|
||||||
|
let metadata = LPLinkMetadata()
|
||||||
|
metadata.originalURL = account.url
|
||||||
|
metadata.url = account.url
|
||||||
|
metadata.title = "\(account.displayName) (@\(account.username)@\(account.url.host!)"
|
||||||
|
if let data = ImageCache.avatars.get(account.avatar),
|
||||||
|
let image = UIImage(data: data) {
|
||||||
|
metadata.iconProvider = NSItemProvider(object: image)
|
||||||
|
}
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ class BookmarkStatusActivity: StatusActivity {
|
||||||
override func perform() {
|
override func perform() {
|
||||||
guard let status = status else { return }
|
guard let status = status else { return }
|
||||||
|
|
||||||
let request = Status.bookmark(status)
|
let request = Status.bookmark(status.id)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case let .success(status, _) = response {
|
if case let .success(status, _) = response {
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
//
|
||||||
|
// MuteConversationActivity.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 5/14/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class MuteConversationActivity: StatusActivity {
|
||||||
|
override var activityType: UIActivity.ActivityType? {
|
||||||
|
return .muteConversation
|
||||||
|
}
|
||||||
|
|
||||||
|
override var activityTitle: String? {
|
||||||
|
return NSLocalizedString("Mute Conversation", comment: "mute conversation activity title")
|
||||||
|
}
|
||||||
|
|
||||||
|
override var activityImage: UIImage? {
|
||||||
|
return UIImage(systemName: "speaker.slash")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func perform() {
|
||||||
|
guard let status = status else { return }
|
||||||
|
|
||||||
|
let request = Status.muteConversation(status.id)
|
||||||
|
mastodonController.run(request) { (response) in
|
||||||
|
if case let .success(status, _) = response {
|
||||||
|
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
|
} else {
|
||||||
|
// todo: display error message
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ class PinStatusActivity: StatusActivity {
|
||||||
override func perform() {
|
override func perform() {
|
||||||
guard let status = status else { return }
|
guard let status = status else { return }
|
||||||
|
|
||||||
let request = Status.pin(status)
|
let request = Status.pin(status.id)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case let .success(status, _) = response {
|
if case let .success(status, _) = response {
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
|
||||||
|
|
||||||
class StatusActivity: MastodonActivity {
|
class StatusActivity: MastodonActivity {
|
||||||
|
|
||||||
|
@ -15,17 +14,17 @@ class StatusActivity: MastodonActivity {
|
||||||
return .action
|
return .action
|
||||||
}
|
}
|
||||||
|
|
||||||
var status: Status?
|
var status: StatusMO?
|
||||||
|
|
||||||
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
|
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
|
||||||
for case is Status in activityItems {
|
for case is StatusMO in activityItems {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(withActivityItems activityItems: [Any]) {
|
override func prepare(withActivityItems activityItems: [Any]) {
|
||||||
for case let status as Status in activityItems {
|
for case let status as StatusMO in activityItems {
|
||||||
self.status = status
|
self.status = status
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
//
|
||||||
|
// UnmuteConversationActivity.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 5/14/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class UnmuteConversationActivity: StatusActivity {
|
||||||
|
override var activityType: UIActivity.ActivityType? {
|
||||||
|
return .unmuteConversation
|
||||||
|
}
|
||||||
|
|
||||||
|
override var activityTitle: String? {
|
||||||
|
return NSLocalizedString("Unmute Conversation", comment: "unmute conversation activity title")
|
||||||
|
}
|
||||||
|
|
||||||
|
override var activityImage: UIImage? {
|
||||||
|
return UIImage(systemName: "speaker")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func perform() {
|
||||||
|
guard let status = status else { return }
|
||||||
|
|
||||||
|
let request = Status.unmuteConversation(status.id)
|
||||||
|
mastodonController.run(request) { (response) in
|
||||||
|
if case let .success(status, _) = response {
|
||||||
|
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
|
} else {
|
||||||
|
// todo: display error message
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ class UnpinStatusActivity: StatusActivity {
|
||||||
override func perform() {
|
override func perform() {
|
||||||
guard let status = status else { return }
|
guard let status = status else { return }
|
||||||
|
|
||||||
let request = Status.unpin(status)
|
let request = Status.unpin(status.id)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case let .success(status, _) = response {
|
if case let .success(status, _) = response {
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// StatusActivityItemSource.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 5/14/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import LinkPresentation
|
||||||
|
import SwiftSoup
|
||||||
|
|
||||||
|
class StatusActivityItemSource: NSObject, UIActivityItemSource {
|
||||||
|
let status: StatusMO
|
||||||
|
|
||||||
|
init(_ status: StatusMO) {
|
||||||
|
self.status = status
|
||||||
|
}
|
||||||
|
|
||||||
|
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
|
||||||
|
let metadata = LPLinkMetadata()
|
||||||
|
metadata.originalURL = status.url!
|
||||||
|
metadata.url = status.url!
|
||||||
|
let doc = try! SwiftSoup.parse(status.content)
|
||||||
|
let content = try! doc.text()
|
||||||
|
metadata.title = "\(status.account.displayName): \"\(content)\""
|
||||||
|
if let data = ImageCache.avatars.get(status.account.avatar),
|
||||||
|
let image = UIImage(data: data) {
|
||||||
|
metadata.iconProvider = NSItemProvider(object: image)
|
||||||
|
}
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,5 +22,7 @@ extension UIActivity.ActivityType {
|
||||||
static let unbookmarkStatus = UIActivity.ActivityType("\(Bundle.main.bundleIdentifier!).unbookmark_status")
|
static let unbookmarkStatus = UIActivity.ActivityType("\(Bundle.main.bundleIdentifier!).unbookmark_status")
|
||||||
static let pinStatus = UIActivity.ActivityType("\(Bundle.main.bundleIdentifier!).pin_status")
|
static let pinStatus = UIActivity.ActivityType("\(Bundle.main.bundleIdentifier!).pin_status")
|
||||||
static let unpinStatus = UIActivity.ActivityType("\(Bundle.main.bundleIdentifier!).unpin_status")
|
static let unpinStatus = UIActivity.ActivityType("\(Bundle.main.bundleIdentifier!).unpin_status")
|
||||||
|
static let muteConversation = UIActivity.ActivityType("\(Bundle.main.bundleIdentifier!).mute_conversation")
|
||||||
|
static let unmuteConversation = UIActivity.ActivityType("\(Bundle.main.bundleIdentifier!).unmute_conversation")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,41 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import CrashReporter
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
|
static private(set) var crashReporter: PLCrashReporter!
|
||||||
|
static var pendingCrashReport: PLCrashReport?
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
#if !DEBUG
|
||||||
|
setupCrashReporter()
|
||||||
|
#endif
|
||||||
|
|
||||||
AppShortcutItem.createItems(for: application)
|
AppShortcutItem.createItems(for: application)
|
||||||
|
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
AudioSessionHelper.disable()
|
||||||
|
AudioSessionHelper.setDefault()
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setupCrashReporter() {
|
||||||
|
let config = PLCrashReporterConfig(signalHandlerType: .BSD, symbolicationStrategy: .all)
|
||||||
|
AppDelegate.crashReporter = PLCrashReporter(configuration: config)
|
||||||
|
|
||||||
|
if AppDelegate.crashReporter.hasPendingCrashReport() {
|
||||||
|
let data = try! AppDelegate.crashReporter.loadPendingCrashReportDataAndReturnError()
|
||||||
|
AppDelegate.crashReporter.purgePendingCrashReport()
|
||||||
|
let report = try! PLCrashReport(data: data)
|
||||||
|
|
||||||
|
AppDelegate.pendingCrashReport = report
|
||||||
|
}
|
||||||
|
|
||||||
|
AppDelegate.crashReporter.enable()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
//
|
||||||
|
// AudioSessionHelper.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/21/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
struct AudioSessionHelper {
|
||||||
|
static func enable() {
|
||||||
|
try? AVAudioSession.sharedInstance().setActive(true, options: [])
|
||||||
|
}
|
||||||
|
|
||||||
|
static func disable() {
|
||||||
|
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func setDefault() {
|
||||||
|
try? AVAudioSession.sharedInstance().setCategory(.playback, options: .mixWithOthers)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func setVideoPlayback() {
|
||||||
|
try? AVAudioSession.sharedInstance().setCategory(.playback, options: [])
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ class ImageCache {
|
||||||
|
|
||||||
let cache: Cache<Data>
|
let cache: Cache<Data>
|
||||||
|
|
||||||
var requests = [URL: RequestGroup]()
|
private var groups = [URL: RequestGroup]()
|
||||||
|
|
||||||
init(name: String, memoryExpiry expiry: Expiry) {
|
init(name: String, memoryExpiry expiry: Expiry) {
|
||||||
let storage = MemoryStorage<Data>(config: MemoryConfig(expiry: expiry))
|
let storage = MemoryStorage<Data>(config: MemoryConfig(expiry: expiry))
|
||||||
|
@ -43,14 +43,18 @@ class ImageCache {
|
||||||
completion?(data)
|
completion?(data)
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
if let completion = completion, let group = requests[url] {
|
if let completion = completion, let group = groups[url] {
|
||||||
return group.addCallback(completion)
|
return group.addCallback(completion)
|
||||||
} else {
|
} else {
|
||||||
let group = RequestGroup(url: url)
|
let group = RequestGroup(url: url) { (data) in
|
||||||
let request = group.addCallback(completion)
|
if let data = data {
|
||||||
group.run { (data) in
|
|
||||||
try? self.cache.setObject(data, forKey: key)
|
try? self.cache.setObject(data, forKey: key)
|
||||||
}
|
}
|
||||||
|
self.groups.removeValue(forKey: url)
|
||||||
|
}
|
||||||
|
groups[url] = group
|
||||||
|
let request = group.addCallback(completion)
|
||||||
|
group.run()
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,29 +65,30 @@ class ImageCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancelWithoutCallback(_ url: URL) {
|
func cancelWithoutCallback(_ url: URL) {
|
||||||
requests[url]?.cancelWithoutCallback()
|
groups[url]?.cancelWithoutCallback()
|
||||||
}
|
}
|
||||||
|
|
||||||
class RequestGroup {
|
private class RequestGroup {
|
||||||
let url: URL
|
let url: URL
|
||||||
|
private let onFinished: (Data?) -> Void
|
||||||
private var task: URLSessionDataTask?
|
private var task: URLSessionDataTask?
|
||||||
private var requests = [Request]()
|
private var requests = [Request]()
|
||||||
|
|
||||||
init(url: URL) {
|
init(url: URL, onFinished: @escaping (Data?) -> Void) {
|
||||||
self.url = url
|
self.url = url
|
||||||
|
self.onFinished = onFinished
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
task?.cancel()
|
task?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(cache: @escaping (Data) -> Void) {
|
func run() {
|
||||||
task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
|
task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
|
||||||
guard error == nil, let data = data else {
|
guard error == nil, let data = data else {
|
||||||
self.complete(with: nil)
|
self.complete(with: nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cache(data)
|
|
||||||
self.complete(with: data)
|
self.complete(with: data)
|
||||||
})
|
})
|
||||||
task!.resume()
|
task!.resume()
|
||||||
|
@ -123,11 +128,12 @@ class ImageCache {
|
||||||
callback(data)
|
callback(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.onFinished(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Request {
|
class Request {
|
||||||
weak var group: RequestGroup?
|
private weak var group: RequestGroup?
|
||||||
private(set) var callback: ((Data?) -> Void)?
|
private(set) var callback: ((Data?) -> Void)?
|
||||||
private(set) var cancelled: Bool = false
|
private(set) var cancelled: Bool = false
|
||||||
|
|
||||||
|
|
|
@ -37,4 +37,17 @@ extension Status.Visibility {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var unfilledImageName: String {
|
||||||
|
switch self {
|
||||||
|
case .public:
|
||||||
|
return "globe"
|
||||||
|
case .unlisted:
|
||||||
|
return "lock.open"
|
||||||
|
case .private:
|
||||||
|
return "lock"
|
||||||
|
case .direct:
|
||||||
|
return "envelope"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,14 +41,18 @@ class Preferences: Codable, ObservableObject {
|
||||||
self.showRepliesInProfiles = try container.decode(Bool.self, forKey: .showRepliesInProfiles)
|
self.showRepliesInProfiles = try container.decode(Bool.self, forKey: .showRepliesInProfiles)
|
||||||
self.avatarStyle = try container.decode(AvatarStyle.self, forKey: .avatarStyle)
|
self.avatarStyle = try container.decode(AvatarStyle.self, forKey: .avatarStyle)
|
||||||
self.hideCustomEmojiInUsernames = try container.decode(Bool.self, forKey: .hideCustomEmojiInUsernames)
|
self.hideCustomEmojiInUsernames = try container.decode(Bool.self, forKey: .hideCustomEmojiInUsernames)
|
||||||
|
self.showIsStatusReplyIcon = try container.decode(Bool.self, forKey: .showIsStatusReplyIcon)
|
||||||
|
self.alwaysShowStatusVisibilityIcon = try container.decode(Bool.self, forKey: .alwaysShowStatusVisibilityIcon)
|
||||||
|
|
||||||
self.defaultPostVisibility = try container.decode(Status.Visibility.self, forKey: .defaultPostVisibility)
|
self.defaultPostVisibility = try container.decode(Status.Visibility.self, forKey: .defaultPostVisibility)
|
||||||
self.automaticallySaveDrafts = try container.decode(Bool.self, forKey: .automaticallySaveDrafts)
|
self.automaticallySaveDrafts = try container.decode(Bool.self, forKey: .automaticallySaveDrafts)
|
||||||
self.requireAttachmentDescriptions = try container.decode(Bool.self, forKey: .requireAttachmentDescriptions)
|
self.requireAttachmentDescriptions = try container.decode(Bool.self, forKey: .requireAttachmentDescriptions)
|
||||||
self.contentWarningCopyMode = try container.decode(ContentWarningCopyMode.self, forKey: .contentWarningCopyMode)
|
self.contentWarningCopyMode = try container.decode(ContentWarningCopyMode.self, forKey: .contentWarningCopyMode)
|
||||||
self.mentionReblogger = try container.decode(Bool.self, forKey: .mentionReblogger)
|
self.mentionReblogger = try container.decode(Bool.self, forKey: .mentionReblogger)
|
||||||
|
|
||||||
self.blurAllMedia = try container.decode(Bool.self, forKey: .blurAllMedia)
|
self.blurAllMedia = try container.decode(Bool.self, forKey: .blurAllMedia)
|
||||||
self.automaticallyPlayGifs = try container.decode(Bool.self, forKey: .automaticallyPlayGifs)
|
self.automaticallyPlayGifs = try container.decode(Bool.self, forKey: .automaticallyPlayGifs)
|
||||||
|
|
||||||
self.openLinksInApps = try container.decode(Bool.self, forKey: .openLinksInApps)
|
self.openLinksInApps = try container.decode(Bool.self, forKey: .openLinksInApps)
|
||||||
self.useInAppSafari = try container.decode(Bool.self, forKey: .useInAppSafari)
|
self.useInAppSafari = try container.decode(Bool.self, forKey: .useInAppSafari)
|
||||||
self.inAppSafariAutomaticReaderMode = try container.decode(Bool.self, forKey: .inAppSafariAutomaticReaderMode)
|
self.inAppSafariAutomaticReaderMode = try container.decode(Bool.self, forKey: .inAppSafariAutomaticReaderMode)
|
||||||
|
@ -67,14 +71,18 @@ class Preferences: Codable, ObservableObject {
|
||||||
try container.encode(showRepliesInProfiles, forKey: .showRepliesInProfiles)
|
try container.encode(showRepliesInProfiles, forKey: .showRepliesInProfiles)
|
||||||
try container.encode(avatarStyle, forKey: .avatarStyle)
|
try container.encode(avatarStyle, forKey: .avatarStyle)
|
||||||
try container.encode(hideCustomEmojiInUsernames, forKey: .hideCustomEmojiInUsernames)
|
try container.encode(hideCustomEmojiInUsernames, forKey: .hideCustomEmojiInUsernames)
|
||||||
|
try container.encode(showIsStatusReplyIcon, forKey: .showIsStatusReplyIcon)
|
||||||
|
try container.encode(alwaysShowStatusVisibilityIcon, forKey: .alwaysShowStatusVisibilityIcon)
|
||||||
|
|
||||||
try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility)
|
try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility)
|
||||||
try container.encode(automaticallySaveDrafts, forKey: .automaticallySaveDrafts)
|
try container.encode(automaticallySaveDrafts, forKey: .automaticallySaveDrafts)
|
||||||
try container.encode(requireAttachmentDescriptions, forKey: .requireAttachmentDescriptions)
|
try container.encode(requireAttachmentDescriptions, forKey: .requireAttachmentDescriptions)
|
||||||
try container.encode(contentWarningCopyMode, forKey: .contentWarningCopyMode)
|
try container.encode(contentWarningCopyMode, forKey: .contentWarningCopyMode)
|
||||||
try container.encode(mentionReblogger, forKey: .mentionReblogger)
|
try container.encode(mentionReblogger, forKey: .mentionReblogger)
|
||||||
|
|
||||||
try container.encode(blurAllMedia, forKey: .blurAllMedia)
|
try container.encode(blurAllMedia, forKey: .blurAllMedia)
|
||||||
try container.encode(automaticallyPlayGifs, forKey: .automaticallyPlayGifs)
|
try container.encode(automaticallyPlayGifs, forKey: .automaticallyPlayGifs)
|
||||||
|
|
||||||
try container.encode(openLinksInApps, forKey: .openLinksInApps)
|
try container.encode(openLinksInApps, forKey: .openLinksInApps)
|
||||||
try container.encode(useInAppSafari, forKey: .useInAppSafari)
|
try container.encode(useInAppSafari, forKey: .useInAppSafari)
|
||||||
try container.encode(inAppSafariAutomaticReaderMode, forKey: .inAppSafariAutomaticReaderMode)
|
try container.encode(inAppSafariAutomaticReaderMode, forKey: .inAppSafariAutomaticReaderMode)
|
||||||
|
@ -86,29 +94,35 @@ class Preferences: Codable, ObservableObject {
|
||||||
try container.encode(statusContentType, forKey: .statusContentType)
|
try container.encode(statusContentType, forKey: .statusContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Appearance
|
// MARK: Appearance
|
||||||
@Published var theme = UIUserInterfaceStyle.unspecified
|
@Published var theme = UIUserInterfaceStyle.unspecified
|
||||||
@Published var showRepliesInProfiles = false
|
@Published var showRepliesInProfiles = false
|
||||||
@Published var avatarStyle = AvatarStyle.roundRect
|
@Published var avatarStyle = AvatarStyle.roundRect
|
||||||
@Published var hideCustomEmojiInUsernames = false
|
@Published var hideCustomEmojiInUsernames = false
|
||||||
|
@Published var showIsStatusReplyIcon = false
|
||||||
|
@Published var alwaysShowStatusVisibilityIcon = false
|
||||||
|
|
||||||
// MARK: - Behavior
|
// MARK: Composing
|
||||||
@Published var defaultPostVisibility = Status.Visibility.public
|
@Published var defaultPostVisibility = Status.Visibility.public
|
||||||
@Published var automaticallySaveDrafts = true
|
@Published var automaticallySaveDrafts = true
|
||||||
@Published var requireAttachmentDescriptions = false
|
@Published var requireAttachmentDescriptions = false
|
||||||
@Published var contentWarningCopyMode = ContentWarningCopyMode.asIs
|
@Published var contentWarningCopyMode = ContentWarningCopyMode.asIs
|
||||||
@Published var mentionReblogger = false
|
@Published var mentionReblogger = false
|
||||||
|
|
||||||
|
// MARK: Media
|
||||||
@Published var blurAllMedia = false
|
@Published var blurAllMedia = false
|
||||||
@Published var automaticallyPlayGifs = true
|
@Published var automaticallyPlayGifs = true
|
||||||
|
|
||||||
|
// MARK: Behavior
|
||||||
@Published var openLinksInApps = true
|
@Published var openLinksInApps = true
|
||||||
@Published var useInAppSafari = true
|
@Published var useInAppSafari = true
|
||||||
@Published var inAppSafariAutomaticReaderMode = false
|
@Published var inAppSafariAutomaticReaderMode = false
|
||||||
|
|
||||||
// MARK: - Digital Wellness
|
// MARK: Digital Wellness
|
||||||
@Published var showFavoriteAndReblogCounts = true
|
@Published var showFavoriteAndReblogCounts = true
|
||||||
@Published var defaultNotificationsMode = NotificationsMode.allNotifications
|
@Published var defaultNotificationsMode = NotificationsMode.allNotifications
|
||||||
|
|
||||||
// MARK: - Advanced
|
// MARK: Advanced
|
||||||
@Published var silentActions: [String: Permission] = [:]
|
@Published var silentActions: [String: Permission] = [:]
|
||||||
@Published var statusContentType: StatusContentType = .plain
|
@Published var statusContentType: StatusContentType = .plain
|
||||||
|
|
||||||
|
@ -117,14 +131,18 @@ class Preferences: Codable, ObservableObject {
|
||||||
case showRepliesInProfiles
|
case showRepliesInProfiles
|
||||||
case avatarStyle
|
case avatarStyle
|
||||||
case hideCustomEmojiInUsernames
|
case hideCustomEmojiInUsernames
|
||||||
|
case showIsStatusReplyIcon
|
||||||
|
case alwaysShowStatusVisibilityIcon
|
||||||
|
|
||||||
case defaultPostVisibility
|
case defaultPostVisibility
|
||||||
case automaticallySaveDrafts
|
case automaticallySaveDrafts
|
||||||
case requireAttachmentDescriptions
|
case requireAttachmentDescriptions
|
||||||
case contentWarningCopyMode
|
case contentWarningCopyMode
|
||||||
case mentionReblogger
|
case mentionReblogger
|
||||||
|
|
||||||
case blurAllMedia
|
case blurAllMedia
|
||||||
case automaticallyPlayGifs
|
case automaticallyPlayGifs
|
||||||
|
|
||||||
case openLinksInApps
|
case openLinksInApps
|
||||||
case useInAppSafari
|
case useInAppSafari
|
||||||
case inAppSafariAutomaticReaderMode
|
case inAppSafariAutomaticReaderMode
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
import CrashReporter
|
||||||
|
import MessageUI
|
||||||
|
|
||||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
|
@ -21,15 +23,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
window = UIWindow(windowScene: windowScene)
|
window = UIWindow(windowScene: windowScene)
|
||||||
|
|
||||||
if LocalData.shared.onboardingComplete {
|
if let report = AppDelegate.pendingCrashReport {
|
||||||
if session.mastodonController == nil {
|
AppDelegate.pendingCrashReport = nil
|
||||||
let account = LocalData.shared.getMostRecentAccount()!
|
handlePendingCrashReport(report, session: session)
|
||||||
session.mastodonController = MastodonController.getForAccount(account)
|
|
||||||
}
|
|
||||||
|
|
||||||
showAppUI()
|
|
||||||
} else {
|
} else {
|
||||||
showOnboardingUI()
|
showAppOrOnboardingUI(session: session)
|
||||||
}
|
}
|
||||||
|
|
||||||
window!.makeKeyAndVisible()
|
window!.makeKeyAndVisible()
|
||||||
|
@ -113,6 +111,32 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
try! scene.session.mastodonController?.persistentContainer.viewContext.save()
|
try! scene.session.mastodonController?.persistentContainer.viewContext.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func handlePendingCrashReport(_ report: PLCrashReport, session: UISceneSession) {
|
||||||
|
#if !DEBUG
|
||||||
|
guard MFMailComposeViewController.canSendMail() else {
|
||||||
|
print("Cannot send email")
|
||||||
|
showAppOrOnboardingUI(session: session)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
window!.rootViewController = CrashReporterViewController.create(report: report)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
func showAppOrOnboardingUI(session: UISceneSession? = nil) {
|
||||||
|
let session = session ?? window!.windowScene!.session
|
||||||
|
if LocalData.shared.onboardingComplete {
|
||||||
|
if session.mastodonController == nil {
|
||||||
|
let account = LocalData.shared.getMostRecentAccount()!
|
||||||
|
session.mastodonController = MastodonController.getForAccount(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
showAppUI()
|
||||||
|
} else {
|
||||||
|
showOnboardingUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func activateAccount(_ account: LocalData.UserAccountInfo) {
|
func activateAccount(_ account: LocalData.UserAccountInfo) {
|
||||||
LocalData.shared.setMostRecentAccount(account)
|
LocalData.shared.setMostRecentAccount(account)
|
||||||
window!.windowScene!.session.mastodonController = MastodonController.getForAccount(account)
|
window!.windowScene!.session.mastodonController = MastodonController.getForAccount(account)
|
||||||
|
@ -154,3 +178,11 @@ extension SceneDelegate: OnboardingViewControllerDelegate {
|
||||||
activateAccount(account)
|
activateAccount(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SceneDelegate: MFMailComposeViewControllerDelegate {
|
||||||
|
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||||
|
controller.dismiss(animated: true) {
|
||||||
|
self.showAppOrOnboardingUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// GalleryPlayerViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/21/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import AVKit
|
||||||
|
|
||||||
|
class GalleryPlayerViewController: AVPlayerViewController {
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -28,8 +28,8 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
||||||
|
|
||||||
var animationSourceView: UIImageView? { sourceViews[currentIndex] }
|
var animationSourceView: UIImageView? { sourceViews[currentIndex] }
|
||||||
var animationImage: UIImage? {
|
var animationImage: UIImage? {
|
||||||
if let page = pages[currentIndex] as? LoadingLargeImageViewController,
|
if let page = pages[currentIndex] as? LargeImageAnimatableViewController,
|
||||||
let image = page.largeImageVC?.image {
|
let image = page.animationImage {
|
||||||
return image
|
return image
|
||||||
} else {
|
} else {
|
||||||
return animationSourceView?.image
|
return animationSourceView?.image
|
||||||
|
@ -65,18 +65,29 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
||||||
self.sourceViews = WeakArray(sourceViews)
|
self.sourceViews = WeakArray(sourceViews)
|
||||||
self.startIndex = startIndex
|
self.startIndex = startIndex
|
||||||
|
|
||||||
self.pages = attachments.map {
|
self.pages = attachments.enumerated().map { (index, attachment) in
|
||||||
switch $0.kind {
|
switch attachment.kind {
|
||||||
case .image:
|
case .image:
|
||||||
let vc = LoadingLargeImageViewController(attachment: $0)
|
let vc = LoadingLargeImageViewController(attachment: attachment)
|
||||||
vc.shrinkGestureEnabled = false
|
vc.shrinkGestureEnabled = false
|
||||||
return vc
|
return vc
|
||||||
case .video, .audio:
|
case .video, .audio:
|
||||||
let vc = AVPlayerViewController()
|
let vc = GalleryPlayerViewController()
|
||||||
vc.player = AVPlayer(url: $0.url)
|
vc.player = AVPlayer(url: attachment.url)
|
||||||
return vc
|
return vc
|
||||||
case .gifv:
|
case .gifv:
|
||||||
return GifvAttachmentViewController(attachment: $0)
|
// Passing the source view to the LargeImageGifvContentView is a crappy workaround for not
|
||||||
|
// having the video size directly inside the content view. 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).
|
||||||
|
// Really, what should happen is the LargeImageGifvContentView should get the size of the video from
|
||||||
|
// the AVFoundation instead of the source view.
|
||||||
|
// 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:
|
default:
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
let mastodonController: MastodonController
|
let mastodonController: MastodonController
|
||||||
|
|
||||||
|
private var loaded = false
|
||||||
|
|
||||||
var statuses: [(id: String, state: StatusState)] = []
|
var statuses: [(id: String, state: StatusState)] = []
|
||||||
|
|
||||||
var newer: RequestRange?
|
var newer: RequestRange?
|
||||||
|
@ -42,10 +44,19 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
tableView.prefetchDataSource = self
|
tableView.prefetchDataSource = self
|
||||||
|
|
||||||
|
userActivity = UserActivityManager.bookmarksActivity()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
if !loaded {
|
||||||
|
loaded = true
|
||||||
|
|
||||||
let request = Client.getBookmarks()
|
let request = Client.getBookmarks()
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
guard case let .success(statuses, pagination) = response else { fatalError() }
|
guard case let .success(statuses, pagination) = response else { fatalError() }
|
||||||
self.mastodonController.persistentContainer.addAll(statuses: statuses)
|
self.mastodonController.persistentContainer.addAll(statuses: statuses) {
|
||||||
self.statuses.append(contentsOf: statuses.map { ($0.id, .unknown) })
|
self.statuses.append(contentsOf: statuses.map { ($0.id, .unknown) })
|
||||||
self.newer = pagination?.newer
|
self.newer = pagination?.newer
|
||||||
self.older = pagination?.older
|
self.older = pagination?.older
|
||||||
|
@ -54,8 +65,8 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
||||||
self.tableView.reloadData()
|
self.tableView.reloadData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
userActivity = UserActivityManager.bookmarksActivity()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Table view data source
|
// MARK: - Table view data source
|
||||||
|
@ -87,7 +98,7 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||||
self.older = pagination?.older
|
self.older = pagination?.older
|
||||||
self.mastodonController.persistentContainer.addAll(statuses: newStatuses)
|
self.mastodonController.persistentContainer.addAll(statuses: newStatuses) {
|
||||||
let newIndexPaths = (self.statuses.count..<(self.statuses.count + newStatuses.count)).map {
|
let newIndexPaths = (self.statuses.count..<(self.statuses.count + newStatuses.count)).map {
|
||||||
IndexPath(row: $0, section: 0)
|
IndexPath(row: $0, section: 0)
|
||||||
}
|
}
|
||||||
|
@ -100,6 +111,7 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
import PencilKit
|
import PencilKit
|
||||||
|
import Photos
|
||||||
|
|
||||||
protocol ComposeAttachmentsViewControllerDelegate: class {
|
protocol ComposeAttachmentsViewControllerDelegate: class {
|
||||||
func composeSelectedAttachmentsDidChange()
|
func composeSelectedAttachmentsDidChange()
|
||||||
|
@ -314,6 +315,15 @@ class ComposeAttachmentsViewController: UITableViewController {
|
||||||
actions.append(UIAction(title: "Edit Drawing", image: UIImage(systemName: "hand.draw"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
|
actions.append(UIAction(title: "Edit Drawing", image: UIImage(systemName: "hand.draw"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
|
||||||
self.presentComposeDrawingViewController(editingAttachmentAt: indexPath.row)
|
self.presentComposeDrawingViewController(editingAttachmentAt: indexPath.row)
|
||||||
}))
|
}))
|
||||||
|
case .asset(_), .image(_):
|
||||||
|
if attachment.data.type == .image,
|
||||||
|
let cell = tableView.cellForRow(at: indexPath) as? ComposeAttachmentTableViewCell {
|
||||||
|
|
||||||
|
let title = NSLocalizedString("Recognize Text", comment: "recognize image attachment text menu item title")
|
||||||
|
actions.append(UIAction(title: title, image: UIImage(systemName: "doc.text.viewfinder"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
|
||||||
|
cell.recognizeTextFromImage()
|
||||||
|
}))
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -364,18 +374,23 @@ class ComposeAttachmentsViewController: UITableViewController {
|
||||||
// MARK: Interaction
|
// MARK: Interaction
|
||||||
|
|
||||||
func addAttachmentPressed() {
|
func addAttachmentPressed() {
|
||||||
if traitCollection.horizontalSizeClass == .compact {
|
PHPhotoLibrary.requestAuthorization { (status) in
|
||||||
|
guard status == .authorized else { return }
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if self.traitCollection.horizontalSizeClass == .compact {
|
||||||
let sheetContainer = AssetPickerSheetContainerViewController()
|
let sheetContainer = AssetPickerSheetContainerViewController()
|
||||||
sheetContainer.assetPicker.assetPickerDelegate = self
|
sheetContainer.assetPicker.assetPickerDelegate = self
|
||||||
present(sheetContainer, animated: true)
|
self.present(sheetContainer, animated: true)
|
||||||
} else {
|
} else {
|
||||||
let picker = AssetPickerViewController()
|
let picker = AssetPickerViewController()
|
||||||
picker.assetPickerDelegate = self
|
picker.assetPickerDelegate = self
|
||||||
picker.overrideUserInterfaceStyle = .dark
|
picker.overrideUserInterfaceStyle = .dark
|
||||||
picker.modalPresentationStyle = .popover
|
picker.modalPresentationStyle = .popover
|
||||||
present(picker, animated: true)
|
self.present(picker, animated: true)
|
||||||
if let presentationController = picker.presentationController as? UIPopoverPresentationController {
|
if let presentationController = picker.presentationController as? UIPopoverPresentationController {
|
||||||
presentationController.sourceView = tableView.cellForRow(at: IndexPath(row: 0, section: 1))
|
presentationController.sourceView = self.tableView.cellForRow(at: IndexPath(row: 0, section: 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -497,6 +512,10 @@ extension ComposeAttachmentsViewController: AssetPickerViewControllerDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComposeAttachmentsViewController: ComposeAttachmentTableViewCellDelegate {
|
extension ComposeAttachmentsViewController: ComposeAttachmentTableViewCellDelegate {
|
||||||
|
func composeAttachment(_ cell: ComposeAttachmentTableViewCell, present viewController: UIViewController, animated: Bool) {
|
||||||
|
self.present(viewController, animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
func removeAttachment(_ cell: ComposeAttachmentTableViewCell) {
|
func removeAttachment(_ cell: ComposeAttachmentTableViewCell) {
|
||||||
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
||||||
attachments.remove(at: indexPath.row)
|
attachments.remove(at: indexPath.row)
|
||||||
|
@ -512,6 +531,12 @@ extension ComposeAttachmentsViewController: ComposeAttachmentTableViewCellDelega
|
||||||
func attachmentDescriptionChanged(_ cell: ComposeAttachmentTableViewCell) {
|
func attachmentDescriptionChanged(_ cell: ComposeAttachmentTableViewCell) {
|
||||||
delegate?.composeRequiresAttachmentDescriptionsDidChange()
|
delegate?.composeRequiresAttachmentDescriptionsDidChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func composeAttachmentDescriptionHeightChanged(_ cell: ComposeAttachmentTableViewCell) {
|
||||||
|
tableView.performBatchUpdates(nil) { (_) in
|
||||||
|
self.updateHeightConstraint()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComposeAttachmentsViewController: ComposeDrawingViewControllerDelegate {
|
extension ComposeAttachmentsViewController: ComposeDrawingViewControllerDelegate {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16092.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16082.1"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -163,7 +163,7 @@
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="trailing" secondItem="Tq7-6P-hMT" secondAttribute="trailing" id="GeN-8q-weq"/>
|
<constraint firstAttribute="trailing" secondItem="Tq7-6P-hMT" secondAttribute="trailing" id="GeN-8q-weq"/>
|
||||||
<constraint firstItem="Heg-g4-sYM" firstAttribute="bottom" secondItem="6Z0-Vy-hMX" secondAttribute="bottom" id="Hf3-Cc-mVX"/>
|
<constraint firstAttribute="bottom" secondItem="6Z0-Vy-hMX" secondAttribute="bottom" id="Hf3-Cc-mVX"/>
|
||||||
<constraint firstItem="Tq7-6P-hMT" firstAttribute="top" secondItem="Heg-g4-sYM" secondAttribute="top" id="LgA-xu-VGE"/>
|
<constraint firstItem="Tq7-6P-hMT" firstAttribute="top" secondItem="Heg-g4-sYM" secondAttribute="top" id="LgA-xu-VGE"/>
|
||||||
<constraint firstItem="Tq7-6P-hMT" firstAttribute="leading" secondItem="7XG-Dk-OGm" secondAttribute="leading" id="agM-ZO-c3E"/>
|
<constraint firstItem="Tq7-6P-hMT" firstAttribute="leading" secondItem="7XG-Dk-OGm" secondAttribute="leading" id="agM-ZO-c3E"/>
|
||||||
<constraint firstItem="Heg-g4-sYM" firstAttribute="trailing" secondItem="6Z0-Vy-hMX" secondAttribute="trailing" id="hjY-W6-wTQ"/>
|
<constraint firstItem="Heg-g4-sYM" firstAttribute="trailing" secondItem="6Z0-Vy-hMX" secondAttribute="trailing" id="hjY-W6-wTQ"/>
|
||||||
|
|
|
@ -127,6 +127,7 @@ class ConversationTableViewController: EnhancedTableViewController {
|
||||||
} else {
|
} else {
|
||||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
|
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
|
||||||
cell.showStatusAutomatically = showStatusesAutomatically
|
cell.showStatusAutomatically = showStatusesAutomatically
|
||||||
|
cell.showReplyIndicator = false
|
||||||
cell.delegate = self
|
cell.delegate = self
|
||||||
cell.updateUI(statusID: id, state: state)
|
cell.updateUI(statusID: id, state: state)
|
||||||
return cell
|
return cell
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
//
|
||||||
|
// CrashReporterViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/20/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import CrashReporter
|
||||||
|
import MessageUI
|
||||||
|
|
||||||
|
class CrashReporterViewController: UIViewController {
|
||||||
|
|
||||||
|
private let report: PLCrashReport
|
||||||
|
private var reportText: String!
|
||||||
|
|
||||||
|
private var reportFilename: String {
|
||||||
|
let timestamp = ISO8601DateFormatter().string(from: report.systemInfo.timestamp)
|
||||||
|
return "Tusker-crash-\(timestamp).crash"
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBOutlet weak var crashReportTextView: UITextView!
|
||||||
|
@IBOutlet weak var sendReportButton: UIButton!
|
||||||
|
|
||||||
|
static func create(report: PLCrashReport) -> UINavigationController {
|
||||||
|
let nav = UINavigationController(rootViewController: CrashReporterViewController(report: report))
|
||||||
|
nav.navigationBar.prefersLargeTitles = true
|
||||||
|
return nav
|
||||||
|
}
|
||||||
|
|
||||||
|
private init(report: PLCrashReport){
|
||||||
|
self.report = report
|
||||||
|
|
||||||
|
super.init(nibName: "CrashReporterViewController", bundle: .main)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
navigationItem.title = NSLocalizedString("Crash Detected", comment: "crash reporter title")
|
||||||
|
navigationItem.largeTitleDisplayMode = .always
|
||||||
|
|
||||||
|
crashReportTextView.font = .monospacedSystemFont(ofSize: 14, weight: .regular)
|
||||||
|
|
||||||
|
reportText = PLCrashReportTextFormatter.stringValue(for: report, with: PLCrashReportTextFormatiOS)!
|
||||||
|
let info = "Tusker has detected that it crashed the last time it was running. You can email the report to the developer or skip sending and continue to the app. You may review the report below before sending.\n\nIf you choose to send the report, please include any additional details about what you were doing prior to the crash that may be pertinent.\n\n"
|
||||||
|
let attributed = NSMutableAttributedString()
|
||||||
|
attributed.append(NSAttributedString(string: info, attributes: [
|
||||||
|
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17),
|
||||||
|
NSAttributedString.Key.foregroundColor: UIColor.label
|
||||||
|
]))
|
||||||
|
attributed.append(NSAttributedString(string: reportText, attributes: [
|
||||||
|
NSAttributedString.Key.font: UIFont.monospacedSystemFont(ofSize: 14, weight: .regular),
|
||||||
|
NSAttributedString.Key.foregroundColor: UIColor.label
|
||||||
|
]))
|
||||||
|
crashReportTextView.attributedText = attributed
|
||||||
|
|
||||||
|
sendReportButton.layer.cornerRadius = 12.5
|
||||||
|
sendReportButton.layer.masksToBounds = true
|
||||||
|
|
||||||
|
sendReportButton.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(sendReportButtonLongPressed)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateSendReportButtonColor(lightened: Bool, animate: Bool) {
|
||||||
|
let color: UIColor
|
||||||
|
if lightened {
|
||||||
|
var hue: CGFloat = 0, saturation: CGFloat = 0, brightness: CGFloat = 0, alpha: CGFloat = 0
|
||||||
|
UIColor.systemBlue.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
|
||||||
|
color = UIColor(hue: hue, saturation: 0.85 * saturation, brightness: brightness, alpha: alpha)
|
||||||
|
} else {
|
||||||
|
color = .systemBlue
|
||||||
|
}
|
||||||
|
if animate {
|
||||||
|
UIView.animate(withDuration: 0.25) {
|
||||||
|
self.sendReportButton.backgroundColor = color
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendReportButton.backgroundColor = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func sendReportTouchDown(_ sender: Any) {
|
||||||
|
updateSendReportButtonColor(lightened: true, animate: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func sendReportButtonTouchDragExit(_ sender: Any) {
|
||||||
|
updateSendReportButtonColor(lightened: false, animate: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func sendReportButtonTouchDragEnter(_ sender: Any) {
|
||||||
|
updateSendReportButtonColor(lightened: true, animate: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func sendReportTouchUpInside(_ sender: Any) {
|
||||||
|
updateSendReportButtonColor(lightened: false, animate: true)
|
||||||
|
|
||||||
|
let composeVC = MFMailComposeViewController()
|
||||||
|
composeVC.mailComposeDelegate = self
|
||||||
|
composeVC.setToRecipients(["me@shadowfacts.net"])
|
||||||
|
composeVC.setSubject("Tusker Crash Report")
|
||||||
|
|
||||||
|
let data = reportText.data(using: .utf8)!
|
||||||
|
composeVC.addAttachmentData(data, mimeType: "text/plain", fileName: reportFilename)
|
||||||
|
|
||||||
|
self.present(composeVC, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func sendReportButtonLongPressed() {
|
||||||
|
let dir = FileManager.default.temporaryDirectory
|
||||||
|
let url = dir.appendingPathComponent(reportFilename)
|
||||||
|
try! reportText.data(using: .utf8)!.write(to: url)
|
||||||
|
let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
|
||||||
|
present(activityController, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func cancelPressed(_ sender: Any) {
|
||||||
|
(view.window!.windowScene!.delegate as! SceneDelegate).showAppOrOnboardingUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CrashReporterViewController: MFMailComposeViewControllerDelegate {
|
||||||
|
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||||
|
controller.dismiss(animated: true) {
|
||||||
|
(self.view.window!.windowScene!.delegate as! SceneDelegate).showAppOrOnboardingUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||||
|
<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="CrashReporterViewController" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<connections>
|
||||||
|
<outlet property="crashReportTextView" destination="hxN-7J-Usc" id="TGd-yq-Ds5"/>
|
||||||
|
<outlet property="sendReportButton" destination="Ofm-5l-nAp" id="6xM-hz-uvw"/>
|
||||||
|
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
|
||||||
|
</connections>
|
||||||
|
</placeholder>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
|
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="a8U-KI-8PM">
|
||||||
|
<rect key="frame" x="0.0" y="44" width="414" height="818"/>
|
||||||
|
<subviews>
|
||||||
|
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="uQy-Yw-Dba">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="722"/>
|
||||||
|
<subviews>
|
||||||
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" verticalHuggingPriority="249" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="hxN-7J-Usc">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="166.5"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||||
|
<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" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
|
</textView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="LRh-7Z-mV1" firstAttribute="trailing" secondItem="hxN-7J-Usc" secondAttribute="trailing" id="1GJ-Nk-pIj"/>
|
||||||
|
<constraint firstItem="hxN-7J-Usc" firstAttribute="leading" secondItem="LRh-7Z-mV1" secondAttribute="leading" id="dtj-bw-No1"/>
|
||||||
|
<constraint firstItem="hxN-7J-Usc" firstAttribute="top" secondItem="LRh-7Z-mV1" secondAttribute="top" id="nvk-aw-YqH"/>
|
||||||
|
<constraint firstItem="LRh-7Z-mV1" firstAttribute="bottom" secondItem="hxN-7J-Usc" secondAttribute="bottom" id="xAU-OS-1Zy"/>
|
||||||
|
</constraints>
|
||||||
|
<viewLayoutGuide key="contentLayoutGuide" id="LRh-7Z-mV1"/>
|
||||||
|
<viewLayoutGuide key="frameLayoutGuide" id="Rgd-t7-8QN"/>
|
||||||
|
</scrollView>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ofm-5l-nAp">
|
||||||
|
<rect key="frame" x="52" y="730" width="310.5" height="50"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBlueColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="50" id="jHf-W0-qQn"/>
|
||||||
|
</constraints>
|
||||||
|
<state key="normal" title="Send Crash Report">
|
||||||
|
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</state>
|
||||||
|
<connections>
|
||||||
|
<action selector="sendReportButtonTouchDragEnter:" destination="-1" eventType="touchDragEnter" id="jSF-RZ-NsG"/>
|
||||||
|
<action selector="sendReportButtonTouchDragExit:" destination="-1" eventType="touchDragExit" id="CrB-QL-bN1"/>
|
||||||
|
<action selector="sendReportTouchDown:" destination="-1" eventType="touchDown" id="P5K-4n-tO1"/>
|
||||||
|
<action selector="sendReportTouchUpInside:" destination="-1" eventType="touchUpInside" id="ggd-fm-Orq"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JiJ-Ng-jOz">
|
||||||
|
<rect key="frame" x="169" y="788" width="76" height="30"/>
|
||||||
|
<state key="normal" title="Don't Send"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="cancelPressed:" destination="-1" eventType="touchUpInside" id="o4R-0Q-STS"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="uQy-Yw-Dba" firstAttribute="width" secondItem="i5M-Pr-FkT" secondAttribute="width" id="AX2-9e-cO0"/>
|
||||||
|
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="a8U-KI-8PM" secondAttribute="bottom" id="Ec3-Px-dSW"/>
|
||||||
|
<constraint firstItem="hxN-7J-Usc" firstAttribute="width" secondItem="i5M-Pr-FkT" secondAttribute="width" id="Evf-Yz-Iui"/>
|
||||||
|
<constraint firstItem="a8U-KI-8PM" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="NDN-Jl-viT"/>
|
||||||
|
<constraint firstItem="a8U-KI-8PM" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" id="O8Y-sl-55I"/>
|
||||||
|
<constraint firstItem="a8U-KI-8PM" firstAttribute="trailing" secondItem="fnl-2z-Ty3" secondAttribute="trailing" id="f59-qB-5T7"/>
|
||||||
|
<constraint firstItem="Ofm-5l-nAp" firstAttribute="width" secondItem="i5M-Pr-FkT" secondAttribute="width" multiplier="0.75" id="ueo-xb-Tfm"/>
|
||||||
|
</constraints>
|
||||||
|
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
|
||||||
|
<point key="canvasLocation" x="133" y="154"/>
|
||||||
|
</view>
|
||||||
|
</objects>
|
||||||
|
</document>
|
|
@ -0,0 +1,84 @@
|
||||||
|
//
|
||||||
|
// LargeImageContentView.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/17/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Gifu
|
||||||
|
import Pachyderm
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
protocol LargeImageContentView {
|
||||||
|
var animationImage: UIImage? { get }
|
||||||
|
var animationGifData: Data? { get }
|
||||||
|
var activityItemsForSharing: [Any] { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
class LargeImageImageContentView: UIImageView, GIFAnimatable, LargeImageContentView {
|
||||||
|
lazy var animator: Animator? = {
|
||||||
|
return Animator(withDelegate: self)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var animationImage: UIImage? { image! }
|
||||||
|
let animationGifData: Data?
|
||||||
|
|
||||||
|
var activityItemsForSharing: [Any] {
|
||||||
|
[image!]
|
||||||
|
}
|
||||||
|
|
||||||
|
init(image: UIImage, gifData: Data?) {
|
||||||
|
self.animationGifData = gifData
|
||||||
|
|
||||||
|
super.init(image: image)
|
||||||
|
|
||||||
|
contentMode = .scaleAspectFit
|
||||||
|
|
||||||
|
if let data = gifData {
|
||||||
|
self.animate(withGIFData: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func display(_ layer: CALayer) {
|
||||||
|
updateImageIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LargeImageGifvContentView: GifvAttachmentView, LargeImageContentView {
|
||||||
|
private(set) var animationImage: UIImage?
|
||||||
|
var animationGifData: Data? { nil }
|
||||||
|
var activityItemsForSharing: [Any] {
|
||||||
|
// todo: what should we share for gifvs?
|
||||||
|
// some SO posts indicate that just sharing a URL to the video should work, but that may need to be a local URL?
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
|
||||||
|
private let asset: AVURLAsset
|
||||||
|
|
||||||
|
// The content view needs to supply an intrinsicContentSize for the LargeImageViewController to handle layout/scrolling/zooming correctly
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
// This is a really sucky workaround for the fact that in the content view, we don't have access to the size of the underlying video.
|
||||||
|
// There's probably some way of getting this from the AVPlayer/AVAsset directly
|
||||||
|
animationImage?.size ?? CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(attachment: Attachment, source: UIImageView) {
|
||||||
|
precondition(attachment.kind == .gifv)
|
||||||
|
|
||||||
|
self.asset = AVURLAsset(url: attachment.url)
|
||||||
|
|
||||||
|
super.init(asset: asset, gravity: .resizeAspect)
|
||||||
|
|
||||||
|
self.animationImage = source.image
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,22 +7,17 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Gifu
|
|
||||||
|
|
||||||
class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeImageAnimatableViewController {
|
class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeImageAnimatableViewController {
|
||||||
|
|
||||||
|
typealias ContentView = UIView & LargeImageContentView
|
||||||
|
|
||||||
weak var animationSourceView: UIImageView?
|
weak var animationSourceView: UIImageView?
|
||||||
var animationImage: UIImage? { image ?? animationSourceView?.image }
|
var animationImage: UIImage? { contentView.animationImage }
|
||||||
var animationGifData: Data? { gifData }
|
var animationGifData: Data? { contentView.animationGifData }
|
||||||
var dismissInteractionController: LargeImageInteractionController?
|
var dismissInteractionController: LargeImageInteractionController?
|
||||||
|
|
||||||
@IBOutlet weak var scrollView: UIScrollView!
|
@IBOutlet weak var scrollView: UIScrollView!
|
||||||
@IBOutlet weak var imageView: GIFImageView!
|
|
||||||
@IBOutlet weak var imageViewLeadingConstraint: NSLayoutConstraint!
|
|
||||||
@IBOutlet weak var imageViewTrailingConstraint: NSLayoutConstraint!
|
|
||||||
@IBOutlet weak var imageViewTopConstraint: NSLayoutConstraint!
|
|
||||||
@IBOutlet weak var imageViewBottomConstraint: NSLayoutConstraint!
|
|
||||||
|
|
||||||
@IBOutlet weak var topControlsView: UIView!
|
@IBOutlet weak var topControlsView: UIView!
|
||||||
@IBOutlet weak var topControlsHeightConstraint: NSLayoutConstraint!
|
@IBOutlet weak var topControlsHeightConstraint: NSLayoutConstraint!
|
||||||
@IBOutlet weak var shareButton: UIButton!
|
@IBOutlet weak var shareButton: UIButton!
|
||||||
|
@ -35,8 +30,10 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
@IBOutlet weak var bottomControlsView: UIView!
|
@IBOutlet weak var bottomControlsView: UIView!
|
||||||
@IBOutlet weak var descriptionLabel: UILabel!
|
@IBOutlet weak var descriptionLabel: UILabel!
|
||||||
|
|
||||||
var image: UIImage?
|
var contentView: ContentView
|
||||||
var gifData: Data?
|
var contentViewLeadingConstraint: NSLayoutConstraint!
|
||||||
|
var contentViewTopConstraint: NSLayoutConstraint!
|
||||||
|
|
||||||
var imageDescription: String?
|
var imageDescription: String?
|
||||||
|
|
||||||
var initialControlsVisible = true
|
var initialControlsVisible = true
|
||||||
|
@ -57,11 +54,12 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
return !controlsVisible
|
return !controlsVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
init(image: UIImage, description: String?, sourceView: UIImageView?) {
|
init(contentView: ContentView, description: String?, sourceView: UIImageView?) {
|
||||||
self.image = image
|
|
||||||
self.imageDescription = description
|
self.imageDescription = description
|
||||||
self.animationSourceView = sourceView
|
self.animationSourceView = sourceView
|
||||||
|
|
||||||
|
self.contentView = contentView
|
||||||
|
|
||||||
super.init(nibName: "LargeImageViewController", bundle: nil)
|
super.init(nibName: "LargeImageViewController", bundle: nil)
|
||||||
|
|
||||||
modalPresentationStyle = .fullScreen
|
modalPresentationStyle = .fullScreen
|
||||||
|
@ -74,15 +72,19 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
setControlsVisible(initialControlsVisible, animated: false)
|
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
scrollView.addSubview(contentView)
|
||||||
|
contentViewLeadingConstraint = contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor)
|
||||||
|
contentViewTopConstraint = contentView.topAnchor.constraint(equalTo: scrollView.topAnchor)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
contentViewLeadingConstraint,
|
||||||
|
contentViewTopConstraint,
|
||||||
|
])
|
||||||
|
|
||||||
imageView.image = image
|
setControlsVisible(initialControlsVisible, animated: false)
|
||||||
if let gifData = gifData {
|
shareButton.isEnabled = !contentView.activityItemsForSharing.isEmpty
|
||||||
imageView.animate(withGIFData: gifData)
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollView.delegate = self
|
scrollView.delegate = self
|
||||||
imageView.bounds = CGRect(origin: .zero, size: imageView.image!.size)
|
|
||||||
|
|
||||||
if let imageDescription = imageDescription {
|
if let imageDescription = imageDescription {
|
||||||
descriptionLabel.text = imageDescription
|
descriptionLabel.text = imageDescription
|
||||||
|
@ -100,15 +102,15 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
view.addGestureRecognizer(doubleTap)
|
view.addGestureRecognizer(doubleTap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
super.viewDidLayoutSubviews()
|
super.viewDidLayoutSubviews()
|
||||||
|
|
||||||
|
// todo: does this need to be in viewDidLayoutSubviews?
|
||||||
// limit the image height to the safe area height, so the image doesn't overlap the top controls
|
// limit the image height to the safe area height, so the image doesn't overlap the top controls
|
||||||
// while zoomed all the way out
|
// while zoomed all the way out
|
||||||
let maxHeight = view.bounds.height - view.safeAreaInsets.top - view.safeAreaInsets.bottom
|
let maxHeight = view.bounds.height - view.safeAreaInsets.top - view.safeAreaInsets.bottom
|
||||||
let heightScale = maxHeight / imageView.bounds.height
|
let heightScale = maxHeight / contentView.intrinsicContentSize.height
|
||||||
let widthScale = view.bounds.width / imageView.bounds.width
|
let widthScale = view.bounds.width / contentView.intrinsicContentSize.width
|
||||||
let minScale = min(widthScale, heightScale)
|
let minScale = min(widthScale, heightScale)
|
||||||
scrollView.minimumZoomScale = minScale
|
scrollView.minimumZoomScale = minScale
|
||||||
scrollView.zoomScale = minScale
|
scrollView.zoomScale = minScale
|
||||||
|
@ -116,6 +118,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
|
|
||||||
centerImage()
|
centerImage()
|
||||||
|
|
||||||
|
// todo: does this need to be in viewDidLayoutSubviews?
|
||||||
if view.safeAreaInsets.top == 44 {
|
if view.safeAreaInsets.top == 44 {
|
||||||
// running on iPhone X style notched device
|
// running on iPhone X style notched device
|
||||||
let notchWidth: CGFloat = 209
|
let notchWidth: CGFloat = 209
|
||||||
|
@ -147,7 +150,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||||
return imageView
|
return contentView
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
||||||
|
@ -163,18 +166,18 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
}
|
}
|
||||||
|
|
||||||
func centerImage() {
|
func centerImage() {
|
||||||
let yOffset = max(0, (view.bounds.size.height - imageView.frame.height) / 2)
|
let yOffset = max(0, (view.bounds.size.height - contentView.frame.height) / 2)
|
||||||
imageViewTopConstraint.constant = yOffset
|
contentViewTopConstraint.constant = yOffset
|
||||||
|
|
||||||
let xOffset = max(0, (view.bounds.size.width - imageView.frame.width) / 2)
|
let xOffset = max(0, (view.bounds.size.width - contentView.frame.width) / 2)
|
||||||
imageViewLeadingConstraint.constant = xOffset
|
contentViewLeadingConstraint.constant = xOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
func zoomRectFor(scale: CGFloat, center: CGPoint) -> CGRect {
|
func zoomRectFor(scale: CGFloat, center: CGPoint) -> CGRect {
|
||||||
var zoomRect = CGRect.zero
|
var zoomRect = CGRect.zero
|
||||||
zoomRect.size.width = imageView.frame.width / scale
|
zoomRect.size.width = contentView.frame.width / scale
|
||||||
zoomRect.size.height = imageView.frame.height / scale
|
zoomRect.size.height = contentView.frame.height / scale
|
||||||
let newCenter = scrollView.convert(center, to: imageView)
|
let newCenter = scrollView.convert(center, to: contentView)
|
||||||
zoomRect.origin.x = newCenter.x - (zoomRect.width / 2)
|
zoomRect.origin.x = newCenter.x - (zoomRect.width / 2)
|
||||||
zoomRect.origin.y = newCenter.y - (zoomRect.height / 2)
|
zoomRect.origin.y = newCenter.y - (zoomRect.height / 2)
|
||||||
return zoomRect
|
return zoomRect
|
||||||
|
@ -225,8 +228,8 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func sharePressed(_ sender: Any) {
|
@IBAction func sharePressed(_ sender: Any) {
|
||||||
guard let image = image else { return }
|
let activityVC = UIActivityViewController(activityItems: contentView.activityItemsForSharing, applicationActivities: nil)
|
||||||
let activityVC = UIActivityViewController(activityItems: [image], applicationActivities: nil)
|
activityVC.popoverPresentationController?.sourceView = shareButton
|
||||||
present(activityVC, animated: true)
|
present(activityVC, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14845" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14799.2"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -14,9 +14,6 @@
|
||||||
<outlet property="closeButtonTopConstraint" destination="ImD-2H-0XK" id="DUe-b1-a2N"/>
|
<outlet property="closeButtonTopConstraint" destination="ImD-2H-0XK" id="DUe-b1-a2N"/>
|
||||||
<outlet property="closeButtonTrailingConstraint" destination="JFe-ig-3Ic" id="cWO-Rr-y3F"/>
|
<outlet property="closeButtonTrailingConstraint" destination="JFe-ig-3Ic" id="cWO-Rr-y3F"/>
|
||||||
<outlet property="descriptionLabel" destination="eo5-fc-RV8" id="vrW-RJ-y5k"/>
|
<outlet property="descriptionLabel" destination="eo5-fc-RV8" id="vrW-RJ-y5k"/>
|
||||||
<outlet property="imageView" destination="qcn-1t-3sS" id="Q01-G2-y1c"/>
|
|
||||||
<outlet property="imageViewLeadingConstraint" destination="bI3-V8-M70" id="nIe-xI-E9u"/>
|
|
||||||
<outlet property="imageViewTopConstraint" destination="tfL-hp-2I2" id="EDV-RO-pTe"/>
|
|
||||||
<outlet property="scrollView" destination="Skj-xq-AgQ" id="TFb-zF-m1b"/>
|
<outlet property="scrollView" destination="Skj-xq-AgQ" id="TFb-zF-m1b"/>
|
||||||
<outlet property="shareButton" destination="vhp-0u-Q0S" id="JZS-K9-4w9"/>
|
<outlet property="shareButton" destination="vhp-0u-Q0S" id="JZS-K9-4w9"/>
|
||||||
<outlet property="shareButtonLeadingConstraint" destination="MJx-2r-p0k" id="Dn5-Eg-Pid"/>
|
<outlet property="shareButtonLeadingConstraint" destination="MJx-2r-p0k" id="Dn5-Eg-Pid"/>
|
||||||
|
@ -31,24 +28,14 @@
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" minimumZoomScale="0.25" maximumZoomScale="2" translatesAutoresizingMaskIntoConstraints="NO" id="Skj-xq-AgQ">
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<subviews>
|
|
||||||
<imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qcn-1t-3sS" customClass="GIFImageView" customModule="Gifu">
|
|
||||||
<rect key="frame" x="0.0" y="-10" width="375" height="647"/>
|
|
||||||
<gestureRecognizers/>
|
<gestureRecognizers/>
|
||||||
</imageView>
|
|
||||||
</subviews>
|
|
||||||
<gestureRecognizers/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="qcn-1t-3sS" firstAttribute="leading" secondItem="Skj-xq-AgQ" secondAttribute="leading" id="bI3-V8-M70"/>
|
|
||||||
<constraint firstItem="qcn-1t-3sS" firstAttribute="top" secondItem="Skj-xq-AgQ" secondAttribute="top" id="tfL-hp-2I2"/>
|
|
||||||
</constraints>
|
|
||||||
</scrollView>
|
</scrollView>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kHo-B9-R7a">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kHo-B9-R7a">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="36"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="36"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vhp-0u-Q0S">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" pointerInteraction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vhp-0u-Q0S">
|
||||||
<rect key="frame" x="16" y="16" width="20" height="20"/>
|
<rect key="frame" x="16" y="16" width="20" height="20"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="20" id="4tF-oL-qXT"/>
|
<constraint firstAttribute="height" constant="20" id="4tF-oL-qXT"/>
|
||||||
|
@ -60,7 +47,7 @@
|
||||||
<action selector="sharePressed:" destination="-1" eventType="touchUpInside" id="7Oz-zv-m2t"/>
|
<action selector="sharePressed:" destination="-1" eventType="touchUpInside" id="7Oz-zv-m2t"/>
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pnA-ne-k0v">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" pointerInteraction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pnA-ne-k0v">
|
||||||
<rect key="frame" x="339" y="16" width="20" height="20"/>
|
<rect key="frame" x="339" y="16" width="20" height="20"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="width" constant="20" id="eg0-hN-rda"/>
|
<constraint firstAttribute="width" constant="20" id="eg0-hN-rda"/>
|
||||||
|
@ -119,7 +106,7 @@
|
||||||
</view>
|
</view>
|
||||||
</objects>
|
</objects>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="square.and.arrow.up" catalog="system" width="56" height="64"/>
|
<image name="square.and.arrow.up" catalog="system" width="115" height="128"/>
|
||||||
<image name="xmark" catalog="system" width="64" height="56"/>
|
<image name="xmark" catalog="system" width="128" height="113"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|
|
@ -36,8 +36,8 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
|
||||||
var shrinkGestureEnabled = true
|
var shrinkGestureEnabled = true
|
||||||
|
|
||||||
weak var animationSourceView: UIImageView?
|
weak var animationSourceView: UIImageView?
|
||||||
var animationImage: UIImage? { largeImageVC?.image ?? animationSourceView?.image }
|
var animationImage: UIImage? { largeImageVC?.animationImage ?? animationSourceView?.image }
|
||||||
var animationGifData: Data? { largeImageVC?.gifData }
|
var animationGifData: Data? { largeImageVC?.animationGifData }
|
||||||
var dismissInteractionController: LargeImageInteractionController?
|
var dismissInteractionController: LargeImageInteractionController?
|
||||||
|
|
||||||
override var prefersStatusBarHidden: Bool {
|
override var prefersStatusBarHidden: Bool {
|
||||||
|
@ -108,12 +108,12 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
|
||||||
|
|
||||||
func createLargeImage(data: Data) {
|
func createLargeImage(data: Data) {
|
||||||
guard let image = UIImage(data: data) else { return }
|
guard let image = UIImage(data: data) else { return }
|
||||||
largeImageVC = LargeImageViewController(image: image, description: imageDescription, sourceView: animationSourceView)
|
let gifData = url.pathExtension == "gif" ? data : nil
|
||||||
|
let imageView = LargeImageImageContentView(image: image, gifData: gifData)
|
||||||
|
|
||||||
|
largeImageVC = LargeImageViewController(contentView: imageView, description: imageDescription, sourceView: animationSourceView)
|
||||||
largeImageVC!.initialControlsVisible = initialControlsVisible
|
largeImageVC!.initialControlsVisible = initialControlsVisible
|
||||||
largeImageVC!.shrinkGestureEnabled = false
|
largeImageVC!.shrinkGestureEnabled = false
|
||||||
if url.pathExtension == "gif" {
|
|
||||||
largeImageVC!.gifData = data
|
|
||||||
}
|
|
||||||
embedChild(largeImageVC!)
|
embedChild(largeImageVC!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
||||||
let excludedTypes: [Pachyderm.Notification.Kind]
|
let excludedTypes: [Pachyderm.Notification.Kind]
|
||||||
let groupTypes = [Notification.Kind.favourite, .reblog, .follow]
|
let groupTypes = [Notification.Kind.favourite, .reblog, .follow]
|
||||||
|
|
||||||
|
private var loaded = false
|
||||||
|
|
||||||
var groups: [NotificationGroup] = []
|
var groups: [NotificationGroup] = []
|
||||||
|
|
||||||
var newer: RequestRange?
|
var newer: RequestRange?
|
||||||
|
@ -54,6 +56,13 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
||||||
tableView.register(UINib(nibName: "BasicTableViewCell", bundle: .main), forCellReuseIdentifier: unknownCell)
|
tableView.register(UINib(nibName: "BasicTableViewCell", bundle: .main), forCellReuseIdentifier: unknownCell)
|
||||||
|
|
||||||
tableView.prefetchDataSource = self
|
tableView.prefetchDataSource = self
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
if !loaded {
|
||||||
|
loaded = true
|
||||||
|
|
||||||
let request = Client.getNotifications(excludeTypes: excludedTypes)
|
let request = Client.getNotifications(excludeTypes: excludedTypes)
|
||||||
mastodonController.run(request) { result in
|
mastodonController.run(request) { result in
|
||||||
|
@ -73,6 +82,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Table view data source
|
// MARK: - Table view data source
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,12 @@ struct AppearancePrefsView : View {
|
||||||
Toggle(isOn: $preferences.hideCustomEmojiInUsernames) {
|
Toggle(isOn: $preferences.hideCustomEmojiInUsernames) {
|
||||||
Text("Hide Custom Emoji in Usernames")
|
Text("Hide Custom Emoji in Usernames")
|
||||||
}
|
}
|
||||||
|
Toggle(isOn: $preferences.showIsStatusReplyIcon) {
|
||||||
|
Text("Show Status Reply Icons")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $preferences.alwaysShowStatusVisibilityIcon) {
|
||||||
|
Text("Always Show Status Visibility Icons")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.listStyle(GroupedListStyle())
|
.listStyle(GroupedListStyle())
|
||||||
.navigationBarTitle(Text("Appearance"))
|
.navigationBarTitle(Text("Appearance"))
|
||||||
|
|
|
@ -14,36 +14,16 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
weak var mastodonController: MastodonController!
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
var accountID: String! {
|
var accountID: String!
|
||||||
didSet {
|
|
||||||
if shouldLoadOnAccountIDSet {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.updateAccountUI()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pinnedStatuses: [(id: String, state: StatusState)] = [] {
|
var pinnedStatuses: [(id: String, state: StatusState)] = []
|
||||||
didSet {
|
var timelineSegments: [[(id: String, state: StatusState)]] = []
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.tableView.reloadData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var timelineSegments: [[(id: String, state: StatusState)]] = [] {
|
|
||||||
didSet {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.tableView.reloadData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var older: RequestRange?
|
var older: RequestRange?
|
||||||
var newer: RequestRange?
|
var newer: RequestRange?
|
||||||
|
|
||||||
var shouldLoadOnAccountIDSet = false
|
private var loadingVC: LoadingViewController? = nil
|
||||||
var loadingVC: LoadingViewController? = nil
|
private var loaded = false
|
||||||
|
|
||||||
init(accountID: String?, mastodonController: MastodonController) {
|
init(accountID: String?, mastodonController: MastodonController) {
|
||||||
self.mastodonController = mastodonController
|
self.mastodonController = mastodonController
|
||||||
|
@ -80,7 +60,22 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
tableView.prefetchDataSource = self
|
tableView.prefetchDataSource = self
|
||||||
|
|
||||||
if let accountID = accountID {
|
if accountID == nil {
|
||||||
|
loadingVC = LoadingViewController()
|
||||||
|
embedChild(loadingVC!)
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
if !loaded, let accountID = accountID {
|
||||||
|
loaded = true
|
||||||
|
loadingVC?.removeViewAndController()
|
||||||
|
loadingVC = nil
|
||||||
|
|
||||||
if mastodonController.persistentContainer.account(for: accountID) != nil {
|
if mastodonController.persistentContainer.account(for: accountID) != nil {
|
||||||
updateAccountUI()
|
updateAccountUI()
|
||||||
} else {
|
} else {
|
||||||
|
@ -106,18 +101,10 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
loadingVC = LoadingViewController()
|
|
||||||
embedChild(loadingVC!)
|
|
||||||
shouldLoadOnAccountIDSet = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateAccountUI() {
|
func updateAccountUI() {
|
||||||
loadingVC?.removeViewAndController()
|
|
||||||
|
|
||||||
updateUIForPreferences()
|
updateUIForPreferences()
|
||||||
|
|
||||||
getStatuses(onlyPinned: true) { (response) in
|
getStatuses(onlyPinned: true) { (response) in
|
||||||
|
@ -125,6 +112,12 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
self.mastodonController.persistentContainer.addAll(statuses: statuses) {
|
self.mastodonController.persistentContainer.addAll(statuses: statuses) {
|
||||||
self.pinnedStatuses = statuses.map { ($0.id, .unknown) }
|
self.pinnedStatuses = statuses.map { ($0.id, .unknown) }
|
||||||
|
let indexPaths = (0..<statuses.count).map { IndexPath(row: $0, section: 1) }
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.tableView.insertRows(at: indexPaths, with: .none)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +129,12 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
self.older = pagination?.older
|
self.older = pagination?.older
|
||||||
self.newer = pagination?.newer
|
self.newer = pagination?.newer
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.tableView.insertSections(IndexSet(integer: 2), with: .none)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,9 +209,16 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||||
|
|
||||||
self.mastodonController.persistentContainer.addAll(statuses: newStatuses) {
|
self.mastodonController.persistentContainer.addAll(statuses: newStatuses) {
|
||||||
self.timelineSegments[indexPath.section - 2].append(contentsOf: newStatuses.map { ($0.id, .unknown) })
|
|
||||||
|
|
||||||
self.older = pagination?.older
|
self.older = pagination?.older
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
let start = self.timelineSegments[indexPath.section - 2].count
|
||||||
|
let indexPaths = (0..<newStatuses.count).map { IndexPath(row: start + $0, section: indexPath.section) }
|
||||||
|
self.timelineSegments[indexPath.section - 2].append(contentsOf: newStatuses.map { ($0.id, .unknown) })
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.tableView.insertRows(at: indexPaths, with: .none)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,13 +243,17 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||||
|
|
||||||
self.mastodonController.persistentContainer.addAll(statuses: newStatuses) {
|
self.mastodonController.persistentContainer.addAll(statuses: newStatuses) {
|
||||||
self.timelineSegments[0].insert(contentsOf: newStatuses.map { ($0.id, .unknown) }, at: 0)
|
|
||||||
|
|
||||||
if let newer = pagination?.newer {
|
if let newer = pagination?.newer {
|
||||||
self.newer = newer
|
self.newer = newer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let indexPaths = (0..<newStatuses.count).map { IndexPath(row: $0, section: 2) }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
self.timelineSegments[0].insert(contentsOf: newStatuses.map { ($0.id, .unknown) }, at: 0)
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.tableView.insertRows(at: indexPaths, with: .none)
|
||||||
|
}
|
||||||
|
|
||||||
self.refreshControl?.endRefreshing()
|
self.refreshControl?.endRefreshing()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,7 +273,12 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
pinnedStatuses.append((status.id, state))
|
pinnedStatuses.append((status.id, state))
|
||||||
}
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
self.pinnedStatuses = pinnedStatuses
|
self.pinnedStatuses = pinnedStatuses
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.tableView.reloadSections(IndexSet(integer: 1), with: .none)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -288,6 +303,16 @@ extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
|
||||||
func showMoreOptions(cell: ProfileHeaderTableViewCell) {
|
func showMoreOptions(cell: ProfileHeaderTableViewCell) {
|
||||||
let account = mastodonController.persistentContainer.account(for: accountID)!
|
let account = mastodonController.persistentContainer.account(for: accountID)!
|
||||||
|
|
||||||
|
func showActivityController(activities: [UIActivity]) {
|
||||||
|
let activityController = UIActivityViewController(activityItems: [account.url, AccountActivityItemSource(account)], applicationActivities: activities)
|
||||||
|
activityController.completionWithItemsHandler = OpenInSafariActivity.completionHandler(viewController: self, url: account.url)
|
||||||
|
activityController.popoverPresentationController?.sourceView = cell.moreButtonVisualEffectView
|
||||||
|
self.present(activityController, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.id == mastodonController.account.id {
|
||||||
|
showActivityController(activities: [OpenInSafariActivity()])
|
||||||
|
} else {
|
||||||
let request = Client.getRelationships(accounts: [account.id])
|
let request = Client.getRelationships(accounts: [account.id])
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
var customActivities: [UIActivity] = [OpenInSafariActivity()]
|
var customActivities: [UIActivity] = [OpenInSafariActivity()]
|
||||||
|
@ -297,10 +322,8 @@ extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let activityController = UIActivityViewController(activityItems: [account.url, account], applicationActivities: customActivities)
|
showActivityController(activities: customActivities)
|
||||||
activityController.completionWithItemsHandler = OpenInSafariActivity.completionHandler(viewController: self, url: account.url)
|
}
|
||||||
activityController.popoverPresentationController?.sourceView = cell.moreButtonVisualEffectView
|
|
||||||
self.present(activityController, animated: true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ class TimelineTableViewController: EnhancedTableViewController {
|
||||||
var timeline: Timeline!
|
var timeline: Timeline!
|
||||||
weak var mastodonController: MastodonController!
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
|
private var loaded = false
|
||||||
|
|
||||||
var timelineSegments: [[(id: String, state: StatusState)]] = []
|
var timelineSegments: [[(id: String, state: StatusState)]] = []
|
||||||
|
|
||||||
var newer: RequestRange?
|
var newer: RequestRange?
|
||||||
|
@ -63,11 +65,18 @@ class TimelineTableViewController: EnhancedTableViewController {
|
||||||
tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell")
|
tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell")
|
||||||
|
|
||||||
tableView.prefetchDataSource = self
|
tableView.prefetchDataSource = self
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
loadInitialStatuses()
|
loadInitialStatuses()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadInitialStatuses() {
|
func loadInitialStatuses() {
|
||||||
|
guard !loaded else { return }
|
||||||
|
loaded = true
|
||||||
|
|
||||||
let request = Client.getStatuses(timeline: timeline)
|
let request = Client.getStatuses(timeline: timeline)
|
||||||
mastodonController.run(request) { response in
|
mastodonController.run(request) { response in
|
||||||
guard case let .success(statuses, pagination) = response else { fatalError() }
|
guard case let .success(statuses, pagination) = response else { fatalError() }
|
||||||
|
@ -99,6 +108,7 @@ class TimelineTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
let (id, state) = timelineSegments[indexPath.section][indexPath.row]
|
let (id, state) = timelineSegments[indexPath.section][indexPath.row]
|
||||||
cell.delegate = self
|
cell.delegate = self
|
||||||
|
|
||||||
cell.updateUI(statusID: id, state: state)
|
cell.updateUI(statusID: id, state: state)
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
|
|
|
@ -36,14 +36,6 @@ protocol TuskerNavigationDelegate: class {
|
||||||
|
|
||||||
func reply(to statusID: String, mentioningAcct: String?)
|
func reply(to statusID: String, mentioningAcct: String?)
|
||||||
|
|
||||||
func largeImage(_ image: UIImage, description: String?, sourceView: UIImageView) -> LargeImageViewController
|
|
||||||
|
|
||||||
func largeImage(gifData: Data, description: String?, sourceView: UIImageView) -> LargeImageViewController
|
|
||||||
|
|
||||||
func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIImageView)
|
|
||||||
|
|
||||||
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIImageView)
|
|
||||||
|
|
||||||
func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController
|
func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController
|
||||||
|
|
||||||
func showLoadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView)
|
func showLoadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView)
|
||||||
|
@ -151,27 +143,6 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
||||||
present(vc, animated: true)
|
present(vc, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func largeImage(_ image: UIImage, description: String?, sourceView: UIImageView) -> LargeImageViewController {
|
|
||||||
let vc = LargeImageViewController(image: image, description: description, sourceView: sourceView)
|
|
||||||
vc.transitioningDelegate = self
|
|
||||||
return vc
|
|
||||||
}
|
|
||||||
|
|
||||||
func largeImage(gifData: Data, description: String?, sourceView: UIImageView) -> LargeImageViewController {
|
|
||||||
let vc = LargeImageViewController(image: UIImage(data: gifData)!, description: description, sourceView: sourceView)
|
|
||||||
vc.transitioningDelegate = self
|
|
||||||
vc.gifData = gifData
|
|
||||||
return vc
|
|
||||||
}
|
|
||||||
|
|
||||||
func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIImageView) {
|
|
||||||
present(largeImage(image, description: description, sourceView: sourceView), animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIImageView) {
|
|
||||||
present(largeImage(gifData: gifData, description: description, sourceView: sourceView), animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController {
|
func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController {
|
||||||
let vc = LoadingLargeImageViewController(url: url, cache: cache, imageDescription: description)
|
let vc = LoadingLargeImageViewController(url: url, cache: cache, imageDescription: description)
|
||||||
vc.animationSourceView = sourceView
|
vc.animationSourceView = sourceView
|
||||||
|
@ -205,24 +176,33 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
||||||
private func moreOptions(forStatus statusID: String) -> UIActivityViewController {
|
private func moreOptions(forStatus statusID: String) -> UIActivityViewController {
|
||||||
guard let status = apiController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
guard let status = apiController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
||||||
guard let url = status.url else { fatalError("Missing url for status \(statusID)") }
|
guard let url = status.url else { fatalError("Missing url for status \(statusID)") }
|
||||||
var customActivites: [UIActivity] = [OpenInSafariActivity()]
|
|
||||||
|
|
||||||
let bookmarked = status.bookmarked ?? false
|
var customActivites: [UIActivity] = [
|
||||||
customActivites.insert(bookmarked ? UnbookmarkStatusActivity() : BookmarkStatusActivity(), at: 0)
|
OpenInSafariActivity(),
|
||||||
|
(status.bookmarked ?? false) ? UnbookmarkStatusActivity() : BookmarkStatusActivity(),
|
||||||
|
status.muted ? UnmuteConversationActivity() : MuteConversationActivity(),
|
||||||
|
]
|
||||||
|
|
||||||
if apiController.account != nil, status.account.id == apiController.account.id {
|
if apiController.account != nil, status.account.id == apiController.account.id {
|
||||||
let pinned = status.pinned ?? false
|
let pinned = status.pinned ?? false
|
||||||
customActivites.insert(pinned ? UnpinStatusActivity() : PinStatusActivity(), at: 1)
|
customActivites.insert(pinned ? UnpinStatusActivity() : PinStatusActivity(), at: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
let activityController = UIActivityViewController(activityItems: [url, status], applicationActivities: customActivites)
|
let activityController = UIActivityViewController(activityItems: [url, StatusActivityItemSource(status)], applicationActivities: customActivites)
|
||||||
activityController.completionWithItemsHandler = OpenInSafariActivity.completionHandler(viewController: self, url: url)
|
activityController.completionWithItemsHandler = OpenInSafariActivity.completionHandler(viewController: self, url: url)
|
||||||
return activityController
|
return activityController
|
||||||
}
|
}
|
||||||
|
|
||||||
private func moreOptions(forAccount accountID: String) -> UIActivityViewController {
|
private func moreOptions(forAccount accountID: String) -> UIActivityViewController {
|
||||||
guard let account = apiController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID)") }
|
guard let account = apiController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID)") }
|
||||||
return moreOptions(forURL: account.url)
|
|
||||||
|
let customActivities: [UIActivity] = [
|
||||||
|
OpenInSafariActivity(),
|
||||||
|
]
|
||||||
|
|
||||||
|
let activityController = UIActivityViewController(activityItems: [account.url, AccountActivityItemSource(account)], applicationActivities: customActivities)
|
||||||
|
activityController.completionWithItemsHandler = OpenInSafariActivity.completionHandler(viewController: self, url: account.url)
|
||||||
|
return activityController
|
||||||
}
|
}
|
||||||
|
|
||||||
func showMoreOptions(forStatus statusID: String, sourceView: UIView?) {
|
func showMoreOptions(forStatus statusID: String, sourceView: UIView?) {
|
||||||
|
|
|
@ -9,10 +9,13 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Photos
|
import Photos
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
import Vision
|
||||||
|
|
||||||
protocol ComposeAttachmentTableViewCellDelegate: class {
|
protocol ComposeAttachmentTableViewCellDelegate: class {
|
||||||
|
func composeAttachment(_ cell: ComposeAttachmentTableViewCell, present viewController: UIViewController, animated: Bool)
|
||||||
func removeAttachment(_ cell: ComposeAttachmentTableViewCell)
|
func removeAttachment(_ cell: ComposeAttachmentTableViewCell)
|
||||||
func attachmentDescriptionChanged(_ cell: ComposeAttachmentTableViewCell)
|
func attachmentDescriptionChanged(_ cell: ComposeAttachmentTableViewCell)
|
||||||
|
func composeAttachmentDescriptionHeightChanged(_ cell: ComposeAttachmentTableViewCell)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ComposeAttachmentTableViewCell: UITableViewCell {
|
class ComposeAttachmentTableViewCell: UITableViewCell {
|
||||||
|
@ -21,11 +24,30 @@ class ComposeAttachmentTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
@IBOutlet weak var assetImageView: UIImageView!
|
@IBOutlet weak var assetImageView: UIImageView!
|
||||||
@IBOutlet weak var descriptionTextView: UITextView!
|
@IBOutlet weak var descriptionTextView: UITextView!
|
||||||
|
@IBOutlet weak var descriptionTextViewHeightConstraint: NSLayoutConstraint!
|
||||||
@IBOutlet weak var descriptionPlaceholderLabel: UILabel!
|
@IBOutlet weak var descriptionPlaceholderLabel: UILabel!
|
||||||
@IBOutlet weak var removeButton: UIButton!
|
@IBOutlet weak var removeButton: UIButton!
|
||||||
|
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
|
||||||
|
|
||||||
var attachment: CompositionAttachment!
|
var attachment: CompositionAttachment!
|
||||||
|
|
||||||
|
var state: State = .allowEntry {
|
||||||
|
didSet {
|
||||||
|
switch state {
|
||||||
|
case .allowEntry:
|
||||||
|
descriptionTextView.isEditable = true
|
||||||
|
updateDescriptionPlaceholderLabel()
|
||||||
|
activityIndicator.stopAnimating()
|
||||||
|
case .recognizingText:
|
||||||
|
descriptionTextView.isEditable = false
|
||||||
|
descriptionPlaceholderLabel.isHidden = true
|
||||||
|
activityIndicator.startAnimating()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var textRecognitionRequest: VNRecognizeTextRequest?
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|
||||||
|
@ -74,21 +96,81 @@ class ComposeAttachmentTableViewCell: UITableViewCell {
|
||||||
removeButton.isEnabled = enabled
|
removeButton.isEnabled = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func recognizeTextFromImage() {
|
||||||
|
precondition(attachment.data.type == .image)
|
||||||
|
state = .recognizingText
|
||||||
|
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
self.attachment.data.getData { (data, mimeType) in
|
||||||
|
let handler = VNImageRequestHandler(data: data, options: [:])
|
||||||
|
let request = VNRecognizeTextRequest { (request, error) in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.state = .allowEntry
|
||||||
|
|
||||||
|
if let results = request.results as? [VNRecognizedTextObservation] {
|
||||||
|
var text = ""
|
||||||
|
for observation in results {
|
||||||
|
let result = observation.topCandidates(1).first!
|
||||||
|
text.append(result.string)
|
||||||
|
text.append("\n")
|
||||||
|
}
|
||||||
|
self.descriptionTextView.text = text
|
||||||
|
self.textViewDidChange(self.descriptionTextView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.recognitionLevel = .accurate
|
||||||
|
request.usesLanguageCorrection = true
|
||||||
|
self.textRecognitionRequest = request
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
do {
|
||||||
|
try handler.perform([request])
|
||||||
|
} catch {
|
||||||
|
// The perform call throws an error with code 1 if the request is cancelled, which we don't want to show an alert for.
|
||||||
|
guard (error as NSError).code != 1 else { return }
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.state = .allowEntry
|
||||||
|
let title = NSLocalizedString("Text Recognition Failed", comment: "text recognition error alert title")
|
||||||
|
let message = error.localizedDescription
|
||||||
|
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||||
|
self.delegate?.composeAttachment(self, present: alert, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
assetImageView.image = nil
|
assetImageView.image = nil
|
||||||
|
descriptionTextViewHeightConstraint.constant = 80
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func removeButtonPressed(_ sender: Any) {
|
@IBAction func removeButtonPressed(_ sender: Any) {
|
||||||
|
textRecognitionRequest?.cancel()
|
||||||
delegate?.removeAttachment(self)
|
delegate?.removeAttachment(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ComposeAttachmentTableViewCell {
|
||||||
|
enum State {
|
||||||
|
case allowEntry, recognizingText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension ComposeAttachmentTableViewCell: UITextViewDelegate {
|
extension ComposeAttachmentTableViewCell: UITextViewDelegate {
|
||||||
func textViewDidChange(_ textView: UITextView) {
|
func textViewDidChange(_ textView: UITextView) {
|
||||||
attachment.attachmentDescription = textView.text
|
attachment.attachmentDescription = textView.text
|
||||||
updateDescriptionPlaceholderLabel()
|
updateDescriptionPlaceholderLabel()
|
||||||
delegate?.attachmentDescriptionChanged(self)
|
delegate?.attachmentDescriptionChanged(self)
|
||||||
|
let smallestSize = textView.sizeThatFits(CGSize(width: textView.bounds.width, height: .greatestFiniteMagnitude))
|
||||||
|
let old = descriptionTextViewHeightConstraint.constant
|
||||||
|
descriptionTextViewHeightConstraint.constant = max(80, smallestSize.height)
|
||||||
|
if old != descriptionTextViewHeightConstraint.constant {
|
||||||
|
delegate?.composeAttachmentDescriptionHeightChanged(self)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16092.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16082.1"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -36,11 +36,14 @@
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="cwP-Eh-5dJ">
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="cwP-Eh-5dJ">
|
||||||
<rect key="frame" x="84" y="0.0" width="194" height="80"/>
|
<rect key="frame" x="84" y="0.0" width="194" height="80"/>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="80" id="6aZ-w8-j9n"/>
|
||||||
|
</constraints>
|
||||||
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
</textView>
|
</textView>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Lvf-I9-aV3">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" pointerInteraction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Lvf-I9-aV3">
|
||||||
<rect key="frame" x="282" y="29" width="22" height="22"/>
|
<rect key="frame" x="282" y="29" width="22" height="22"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="22" id="aIh-Ym-ARv"/>
|
<constraint firstAttribute="height" constant="22" id="aIh-Ym-ARv"/>
|
||||||
|
@ -57,6 +60,9 @@
|
||||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="80" id="jWo-An-3h6"/>
|
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="80" id="jWo-An-3h6"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</stackView>
|
</stackView>
|
||||||
|
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="medium" translatesAutoresizingMaskIntoConstraints="NO" id="Kzy-5r-UW8">
|
||||||
|
<rect key="frame" x="179" y="38" width="20" height="20"/>
|
||||||
|
</activityIndicatorView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="bottom" secondItem="xRe-ec-Coh" secondAttribute="bottom" constant="8" id="DOS-Wv-G3s"/>
|
<constraint firstAttribute="bottom" secondItem="xRe-ec-Coh" secondAttribute="bottom" constant="8" id="DOS-Wv-G3s"/>
|
||||||
|
@ -64,15 +70,19 @@
|
||||||
<constraint firstItem="h6T-x4-yzl" firstAttribute="trailing" secondItem="cwP-Eh-5dJ" secondAttribute="trailing" constant="4" id="KN2-Ve-3B2"/>
|
<constraint firstItem="h6T-x4-yzl" firstAttribute="trailing" secondItem="cwP-Eh-5dJ" secondAttribute="trailing" constant="4" id="KN2-Ve-3B2"/>
|
||||||
<constraint firstItem="h6T-x4-yzl" firstAttribute="top" secondItem="cwP-Eh-5dJ" secondAttribute="top" constant="8" id="P3B-Jo-XMs"/>
|
<constraint firstItem="h6T-x4-yzl" firstAttribute="top" secondItem="cwP-Eh-5dJ" secondAttribute="top" constant="8" id="P3B-Jo-XMs"/>
|
||||||
<constraint firstItem="h6T-x4-yzl" firstAttribute="leading" secondItem="cwP-Eh-5dJ" secondAttribute="leading" constant="4" id="UjP-Gs-ZjO"/>
|
<constraint firstItem="h6T-x4-yzl" firstAttribute="leading" secondItem="cwP-Eh-5dJ" secondAttribute="leading" constant="4" id="UjP-Gs-ZjO"/>
|
||||||
|
<constraint firstItem="Kzy-5r-UW8" firstAttribute="centerX" secondItem="cwP-Eh-5dJ" secondAttribute="centerX" id="czP-Ia-Ddc"/>
|
||||||
|
<constraint firstItem="Kzy-5r-UW8" firstAttribute="centerY" secondItem="cwP-Eh-5dJ" secondAttribute="centerY" id="eel-xx-aFq"/>
|
||||||
<constraint firstItem="xRe-ec-Coh" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="8" id="gRN-PV-gm6"/>
|
<constraint firstItem="xRe-ec-Coh" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="8" id="gRN-PV-gm6"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="xRe-ec-Coh" secondAttribute="trailing" constant="8" id="tyE-HK-4qb"/>
|
<constraint firstAttribute="trailing" secondItem="xRe-ec-Coh" secondAttribute="trailing" constant="8" id="tyE-HK-4qb"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</tableViewCellContentView>
|
</tableViewCellContentView>
|
||||||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||||
<connections>
|
<connections>
|
||||||
|
<outlet property="activityIndicator" destination="Kzy-5r-UW8" id="lmy-NY-Owu"/>
|
||||||
<outlet property="assetImageView" destination="GLY-o8-47z" id="hZH-ur-m4z"/>
|
<outlet property="assetImageView" destination="GLY-o8-47z" id="hZH-ur-m4z"/>
|
||||||
<outlet property="descriptionPlaceholderLabel" destination="h6T-x4-yzl" id="jBe-R0-Sfn"/>
|
<outlet property="descriptionPlaceholderLabel" destination="h6T-x4-yzl" id="jBe-R0-Sfn"/>
|
||||||
<outlet property="descriptionTextView" destination="cwP-Eh-5dJ" id="pxJ-zF-GKC"/>
|
<outlet property="descriptionTextView" destination="cwP-Eh-5dJ" id="pxJ-zF-GKC"/>
|
||||||
|
<outlet property="descriptionTextViewHeightConstraint" destination="6aZ-w8-j9n" id="ees-sT-Trc"/>
|
||||||
<outlet property="removeButton" destination="Lvf-I9-aV3" id="3qk-Zr-je1"/>
|
<outlet property="removeButton" destination="Lvf-I9-aV3" id="3qk-Zr-je1"/>
|
||||||
</connections>
|
</connections>
|
||||||
<point key="canvasLocation" x="107" y="181"/>
|
<point key="canvasLocation" x="107" y="181"/>
|
||||||
|
|
|
@ -19,8 +19,8 @@ class GifvAttachmentView: UIView {
|
||||||
layer as! AVPlayerLayer
|
layer as! AVPlayerLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
private let item: AVPlayerItem
|
let item: AVPlayerItem
|
||||||
private let player: AVPlayer
|
let player: AVPlayer
|
||||||
|
|
||||||
init(asset: AVAsset, gravity: AVLayerVideoGravity) {
|
init(asset: AVAsset, gravity: AVLayerVideoGravity) {
|
||||||
item = AVPlayerItem(asset: asset)
|
item = AVPlayerItem(asset: asset)
|
||||||
|
@ -30,6 +30,7 @@ class GifvAttachmentView: UIView {
|
||||||
|
|
||||||
playerLayer.player = player
|
playerLayer.player = player
|
||||||
playerLayer.videoGravity = gravity
|
playerLayer.videoGravity = gravity
|
||||||
|
player.isMuted = true
|
||||||
player.play()
|
player.play()
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(restartItem), name: .AVPlayerItemDidPlayToEndTime, object: item)
|
NotificationCenter.default.addObserver(self, selector: #selector(restartItem), name: .AVPlayerItemDidPlayToEndTime, object: item)
|
||||||
|
|
|
@ -43,7 +43,7 @@ class ComposeStatusReplyView: UIView {
|
||||||
displayNameLabel.updateForAccountDisplayName(account: status.account)
|
displayNameLabel.updateForAccountDisplayName(account: status.account)
|
||||||
usernameLabel.text = "@\(status.account.acct)"
|
usernameLabel.text = "@\(status.account.acct)"
|
||||||
statusContentTextView.overrideMastodonController = mastodonController
|
statusContentTextView.overrideMastodonController = mastodonController
|
||||||
statusContentTextView.statusID = status.id
|
statusContentTextView.setTextFrom(status: status)
|
||||||
|
|
||||||
avatarRequest = ImageCache.avatars.get(status.account.avatar) { [weak self] (data) in
|
avatarRequest = ImageCache.avatars.get(status.account.avatar) { [weak self] (data) in
|
||||||
guard let self = self, let data = data else { return }
|
guard let self = self, let data = data else { return }
|
||||||
|
|
|
@ -28,7 +28,7 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
||||||
@IBOutlet weak var noteTextView: StatusContentTextView!
|
@IBOutlet weak var noteTextView: StatusContentTextView!
|
||||||
@IBOutlet weak var fieldsStackView: UIStackView!
|
@IBOutlet weak var fieldsStackView: UIStackView!
|
||||||
@IBOutlet weak var fieldNamesStackView: UIStackView!
|
@IBOutlet weak var fieldNamesStackView: UIStackView!
|
||||||
@IBOutlet weak var fieldValuesStack: UIStackView!
|
@IBOutlet weak var fieldValuesStackView: UIStackView!
|
||||||
@IBOutlet weak var moreButtonVisualEffectView: UIVisualEffectView!
|
@IBOutlet weak var moreButtonVisualEffectView: UIVisualEffectView!
|
||||||
|
|
||||||
var accountID: String!
|
var accountID: String!
|
||||||
|
@ -102,13 +102,16 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
fieldsStackView.isHidden = account.fields.isEmpty
|
fieldsStackView.isHidden = account.fields.isEmpty
|
||||||
|
|
||||||
fieldsStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
fieldNamesStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||||
|
fieldValuesStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||||
for field in account.fields {
|
for field in account.fields {
|
||||||
let nameLabel = UILabel()
|
let nameLabel = UILabel()
|
||||||
nameLabel.text = field.name
|
nameLabel.text = field.name
|
||||||
nameLabel.font = .boldSystemFont(ofSize: 17)
|
nameLabel.font = .boldSystemFont(ofSize: 17)
|
||||||
nameLabel.textAlignment = .right
|
nameLabel.textAlignment = .right
|
||||||
nameLabel.numberOfLines = 0
|
nameLabel.numberOfLines = 0
|
||||||
|
nameLabel.lineBreakMode = .byWordWrapping
|
||||||
|
nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
fieldNamesStackView.addArrangedSubview(nameLabel)
|
fieldNamesStackView.addArrangedSubview(nameLabel)
|
||||||
|
|
||||||
let valueTextView = ContentTextView()
|
let valueTextView = ContentTextView()
|
||||||
|
@ -119,7 +122,10 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
||||||
valueTextView.textAlignment = .left
|
valueTextView.textAlignment = .left
|
||||||
valueTextView.awakeFromNib()
|
valueTextView.awakeFromNib()
|
||||||
valueTextView.navigationDelegate = delegate
|
valueTextView.navigationDelegate = delegate
|
||||||
fieldValuesStack.addArrangedSubview(valueTextView)
|
valueTextView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
|
fieldValuesStackView.addArrangedSubview(valueTextView)
|
||||||
|
|
||||||
|
nameLabel.heightAnchor.constraint(equalTo: valueTextView.heightAnchor).isActive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if accountUpdater == nil {
|
if accountUpdater == nil {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16092.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16082.1"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -78,13 +78,17 @@
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="sHU-GU-klv">
|
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="sHU-GU-klv">
|
||||||
<rect key="frame" x="16" y="238" width="343" height="50"/>
|
<rect key="frame" x="16" y="238" width="343" height="50"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="pV2-Mz-54W">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="pV2-Mz-54W">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="147" height="50"/>
|
<rect key="frame" x="0.0" y="0.0" width="167.5" height="50"/>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="oza-9d-8v4">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="oza-9d-8v4">
|
||||||
<rect key="frame" x="155" y="0.0" width="188" height="50"/>
|
<rect key="frame" x="175.5" y="0.0" width="167.5" height="50"/>
|
||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="oza-9d-8v4" firstAttribute="width" relation="lessThanOrEqual" secondItem="pV2-Mz-54W" secondAttribute="width" multiplier="2" id="LHm-6k-LyV"/>
|
||||||
|
<constraint firstItem="oza-9d-8v4" firstAttribute="width" relation="greaterThanOrEqual" secondItem="pV2-Mz-54W" secondAttribute="width" multiplier="0.5" id="Zbr-l3-Lff"/>
|
||||||
|
</constraints>
|
||||||
</stackView>
|
</stackView>
|
||||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="mQY-XN-PfZ">
|
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="mQY-XN-PfZ">
|
||||||
<rect key="frame" x="335" y="110" width="32" height="32"/>
|
<rect key="frame" x="335" y="110" width="32" height="32"/>
|
||||||
|
@ -161,7 +165,7 @@
|
||||||
<outlet property="avatarImageView" destination="tH8-sR-DHC" id="6ll-yL-g1o"/>
|
<outlet property="avatarImageView" destination="tH8-sR-DHC" id="6ll-yL-g1o"/>
|
||||||
<outlet property="displayNameLabel" destination="LjK-72-Bez" id="nIU-ey-H1C"/>
|
<outlet property="displayNameLabel" destination="LjK-72-Bez" id="nIU-ey-H1C"/>
|
||||||
<outlet property="fieldNamesStackView" destination="pV2-Mz-54W" id="xfG-60-K0s"/>
|
<outlet property="fieldNamesStackView" destination="pV2-Mz-54W" id="xfG-60-K0s"/>
|
||||||
<outlet property="fieldValuesStack" destination="oza-9d-8v4" id="UIS-KM-5fR"/>
|
<outlet property="fieldValuesStackView" destination="oza-9d-8v4" id="UIS-KM-5fR"/>
|
||||||
<outlet property="fieldsStackView" destination="sHU-GU-klv" id="Gli-Gf-Ubh"/>
|
<outlet property="fieldsStackView" destination="sHU-GU-klv" id="Gli-Gf-Ubh"/>
|
||||||
<outlet property="followsYouLabel" destination="a32-1a-xXZ" id="phY-0L-NnN"/>
|
<outlet property="followsYouLabel" destination="a32-1a-xXZ" id="phY-0L-NnN"/>
|
||||||
<outlet property="headerImageView" destination="Fw7-OL-iy5" id="6sv-E5-D73"/>
|
<outlet property="headerImageView" destination="Fw7-OL-iy5" id="6sv-E5-D73"/>
|
||||||
|
|
|
@ -27,6 +27,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
@IBOutlet weak var avatarImageView: UIImageView!
|
@IBOutlet weak var avatarImageView: UIImageView!
|
||||||
@IBOutlet weak var displayNameLabel: EmojiLabel!
|
@IBOutlet weak var displayNameLabel: EmojiLabel!
|
||||||
@IBOutlet weak var usernameLabel: UILabel!
|
@IBOutlet weak var usernameLabel: UILabel!
|
||||||
|
@IBOutlet weak var visibilityImageView: UIImageView!
|
||||||
@IBOutlet weak var contentWarningLabel: EmojiLabel!
|
@IBOutlet weak var contentWarningLabel: EmojiLabel!
|
||||||
@IBOutlet weak var collapseButton: UIButton!
|
@IBOutlet weak var collapseButton: UIButton!
|
||||||
@IBOutlet weak var contentTextView: StatusContentTextView!
|
@IBOutlet weak var contentTextView: StatusContentTextView!
|
||||||
|
@ -86,7 +87,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!]
|
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!]
|
||||||
attachmentsView.isAccessibilityElement = true
|
attachmentsView.isAccessibilityElement = true
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
open func createObserversIfNecessary() {
|
open func createObserversIfNecessary() {
|
||||||
|
@ -125,8 +126,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
let account = status.account
|
let account = status.account
|
||||||
self.accountID = account.id
|
self.accountID = account.id
|
||||||
updateUI(account: account)
|
updateUI(account: account)
|
||||||
|
updateUIForPreferences(account: account)
|
||||||
updateUIForPreferences()
|
|
||||||
|
|
||||||
attachmentsView.updateUI(status: status)
|
attachmentsView.updateUI(status: status)
|
||||||
attachmentsView.isAccessibilityElement = status.attachments.count > 0
|
attachmentsView.isAccessibilityElement = status.attachments.count > 0
|
||||||
|
@ -134,7 +134,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
updateStatusState(status: status)
|
updateStatusState(status: status)
|
||||||
|
|
||||||
contentTextView.statusID = statusID
|
contentTextView.setTextFrom(status: status)
|
||||||
|
|
||||||
contentWarningLabel.text = status.spoilerText
|
contentWarningLabel.text = status.spoilerText
|
||||||
contentWarningLabel.isHidden = status.spoilerText.isEmpty
|
contentWarningLabel.isHidden = status.spoilerText.isEmpty
|
||||||
|
@ -142,6 +142,18 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
contentWarningLabel.setEmojis(status.emojis, identifier: statusID)
|
contentWarningLabel.setEmojis(status.emojis, identifier: statusID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let reblogDisabled: Bool
|
||||||
|
switch mastodonController.instance.instanceType {
|
||||||
|
case .mastodon:
|
||||||
|
reblogDisabled = status.visibility == .private || status.visibility == .direct
|
||||||
|
case .pleroma:
|
||||||
|
// Pleroma allows 'Boost to original audience' for your own private posts
|
||||||
|
reblogDisabled = status.visibility == .direct || (status.visibility == .private && status.account.id != mastodonController.account.id)
|
||||||
|
}
|
||||||
|
reblogButton.isEnabled = !reblogDisabled
|
||||||
|
|
||||||
|
updateStatusIconsForPreferences(status)
|
||||||
|
|
||||||
if state.unknown {
|
if state.unknown {
|
||||||
collapsible = !status.spoilerText.isEmpty
|
collapsible = !status.spoilerText.isEmpty
|
||||||
var shouldCollapse = collapsible
|
var shouldCollapse = collapsible
|
||||||
|
@ -191,13 +203,35 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func updateUIForPreferences() {
|
@objc func preferencesChanged() {
|
||||||
guard let mastodonController = mastodonController, let account = mastodonController.persistentContainer.account(for: accountID) else { return }
|
guard let mastodonController = mastodonController,
|
||||||
|
let account = mastodonController.persistentContainer.account(for: accountID),
|
||||||
|
let status = mastodonController.persistentContainer.status(for: statusID) else { return }
|
||||||
|
updateUIForPreferences(account: account)
|
||||||
|
updateStatusIconsForPreferences(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIForPreferences(account: AccountMO) {
|
||||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||||
displayNameLabel.updateForAccountDisplayName(account: account)
|
displayNameLabel.updateForAccountDisplayName(account: account)
|
||||||
attachmentsView.contentHidden = Preferences.shared.blurAllMedia || (mastodonController.persistentContainer.status(for: statusID)?.sensitive ?? false)
|
attachmentsView.contentHidden = Preferences.shared.blurAllMedia || (mastodonController.persistentContainer.status(for: statusID)?.sensitive ?? false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateStatusIconsForPreferences(_ status: StatusMO) {
|
||||||
|
visibilityImageView.isHidden = !Preferences.shared.alwaysShowStatusVisibilityIcon
|
||||||
|
if Preferences.shared.alwaysShowStatusVisibilityIcon {
|
||||||
|
visibilityImageView.image = UIImage(systemName: status.visibility.unfilledImageName)
|
||||||
|
visibilityImageView.accessibilityLabel = String(format: NSLocalizedString("Visibility: %@", comment: "status visibility indicator accessibility label"), status.visibility.displayName)
|
||||||
|
}
|
||||||
|
let reblogButtonImage: UIImage
|
||||||
|
if Preferences.shared.alwaysShowStatusVisibilityIcon || reblogButton.isEnabled {
|
||||||
|
reblogButtonImage = UIImage(systemName: "repeat")!
|
||||||
|
} else {
|
||||||
|
reblogButtonImage = UIImage(systemName: status.visibility.imageName)!
|
||||||
|
}
|
||||||
|
reblogButton.setImage(reblogButtonImage, for: .normal)
|
||||||
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
|
|
||||||
|
|
|
@ -63,8 +63,8 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
profileAccessibilityElement.accessibilityLabel = account.displayNameWithoutCustomEmoji
|
profileAccessibilityElement.accessibilityLabel = account.displayNameWithoutCustomEmoji
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc override func updateUIForPreferences() {
|
override func updateUIForPreferences(account: AccountMO) {
|
||||||
super.updateUIForPreferences()
|
super.updateUIForPreferences(account: account)
|
||||||
|
|
||||||
favoriteAndReblogCountStackView.isHidden = !Preferences.shared.showFavoriteAndReblogCounts
|
favoriteAndReblogCountStackView.isHidden = !Preferences.shared.showFavoriteAndReblogCounts
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16086"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
</constraints>
|
</constraints>
|
||||||
</imageView>
|
</imageView>
|
||||||
<label opaque="NO" contentMode="left" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lZY-2e-17d" customClass="EmojiLabel" customModule="Tusker" customModuleProvider="target">
|
<label opaque="NO" contentMode="left" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lZY-2e-17d" customClass="EmojiLabel" customModule="Tusker" customModuleProvider="target">
|
||||||
<rect key="frame" x="58" y="0.0" width="277" height="29"/>
|
<rect key="frame" x="58" y="0.0" width="255" height="29"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="24"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="24"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
|
@ -38,18 +38,29 @@
|
||||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="globe" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="3Qu-IO-5wt">
|
||||||
|
<rect key="frame" x="321" y="1" width="22" height="20"/>
|
||||||
|
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="22" id="Kqh-qI-dSa"/>
|
||||||
|
<constraint firstAttribute="width" constant="22" id="QY1-tL-QHr"/>
|
||||||
|
</constraints>
|
||||||
|
<preferredSymbolConfiguration key="preferredSymbolConfiguration" weight="thin"/>
|
||||||
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="trailing" secondItem="SWg-Ka-QyP" secondAttribute="trailing" id="4g6-BT-eW4"/>
|
<constraint firstAttribute="trailing" secondItem="SWg-Ka-QyP" secondAttribute="trailing" id="4g6-BT-eW4"/>
|
||||||
<constraint firstItem="lZY-2e-17d" firstAttribute="top" secondItem="Cnd-Fj-B7l" secondAttribute="top" id="8fU-y9-K5Z"/>
|
<constraint firstItem="lZY-2e-17d" firstAttribute="top" secondItem="Cnd-Fj-B7l" secondAttribute="top" id="8fU-y9-K5Z"/>
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="lZY-2e-17d" secondAttribute="trailing" id="AAJ-pd-omx"/>
|
|
||||||
<constraint firstItem="lZY-2e-17d" firstAttribute="leading" secondItem="mB9-HO-1vf" secondAttribute="trailing" constant="8" id="Aqj-co-Szp"/>
|
<constraint firstItem="lZY-2e-17d" firstAttribute="leading" secondItem="mB9-HO-1vf" secondAttribute="trailing" constant="8" id="Aqj-co-Szp"/>
|
||||||
|
<constraint firstItem="3Qu-IO-5wt" firstAttribute="leading" secondItem="lZY-2e-17d" secondAttribute="trailing" constant="8" id="MS8-zq-SWT"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="3Qu-IO-5wt" secondAttribute="trailing" id="NWa-lL-aLk"/>
|
||||||
<constraint firstItem="mB9-HO-1vf" firstAttribute="top" secondItem="Cnd-Fj-B7l" secondAttribute="top" id="R7P-rD-Gbm"/>
|
<constraint firstItem="mB9-HO-1vf" firstAttribute="top" secondItem="Cnd-Fj-B7l" secondAttribute="top" id="R7P-rD-Gbm"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="mB9-HO-1vf" secondAttribute="bottom" id="Wd0-Qh-idS"/>
|
<constraint firstAttribute="bottom" secondItem="mB9-HO-1vf" secondAttribute="bottom" id="Wd0-Qh-idS"/>
|
||||||
<constraint firstItem="mB9-HO-1vf" firstAttribute="leading" secondItem="Cnd-Fj-B7l" secondAttribute="leading" id="bxq-Fs-1aH"/>
|
<constraint firstItem="mB9-HO-1vf" firstAttribute="leading" secondItem="Cnd-Fj-B7l" secondAttribute="leading" id="bxq-Fs-1aH"/>
|
||||||
<constraint firstItem="SWg-Ka-QyP" firstAttribute="leading" secondItem="mB9-HO-1vf" secondAttribute="trailing" constant="8" id="e45-gE-myI"/>
|
<constraint firstItem="SWg-Ka-QyP" firstAttribute="leading" secondItem="mB9-HO-1vf" secondAttribute="trailing" constant="8" id="e45-gE-myI"/>
|
||||||
<constraint firstItem="SWg-Ka-QyP" firstAttribute="top" secondItem="lZY-2e-17d" secondAttribute="bottom" id="lvX-1b-8cN"/>
|
<constraint firstItem="SWg-Ka-QyP" firstAttribute="top" secondItem="lZY-2e-17d" secondAttribute="bottom" id="lvX-1b-8cN"/>
|
||||||
|
<constraint firstItem="3Qu-IO-5wt" firstAttribute="top" secondItem="Cnd-Fj-B7l" secondAttribute="top" id="pPU-WS-Y6B"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" verticalCompressionResistancePriority="751" text="Content Warning" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cwQ-mR-L1b" customClass="EmojiLabel" customModule="Tusker" customModuleProvider="target">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" verticalCompressionResistancePriority="751" text="Content Warning" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cwQ-mR-L1b" customClass="EmojiLabel" customModule="Tusker" customModuleProvider="target">
|
||||||
|
@ -83,10 +94,10 @@
|
||||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
</textView>
|
</textView>
|
||||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IF9-9U-Gk0" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IF9-9U-Gk0" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="184" width="343" height="0.0"/>
|
<rect key="frame" x="0.0" y="184" width="343" height="193"/>
|
||||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" priority="999" constant="200" id="UMv-Bk-ZyY"/>
|
<constraint firstAttribute="width" secondItem="IF9-9U-Gk0" secondAttribute="height" multiplier="16:9" id="5oh-eK-J5d"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ejU-sO-Og5">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ejU-sO-Og5">
|
||||||
|
@ -219,6 +230,7 @@
|
||||||
<outlet property="totalFavoritesButton" destination="yyj-Bs-Vjq" id="4pV-Qi-Z2X"/>
|
<outlet property="totalFavoritesButton" destination="yyj-Bs-Vjq" id="4pV-Qi-Z2X"/>
|
||||||
<outlet property="totalReblogsButton" destination="dem-vG-cPB" id="i9E-Qn-d76"/>
|
<outlet property="totalReblogsButton" destination="dem-vG-cPB" id="i9E-Qn-d76"/>
|
||||||
<outlet property="usernameLabel" destination="SWg-Ka-QyP" id="h2I-g4-AD9"/>
|
<outlet property="usernameLabel" destination="SWg-Ka-QyP" id="h2I-g4-AD9"/>
|
||||||
|
<outlet property="visibilityImageView" destination="3Qu-IO-5wt" id="sFB-ni-FcZ"/>
|
||||||
</connections>
|
</connections>
|
||||||
<point key="canvasLocation" x="40.799999999999997" y="-122.78860569715144"/>
|
<point key="canvasLocation" x="40.799999999999997" y="-122.78860569715144"/>
|
||||||
</view>
|
</view>
|
||||||
|
@ -227,6 +239,7 @@
|
||||||
<image name="arrowshape.turn.up.left.fill" catalog="system" width="128" height="106"/>
|
<image name="arrowshape.turn.up.left.fill" catalog="system" width="128" height="106"/>
|
||||||
<image name="chevron.down" catalog="system" width="128" height="72"/>
|
<image name="chevron.down" catalog="system" width="128" height="72"/>
|
||||||
<image name="ellipsis" catalog="system" width="128" height="37"/>
|
<image name="ellipsis" catalog="system" width="128" height="37"/>
|
||||||
|
<image name="globe" catalog="system" width="128" height="121"/>
|
||||||
<image name="repeat" catalog="system" width="128" height="99"/>
|
<image name="repeat" catalog="system" width="128" height="99"/>
|
||||||
<image name="star.fill" catalog="system" width="128" height="116"/>
|
<image name="star.fill" catalog="system" width="128" height="116"/>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -22,11 +22,13 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
@IBOutlet weak var reblogLabel: EmojiLabel!
|
@IBOutlet weak var reblogLabel: EmojiLabel!
|
||||||
@IBOutlet weak var timestampLabel: UILabel!
|
@IBOutlet weak var timestampLabel: UILabel!
|
||||||
@IBOutlet weak var pinImageView: UIImageView!
|
@IBOutlet weak var pinImageView: UIImageView!
|
||||||
|
@IBOutlet weak var replyImageView: UIImageView!
|
||||||
|
|
||||||
var reblogStatusID: String?
|
var reblogStatusID: String?
|
||||||
var rebloggerID: String?
|
var rebloggerID: String?
|
||||||
|
|
||||||
var showPinned: Bool = false
|
var showPinned = false
|
||||||
|
var showReplyIndicator = true
|
||||||
|
|
||||||
var updateTimestampWorkItem: DispatchWorkItem?
|
var updateTimestampWorkItem: DispatchWorkItem?
|
||||||
|
|
||||||
|
@ -66,9 +68,11 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
if let rebloggedStatus = status.reblog {
|
if let rebloggedStatus = status.reblog {
|
||||||
reblogStatusID = statusID
|
reblogStatusID = statusID
|
||||||
rebloggerID = status.account.id
|
rebloggerID = status.account.id
|
||||||
|
reblogLabel.isHidden = false
|
||||||
|
updateRebloggerLabel(reblogger: status.account)
|
||||||
|
|
||||||
status = rebloggedStatus
|
status = rebloggedStatus
|
||||||
realStatusID = rebloggedStatus.id
|
realStatusID = rebloggedStatus.id
|
||||||
reblogLabel.isHidden = false
|
|
||||||
} else {
|
} else {
|
||||||
reblogStatusID = nil
|
reblogStatusID = nil
|
||||||
rebloggerID = nil
|
rebloggerID = nil
|
||||||
|
@ -80,13 +84,14 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
|
|
||||||
updateTimestamp()
|
updateTimestamp()
|
||||||
|
|
||||||
let pinned = status.pinned ?? false
|
let pinned = showPinned && (status.pinned ?? false)
|
||||||
pinImageView.isHidden = !(pinned && showPinned)
|
timestampLabel.isHidden = pinned
|
||||||
timestampLabel.isHidden = !pinImageView.isHidden
|
pinImageView.isHidden = !pinned
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc override func updateUIForPreferences() {
|
@objc override func preferencesChanged() {
|
||||||
super.updateUIForPreferences()
|
super.preferencesChanged()
|
||||||
|
|
||||||
if let rebloggerID = rebloggerID,
|
if let rebloggerID = rebloggerID,
|
||||||
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
|
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
|
||||||
updateRebloggerLabel(reblogger: reblogger)
|
updateRebloggerLabel(reblogger: reblogger)
|
||||||
|
@ -103,6 +108,12 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func updateStatusIconsForPreferences(_ status: StatusMO) {
|
||||||
|
super.updateStatusIconsForPreferences(status)
|
||||||
|
|
||||||
|
replyImageView.isHidden = !Preferences.shared.showIsStatusReplyIcon || !showReplyIndicator || status.inReplyToID == nil
|
||||||
|
}
|
||||||
|
|
||||||
func updateTimestamp() {
|
func updateTimestamp() {
|
||||||
// if the mastodonController is nil (i.e. the delegate is nil), then the screen this cell was a part of has been deallocated
|
// if the mastodonController is nil (i.e. the delegate is nil), then the screen this cell was a part of has been deallocated
|
||||||
// so we bail out immediately, since there's nothing to update
|
// so we bail out immediately, since there's nothing to update
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16086"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<view contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="ve3-Y1-NQH">
|
<view contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="ve3-Y1-NQH">
|
||||||
<rect key="frame" x="0.0" y="28.5" width="343" height="165.5"/>
|
<rect key="frame" x="0.0" y="28.5" width="343" height="195.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QMP-j2-HLn">
|
<imageView contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QMP-j2-HLn">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||||
|
@ -37,9 +37,9 @@
|
||||||
</constraints>
|
</constraints>
|
||||||
</imageView>
|
</imageView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="751" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="gIY-Wp-RSk">
|
<stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="751" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="gIY-Wp-RSk">
|
||||||
<rect key="frame" x="58" y="0.0" width="277" height="165.5"/>
|
<rect key="frame" x="58" y="0.0" width="277" height="195.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="3Sm-P0-ySf">
|
<stackView opaque="NO" contentMode="scaleToFill" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="3Sm-P0-ySf">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="277" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="277" height="20.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" horizontalCompressionResistancePriority="749" verticalCompressionResistancePriority="752" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gll-xe-FSr" customClass="EmojiLabel" customModule="Tusker" customModuleProvider="target">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" horizontalCompressionResistancePriority="749" verticalCompressionResistancePriority="752" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gll-xe-FSr" customClass="EmojiLabel" customModule="Tusker" customModuleProvider="target">
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="249" verticalHuggingPriority="252" horizontalCompressionResistancePriority="748" verticalCompressionResistancePriority="752" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="j89-zc-SFa">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="249" verticalHuggingPriority="252" horizontalCompressionResistancePriority="748" verticalCompressionResistancePriority="752" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="j89-zc-SFa">
|
||||||
<rect key="frame" x="115" y="0.0" width="129.5" height="20.5"/>
|
<rect key="frame" x="111" y="0.0" width="137.5" height="20.5"/>
|
||||||
<accessibility key="accessibilityConfiguration">
|
<accessibility key="accessibilityConfiguration">
|
||||||
<accessibilityTraits key="traits" staticText="YES" notEnabled="YES"/>
|
<accessibilityTraits key="traits" staticText="YES" notEnabled="YES"/>
|
||||||
</accessibility>
|
</accessibility>
|
||||||
|
@ -62,8 +62,8 @@
|
||||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="pin.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="LRh-Cc-1br">
|
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="pin.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="wtt-8G-Ua1">
|
||||||
<rect key="frame" x="248.5" y="-0.5" width="0.0" height="22"/>
|
<rect key="frame" x="250.5" y="-0.5" width="0.0" height="22"/>
|
||||||
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<accessibility key="accessibilityConfiguration" label="Pinned Status"/>
|
<accessibility key="accessibilityConfiguration" label="Pinned Status"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
|
@ -106,34 +106,21 @@
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" delaysContentTouches="NO" editable="NO" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="waJ-f5-LKv" customClass="StatusContentTextView" customModule="Tusker" customModuleProvider="target">
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" delaysContentTouches="NO" editable="NO" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="waJ-f5-LKv" customClass="StatusContentTextView" customModule="Tusker" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="83" width="277" height="82.5"/>
|
<rect key="frame" x="0.0" y="83" width="277" height="86.5"/>
|
||||||
<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>
|
<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" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
</textView>
|
</textView>
|
||||||
</subviews>
|
|
||||||
</stackView>
|
|
||||||
</subviews>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="gIY-Wp-RSk" firstAttribute="leading" secondItem="QMP-j2-HLn" secondAttribute="trailing" constant="8" id="0Tm-v7-Ts4"/>
|
|
||||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="QMP-j2-HLn" secondAttribute="bottom" constant="8" id="2Ao-Gj-fY3"/>
|
|
||||||
<constraint firstItem="QMP-j2-HLn" firstAttribute="top" secondItem="ve3-Y1-NQH" secondAttribute="top" id="PC4-Bi-QXm"/>
|
|
||||||
<constraint firstItem="gIY-Wp-RSk" firstAttribute="top" secondItem="QMP-j2-HLn" secondAttribute="top" id="fEd-wN-kuQ"/>
|
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="gIY-Wp-RSk" secondAttribute="trailing" id="hKk-kO-wFT"/>
|
|
||||||
<constraint firstAttribute="bottom" secondItem="gIY-Wp-RSk" secondAttribute="bottom" id="kRU-Ct-CIg"/>
|
|
||||||
<constraint firstItem="QMP-j2-HLn" firstAttribute="leading" secondItem="ve3-Y1-NQH" secondAttribute="leading" id="zeW-tQ-uJl"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
<view hidden="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="nbq-yr-2mA" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
<view hidden="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="nbq-yr-2mA" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="198" width="343" height="0.0"/>
|
<rect key="frame" x="0.0" y="171.5" width="277" height="156"/>
|
||||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" priority="999" constant="200" id="J42-49-2MU"/>
|
<constraint firstAttribute="width" secondItem="nbq-yr-2mA" secondAttribute="height" multiplier="16:9" id="Rvt-zs-fkd"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" distribution="equalSpacing" alignment="bottom" translatesAutoresizingMaskIntoConstraints="NO" id="Zlb-yt-NTw">
|
<stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" distribution="equalSpacing" alignment="bottom" translatesAutoresizingMaskIntoConstraints="NO" id="Zlb-yt-NTw">
|
||||||
<rect key="frame" x="0.0" y="202" width="343" height="22"/>
|
<rect key="frame" x="0.0" y="173.5" width="277" height="22"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" pointerInteraction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rKF-yF-KIa">
|
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" pointerInteraction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rKF-yF-KIa">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="21" height="22"/>
|
<rect key="frame" x="0.0" y="0.0" width="21" height="22"/>
|
||||||
|
@ -144,7 +131,7 @@
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" pointerInteraction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="x0t-TR-jJ4">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" pointerInteraction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="x0t-TR-jJ4">
|
||||||
<rect key="frame" x="107" y="0.0" width="22" height="22"/>
|
<rect key="frame" x="85" y="0.0" width="22" height="22"/>
|
||||||
<accessibility key="accessibilityConfiguration" label="Favorite"/>
|
<accessibility key="accessibilityConfiguration" label="Favorite"/>
|
||||||
<state key="normal" image="star.fill" catalog="system"/>
|
<state key="normal" image="star.fill" catalog="system"/>
|
||||||
<connections>
|
<connections>
|
||||||
|
@ -152,7 +139,7 @@
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" pointerInteraction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6tW-z8-Qh9">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" pointerInteraction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6tW-z8-Qh9">
|
||||||
<rect key="frame" x="215.5" y="0.0" width="22.5" height="22"/>
|
<rect key="frame" x="171.5" y="0.0" width="22.5" height="22"/>
|
||||||
<accessibility key="accessibilityConfiguration" label="Reblog"/>
|
<accessibility key="accessibilityConfiguration" label="Reblog"/>
|
||||||
<state key="normal" image="repeat" catalog="system"/>
|
<state key="normal" image="repeat" catalog="system"/>
|
||||||
<connections>
|
<connections>
|
||||||
|
@ -160,7 +147,7 @@
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" pointerInteraction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="982-J4-NGl">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" pointerInteraction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="982-J4-NGl">
|
||||||
<rect key="frame" x="324" y="0.0" width="19" height="22"/>
|
<rect key="frame" x="258" y="0.0" width="19" height="22"/>
|
||||||
<accessibility key="accessibilityConfiguration" label="More Actions"/>
|
<accessibility key="accessibilityConfiguration" label="More Actions"/>
|
||||||
<state key="normal" image="ellipsis" catalog="system"/>
|
<state key="normal" image="ellipsis" catalog="system"/>
|
||||||
<connections>
|
<connections>
|
||||||
|
@ -170,9 +157,45 @@
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="oie-wK-IpU">
|
||||||
|
<rect key="frame" x="0.5" y="54" width="49.5" height="22"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="bubble.left.and.bubble.right" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="KdQ-Zn-IhD">
|
||||||
|
<rect key="frame" x="0.0" y="1" width="25.5" height="21.5"/>
|
||||||
|
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<accessibility key="accessibilityConfiguration" label="Is a reply"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="22" id="x0C-Qo-YVA"/>
|
||||||
|
</constraints>
|
||||||
|
<preferredSymbolConfiguration key="preferredSymbolConfiguration" weight="thin"/>
|
||||||
|
</imageView>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="globe" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="LRh-Cc-1br">
|
||||||
|
<rect key="frame" x="30.5" y="1" width="19" height="20"/>
|
||||||
|
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="22" id="3Mk-NN-6fY"/>
|
||||||
|
</constraints>
|
||||||
|
<preferredSymbolConfiguration key="preferredSymbolConfiguration" weight="thin"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="gIY-Wp-RSk" firstAttribute="leading" secondItem="QMP-j2-HLn" secondAttribute="trailing" constant="8" id="0Tm-v7-Ts4"/>
|
||||||
|
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="QMP-j2-HLn" secondAttribute="bottom" constant="8" id="2Ao-Gj-fY3"/>
|
||||||
|
<constraint firstItem="oie-wK-IpU" firstAttribute="top" secondItem="QMP-j2-HLn" secondAttribute="bottom" constant="4" id="7Mp-WS-FhY"/>
|
||||||
|
<constraint firstItem="QMP-j2-HLn" firstAttribute="top" secondItem="ve3-Y1-NQH" secondAttribute="top" id="PC4-Bi-QXm"/>
|
||||||
|
<constraint firstItem="oie-wK-IpU" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="ve3-Y1-NQH" secondAttribute="leading" id="QKi-ny-jOJ"/>
|
||||||
|
<constraint firstItem="gIY-Wp-RSk" firstAttribute="top" secondItem="QMP-j2-HLn" secondAttribute="top" id="fEd-wN-kuQ"/>
|
||||||
|
<constraint firstItem="gIY-Wp-RSk" firstAttribute="leading" secondItem="oie-wK-IpU" secondAttribute="trailing" constant="8" id="fqd-p6-oGe"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="gIY-Wp-RSk" secondAttribute="trailing" id="hKk-kO-wFT"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="gIY-Wp-RSk" secondAttribute="bottom" id="kRU-Ct-CIg"/>
|
||||||
|
<constraint firstItem="QMP-j2-HLn" firstAttribute="leading" secondItem="ve3-Y1-NQH" secondAttribute="leading" id="zeW-tQ-uJl"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="nbq-yr-2mA" firstAttribute="width" secondItem="yNh-ac-v6c" secondAttribute="width" id="JCZ-x5-Xa2"/>
|
|
||||||
<constraint firstItem="Zlb-yt-NTw" firstAttribute="width" secondItem="yNh-ac-v6c" secondAttribute="width" id="wxD-pe-Udd"/>
|
|
||||||
<constraint firstItem="ve3-Y1-NQH" firstAttribute="width" secondItem="yNh-ac-v6c" secondAttribute="width" id="xN6-cs-Tnn"/>
|
<constraint firstItem="ve3-Y1-NQH" firstAttribute="width" secondItem="yNh-ac-v6c" secondAttribute="width" id="xN6-cs-Tnn"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</stackView>
|
</stackView>
|
||||||
|
@ -195,20 +218,24 @@
|
||||||
<outlet property="displayNameLabel" destination="gll-xe-FSr" id="vVS-WM-Wqx"/>
|
<outlet property="displayNameLabel" destination="gll-xe-FSr" id="vVS-WM-Wqx"/>
|
||||||
<outlet property="favoriteButton" destination="x0t-TR-jJ4" id="guV-yz-Lm6"/>
|
<outlet property="favoriteButton" destination="x0t-TR-jJ4" id="guV-yz-Lm6"/>
|
||||||
<outlet property="moreButton" destination="982-J4-NGl" id="Pux-tL-aWe"/>
|
<outlet property="moreButton" destination="982-J4-NGl" id="Pux-tL-aWe"/>
|
||||||
<outlet property="pinImageView" destination="LRh-Cc-1br" id="9jn-0V-PdJ"/>
|
<outlet property="pinImageView" destination="wtt-8G-Ua1" id="mE8-oe-m1l"/>
|
||||||
<outlet property="reblogButton" destination="6tW-z8-Qh9" id="u2t-8D-kOn"/>
|
<outlet property="reblogButton" destination="6tW-z8-Qh9" id="u2t-8D-kOn"/>
|
||||||
<outlet property="reblogLabel" destination="lDH-50-AJZ" id="uJf-Pt-cEP"/>
|
<outlet property="reblogLabel" destination="lDH-50-AJZ" id="uJf-Pt-cEP"/>
|
||||||
<outlet property="replyButton" destination="rKF-yF-KIa" id="rka-q1-o4a"/>
|
<outlet property="replyButton" destination="rKF-yF-KIa" id="rka-q1-o4a"/>
|
||||||
|
<outlet property="replyImageView" destination="KdQ-Zn-IhD" id="jqs-FK-K1N"/>
|
||||||
<outlet property="timestampLabel" destination="35d-EA-ReR" id="Ny2-nV-nqP"/>
|
<outlet property="timestampLabel" destination="35d-EA-ReR" id="Ny2-nV-nqP"/>
|
||||||
<outlet property="usernameLabel" destination="j89-zc-SFa" id="bXX-FZ-fCp"/>
|
<outlet property="usernameLabel" destination="j89-zc-SFa" id="bXX-FZ-fCp"/>
|
||||||
|
<outlet property="visibilityImageView" destination="LRh-Cc-1br" id="pxm-JK-jAz"/>
|
||||||
</connections>
|
</connections>
|
||||||
<point key="canvasLocation" x="29.600000000000001" y="79.160419790104953"/>
|
<point key="canvasLocation" x="29.600000000000001" y="79.160419790104953"/>
|
||||||
</view>
|
</view>
|
||||||
</objects>
|
</objects>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="arrowshape.turn.up.left.fill" catalog="system" width="128" height="106"/>
|
<image name="arrowshape.turn.up.left.fill" catalog="system" width="128" height="106"/>
|
||||||
|
<image name="bubble.left.and.bubble.right" catalog="system" width="128" height="96"/>
|
||||||
<image name="chevron.down" catalog="system" width="128" height="72"/>
|
<image name="chevron.down" catalog="system" width="128" height="72"/>
|
||||||
<image name="ellipsis" catalog="system" width="128" height="37"/>
|
<image name="ellipsis" catalog="system" width="128" height="37"/>
|
||||||
|
<image name="globe" catalog="system" width="128" height="121"/>
|
||||||
<image name="pin.fill" catalog="system" width="119" height="128"/>
|
<image name="pin.fill" catalog="system" width="119" height="128"/>
|
||||||
<image name="repeat" catalog="system" width="128" height="99"/>
|
<image name="repeat" catalog="system" width="128" height="99"/>
|
||||||
<image name="star.fill" catalog="system" width="128" height="116"/>
|
<image name="star.fill" catalog="system" width="128" height="116"/>
|
||||||
|
|
|
@ -11,17 +11,13 @@ import Pachyderm
|
||||||
|
|
||||||
class StatusContentTextView: ContentTextView {
|
class StatusContentTextView: ContentTextView {
|
||||||
|
|
||||||
var statusID: String? {
|
private var statusID: String?
|
||||||
didSet {
|
|
||||||
guard let statusID = statusID else { return }
|
func setTextFrom(status: StatusMO) {
|
||||||
guard let mastodonController = mastodonController,
|
statusID = status.id
|
||||||
let status = mastodonController.persistentContainer.status(for: statusID) else {
|
|
||||||
fatalError("Can't set StatusContentTextView text without cached status for \(statusID)")
|
|
||||||
}
|
|
||||||
setTextFromHtml(status.content)
|
setTextFromHtml(status.content)
|
||||||
setEmojis(status.emojis)
|
setEmojis(status.emojis)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override func getMention(for url: URL, text: String) -> Mention? {
|
override func getMention(for url: URL, text: String) -> Mention? {
|
||||||
let mention: Mention?
|
let mention: Mention?
|
||||||
|
|
Loading…
Reference in New Issue