Compare commits
7 Commits
f89d2c1cca
...
22fe1e8ab1
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 22fe1e8ab1 | |
Shadowfacts | 813d0433d6 | |
Shadowfacts | cd9d64410f | |
Shadowfacts | 2b66f98832 | |
Shadowfacts | 6ebcc162e6 | |
Shadowfacts | 8b7c78e3b1 | |
Shadowfacts | ab8ccbb408 |
|
@ -33,8 +33,13 @@ public struct Endpoint: ExpressibleByStringInterpolation, CustomStringConvertibl
|
||||||
switch $0 {
|
switch $0 {
|
||||||
case .literal(let s):
|
case .literal(let s):
|
||||||
return s
|
return s
|
||||||
|
#if DEBUG
|
||||||
|
case .interpolated(let s):
|
||||||
|
return s
|
||||||
|
#else
|
||||||
case .interpolated(_):
|
case .interpolated(_):
|
||||||
return "<redacted>"
|
return "<redacted>"
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}.joined(separator: "")
|
}.joined(separator: "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,18 +87,15 @@
|
||||||
D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A542263634100095BD04 /* PollOptionCheckboxView.swift */; };
|
D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A542263634100095BD04 /* PollOptionCheckboxView.swift */; };
|
||||||
D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */; };
|
D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */; };
|
||||||
D6262C9A28D01C4B00390C1F /* LoadingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6262C9928D01C4B00390C1F /* LoadingTableViewCell.swift */; };
|
D6262C9A28D01C4B00390C1F /* LoadingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6262C9928D01C4B00390C1F /* LoadingTableViewCell.swift */; };
|
||||||
D626493523BD94CE00612E6E /* CompositionAttachmentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */; };
|
|
||||||
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */; };
|
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */; };
|
||||||
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */; };
|
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */; };
|
||||||
D626493C23C1000300612E6E /* AlbumTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493A23C1000300612E6E /* AlbumTableViewCell.swift */; };
|
D626493C23C1000300612E6E /* AlbumTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493A23C1000300612E6E /* AlbumTableViewCell.swift */; };
|
||||||
D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493B23C1000300612E6E /* AlbumTableViewCell.xib */; };
|
D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493B23C1000300612E6E /* AlbumTableViewCell.xib */; };
|
||||||
D626493F23C101C500612E6E /* AlbumAssetCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */; };
|
|
||||||
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943123A5466600D38C68 /* SelectableTableViewCell.swift */; };
|
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943123A5466600D38C68 /* SelectableTableViewCell.swift */; };
|
||||||
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */; };
|
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */; };
|
||||||
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */; };
|
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */; };
|
||||||
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */; };
|
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */; };
|
||||||
D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944E23A9C99800D38C68 /* EditListAccountsViewController.swift */; };
|
D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944E23A9C99800D38C68 /* EditListAccountsViewController.swift */; };
|
||||||
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6285B5221EA708700FE4B39 /* StatusFormat.swift */; };
|
|
||||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
|
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
|
||||||
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.swift */; };
|
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.swift */; };
|
||||||
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */; };
|
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */; };
|
||||||
|
@ -117,7 +114,6 @@
|
||||||
D63CC7102911F1E4000E19DE /* UIScrollView+Top.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC70F2911F1E4000E19DE /* UIScrollView+Top.swift */; };
|
D63CC7102911F1E4000E19DE /* UIScrollView+Top.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC70F2911F1E4000E19DE /* UIScrollView+Top.swift */; };
|
||||||
D63CC7122911F57C000E19DE /* StatusBarTappableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC7112911F57C000E19DE /* StatusBarTappableViewController.swift */; };
|
D63CC7122911F57C000E19DE /* StatusBarTappableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC7112911F57C000E19DE /* StatusBarTappableViewController.swift */; };
|
||||||
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63D8DF32850FE7A008D95E1 /* ViewTags.swift */; };
|
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63D8DF32850FE7A008D95E1 /* ViewTags.swift */; };
|
||||||
D63F9C6E241D2D85004C03CF /* CompositionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63F9C6D241D2D85004C03CF /* CompositionAttachment.swift */; };
|
|
||||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */ = {isa = PBXBuildFile; fileRef = D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */; };
|
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */ = {isa = PBXBuildFile; fileRef = D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */; };
|
||||||
D6412B0324AFF6A600F5412E /* TabBarScrollableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6412B0224AFF6A600F5412E /* TabBarScrollableViewController.swift */; };
|
D6412B0324AFF6A600F5412E /* TabBarScrollableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6412B0224AFF6A600F5412E /* TabBarScrollableViewController.swift */; };
|
||||||
D6412B0924B0291E00F5412E /* MyProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6412B0824B0291E00F5412E /* MyProfileViewController.swift */; };
|
D6412B0924B0291E00F5412E /* MyProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6412B0824B0291E00F5412E /* MyProfileViewController.swift */; };
|
||||||
|
@ -131,7 +127,6 @@
|
||||||
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9026C80DC600FC57FB /* ToastView.swift */; };
|
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9026C80DC600FC57FB /* ToastView.swift */; };
|
||||||
D64AAE9526C88C5000FC57FB /* ToastableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */; };
|
D64AAE9526C88C5000FC57FB /* ToastableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */; };
|
||||||
D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9626C88DC400FC57FB /* ToastConfiguration.swift */; };
|
D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9626C88DC400FC57FB /* ToastConfiguration.swift */; };
|
||||||
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */; };
|
|
||||||
D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */; };
|
D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */; };
|
||||||
D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */; };
|
D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */; };
|
||||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
||||||
|
@ -252,9 +247,6 @@
|
||||||
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; };
|
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; };
|
||||||
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */; };
|
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */; };
|
||||||
D6B0026E29B5248800C70BE2 /* UserAccounts in Frameworks */ = {isa = PBXBuildFile; productRef = D6B0026D29B5248800C70BE2 /* UserAccounts */; };
|
D6B0026E29B5248800C70BE2 /* UserAccounts in Frameworks */ = {isa = PBXBuildFile; productRef = D6B0026D29B5248800C70BE2 /* UserAccounts */; };
|
||||||
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A123BD2C0600A066FA /* AssetPickerViewController.swift */; };
|
|
||||||
D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */; };
|
|
||||||
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */; };
|
|
||||||
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */; };
|
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */; };
|
||||||
D6B053AC23BD2F1400A066FA /* AssetCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */; };
|
D6B053AC23BD2F1400A066FA /* AssetCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */; };
|
||||||
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B17254254F88B800128392 /* OppositeCollapseKeywordsView.swift */; };
|
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B17254254F88B800128392 /* OppositeCollapseKeywordsView.swift */; };
|
||||||
|
@ -481,18 +473,15 @@
|
||||||
D623A542263634100095BD04 /* PollOptionCheckboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionCheckboxView.swift; sourceTree = "<group>"; };
|
D623A542263634100095BD04 /* PollOptionCheckboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionCheckboxView.swift; sourceTree = "<group>"; };
|
||||||
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableTableViewCell.swift; sourceTree = "<group>"; };
|
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6262C9928D01C4B00390C1F /* LoadingTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingTableViewCell.swift; sourceTree = "<group>"; };
|
D6262C9928D01C4B00390C1F /* LoadingTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentData.swift; sourceTree = "<group>"; };
|
|
||||||
D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = "<group>"; };
|
D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AllPhotosTableViewCell.xib; sourceTree = "<group>"; };
|
D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AllPhotosTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
D626493A23C1000300612E6E /* AlbumTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumTableViewCell.swift; sourceTree = "<group>"; };
|
D626493A23C1000300612E6E /* AlbumTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D626493B23C1000300612E6E /* AlbumTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumTableViewCell.xib; sourceTree = "<group>"; };
|
D626493B23C1000300612E6E /* AlbumTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumAssetCollectionViewController.swift; sourceTree = "<group>"; };
|
|
||||||
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableTableViewCell.swift; sourceTree = "<group>"; };
|
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigableTableViewCell.swift; sourceTree = "<group>"; };
|
D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigableTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BasicTableViewCell.xib; sourceTree = "<group>"; };
|
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BasicTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTimelineViewController.swift; sourceTree = "<group>"; };
|
D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTimelineViewController.swift; sourceTree = "<group>"; };
|
||||||
D627944E23A9C99800D38C68 /* EditListAccountsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditListAccountsViewController.swift; sourceTree = "<group>"; };
|
D627944E23A9C99800D38C68 /* EditListAccountsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditListAccountsViewController.swift; sourceTree = "<group>"; };
|
||||||
D6285B5221EA708700FE4B39 /* StatusFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFormat.swift; sourceTree = "<group>"; };
|
|
||||||
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; };
|
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; };
|
||||||
D62D2421217AA7E1005076CC /* UserActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityManager.swift; sourceTree = "<group>"; };
|
D62D2421217AA7E1005076CC /* UserActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityManager.swift; sourceTree = "<group>"; };
|
||||||
D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserActivity+Extensions.swift"; sourceTree = "<group>"; };
|
D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserActivity+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -510,7 +499,6 @@
|
||||||
D63CC70F2911F1E4000E19DE /* UIScrollView+Top.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Top.swift"; sourceTree = "<group>"; };
|
D63CC70F2911F1E4000E19DE /* UIScrollView+Top.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Top.swift"; sourceTree = "<group>"; };
|
||||||
D63CC7112911F57C000E19DE /* StatusBarTappableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarTappableViewController.swift; sourceTree = "<group>"; };
|
D63CC7112911F57C000E19DE /* StatusBarTappableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarTappableViewController.swift; sourceTree = "<group>"; };
|
||||||
D63D8DF32850FE7A008D95E1 /* ViewTags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTags.swift; sourceTree = "<group>"; };
|
D63D8DF32850FE7A008D95E1 /* ViewTags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTags.swift; sourceTree = "<group>"; };
|
||||||
D63F9C6D241D2D85004C03CF /* CompositionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachment.swift; sourceTree = "<group>"; };
|
|
||||||
D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = DomainBlocks.plist; sourceTree = "<group>"; };
|
D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = DomainBlocks.plist; sourceTree = "<group>"; };
|
||||||
D6412B0224AFF6A600F5412E /* TabBarScrollableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarScrollableViewController.swift; sourceTree = "<group>"; };
|
D6412B0224AFF6A600F5412E /* TabBarScrollableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarScrollableViewController.swift; sourceTree = "<group>"; };
|
||||||
D6412B0824B0291E00F5412E /* MyProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyProfileViewController.swift; sourceTree = "<group>"; };
|
D6412B0824B0291E00F5412E /* MyProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyProfileViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -524,7 +512,6 @@
|
||||||
D64AAE9026C80DC600FC57FB /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
|
D64AAE9026C80DC600FC57FB /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
|
||||||
D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastableViewController.swift; sourceTree = "<group>"; };
|
D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastableViewController.swift; sourceTree = "<group>"; };
|
||||||
D64AAE9626C88DC400FC57FB /* ToastConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastConfiguration.swift; sourceTree = "<group>"; };
|
D64AAE9626C88DC400FC57FB /* ToastConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastConfiguration.swift; sourceTree = "<group>"; };
|
||||||
D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPreviewViewController.swift; sourceTree = "<group>"; };
|
|
||||||
D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowRequestNotificationTableViewCell.swift; sourceTree = "<group>"; };
|
D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowRequestNotificationTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowRequestNotificationTableViewCell.xib; sourceTree = "<group>"; };
|
D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowRequestNotificationTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
|
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -648,9 +635,6 @@
|
||||||
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivity+Types.swift"; sourceTree = "<group>"; };
|
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivity+Types.swift"; sourceTree = "<group>"; };
|
||||||
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInSafariActivity.swift; sourceTree = "<group>"; };
|
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInSafariActivity.swift; sourceTree = "<group>"; };
|
||||||
D6B0026C29B5245400C70BE2 /* UserAccounts */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = UserAccounts; path = Packages/UserAccounts; sourceTree = "<group>"; };
|
D6B0026C29B5245400C70BE2 /* UserAccounts */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = UserAccounts; path = Packages/UserAccounts; sourceTree = "<group>"; };
|
||||||
D6B053A123BD2C0600A066FA /* AssetPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPickerViewController.swift; sourceTree = "<group>"; };
|
|
||||||
D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionsListViewController.swift; sourceTree = "<group>"; };
|
|
||||||
D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewController.swift; sourceTree = "<group>"; };
|
|
||||||
D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AssetCollectionViewCell.xib; sourceTree = "<group>"; };
|
D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AssetCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||||
D6B17254254F88B800128392 /* OppositeCollapseKeywordsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OppositeCollapseKeywordsView.swift; sourceTree = "<group>"; };
|
D6B17254254F88B800128392 /* OppositeCollapseKeywordsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OppositeCollapseKeywordsView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -836,9 +820,6 @@
|
||||||
D61959D2241E846D00A37B8E /* Models */ = {
|
D61959D2241E846D00A37B8E /* Models */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D6285B5221EA708700FE4B39 /* StatusFormat.swift */,
|
|
||||||
D63F9C6D241D2D85004C03CF /* CompositionAttachment.swift */,
|
|
||||||
D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */,
|
|
||||||
D61F75AE293AF50C00C0B37F /* EditedFilter.swift */,
|
D61F75AE293AF50C00C0B37F /* EditedFilter.swift */,
|
||||||
D65B4B532971F71D00DABDFB /* EditedReport.swift */,
|
D65B4B532971F71D00DABDFB /* EditedReport.swift */,
|
||||||
D600891A29848289005B4D00 /* PinnedTimeline.swift */,
|
D600891A29848289005B4D00 /* PinnedTimeline.swift */,
|
||||||
|
@ -989,14 +970,13 @@
|
||||||
children = (
|
children = (
|
||||||
D65B4B89297879DE00DABDFB /* Account Follows */,
|
D65B4B89297879DE00DABDFB /* Account Follows */,
|
||||||
D6A3BC822321F69400FD64D5 /* Account List */,
|
D6A3BC822321F69400FD64D5 /* Account List */,
|
||||||
D6B053A023BD2BED00A066FA /* Asset Picker */,
|
|
||||||
0411610522B457290030A9B7 /* Attachment Gallery */,
|
0411610522B457290030A9B7 /* Attachment Gallery */,
|
||||||
D641C787213DD862004B4513 /* Compose */,
|
D641C787213DD862004B4513 /* Compose */,
|
||||||
D641C785213DD83B004B4513 /* Conversation */,
|
D641C785213DD83B004B4513 /* Conversation */,
|
||||||
D6F2E960249E772F005846BB /* Crash Reporter */,
|
D6F2E960249E772F005846BB /* Crash Reporter */,
|
||||||
|
D61F759729384D4200C0B37F /* Customize Timelines */,
|
||||||
D627943C23A5635D00D38C68 /* Explore */,
|
D627943C23A5635D00D38C68 /* Explore */,
|
||||||
D6A4DCC92553666600D9DE31 /* Fast Account Switcher */,
|
D6A4DCC92553666600D9DE31 /* Fast Account Switcher */,
|
||||||
D61F759729384D4200C0B37F /* Customize Timelines */,
|
|
||||||
D641C788213DD86D004B4513 /* Large Image */,
|
D641C788213DD86D004B4513 /* Large Image */,
|
||||||
D627944B23A9A02400D38C68 /* Lists */,
|
D627944B23A9A02400D38C68 /* Lists */,
|
||||||
D627944823A6AD5100D38C68 /* Local Predicate Statuses List */,
|
D627944823A6AD5100D38C68 /* Local Predicate Statuses List */,
|
||||||
|
@ -1362,18 +1342,6 @@
|
||||||
path = Activities;
|
path = Activities;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D6B053A023BD2BED00A066FA /* Asset Picker */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D6B053A123BD2C0600A066FA /* AssetPickerViewController.swift */,
|
|
||||||
D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */,
|
|
||||||
D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */,
|
|
||||||
D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */,
|
|
||||||
D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */,
|
|
||||||
);
|
|
||||||
path = "Asset Picker";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D6BC9DD8232D8BCA002CA326 /* Search */ = {
|
D6BC9DD8232D8BCA002CA326 /* Search */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1910,9 +1878,7 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D626493523BD94CE00612E6E /* CompositionAttachmentData.swift in Sources */,
|
|
||||||
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */,
|
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */,
|
||||||
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */,
|
|
||||||
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */,
|
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */,
|
||||||
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
||||||
D6C3F4F5298ED0890009FCFF /* LocalPredicateStatusesViewController.swift in Sources */,
|
D6C3F4F5298ED0890009FCFF /* LocalPredicateStatusesViewController.swift in Sources */,
|
||||||
|
@ -2002,7 +1968,6 @@
|
||||||
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameLabel.swift in Sources */,
|
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameLabel.swift in Sources */,
|
||||||
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */,
|
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */,
|
||||||
D61DC84B28F4FD2000B82C6E /* ProfileHeaderCollectionViewCell.swift in Sources */,
|
D61DC84B28F4FD2000B82C6E /* ProfileHeaderCollectionViewCell.swift in Sources */,
|
||||||
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */,
|
|
||||||
D61A45E828DF477D002BE511 /* LoadingCollectionViewCell.swift in Sources */,
|
D61A45E828DF477D002BE511 /* LoadingCollectionViewCell.swift in Sources */,
|
||||||
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */,
|
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */,
|
||||||
D68A76EC295369A8001DA1B3 /* AboutView.swift in Sources */,
|
D68A76EC295369A8001DA1B3 /* AboutView.swift in Sources */,
|
||||||
|
@ -2040,7 +2005,6 @@
|
||||||
D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */,
|
D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */,
|
||||||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
||||||
D65B4B5E2973040D00DABDFB /* ReportAddStatusView.swift in Sources */,
|
D65B4B5E2973040D00DABDFB /* ReportAddStatusView.swift in Sources */,
|
||||||
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */,
|
|
||||||
D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */,
|
D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */,
|
||||||
D6CA8CDE296387310050C433 /* SaveToPhotosActivity.swift in Sources */,
|
D6CA8CDE296387310050C433 /* SaveToPhotosActivity.swift in Sources */,
|
||||||
D6B936712829F72900237D0E /* NSManagedObjectContext+Helpers.swift in Sources */,
|
D6B936712829F72900237D0E /* NSManagedObjectContext+Helpers.swift in Sources */,
|
||||||
|
@ -2052,7 +2016,6 @@
|
||||||
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */,
|
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */,
|
||||||
D65B4B6429771EFF00DABDFB /* ConversationViewController.swift in Sources */,
|
D65B4B6429771EFF00DABDFB /* ConversationViewController.swift in Sources */,
|
||||||
D68E6F59253C9969001A1B4C /* MultiSourceEmojiLabel.swift in Sources */,
|
D68E6F59253C9969001A1B4C /* MultiSourceEmojiLabel.swift in Sources */,
|
||||||
D63F9C6E241D2D85004C03CF /* CompositionAttachment.swift in Sources */,
|
|
||||||
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */,
|
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */,
|
||||||
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */,
|
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */,
|
||||||
D6F0B17524A3A1AA001E48C3 /* MainSidebarViewController.swift in Sources */,
|
D6F0B17524A3A1AA001E48C3 /* MainSidebarViewController.swift in Sources */,
|
||||||
|
@ -2078,7 +2041,6 @@
|
||||||
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */,
|
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */,
|
||||||
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */,
|
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */,
|
||||||
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */,
|
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */,
|
||||||
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */,
|
|
||||||
D6F6A550291F058600F496A8 /* CreateListService.swift in Sources */,
|
D6F6A550291F058600F496A8 /* CreateListService.swift in Sources */,
|
||||||
D601FA5B29787AB100A8E8B5 /* AccountFollowsListViewController.swift in Sources */,
|
D601FA5B29787AB100A8E8B5 /* AccountFollowsListViewController.swift in Sources */,
|
||||||
D61ABEFE28F1C92600B29151 /* FavoriteService.swift in Sources */,
|
D61ABEFE28F1C92600B29151 /* FavoriteService.swift in Sources */,
|
||||||
|
@ -2176,7 +2138,6 @@
|
||||||
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 */,
|
D681E4D9246E346E0053414F /* AccountActivityItemSource.swift in Sources */,
|
||||||
D626493F23C101C500612E6E /* AlbumAssetCollectionViewController.swift in Sources */,
|
|
||||||
D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */,
|
D68015422401A74600D6103B /* MediaPrefsView.swift in Sources */,
|
||||||
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */,
|
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */,
|
||||||
D6E77D09286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift in Sources */,
|
D6E77D09286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift in Sources */,
|
||||||
|
@ -2192,7 +2153,6 @@
|
||||||
D6D12B56292D57E800D528E1 /* AccountCollectionViewCell.swift in Sources */,
|
D6D12B56292D57E800D528E1 /* AccountCollectionViewCell.swift in Sources */,
|
||||||
D60088F22980DAA0005B4D00 /* TipJarView.swift in Sources */,
|
D60088F22980DAA0005B4D00 /* TipJarView.swift in Sources */,
|
||||||
D6C82B4125C5BB7E0017F1E6 /* ExploreViewController.swift in Sources */,
|
D6C82B4125C5BB7E0017F1E6 /* ExploreViewController.swift in Sources */,
|
||||||
D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */,
|
|
||||||
D6A3A380295515550036B6EF /* ProfileHeaderButton.swift in Sources */,
|
D6A3A380295515550036B6EF /* ProfileHeaderButton.swift in Sources */,
|
||||||
D65B4B562971F98300DABDFB /* ReportView.swift in Sources */,
|
D65B4B562971F98300DABDFB /* ReportView.swift in Sources */,
|
||||||
D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */,
|
D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */,
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
//
|
|
||||||
// CompositionAttachment.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 3/14/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import UniformTypeIdentifiers
|
|
||||||
|
|
||||||
final class CompositionAttachment: NSObject, Codable, ObservableObject {
|
|
||||||
static let typeIdentifier = "space.vaccor.Tusker.composition-attachment"
|
|
||||||
|
|
||||||
let id: UUID
|
|
||||||
@Published var data: CompositionAttachmentData
|
|
||||||
@Published var attachmentDescription: String
|
|
||||||
|
|
||||||
init(data: CompositionAttachmentData, description: String = "") {
|
|
||||||
self.id = UUID()
|
|
||||||
self.data = data
|
|
||||||
self.attachmentDescription = description
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
|
|
||||||
self.id = try container.decode(UUID.self, forKey: .id)
|
|
||||||
self.data = try container.decode(CompositionAttachmentData.self, forKey: .data)
|
|
||||||
self.attachmentDescription = try container.decode(String.self, forKey: .attachmentDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(to encoder: Encoder) throws {
|
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
||||||
|
|
||||||
try container.encode(id, forKey: .id)
|
|
||||||
try container.encode(data, forKey: .data)
|
|
||||||
try container.encode(attachmentDescription, forKey: .attachmentDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func ==(lhs: CompositionAttachment, rhs: CompositionAttachment) -> Bool {
|
|
||||||
return lhs.id == rhs.id
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case id
|
|
||||||
case data
|
|
||||||
case attachmentDescription
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CompositionAttachment: Identifiable {}
|
|
||||||
|
|
||||||
private let imageType = UTType.image.identifier
|
|
||||||
private let mp4Type = UTType.mpeg4Movie.identifier
|
|
||||||
private let quickTimeType = UTType.quickTimeMovie.identifier
|
|
||||||
private let dataType = UTType.data.identifier
|
|
||||||
private let gifType = UTType.gif.identifier
|
|
||||||
|
|
||||||
extension CompositionAttachment: NSItemProviderWriting {
|
|
||||||
static var writableTypeIdentifiersForItemProvider: [String] {
|
|
||||||
[typeIdentifier]
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
|
|
||||||
if typeIdentifier == CompositionAttachment.typeIdentifier {
|
|
||||||
do {
|
|
||||||
completionHandler(try PropertyListEncoder().encode(self), nil)
|
|
||||||
} catch {
|
|
||||||
completionHandler(nil, error)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
completionHandler(nil, ItemProviderError.incompatibleTypeIdentifier)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ItemProviderError: Error {
|
|
||||||
case incompatibleTypeIdentifier
|
|
||||||
|
|
||||||
var localizedDescription: String {
|
|
||||||
switch self {
|
|
||||||
case .incompatibleTypeIdentifier:
|
|
||||||
return "Cannot provide data for given type"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CompositionAttachment: NSItemProviderReading {
|
|
||||||
static var readableTypeIdentifiersForItemProvider: [String] {
|
|
||||||
// todo: is there a better way of handling movies than manually adding all possible UTI types?
|
|
||||||
// just using kUTTypeMovie doesn't work, because we need the actually type in order to get the file extension
|
|
||||||
// without the file extension, getting the thumbnail and exporting the video for attachment upload fails
|
|
||||||
[typeIdentifier] + UIImage.readableTypeIdentifiersForItemProvider + [mp4Type, quickTimeType] + NSURL.readableTypeIdentifiersForItemProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> CompositionAttachment {
|
|
||||||
if typeIdentifier == CompositionAttachment.typeIdentifier {
|
|
||||||
return try PropertyListDecoder().decode(CompositionAttachment.self, from: data)
|
|
||||||
} else if typeIdentifier == gifType {
|
|
||||||
return CompositionAttachment(data: .gif(data))
|
|
||||||
} else if UIImage.readableTypeIdentifiersForItemProvider.contains(typeIdentifier), let image = try? UIImage.object(withItemProviderData: data, typeIdentifier: typeIdentifier) {
|
|
||||||
return CompositionAttachment(data: .image(image))
|
|
||||||
} else if let type = UTType(typeIdentifier), type == .mpeg4Movie || type == .quickTimeMovie {
|
|
||||||
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
|
||||||
let temporaryFileName = ProcessInfo().globallyUniqueString
|
|
||||||
let fileExt = type.preferredFilenameExtension!
|
|
||||||
let temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(temporaryFileName).appendingPathExtension(fileExt)
|
|
||||||
try data.write(to: temporaryFileURL)
|
|
||||||
return CompositionAttachment(data: .video(temporaryFileURL))
|
|
||||||
} else if NSURL.readableTypeIdentifiersForItemProvider.contains(typeIdentifier), let url = try? NSURL.object(withItemProviderData: data, typeIdentifier: typeIdentifier) as URL {
|
|
||||||
return CompositionAttachment(data: .video(url))
|
|
||||||
} else {
|
|
||||||
throw ItemProviderError.incompatibleTypeIdentifier
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,259 +0,0 @@
|
||||||
//
|
|
||||||
// CompositionAttachmentData.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 1/1/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Photos
|
|
||||||
import UniformTypeIdentifiers
|
|
||||||
import PencilKit
|
|
||||||
import InstanceFeatures
|
|
||||||
|
|
||||||
enum CompositionAttachmentData {
|
|
||||||
case asset(PHAsset)
|
|
||||||
case image(UIImage)
|
|
||||||
case video(URL)
|
|
||||||
case drawing(PKDrawing)
|
|
||||||
case gif(Data)
|
|
||||||
|
|
||||||
var type: AttachmentType {
|
|
||||||
switch self {
|
|
||||||
case let .asset(asset):
|
|
||||||
return asset.attachmentType!
|
|
||||||
case .image(_):
|
|
||||||
return .image
|
|
||||||
case .video(_):
|
|
||||||
return .video
|
|
||||||
case .drawing(_):
|
|
||||||
return .image
|
|
||||||
case .gif(_):
|
|
||||||
return .image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isAsset: Bool {
|
|
||||||
switch self {
|
|
||||||
case .asset(_):
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var canSaveToDraft: Bool {
|
|
||||||
switch self {
|
|
||||||
case .video(_):
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getData(features: InstanceFeatures, skipAllConversion: Bool = false, completion: @escaping (Result<(Data, UTType), Error>) -> Void) {
|
|
||||||
switch self {
|
|
||||||
case let .image(image):
|
|
||||||
// Export as JPEG instead of PNG, otherweise photos straight from the camera are too large
|
|
||||||
// for Mastodon in its default configuration (max of 10MB).
|
|
||||||
// The quality of 0.8 was chosen completely arbitrarily, it may need to be tuned in the future.
|
|
||||||
completion(.success((image.jpegData(compressionQuality: 0.8)!, .jpeg)))
|
|
||||||
case let .asset(asset):
|
|
||||||
if asset.mediaType == .image {
|
|
||||||
let options = PHImageRequestOptions()
|
|
||||||
options.version = .current
|
|
||||||
options.deliveryMode = .highQualityFormat
|
|
||||||
options.resizeMode = .none
|
|
||||||
options.isNetworkAccessAllowed = true
|
|
||||||
PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { (data, dataUTI, orientation, info) in
|
|
||||||
guard var data = data, let dataUTI = dataUTI else {
|
|
||||||
completion(.failure(.missingData))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard !skipAllConversion else {
|
|
||||||
completion(.success((data, UTType(dataUTI)!)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let utType: UTType
|
|
||||||
let image = CIImage(data: data)!
|
|
||||||
let needsColorSpaceConversion = features.needsWideColorGamutHack && image.colorSpace?.name != CGColorSpace.sRGB
|
|
||||||
|
|
||||||
// neither Mastodon nor Pleroma handles HEIC well, so convert to JPEG
|
|
||||||
// they also do a bad job converting wide color gamut images (they seem to just drop the profile, letting the wide-gamut values be reinterprete as sRGB)
|
|
||||||
// if that changes in the future, we'll need to pass the InstanceFeatures in here somehow and gate the conversion
|
|
||||||
if needsColorSpaceConversion || dataUTI == "public.heic" {
|
|
||||||
let context = CIContext()
|
|
||||||
let colorSpace = needsColorSpaceConversion || image.colorSpace != nil ? CGColorSpace(name: CGColorSpace.sRGB)! : image.colorSpace!
|
|
||||||
if dataUTI == "public.png" {
|
|
||||||
data = context.pngRepresentation(of: image, format: .ARGB8, colorSpace: colorSpace)!
|
|
||||||
utType = .png
|
|
||||||
} else {
|
|
||||||
data = context.jpegRepresentation(of: image, colorSpace: colorSpace)!
|
|
||||||
utType = .jpeg
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
utType = UTType(dataUTI)!
|
|
||||||
}
|
|
||||||
|
|
||||||
completion(.success((data, utType)))
|
|
||||||
}
|
|
||||||
} else if asset.mediaType == .video {
|
|
||||||
let options = PHVideoRequestOptions()
|
|
||||||
options.deliveryMode = .automatic
|
|
||||||
options.isNetworkAccessAllowed = true
|
|
||||||
options.version = .current
|
|
||||||
PHImageManager.default().requestExportSession(forVideo: asset, options: options, exportPreset: AVAssetExportPresetHighestQuality) { (exportSession, info) in
|
|
||||||
if let exportSession = exportSession {
|
|
||||||
CompositionAttachmentData.exportVideoData(session: exportSession, completion: completion)
|
|
||||||
} else if let error = info?[PHImageErrorKey] as? Error {
|
|
||||||
completion(.failure(.videoExport(error)))
|
|
||||||
} else {
|
|
||||||
completion(.failure(.noVideoExportSession))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fatalError("assetType must be either image or video")
|
|
||||||
}
|
|
||||||
case let .video(url):
|
|
||||||
let asset = AVURLAsset(url: url)
|
|
||||||
guard let session = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {
|
|
||||||
completion(.failure(.noVideoExportSession))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
CompositionAttachmentData.exportVideoData(session: session, completion: completion)
|
|
||||||
|
|
||||||
case let .drawing(drawing):
|
|
||||||
let image = drawing.imageInLightMode(from: drawing.bounds, scale: 1)
|
|
||||||
completion(.success((image.pngData()!, .png)))
|
|
||||||
case let .gif(data):
|
|
||||||
completion(.success((data, .gif)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func exportVideoData(session: AVAssetExportSession, completion: @escaping (Result<(Data, UTType), Error>) -> Void) {
|
|
||||||
session.outputFileType = .mp4
|
|
||||||
session.outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("exported_video_\(UUID())").appendingPathExtension("mp4")
|
|
||||||
session.exportAsynchronously {
|
|
||||||
guard session.status == .completed else {
|
|
||||||
completion(.failure(.videoExport(session.error!)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
let data = try Data(contentsOf: session.outputURL!)
|
|
||||||
completion(.success((data, .mpeg4Movie)))
|
|
||||||
} catch {
|
|
||||||
completion(.failure(.videoExport(error)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AttachmentType {
|
|
||||||
case image, video
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Error: Swift.Error, LocalizedError {
|
|
||||||
case missingData
|
|
||||||
case videoExport(Swift.Error)
|
|
||||||
case noVideoExportSession
|
|
||||||
|
|
||||||
var localizedDescription: String {
|
|
||||||
switch self {
|
|
||||||
case .missingData:
|
|
||||||
return "Missing Data"
|
|
||||||
case .videoExport(let error):
|
|
||||||
return "Exporting video: \(error)"
|
|
||||||
case .noVideoExportSession:
|
|
||||||
return "Couldn't create video export session"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PHAsset {
|
|
||||||
var attachmentType: CompositionAttachmentData.AttachmentType? {
|
|
||||||
switch self.mediaType {
|
|
||||||
case .image:
|
|
||||||
return .image
|
|
||||||
case .video:
|
|
||||||
return .video
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CompositionAttachmentData: Codable {
|
|
||||||
func encode(to encoder: Encoder) throws {
|
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
||||||
|
|
||||||
switch self {
|
|
||||||
case let .asset(asset):
|
|
||||||
try container.encode("asset", forKey: .type)
|
|
||||||
try container.encode(asset.localIdentifier, forKey: .assetIdentifier)
|
|
||||||
case let .image(image):
|
|
||||||
try container.encode("image", forKey: .type)
|
|
||||||
try container.encode(image.pngData()!, forKey: .imageData)
|
|
||||||
case .video(_):
|
|
||||||
throw EncodingError.invalidValue(self, EncodingError.Context(codingPath: [], debugDescription: "video CompositionAttachments cannot be encoded"))
|
|
||||||
case let .drawing(drawing):
|
|
||||||
try container.encode("drawing", forKey: .type)
|
|
||||||
let drawingData = drawing.dataRepresentation()
|
|
||||||
try container.encode(drawingData, forKey: .drawing)
|
|
||||||
case .gif(_):
|
|
||||||
throw EncodingError.invalidValue(self, EncodingError.Context(codingPath: [], debugDescription: "gif CompositionAttachments cannot be encoded"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
|
|
||||||
switch try container.decode(String.self, forKey: .type) {
|
|
||||||
case "asset":
|
|
||||||
let identifier = try container.decode(String.self, forKey: .assetIdentifier)
|
|
||||||
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil).firstObject else {
|
|
||||||
throw DecodingError.dataCorruptedError(forKey: .assetIdentifier, in: container, debugDescription: "Could not fetch asset with local identifier")
|
|
||||||
}
|
|
||||||
self = .asset(asset)
|
|
||||||
case "image":
|
|
||||||
guard let image = UIImage(data: try container.decode(Data.self, forKey: .imageData)) else {
|
|
||||||
throw DecodingError.dataCorruptedError(forKey: .imageData, in: container, debugDescription: "Could not decode UIImage from image data")
|
|
||||||
}
|
|
||||||
self = .image(image)
|
|
||||||
case "drawing":
|
|
||||||
let drawingData = try container.decode(Data.self, forKey: .drawing)
|
|
||||||
let drawing = try PKDrawing(data: drawingData)
|
|
||||||
self = .drawing(drawing)
|
|
||||||
default:
|
|
||||||
throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "CompositionAttachment type must be one of image, asset, or drawing")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CodingKeys: CodingKey {
|
|
||||||
case type
|
|
||||||
case imageData
|
|
||||||
/// The local identifier of the PHAsset for this attachment
|
|
||||||
case assetIdentifier
|
|
||||||
/// The PKDrawing object for this attachment.
|
|
||||||
case drawing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CompositionAttachmentData: Equatable {
|
|
||||||
static func ==(lhs: CompositionAttachmentData, rhs: CompositionAttachmentData) -> Bool {
|
|
||||||
switch (lhs, rhs) {
|
|
||||||
case let (.asset(a), .asset(b)):
|
|
||||||
return a.localIdentifier == b.localIdentifier
|
|
||||||
case let (.image(a), .image(b)):
|
|
||||||
return a == b
|
|
||||||
case let (.video(a), .video(b)):
|
|
||||||
return a == b
|
|
||||||
case let (.drawing(a), .drawing(b)):
|
|
||||||
return a == b
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
//
|
|
||||||
// StatusFormat.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 1/12/19.
|
|
||||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Pachyderm
|
|
||||||
|
|
||||||
enum StatusFormat: Int, CaseIterable {
|
|
||||||
case bold, italics, strikethrough, code
|
|
||||||
|
|
||||||
var insertionResult: FormatInsertionResult? {
|
|
||||||
switch Preferences.shared.statusContentType {
|
|
||||||
case .plain:
|
|
||||||
return nil
|
|
||||||
case .markdown:
|
|
||||||
return Markdown.format(self)
|
|
||||||
case .html:
|
|
||||||
return HTML.format(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var imageName: String? {
|
|
||||||
switch self {
|
|
||||||
case .italics:
|
|
||||||
return "italic"
|
|
||||||
case .bold:
|
|
||||||
return "bold"
|
|
||||||
case .strikethrough:
|
|
||||||
return "strikethrough"
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var title: (String, [NSAttributedString.Key: Any])? {
|
|
||||||
if self == .code {
|
|
||||||
return ("</>", [.font: UIFont(name: "Menlo", size: 17)!])
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var accessibilityLabel: String {
|
|
||||||
switch self {
|
|
||||||
case .italics:
|
|
||||||
return NSLocalizedString("Italics", comment: "italics text format accessibility label")
|
|
||||||
case .bold:
|
|
||||||
return NSLocalizedString("Bold", comment: "bold text format accessibility label")
|
|
||||||
case .strikethrough:
|
|
||||||
return NSLocalizedString("Strikethrough", comment: "strikethrough text format accessibility label")
|
|
||||||
case .code:
|
|
||||||
return NSLocalizedString("Code", comment: "code text format accessibility label")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias FormatInsertionResult = (prefix: String, suffix: String, insertionPoint: Int)
|
|
||||||
|
|
||||||
protocol FormatType {
|
|
||||||
static func format(_ format: StatusFormat) -> FormatInsertionResult
|
|
||||||
}
|
|
||||||
|
|
||||||
extension StatusFormat {
|
|
||||||
struct Markdown: FormatType {
|
|
||||||
static var formats: [StatusFormat: String] = [
|
|
||||||
.italics: "_",
|
|
||||||
.bold: "**",
|
|
||||||
.strikethrough: "~~",
|
|
||||||
.code: "`"
|
|
||||||
]
|
|
||||||
|
|
||||||
static func format(_ format: StatusFormat) -> FormatInsertionResult {
|
|
||||||
let str = formats[format]!
|
|
||||||
return (str, str, str.count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HTML: FormatType {
|
|
||||||
static var tags: [StatusFormat: String] = [
|
|
||||||
.italics: "em",
|
|
||||||
.bold: "strong",
|
|
||||||
.strikethrough: "del",
|
|
||||||
.code: "code"
|
|
||||||
]
|
|
||||||
|
|
||||||
static func format(_ format: StatusFormat) -> FormatInsertionResult {
|
|
||||||
let tag = tags[format]!
|
|
||||||
return ("<\(tag)>", "</\(tag)>", tag.count + 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
//
|
|
||||||
// AlbumAssetCollectionViewController.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 1/4/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Photos
|
|
||||||
|
|
||||||
class AlbumAssetCollectionViewController: AssetCollectionViewController {
|
|
||||||
|
|
||||||
let collection: PHAssetCollection
|
|
||||||
|
|
||||||
init(collection: PHAssetCollection) {
|
|
||||||
self.collection = collection
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override func fetchAssets(with options: PHFetchOptions) -> PHFetchResult<PHAsset> {
|
|
||||||
return PHAsset.fetchAssets(in: collection, options: options)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,269 +0,0 @@
|
||||||
//
|
|
||||||
// AssetCollectionViewController.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 1/1/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Photos
|
|
||||||
|
|
||||||
private let reuseIdentifier = "assetCell"
|
|
||||||
private let cameraReuseIdentifier = "showCameraCell"
|
|
||||||
|
|
||||||
protocol AssetCollectionViewControllerDelegate: AnyObject {
|
|
||||||
func shouldSelectAsset(_ asset: PHAsset) -> Bool
|
|
||||||
func didSelectAssets(_ assets: [PHAsset])
|
|
||||||
func captureFromCamera()
|
|
||||||
}
|
|
||||||
|
|
||||||
class AssetCollectionViewController: UIViewController, UICollectionViewDelegate {
|
|
||||||
|
|
||||||
weak var delegate: AssetCollectionViewControllerDelegate?
|
|
||||||
|
|
||||||
private var collectionView: UICollectionView!
|
|
||||||
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
|
||||||
|
|
||||||
private var thumbnailSize: CGSize!
|
|
||||||
|
|
||||||
private let imageManager = PHCachingImageManager()
|
|
||||||
private var fetchResult: PHFetchResult<PHAsset>!
|
|
||||||
|
|
||||||
var selectedAssets: [PHAsset] {
|
|
||||||
return collectionView.indexPathsForSelectedItems?.compactMap { (indexPath) in
|
|
||||||
guard case let .asset(asset) = dataSource.itemIdentifier(for: indexPath) else { return nil }
|
|
||||||
return asset
|
|
||||||
} ?? []
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .fractionalWidth(1/3))
|
|
||||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
|
||||||
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(1/3))
|
|
||||||
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 3)
|
|
||||||
group.interItemSpacing = .fixed(4)
|
|
||||||
let section = NSCollectionLayoutSection(group: group)
|
|
||||||
let layout = UICollectionViewCompositionalLayout(section: section)
|
|
||||||
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
|
|
||||||
collectionView.delegate = self
|
|
||||||
view.addSubview(collectionView)
|
|
||||||
|
|
||||||
// use the safe area layout guide instead of letting it automatically use the safe area insets
|
|
||||||
// because otherwise, when presented in a popover with the arrow on the left or right side,
|
|
||||||
// the collection view content will be cut off by the width of the arrow because the popover
|
|
||||||
// doesn't respect safe area insets
|
|
||||||
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
view.safeAreaLayoutGuide.leadingAnchor.constraint(equalTo: collectionView.leadingAnchor),
|
|
||||||
view.safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: collectionView.trailingAnchor),
|
|
||||||
// top ignores safe area because when presented in the sheet container, it simplifies the top content offset
|
|
||||||
view.topAnchor.constraint(equalTo: collectionView.topAnchor),
|
|
||||||
// bottom ignores safe area because we want cells to underflow bottom of the screen on notched iPhones
|
|
||||||
view.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor),
|
|
||||||
])
|
|
||||||
view.backgroundColor = .appBackground
|
|
||||||
collectionView.backgroundColor = .appBackground
|
|
||||||
|
|
||||||
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed))
|
|
||||||
|
|
||||||
collectionView.alwaysBounceVertical = true
|
|
||||||
collectionView.allowsMultipleSelection = true
|
|
||||||
collectionView.allowsSelection = true
|
|
||||||
collectionView.allowsFocus = true
|
|
||||||
|
|
||||||
collectionView.register(UINib(nibName: "AssetCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: reuseIdentifier)
|
|
||||||
|
|
||||||
let controlCell = UICollectionView.CellRegistration<AssetPickerControlCollectionViewCell, Item> { cell, indexPath, itemIdentifier in
|
|
||||||
switch itemIdentifier {
|
|
||||||
case .showCamera:
|
|
||||||
cell.imageView.image = UIImage(systemName: "camera")
|
|
||||||
cell.label.text = "Take a Photo"
|
|
||||||
case .changeLimitedSelection:
|
|
||||||
cell.imageView.image = UIImage(systemName: "photo.on.rectangle.angled")
|
|
||||||
cell.label.text = "Select More Photos"
|
|
||||||
case .asset(_):
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView, cellProvider: { (collectionView, indexPath, item) -> UICollectionViewCell? in
|
|
||||||
switch item {
|
|
||||||
case .showCamera, .changeLimitedSelection:
|
|
||||||
return collectionView.dequeueConfiguredReusableCell(using: controlCell, for: indexPath, item: item)
|
|
||||||
case let .asset(asset):
|
|
||||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! AssetCollectionViewCell
|
|
||||||
|
|
||||||
cell.updateUI(asset: asset)
|
|
||||||
self.imageManager.requestImage(for: asset, targetSize: self.thumbnailSize, contentMode: .aspectFill, options: nil) { (image, _) in
|
|
||||||
guard let image = image else { return }
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
guard cell.assetIdentifier == asset.localIdentifier else { return }
|
|
||||||
cell.thumbnailImage = image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
updateItemsSelectedCount()
|
|
||||||
|
|
||||||
if let singleFingerPanGesture = collectionView.gestureRecognizers?.first(where: {
|
|
||||||
$0.name == "multi-select.singleFingerPanGesture"
|
|
||||||
}),
|
|
||||||
let interactivePopGesture = navigationController?.interactivePopGestureRecognizer {
|
|
||||||
singleFingerPanGesture.require(toFail: interactivePopGesture)
|
|
||||||
}
|
|
||||||
|
|
||||||
PHPhotoLibrary.shared().register(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
|
||||||
super.viewWillAppear(animated)
|
|
||||||
|
|
||||||
let scale = UIScreen.main.scale
|
|
||||||
let cellWidth = view.bounds.width / 3
|
|
||||||
thumbnailSize = CGSize(width: cellWidth * scale, height: cellWidth * scale)
|
|
||||||
|
|
||||||
loadAssets()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func loadAssets() {
|
|
||||||
var items = [Item.showCamera]
|
|
||||||
|
|
||||||
switch PHPhotoLibrary.authorizationStatus(for: .readWrite) {
|
|
||||||
case .notDetermined:
|
|
||||||
PHPhotoLibrary.requestAuthorization(for: .readWrite) { (_) in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.loadAssets()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
|
|
||||||
case .restricted, .denied:
|
|
||||||
// todo: better UI for this
|
|
||||||
return
|
|
||||||
|
|
||||||
case .authorized:
|
|
||||||
break
|
|
||||||
|
|
||||||
case .limited:
|
|
||||||
items.append(.changeLimitedSelection)
|
|
||||||
break
|
|
||||||
|
|
||||||
@unknown default:
|
|
||||||
// who knows, just try anyways
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
let options = PHFetchOptions()
|
|
||||||
options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
|
|
||||||
fetchResult = fetchAssets(with: options)
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
|
||||||
snapshot.appendSections([.assets])
|
|
||||||
fetchResult.enumerateObjects { (asset, _, _) in
|
|
||||||
items.append(.asset(asset))
|
|
||||||
}
|
|
||||||
snapshot.appendItems(items)
|
|
||||||
dataSource.apply(snapshot, animatingDifferences: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
open func fetchAssets(with options: PHFetchOptions) -> PHFetchResult<PHAsset> {
|
|
||||||
return PHAsset.fetchAssets(with: options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateItemsSelectedCount() {
|
|
||||||
let selected = collectionView.indexPathsForSelectedItems?.count ?? 0
|
|
||||||
|
|
||||||
navigationItem.title = "\(selected) selected"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: UICollectionViewDelegate
|
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
|
||||||
guard let item = dataSource.itemIdentifier(for: indexPath) else { return false }
|
|
||||||
if let delegate = delegate,
|
|
||||||
case let .asset(asset) = item {
|
|
||||||
return delegate.shouldSelectAsset(asset)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
||||||
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
|
|
||||||
switch item {
|
|
||||||
case .showCamera:
|
|
||||||
collectionView.deselectItem(at: indexPath, animated: false)
|
|
||||||
delegate?.captureFromCamera()
|
|
||||||
case .changeLimitedSelection:
|
|
||||||
// todo: change observer
|
|
||||||
PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: self)
|
|
||||||
case .asset(_):
|
|
||||||
updateItemsSelectedCount()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
|
|
||||||
updateItemsSelectedCount()
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
|
||||||
guard case let .asset(asset) = dataSource.itemIdentifier(for: indexPath) else { return nil }
|
|
||||||
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: { () -> UIViewController? in
|
|
||||||
return AssetPreviewViewController(asset: asset)
|
|
||||||
}, actionProvider: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
|
|
||||||
if let indexPath = (configuration.identifier as? NSIndexPath) as IndexPath?,
|
|
||||||
let cell = collectionView.cellForItem(at: indexPath) as? AssetCollectionViewCell {
|
|
||||||
let parameters = UIPreviewParameters()
|
|
||||||
parameters.backgroundColor = .black
|
|
||||||
return UITargetedPreview(view: cell.imageView, parameters: parameters)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Interaction
|
|
||||||
|
|
||||||
@objc func donePressed() {
|
|
||||||
delegate?.didSelectAssets(selectedAssets)
|
|
||||||
dismiss(animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AssetCollectionViewController {
|
|
||||||
enum Section: Hashable {
|
|
||||||
case assets
|
|
||||||
}
|
|
||||||
enum Item: Hashable {
|
|
||||||
case showCamera
|
|
||||||
case changeLimitedSelection
|
|
||||||
case asset(PHAsset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AssetCollectionViewController: PHPhotoLibraryChangeObserver {
|
|
||||||
func photoLibraryDidChange(_ changeInstance: PHChange) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.loadAssets()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,158 +0,0 @@
|
||||||
//
|
|
||||||
// AssetCollectionsListViewController.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 1/1/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Photos
|
|
||||||
|
|
||||||
class AssetCollectionsListViewController: UITableViewController {
|
|
||||||
|
|
||||||
weak var assetCollectionDelegate: AssetCollectionViewControllerDelegate?
|
|
||||||
|
|
||||||
var dataSource: DataSource!
|
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init(style: .plain)
|
|
||||||
|
|
||||||
title = NSLocalizedString("Collections", comment: "asset collections list title")
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelPressed))
|
|
||||||
|
|
||||||
tableView.register(UINib(nibName: "AllPhotosTableViewCell", bundle: .main), forCellReuseIdentifier: "allPhotosCell")
|
|
||||||
tableView.register(UINib(nibName: "AlbumTableViewCell", bundle: .main), forCellReuseIdentifier: "albumCell")
|
|
||||||
|
|
||||||
tableView.allowsFocus = true
|
|
||||||
tableView.backgroundColor = .appGroupedBackground
|
|
||||||
|
|
||||||
dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in
|
|
||||||
switch item {
|
|
||||||
case .cameraRoll:
|
|
||||||
return tableView.dequeueReusableCell(withIdentifier: "allPhotosCell", for: indexPath)
|
|
||||||
|
|
||||||
case let .album(collection):
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "albumCell", for: indexPath) as! AlbumTableViewCell
|
|
||||||
cell.updateUI(album: collection)
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
|
||||||
snapshot.appendSections([.system, .albums, .sharedAlbums, .smartAlbums])
|
|
||||||
snapshot.appendItems([.cameraRoll], toSection: .system)
|
|
||||||
|
|
||||||
let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .any, options: nil)
|
|
||||||
var smartAlbumItems = [Item]()
|
|
||||||
smartAlbums.enumerateObjects { (collection, _, _) in
|
|
||||||
guard collection.assetCollectionSubtype != .smartAlbumAllHidden else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
smartAlbumItems.append(.album(collection))
|
|
||||||
}
|
|
||||||
// sort these manually, using PHFetchOptions.sortDescriptors seems like it just doesn't work with fetchAssetCollections
|
|
||||||
smartAlbumItems.sort(by: { $0.title < $1.title })
|
|
||||||
snapshot.appendItems(smartAlbumItems, toSection: .smartAlbums)
|
|
||||||
|
|
||||||
let albums = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: nil)
|
|
||||||
var albumItems = [Item]()
|
|
||||||
var sharedItems = [Item]()
|
|
||||||
albums.enumerateObjects { (collection, _, _) in
|
|
||||||
if collection.estimatedAssetCount > 0 {
|
|
||||||
if collection.assetCollectionSubtype == .albumCloudShared {
|
|
||||||
sharedItems.append(.album(collection))
|
|
||||||
} else {
|
|
||||||
albumItems.append(.album(collection))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
albumItems.sort(by: { $0.title < $1.title })
|
|
||||||
sharedItems.sort(by: { $0.title < $1.title })
|
|
||||||
snapshot.appendItems(albumItems, toSection: .albums)
|
|
||||||
snapshot.appendItems(sharedItems, toSection: .sharedAlbums)
|
|
||||||
|
|
||||||
dataSource.apply(snapshot, animatingDifferences: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Table view delegate
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
||||||
switch dataSource.itemIdentifier(for: indexPath) {
|
|
||||||
case nil:
|
|
||||||
return
|
|
||||||
|
|
||||||
case .cameraRoll:
|
|
||||||
let assetCollection = AssetCollectionViewController()
|
|
||||||
assetCollection.delegate = assetCollectionDelegate
|
|
||||||
show(assetCollection, sender: self)
|
|
||||||
|
|
||||||
case let .album(collection):
|
|
||||||
let assetCollection = AlbumAssetCollectionViewController(collection: collection)
|
|
||||||
assetCollection.delegate = assetCollectionDelegate
|
|
||||||
show(assetCollection, sender: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Interaction
|
|
||||||
|
|
||||||
@objc func cancelPressed() {
|
|
||||||
dismiss(animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AssetCollectionsListViewController {
|
|
||||||
enum Section {
|
|
||||||
case system
|
|
||||||
case albums
|
|
||||||
case sharedAlbums
|
|
||||||
case smartAlbums
|
|
||||||
}
|
|
||||||
enum Item: Hashable {
|
|
||||||
case cameraRoll
|
|
||||||
case album(PHAssetCollection)
|
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
|
||||||
switch self {
|
|
||||||
case .cameraRoll:
|
|
||||||
hasher.combine("cameraRoll")
|
|
||||||
case let .album(collection):
|
|
||||||
hasher.combine("album")
|
|
||||||
hasher.combine(collection.localIdentifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var title: String {
|
|
||||||
switch self {
|
|
||||||
case .cameraRoll:
|
|
||||||
return "All Photos"
|
|
||||||
case .album(let collection):
|
|
||||||
return collection.localizedTitle ?? ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class DataSource: UITableViewDiffableDataSource<Section, Item> {
|
|
||||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
|
||||||
switch sectionIdentifier(for: section) {
|
|
||||||
case .albums:
|
|
||||||
return NSLocalizedString("Albums", comment: "albums section title")
|
|
||||||
case .sharedAlbums:
|
|
||||||
return NSLocalizedString("Shared Albums", comment: "shared albums section title")
|
|
||||||
case .smartAlbums:
|
|
||||||
return NSLocalizedString("Smart Albums", comment: "smart albums section title")
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
//
|
|
||||||
// AssetPickerViewController.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 1/1/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Photos
|
|
||||||
|
|
||||||
protocol AssetPickerViewControllerDelegate: AnyObject {
|
|
||||||
func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachmentData.AttachmentType) -> Bool
|
|
||||||
func assetPicker(_ assetPicker: AssetPickerViewController, didSelectAttachments attachments: [CompositionAttachmentData])
|
|
||||||
}
|
|
||||||
|
|
||||||
class AssetPickerViewController: UINavigationController {
|
|
||||||
|
|
||||||
weak var assetPickerDelegate: AssetPickerViewControllerDelegate?
|
|
||||||
|
|
||||||
var currentCollectionSelectedAssets: [CompositionAttachmentData] {
|
|
||||||
if let vc = visibleViewController as? AssetCollectionViewController {
|
|
||||||
return vc.selectedAssets.map { .asset($0) }
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init(navigationBarClass: nil, toolbarClass: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
let assetCollectionsList = AssetCollectionsListViewController()
|
|
||||||
assetCollectionsList.assetCollectionDelegate = self
|
|
||||||
let assetCollection = AssetCollectionViewController()
|
|
||||||
assetCollection.delegate = self
|
|
||||||
setViewControllers([assetCollectionsList, assetCollection], animated: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func presentImagePicker(animated: Bool) {
|
|
||||||
let imagePicker = UIImagePickerController()
|
|
||||||
imagePicker.delegate = self
|
|
||||||
imagePicker.sourceType = .camera
|
|
||||||
imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .camera)!
|
|
||||||
self.present(imagePicker, animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AssetPickerViewController: AssetCollectionViewControllerDelegate {
|
|
||||||
func shouldSelectAsset(_ asset: PHAsset) -> Bool {
|
|
||||||
guard let delegate = assetPickerDelegate else { return true }
|
|
||||||
guard let type = asset.attachmentType else { return false }
|
|
||||||
return delegate.assetPicker(self, shouldAllowAssetOfType: type)
|
|
||||||
}
|
|
||||||
func didSelectAssets(_ assets: [PHAsset]) {
|
|
||||||
assetPickerDelegate?.assetPicker(self, didSelectAttachments: assets.map { .asset($0) })
|
|
||||||
}
|
|
||||||
func captureFromCamera() {
|
|
||||||
presentImagePicker(animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AssetPickerViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
|
||||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
|
||||||
let attachment: CompositionAttachmentData
|
|
||||||
if let image = info[.originalImage] as? UIImage {
|
|
||||||
attachment = .image(image)
|
|
||||||
} else if let url = info[.mediaURL] as? URL {
|
|
||||||
attachment = .video(url)
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if assetPickerDelegate?.assetPicker(self, shouldAllowAssetOfType: attachment.type) ?? true {
|
|
||||||
assetPickerDelegate?.assetPicker(self, didSelectAttachments: [attachment])
|
|
||||||
// dismiss image picker
|
|
||||||
dismiss(animated: true) {
|
|
||||||
// dismiss asset picker
|
|
||||||
self.dismiss(animated: true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dismiss(animated: false) {
|
|
||||||
self.presentImagePicker(animated: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
//
|
|
||||||
// AssetPreviewViewController.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 1/4/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Photos
|
|
||||||
import PhotosUI
|
|
||||||
import AVKit
|
|
||||||
|
|
||||||
class AssetPreviewViewController: UIViewController {
|
|
||||||
|
|
||||||
let asset: PHAsset
|
|
||||||
|
|
||||||
init(asset: PHAsset) {
|
|
||||||
self.asset = asset
|
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
view.backgroundColor = .black
|
|
||||||
|
|
||||||
switch asset.mediaType {
|
|
||||||
case .image:
|
|
||||||
if asset.mediaSubtypes.contains(.photoLive) {
|
|
||||||
showLivePhoto(asset)
|
|
||||||
} else {
|
|
||||||
showAssetImage(asset)
|
|
||||||
}
|
|
||||||
case .video:
|
|
||||||
showAssetVideo(asset)
|
|
||||||
default:
|
|
||||||
fatalError("asset mediaType must be image or video")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showImage(_ image: UIImage) {
|
|
||||||
let imageView = UIImageView(image: image)
|
|
||||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
imageView.contentMode = .scaleAspectFit
|
|
||||||
view.addSubview(imageView)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
||||||
imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
||||||
imageView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
||||||
imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
|
||||||
])
|
|
||||||
preferredContentSize = image.size
|
|
||||||
}
|
|
||||||
|
|
||||||
func showAssetImage(_ asset: PHAsset) {
|
|
||||||
let options = PHImageRequestOptions()
|
|
||||||
options.version = .current
|
|
||||||
options.deliveryMode = .opportunistic
|
|
||||||
options.resizeMode = .none
|
|
||||||
options.isNetworkAccessAllowed = true
|
|
||||||
PHImageManager.default().requestImage(for: asset, targetSize: view.bounds.size, contentMode: .aspectFit, options: options) { (image, _) in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.showImage(image!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showLivePhoto(_ asset: PHAsset) {
|
|
||||||
let options = PHLivePhotoRequestOptions()
|
|
||||||
options.deliveryMode = .opportunistic
|
|
||||||
options.version = .current
|
|
||||||
options.isNetworkAccessAllowed = true
|
|
||||||
PHImageManager.default().requestLivePhoto(for: asset, targetSize: view.bounds.size, contentMode: .aspectFit, options: options) { (livePhoto, _) in
|
|
||||||
guard let livePhoto = livePhoto else {
|
|
||||||
fatalError("failed to get live photo")
|
|
||||||
}
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let livePhotoView = PHLivePhotoView()
|
|
||||||
livePhotoView.livePhoto = livePhoto
|
|
||||||
livePhotoView.isMuted = true
|
|
||||||
livePhotoView.startPlayback(with: .full)
|
|
||||||
livePhotoView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
livePhotoView.contentMode = .scaleAspectFit
|
|
||||||
self.view.addSubview(livePhotoView)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
livePhotoView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
|
|
||||||
livePhotoView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
|
|
||||||
livePhotoView.topAnchor.constraint(equalTo: self.view.topAnchor),
|
|
||||||
livePhotoView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
|
|
||||||
])
|
|
||||||
self.preferredContentSize = livePhoto.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showVideo(asset: AVAsset) {
|
|
||||||
let playerController = AVPlayerViewController()
|
|
||||||
let item = AVPlayerItem(asset: asset)
|
|
||||||
let player = AVPlayer(playerItem: item)
|
|
||||||
player.isMuted = true
|
|
||||||
player.play()
|
|
||||||
playerController.player = player
|
|
||||||
self.embedChild(playerController)
|
|
||||||
self.preferredContentSize = item.presentationSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func showAssetVideo(_ asset: PHAsset) {
|
|
||||||
let options = PHVideoRequestOptions()
|
|
||||||
options.deliveryMode = .automatic
|
|
||||||
options.isNetworkAccessAllowed = true
|
|
||||||
options.version = .current
|
|
||||||
PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { (avAsset, _, _) in
|
|
||||||
guard let avAsset = avAsset else {
|
|
||||||
fatalError("failed to get AVAsset")
|
|
||||||
}
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.showVideo(asset: avAsset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -32,6 +32,7 @@ struct AddHashtagPinnedTimelineView: View {
|
||||||
@State private var searchTask: Task<Void, Never>?
|
@State private var searchTask: Task<Void, Never>?
|
||||||
@State private var isSearching = false
|
@State private var isSearching = false
|
||||||
@State private var searchResults: [String] = []
|
@State private var searchResults: [String] = []
|
||||||
|
@State private var trendingTags: [String] = []
|
||||||
|
|
||||||
private var savedAndFollowedHashtags: [String] {
|
private var savedAndFollowedHashtags: [String] {
|
||||||
var tags = Set<String>()
|
var tags = Set<String>()
|
||||||
|
@ -42,17 +43,19 @@ struct AddHashtagPinnedTimelineView: View {
|
||||||
for followed in mastodonController.followedHashtags {
|
for followed in mastodonController.followedHashtags {
|
||||||
tags.insert(followed.name)
|
tags.insert(followed.name)
|
||||||
}
|
}
|
||||||
return Array(tags).sorted(using: SemiCaseSensitiveComparator())
|
return tags.sorted(using: SemiCaseSensitiveComparator())
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
list
|
list
|
||||||
.listStyle(.grouped)
|
|
||||||
.appGroupedListBackground(container: AddHashtagPinnedTimelineRepresentable.UIViewControllerType.self)
|
.appGroupedListBackground(container: AddHashtagPinnedTimelineRepresentable.UIViewControllerType.self)
|
||||||
|
.listStyle(.grouped)
|
||||||
.navigationTitle("Add Hashtag")
|
.navigationTitle("Add Hashtag")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.searchable(text: $viewModel.searchQuery, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Search for hashtags"))
|
.searchable(text: $viewModel.searchQuery, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Search for hashtags"))
|
||||||
|
.autocorrectionDisabled()
|
||||||
|
.textInputAutocapitalization(.never)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
Button("Cancel") {
|
Button("Cancel") {
|
||||||
|
@ -71,35 +74,45 @@ struct AddHashtagPinnedTimelineView: View {
|
||||||
try? await updateSearchResults()
|
try? await updateSearchResults()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.task {
|
||||||
|
await fetchTrendingTags()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var list: some View {
|
private var list: some View {
|
||||||
let list = List {
|
List {
|
||||||
Section {
|
if !viewModel.searchQuery.isEmpty {
|
||||||
if viewModel.searchQuery.isEmpty {
|
Section {
|
||||||
forEachTag(savedAndFollowedHashtags)
|
|
||||||
} else {
|
|
||||||
forEachTag(searchResults)
|
|
||||||
}
|
|
||||||
} header: {
|
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.progressViewStyle(.circular)
|
.progressViewStyle(.circular)
|
||||||
.opacity(isSearching ? 1 : 0)
|
.opacity(isSearching ? 1 : 0)
|
||||||
|
.animation(.linear(duration: 0.1), value: isSearching)
|
||||||
.frame(maxWidth: .infinity, alignment: .center)
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
.listRowBackground(EmptyView())
|
.listRowBackground(EmptyView())
|
||||||
.listRowSeparator(.hidden)
|
.listRowSeparator(.hidden)
|
||||||
}
|
|
||||||
.appGroupedListRowBackground()
|
|
||||||
}
|
|
||||||
.listStyle(.grouped)
|
|
||||||
|
|
||||||
if #available(iOS 16.0, *) {
|
forEachTag(searchResults)
|
||||||
list
|
}
|
||||||
.scrollContentBackground(.hidden)
|
.appGroupedListRowBackground()
|
||||||
.background(Color.appGroupedBackground)
|
}
|
||||||
} else {
|
|
||||||
list
|
if !savedAndFollowedHashtags.isEmpty {
|
||||||
|
Section {
|
||||||
|
forEachTag(savedAndFollowedHashtags)
|
||||||
|
} header: {
|
||||||
|
Text("Saved and Followed Hashtags")
|
||||||
|
}
|
||||||
|
.appGroupedListRowBackground()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !trendingTags.isEmpty {
|
||||||
|
Section {
|
||||||
|
forEachTag(trendingTags)
|
||||||
|
} header: {
|
||||||
|
Text("Trending Hashtags")
|
||||||
|
}
|
||||||
|
.appGroupedListRowBackground()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +139,14 @@ struct AddHashtagPinnedTimelineView: View {
|
||||||
searchResults = results.hashtags.map(\.name)
|
searchResults = results.hashtags.map(\.name)
|
||||||
isSearching = false
|
isSearching = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func fetchTrendingTags() async {
|
||||||
|
guard mastodonController.instanceFeatures.trends else { return }
|
||||||
|
let req = Client.getTrendingHashtags()
|
||||||
|
if let (results, _) = try? await mastodonController.run(req) {
|
||||||
|
trendingTags = results.map(\.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SearchViewModel: ObservableObject {
|
private class SearchViewModel: ObservableObject {
|
||||||
|
|
|
@ -89,7 +89,7 @@ struct CustomizeTimelinesList: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listStyle(.insetGrouped)
|
.listStyle(.insetGrouped)
|
||||||
.appGroupedListBackground(container: UIHostingController<CustomizeTimelinesList>.self)
|
.appGroupedListBackground(container: UIHostingController<CustomizeTimelinesView>.self)
|
||||||
.navigationTitle(Text("Customize Timelines"))
|
.navigationTitle(Text("Customize Timelines"))
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|
|
@ -165,16 +165,21 @@ class NotificationsTableViewController: DiffableTimelineLikeTableViewController<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func validateNotifications(_ notifications: [Pachyderm.Notification]) {
|
private func validateNotifications(_ notifications: [Pachyderm.Notification]) -> [Pachyderm.Notification] {
|
||||||
for notif in notifications where notif.status == nil && (notif.kind == .mention || notif.kind == .reblog || notif.kind == .favourite) {
|
return notifications.compactMap { notif in
|
||||||
let crumb = Breadcrumb(level: .fatal, category: "notifications")
|
if notif.status == nil && (notif.kind == .mention || notif.kind == .reblog || notif.kind == .favourite) {
|
||||||
crumb.data = [
|
let crumb = Breadcrumb(level: .fatal, category: "notifications")
|
||||||
"id": notif.id,
|
crumb.data = [
|
||||||
"type": notif.kind.rawValue,
|
"id": notif.id,
|
||||||
"created_at": notif.createdAt.formatted(.iso8601),
|
"type": notif.kind.rawValue,
|
||||||
"account": notif.account.id,
|
"created_at": notif.createdAt.formatted(.iso8601),
|
||||||
]
|
"account": notif.account.id,
|
||||||
SentrySDK.addBreadcrumb(crumb)
|
]
|
||||||
|
SentrySDK.addBreadcrumb(crumb)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return notif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +190,7 @@ class NotificationsTableViewController: DiffableTimelineLikeTableViewController<
|
||||||
completion(.failure(.client(error)))
|
completion(.failure(.client(error)))
|
||||||
|
|
||||||
case let .success(notifications, _):
|
case let .success(notifications, _):
|
||||||
self.validateNotifications(notifications)
|
let notifications = self.validateNotifications(notifications)
|
||||||
let groups = NotificationGroup.createGroups(notifications: notifications, only: self.groupTypes)
|
let groups = NotificationGroup.createGroups(notifications: notifications, only: self.groupTypes)
|
||||||
|
|
||||||
if !notifications.isEmpty {
|
if !notifications.isEmpty {
|
||||||
|
@ -215,7 +220,7 @@ class NotificationsTableViewController: DiffableTimelineLikeTableViewController<
|
||||||
completion(.failure(.client(error)))
|
completion(.failure(.client(error)))
|
||||||
|
|
||||||
case let .success(newNotifications, _):
|
case let .success(newNotifications, _):
|
||||||
self.validateNotifications(newNotifications)
|
let newNotifications = self.validateNotifications(newNotifications)
|
||||||
if !newNotifications.isEmpty {
|
if !newNotifications.isEmpty {
|
||||||
self.older = .before(id: newNotifications.last!.id, count: nil)
|
self.older = .before(id: newNotifications.last!.id, count: nil)
|
||||||
}
|
}
|
||||||
|
@ -246,7 +251,7 @@ class NotificationsTableViewController: DiffableTimelineLikeTableViewController<
|
||||||
completion(.failure(.client(error)))
|
completion(.failure(.client(error)))
|
||||||
|
|
||||||
case let .success(newNotifications, _):
|
case let .success(newNotifications, _):
|
||||||
self.validateNotifications(newNotifications)
|
let newNotifications = self.validateNotifications(newNotifications)
|
||||||
guard !newNotifications.isEmpty else {
|
guard !newNotifications.isEmpty else {
|
||||||
completion(.failure(.allCaughtUp))
|
completion(.failure(.allCaughtUp))
|
||||||
return
|
return
|
||||||
|
|
|
@ -45,7 +45,9 @@ struct AboutView: View {
|
||||||
.appGroupedListRowBackground()
|
.appGroupedListRowBackground()
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Link("Website", destination: URL(string: "https://vaccor.space/tusker")!)
|
Link(destination: URL(string: "https://vaccor.space/tusker")!) {
|
||||||
|
Label("Website", systemImage: "safari")
|
||||||
|
}
|
||||||
Button {
|
Button {
|
||||||
if MFMailComposeViewController.canSendMail() {
|
if MFMailComposeViewController.canSendMail() {
|
||||||
Task {
|
Task {
|
||||||
|
@ -56,7 +58,7 @@ struct AboutView: View {
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Get Support")
|
Label("Get Support", systemImage: "envelope")
|
||||||
Spacer()
|
Spacer()
|
||||||
if isGettingLogData {
|
if isGettingLogData {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
|
@ -65,9 +67,14 @@ struct AboutView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disabled(isGettingLogData)
|
.disabled(isGettingLogData)
|
||||||
Link("Source Code", destination: URL(string: "https://git.shadowfacts.net/shadowfacts/Tusker")!)
|
Link(destination: URL(string: "https://git.shadowfacts.net/shadowfacts/Tusker")!) {
|
||||||
Link("Issue Tracker", destination: URL(string: "https://git.shadowfacts.net/shadowfacts/Tusker/issues")!)
|
Label("Source Code", systemImage: "curlybraces")
|
||||||
|
}
|
||||||
|
Link(destination: URL(string: "https://git.shadowfacts.net/shadowfacts/Tusker/issues")!) {
|
||||||
|
Label("Issue Tracker", systemImage: "checklist")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.labelStyle(AboutLinksLabelStyle())
|
||||||
.appGroupedListRowBackground()
|
.appGroupedListRowBackground()
|
||||||
}
|
}
|
||||||
.listStyle(.insetGrouped)
|
.listStyle(.insetGrouped)
|
||||||
|
@ -166,6 +173,15 @@ private struct MailSheet: UIViewControllerRepresentable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct AboutLinksLabelStyle: LabelStyle {
|
||||||
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
|
HStack(alignment: .lastTextBaseline, spacing: 8) {
|
||||||
|
configuration.icon
|
||||||
|
configuration.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct AboutView_Previews: PreviewProvider {
|
struct AboutView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
AboutView()
|
AboutView()
|
||||||
|
|
|
@ -18,6 +18,10 @@ class ProfileNoContentCollectionViewCell: UICollectionViewListCell {
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
var config = UIBackgroundConfiguration.listPlainCell()
|
||||||
|
config.backgroundColor = .appBackground
|
||||||
|
backgroundConfiguration = config
|
||||||
|
|
||||||
let title = UILabel()
|
let title = UILabel()
|
||||||
title.text = "There's nothing here"
|
title.text = "There's nothing here"
|
||||||
title.adjustsFontForContentSizeCategory = true
|
title.adjustsFontForContentSizeCategory = true
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import Sentry
|
import Sentry
|
||||||
|
import OSLog
|
||||||
|
|
||||||
struct ToastConfiguration {
|
struct ToastConfiguration {
|
||||||
var systemImageName: String?
|
var systemImageName: String?
|
||||||
|
@ -89,6 +90,8 @@ fileprivate extension Pachyderm.Client.Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let toastErrorLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ToastError")
|
||||||
|
|
||||||
private func captureError(_ error: Client.Error, title: String) {
|
private func captureError(_ error: Client.Error, title: String) {
|
||||||
let event = Event(error: error)
|
let event = Event(error: error)
|
||||||
event.message = SentryMessage(formatted: "\(title): \(error)")
|
event.message = SentryMessage(formatted: "\(title): \(error)")
|
||||||
|
@ -123,4 +126,6 @@ private func captureError(_ error: Client.Error, title: String) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
SentrySDK.capture(event: event)
|
SentrySDK.capture(event: event)
|
||||||
|
|
||||||
|
toastErrorLogger.error("\(title, privacy: .public): \(error), \(event.tags!.debugDescription, privacy: .public)")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue