Compare commits
7 Commits
cc10a13785
...
5b70c713b2
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 5b70c713b2 | |
Shadowfacts | efb96eddf3 | |
Shadowfacts | 5cb25c8c1f | |
Shadowfacts | 700cc2c67c | |
Shadowfacts | a9e0bffe5f | |
Shadowfacts | 512e0e9053 | |
Shadowfacts | b842389449 |
|
@ -39,3 +39,9 @@ extension Emoji: CustomDebugStringConvertible {
|
|||
return ":\(shortcode):"
|
||||
}
|
||||
}
|
||||
|
||||
extension Emoji: Equatable {
|
||||
public static func ==(lhs: Emoji, rhs: Emoji) -> Bool {
|
||||
return lhs.shortcode == rhs.shortcode && lhs.url == rhs.url
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
||||
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */; };
|
||||
D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */; };
|
||||
D6093FB125BE0B01004811E6 /* TrendingHashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */; };
|
||||
D6093FB725BE0CF3004811E6 /* TrendHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */; };
|
||||
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D60CFFDA24A290BA00D00083 /* SwiftSoup */; };
|
||||
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
|
||||
|
@ -221,13 +219,11 @@
|
|||
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4423216AF800E5038B /* FollowAccountActivity.swift */; };
|
||||
D6AEBB4823216B1D00E5038B /* AccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4723216B1D00E5038B /* AccountActivity.swift */; };
|
||||
D6AEBB4A23216F0400E5038B /* UnfollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4923216F0400E5038B /* UnfollowAccountActivity.swift */; };
|
||||
D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */ = {isa = PBXBuildFile; productRef = D6B0539E23BD2BA300A066FA /* SheetController */; };
|
||||
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 */; };
|
||||
D6B053AC23BD2F1400A066FA /* AssetCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */; };
|
||||
D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053AD23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift */; };
|
||||
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B17254254F88B800128392 /* OppositeCollapseKeywordsView.swift */; };
|
||||
D6B22A0F2560D52D004D82EF /* TabbedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B22A0E2560D52D004D82EF /* TabbedPageViewController.swift */; };
|
||||
D6B30E09254BAF63009CAEE5 /* ImageGrayscalifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */; };
|
||||
|
@ -282,7 +278,6 @@
|
|||
D6DFC69E242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DFC69D242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift */; };
|
||||
D6DFC6A0242C4CCC00ACC392 /* WeakArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */; };
|
||||
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0DC8D216EDF1E00369478 /* Previewing.swift */; };
|
||||
D6E1EEF4285443EF00D20549 /* UIAction+Subtitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E1EEF3285443EF00D20549 /* UIAction+Subtitle.swift */; };
|
||||
D6E343AB265AAD6B00C4AA01 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AA265AAD6B00C4AA01 /* Media.xcassets */; };
|
||||
D6E343AD265AAD6B00C4AA01 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */; };
|
||||
D6E343B0265AAD6B00C4AA01 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AE265AAD6B00C4AA01 /* MainInterface.storyboard */; };
|
||||
|
@ -293,6 +288,10 @@
|
|||
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */; };
|
||||
D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */; };
|
||||
D6E57FA325C26FAB00341037 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = D6E57FA525C26FAB00341037 /* Localizable.stringsdict */; };
|
||||
D6E77D09286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E77D08286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift */; };
|
||||
D6E77D0B286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E77D0A286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift */; };
|
||||
D6E77D0D286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6E77D0C286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib */; };
|
||||
D6E77D0F286F773900D8B732 /* SplitNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E77D0E286F773900D8B732 /* SplitNavigationController.swift */; };
|
||||
D6E9CDA8281A427800BBC98E /* PostService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E9CDA7281A427800BBC98E /* PostService.swift */; };
|
||||
D6EAE0DB2550CC8A002DB0AC /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */; };
|
||||
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */; };
|
||||
|
@ -367,8 +366,6 @@
|
|||
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
||||
D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagSearchResultsViewController.swift; sourceTree = "<group>"; };
|
||||
D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrendingHashtagTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendHistoryView.swift; sourceTree = "<group>"; };
|
||||
D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D60E2F232442372B005F8713 /* StatusMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMO.swift; sourceTree = "<group>"; };
|
||||
|
@ -573,7 +570,6 @@
|
|||
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>"; };
|
||||
D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AssetCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||
D6B053AD23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPickerSheetContainerViewController.swift; sourceTree = "<group>"; };
|
||||
D6B17254254F88B800128392 /* OppositeCollapseKeywordsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OppositeCollapseKeywordsView.swift; sourceTree = "<group>"; };
|
||||
D6B22A0E2560D52D004D82EF /* TabbedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabbedPageViewController.swift; sourceTree = "<group>"; };
|
||||
D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGrayscalifier.swift; sourceTree = "<group>"; };
|
||||
|
@ -634,7 +630,6 @@
|
|||
D6DFC69D242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackpadScrollGestureRecognizer.swift; sourceTree = "<group>"; };
|
||||
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakArray.swift; sourceTree = "<group>"; };
|
||||
D6E0DC8D216EDF1E00369478 /* Previewing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Previewing.swift; sourceTree = "<group>"; };
|
||||
D6E1EEF3285443EF00D20549 /* UIAction+Subtitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAction+Subtitle.swift"; sourceTree = "<group>"; };
|
||||
D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenInTusker.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D6E343AA265AAD6B00C4AA01 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = "<group>"; };
|
||||
D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -648,6 +643,10 @@
|
|||
D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiImageView.swift; sourceTree = "<group>"; };
|
||||
D6E4885C24A2890C0011C13E /* Tusker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Tusker.entitlements; sourceTree = "<group>"; };
|
||||
D6E57FA425C26FAB00341037 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
D6E77D08286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D6E77D0A286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingLinkCardCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D6E77D0C286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrendingLinkCardCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||
D6E77D0E286F773900D8B732 /* SplitNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitNavigationController.swift; sourceTree = "<group>"; };
|
||||
D6E9CDA7281A427800BBC98E /* PostService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostService.swift; sourceTree = "<group>"; };
|
||||
D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableTextField.swift; sourceTree = "<group>"; };
|
||||
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Scenes.swift"; sourceTree = "<group>"; };
|
||||
|
@ -667,7 +666,6 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */,
|
||||
D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */,
|
||||
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */,
|
||||
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */,
|
||||
D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */,
|
||||
|
@ -716,8 +714,7 @@
|
|||
children = (
|
||||
D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */,
|
||||
D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */,
|
||||
D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */,
|
||||
D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */,
|
||||
D6E77D08286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift */,
|
||||
);
|
||||
path = "Hashtag Cell";
|
||||
sourceTree = "<group>";
|
||||
|
@ -803,6 +800,8 @@
|
|||
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */,
|
||||
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */,
|
||||
D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */,
|
||||
D6E77D0A286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift */,
|
||||
D6E77D0C286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib */,
|
||||
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */,
|
||||
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */,
|
||||
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */,
|
||||
|
@ -1123,7 +1122,6 @@
|
|||
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */,
|
||||
D69693F32585941A00F4E116 /* UIWindowSceneDelegate+Close.swift */,
|
||||
D62E9984279CA23900C26176 /* URLSession+Development.swift */,
|
||||
D6E1EEF3285443EF00D20549 /* UIAction+Subtitle.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1242,7 +1240,6 @@
|
|||
D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */,
|
||||
D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */,
|
||||
D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */,
|
||||
D6B053AD23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift */,
|
||||
);
|
||||
path = "Asset Picker";
|
||||
sourceTree = "<group>";
|
||||
|
@ -1303,6 +1300,7 @@
|
|||
D653F410267D1E32004E32B1 /* DiffableTimelineLikeTableViewController.swift */,
|
||||
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */,
|
||||
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */,
|
||||
D6E77D0E286F773900D8B732 /* SplitNavigationController.swift */,
|
||||
D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */,
|
||||
D693DE5823FE24300061E07D /* InteractivePushTransition.swift */,
|
||||
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */,
|
||||
|
@ -1504,7 +1502,6 @@
|
|||
);
|
||||
name = Tusker;
|
||||
packageProductDependencies = (
|
||||
D6B0539E23BD2BA300A066FA /* SheetController */,
|
||||
D69CCBBE249E6EFD000AF167 /* CrashReporter */,
|
||||
D60CFFDA24A290BA00D00083 /* SwiftSoup */,
|
||||
D6676CA427A8D0020052936B /* WebURLFoundationExtras */,
|
||||
|
@ -1614,7 +1611,6 @@
|
|||
);
|
||||
mainGroup = D6D4DDC3212518A000E1C4BB;
|
||||
packageReferences = (
|
||||
D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */,
|
||||
D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */,
|
||||
D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
||||
D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */,
|
||||
|
@ -1637,6 +1633,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */,
|
||||
D6E77D0D286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib in Resources */,
|
||||
D693A73025CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib in Resources */,
|
||||
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */,
|
||||
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */,
|
||||
|
@ -1653,7 +1650,6 @@
|
|||
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
|
||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
|
||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
||||
D6093FB125BE0B01004811E6 /* TrendingHashtagTableViewCell.xib in Resources */,
|
||||
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
||||
D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */,
|
||||
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */,
|
||||
|
@ -1764,7 +1760,6 @@
|
|||
D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */,
|
||||
D62E9989279DB2D100C26176 /* InstanceFeatures.swift in Sources */,
|
||||
D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */,
|
||||
D6E1EEF4285443EF00D20549 /* UIAction+Subtitle.swift in Sources */,
|
||||
D69693FA25859A8000F4E116 /* ComposeSceneDelegate.swift in Sources */,
|
||||
D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */,
|
||||
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */,
|
||||
|
@ -1812,6 +1807,7 @@
|
|||
D662AEF2263A4BE10082A153 /* ComposePollView.swift in Sources */,
|
||||
D677284A24ECBDF400C732D3 /* ComposeCurrentAccount.swift in Sources */,
|
||||
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */,
|
||||
D6E77D0F286F773900D8B732 /* SplitNavigationController.swift in Sources */,
|
||||
D6B30E09254BAF63009CAEE5 /* ImageGrayscalifier.swift in Sources */,
|
||||
D6A6C10F25B62D2400298D0F /* DiskCache.swift in Sources */,
|
||||
D6B81F3C2560365300F6E31D /* RefreshableViewController.swift in Sources */,
|
||||
|
@ -1857,7 +1853,6 @@
|
|||
D6EAE0DB2550CC8A002DB0AC /* FocusableTextField.swift in Sources */,
|
||||
D623A53D2635F5590095BD04 /* StatusPollView.swift in Sources */,
|
||||
D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */,
|
||||
D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */,
|
||||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
||||
D6E9CDA8281A427800BBC98E /* PostService.swift in Sources */,
|
||||
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */,
|
||||
|
@ -1888,7 +1883,6 @@
|
|||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
||||
D6945C2F23AC47C3005C403C /* SavedDataManager.swift in Sources */,
|
||||
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
||||
D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */,
|
||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
||||
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
||||
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */,
|
||||
|
@ -1929,6 +1923,7 @@
|
|||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
||||
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
|
||||
D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */,
|
||||
D6E77D0B286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift in Sources */,
|
||||
D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */,
|
||||
D670F8B62537DC890046588A /* EmojiPickerWrapper.swift in Sources */,
|
||||
D6B81F442560390300F6E31D /* MenuController.swift in Sources */,
|
||||
|
@ -1972,6 +1967,7 @@
|
|||
D6C99FCB24FADC91005C74D3 /* MainComposeTextView.swift in Sources */,
|
||||
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */,
|
||||
D6E4267725327FB400C02E1C /* ComposeAutocompleteView.swift in Sources */,
|
||||
D6E77D09286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift in Sources */,
|
||||
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */,
|
||||
D677284E24ECC01D00C732D3 /* Draft.swift in Sources */,
|
||||
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
|
||||
|
@ -2205,7 +2201,7 @@
|
|||
CURRENT_PROJECT_VERSION = 31;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = Tusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.3;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
@ -2235,7 +2231,7 @@
|
|||
CURRENT_PROJECT_VERSION = 31;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = Tusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.3;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
@ -2463,14 +2459,6 @@
|
|||
minimumVersion = 1.8.0;
|
||||
};
|
||||
};
|
||||
D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://git.shadowfacts.net/shadowfacts/SheetController.git";
|
||||
requirement = {
|
||||
branch = master;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
|
@ -2493,11 +2481,6 @@
|
|||
package = D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */;
|
||||
productName = CrashReporter;
|
||||
};
|
||||
D6B0539E23BD2BA300A066FA /* SheetController */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */;
|
||||
productName = SheetController;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
|
|
|
@ -99,6 +99,11 @@
|
|||
value = "1"
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "CG_NUMERICS_SHOW_BACKTRACE"
|
||||
value = ""
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "DEBUG_BLUR_HASH"
|
||||
value = "1"
|
||||
|
|
|
@ -41,7 +41,6 @@ class ImageCache {
|
|||
let wrappedCompletion: ((Data?, UIImage?) -> Void)?
|
||||
if let completion = completion {
|
||||
wrappedCompletion = { (data, image) in
|
||||
if #available(iOS 15.0, *) {
|
||||
if !loadOriginal,
|
||||
let size = self.desiredPixelSize {
|
||||
image?.prepareThumbnail(of: size, completionHandler: {
|
||||
|
@ -52,11 +51,6 @@ class ImageCache {
|
|||
completion(data, $0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.backgroundQueue.async {
|
||||
completion(data, image)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
wrappedCompletion = nil
|
||||
|
|
|
@ -48,7 +48,7 @@ class MastodonController: ObservableObject {
|
|||
@Published private(set) var instanceFeatures = InstanceFeatures()
|
||||
private(set) var customEmojis: [Emoji]?
|
||||
|
||||
private var pendingOwnInstanceRequestCallbacks = [(Instance) -> Void]()
|
||||
private var pendingOwnInstanceRequestCallbacks = [(Result<Instance, Client.Error>) -> Void]()
|
||||
private var ownInstanceRequest: URLSessionTask?
|
||||
|
||||
var loggedIn: Bool {
|
||||
|
@ -159,15 +159,28 @@ class MastodonController: ObservableObject {
|
|||
}
|
||||
|
||||
func getOwnInstance(completion: ((Instance) -> Void)? = nil) {
|
||||
getOwnInstanceInternal(retryAttempt: 0, completion: completion)
|
||||
getOwnInstanceInternal(retryAttempt: 0) {
|
||||
if case let .success(instance) = $0 {
|
||||
completion?(instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getOwnInstanceInternal(retryAttempt: Int, completion: ((Instance) -> Void)?) {
|
||||
@MainActor
|
||||
func getOwnInstance() async throws -> Instance {
|
||||
return try await withCheckedThrowingContinuation({ continuation in
|
||||
getOwnInstanceInternal(retryAttempt: 0) { result in
|
||||
continuation.resume(with: result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func getOwnInstanceInternal(retryAttempt: Int, completion: ((Result<Instance, Client.Error>) -> Void)?) {
|
||||
// this is main thread only to prevent concurrent access to ownInstanceRequest and pendingOwnInstanceRequestCallbacks
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
if let instance = self.instance {
|
||||
completion?(instance)
|
||||
completion?(.success(instance))
|
||||
} else {
|
||||
if let completion = completion {
|
||||
pendingOwnInstanceRequestCallbacks.append(completion)
|
||||
|
@ -177,7 +190,7 @@ class MastodonController: ObservableObject {
|
|||
let request = Client.getInstance()
|
||||
ownInstanceRequest = run(request) { (response) in
|
||||
switch response {
|
||||
case .failure(_):
|
||||
case .failure(let error):
|
||||
let delay: DispatchTimeInterval
|
||||
switch retryAttempt {
|
||||
case 0:
|
||||
|
@ -190,6 +203,10 @@ class MastodonController: ObservableObject {
|
|||
delay = .seconds(60)
|
||||
default:
|
||||
// if we've failed four times, just give up :/
|
||||
for completion in self.pendingOwnInstanceRequestCallbacks {
|
||||
completion(.failure(error))
|
||||
}
|
||||
self.pendingOwnInstanceRequestCallbacks = []
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||
|
@ -204,7 +221,7 @@ class MastodonController: ObservableObject {
|
|||
self.instanceFeatures.update(instance: instance, nodeInfo: self.nodeInfo)
|
||||
|
||||
for completion in self.pendingOwnInstanceRequestCallbacks {
|
||||
completion(instance)
|
||||
completion(.success(instance))
|
||||
}
|
||||
self.pendingOwnInstanceRequestCallbacks = []
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ struct MenuController {
|
|||
let data: Any
|
||||
if case let .tab(tab) = item {
|
||||
data = tab.rawValue
|
||||
} else if case .search = item {
|
||||
} else if case .explore = item {
|
||||
data = "search"
|
||||
} else if case .bookmarks = item {
|
||||
data = "bookmarks"
|
||||
|
@ -42,7 +42,7 @@ struct MenuController {
|
|||
static let sidebarItemKeyCommands: [UIKeyCommand] = [
|
||||
sidebarCommand(item: .tab(.timelines), command: "1"),
|
||||
sidebarCommand(item: .tab(.notifications), command: "2"),
|
||||
sidebarCommand(item: .search, command: "3"),
|
||||
sidebarCommand(item: .explore, command: "3"),
|
||||
sidebarCommand(item: .bookmarks, command: "4"),
|
||||
sidebarCommand(item: .tab(.myProfile), command: "5"),
|
||||
]
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// UIAction+Subtitle.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 6/10/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIAction {
|
||||
convenience init(title: String, subtitle: String?, image: UIImage?, state: UIAction.State, handler: @escaping UIActionHandler) {
|
||||
if #available(iOS 15.0, *) {
|
||||
self.init(title: title, subtitle: subtitle, image: image, identifier: nil, discoverabilityTitle: nil, attributes: [], state: state, handler: handler)
|
||||
} else {
|
||||
self.init(title: title, image: image, identifier: nil, discoverabilityTitle: nil, attributes: [], state: state, handler: handler)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,6 +34,10 @@ struct InstanceFeatures {
|
|||
instanceType != .pixelfed
|
||||
}
|
||||
|
||||
var trends: Bool {
|
||||
instanceType == .mastodon
|
||||
}
|
||||
|
||||
var trendingStatusesAndLinks: Bool {
|
||||
instanceType == .mastodon && version != nil && version! >= Version(3, 5, 0)
|
||||
}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
//
|
||||
// AssetPickerSheetContainerViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/1/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SheetController
|
||||
import Photos
|
||||
|
||||
class AssetPickerSheetContainerViewController: SheetContainerViewController {
|
||||
|
||||
let assetPicker = AssetPickerViewController()
|
||||
|
||||
init() {
|
||||
super.init(content: assetPicker)
|
||||
|
||||
assetPicker.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
assetPicker.view.layer.masksToBounds = true
|
||||
|
||||
delegate = self
|
||||
assetPicker.delegate = self
|
||||
detents = [.bottom, .middle, .top]
|
||||
|
||||
overrideUserInterfaceStyle = .dark
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
assetPicker.view.layer.cornerRadius = view.bounds.width * 0.02
|
||||
// don't round bottom corners, since they'll always be cut off by the device
|
||||
assetPicker.view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AssetPickerSheetContainerViewController: SheetContainerViewControllerDelegate {
|
||||
func sheetContainerContentScrollView(_ sheetContainer: SheetContainerViewController) -> UIScrollView? {
|
||||
if let vc = assetPicker.visibleViewController as? UITableViewController {
|
||||
return vc.tableView
|
||||
} else if let vc = assetPicker.visibleViewController as? UICollectionViewController {
|
||||
return vc.collectionView
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func sheetContainer(_ sheetContainer: SheetContainerViewController, topContentOffsetForScrollView scrollView: UIScrollView) -> CGFloat {
|
||||
return assetPicker.navigationBar.bounds.height
|
||||
}
|
||||
}
|
||||
|
||||
extension AssetPickerSheetContainerViewController: UINavigationControllerDelegate {
|
||||
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
|
||||
contentScrollViewChanged()
|
||||
// viewController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed))
|
||||
}
|
||||
}
|
|
@ -37,15 +37,9 @@ struct ComposeAttachmentRow: View {
|
|||
}
|
||||
}
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
Button(action: self.removeAttachment) {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}.foregroundStyle(.red)
|
||||
} else {
|
||||
Button(action: self.removeAttachment) {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
} previewIfAvailable: {
|
||||
ComposeAttachmentImage(attachment: attachment, fullSize: true)
|
||||
}
|
||||
|
|
|
@ -191,16 +191,6 @@ struct ComposeAttachmentsList: View {
|
|||
}
|
||||
|
||||
fileprivate extension View {
|
||||
@available(iOS, obsoleted: 15.0)
|
||||
@ViewBuilder
|
||||
func onDragWithPreviewIfAvailable<V>(_ data: @escaping () -> NSItemProvider, preview: () -> V) -> some View where V : View {
|
||||
if #available(iOS 15.0, *) {
|
||||
self.onDrag(data, preview: preview)
|
||||
} else {
|
||||
self.onDrag(data)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS, obsoleted: 16.0)
|
||||
@ViewBuilder
|
||||
func sheetOrPopover(isPresented: Binding<Bool>, @ViewBuilder content: @escaping () -> some View) -> some View {
|
||||
|
|
|
@ -25,8 +25,6 @@ struct ComposeAutocompleteView: View {
|
|||
|
||||
var body: some View {
|
||||
suggestionsView
|
||||
// animate changes of the scroll view items
|
||||
.animation(.default)
|
||||
.background(backgroundColor)
|
||||
.overlay(borderColor.frame(height: 0.5), alignment: .top)
|
||||
}
|
||||
|
@ -85,8 +83,8 @@ struct ComposeAutocompleteMentionsView: View {
|
|||
}
|
||||
.frame(height: 30)
|
||||
.padding(.vertical, 8)
|
||||
.animation(.linear(duration: 0.1))
|
||||
}
|
||||
.animation(.linear(duration: 0.1), value: accounts)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
@ -167,7 +165,7 @@ struct ComposeAutocompleteMentionsView: View {
|
|||
.map(\.0)
|
||||
}
|
||||
|
||||
private enum EitherAccount {
|
||||
private enum EitherAccount: Equatable {
|
||||
case pachyderm(Account)
|
||||
case coreData(AccountMO)
|
||||
|
||||
|
@ -197,6 +195,10 @@ struct ComposeAutocompleteMentionsView: View {
|
|||
return account.avatar
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: EitherAccount, rhs: EitherAccount) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,7 +214,7 @@ struct ComposeAutocompleteEmojisView: View {
|
|||
HStack(alignment: expanded ? .top : .center, spacing: 0) {
|
||||
if case let .emoji(query) = uiState.autocompleteState {
|
||||
emojiList(query: query)
|
||||
.animation(.default)
|
||||
.animation(.default, value: expanded)
|
||||
.transition(.move(edge: .bottom))
|
||||
} else {
|
||||
// when the autocomplete view is animating out, the autocomplete state is nil
|
||||
|
@ -259,8 +261,8 @@ struct ComposeAutocompleteEmojisView: View {
|
|||
}
|
||||
.frame(height: 30)
|
||||
.padding(.vertical, 8)
|
||||
.animation(.linear(duration: 0.2))
|
||||
}
|
||||
.animation(.linear(duration: 0.2), value: emojis)
|
||||
|
||||
Spacer(minLength: 30)
|
||||
}
|
||||
|
@ -319,8 +321,8 @@ struct ComposeAutocompleteHashtagsView: View {
|
|||
}
|
||||
.frame(height: 30)
|
||||
.padding(.vertical, 8)
|
||||
.animation(.linear(duration: 0.1))
|
||||
}
|
||||
.animation(.linear(duration: 0.1), value: hashtags)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
|
|
@ -339,7 +339,6 @@ extension ComposeHostingController: ComposeUIStateDelegate {
|
|||
}
|
||||
|
||||
func presentAssetPickerSheet() {
|
||||
if #available(iOS 15.0, *) {
|
||||
let picker = AssetPickerViewController()
|
||||
picker.assetPickerDelegate = self
|
||||
picker.modalPresentationStyle = .pageSheet
|
||||
|
@ -348,15 +347,6 @@ extension ComposeHostingController: ComposeUIStateDelegate {
|
|||
sheet.detents = [.medium(), .large()]
|
||||
sheet.prefersEdgeAttachedInCompactHeight = true
|
||||
self.present(picker, animated: true)
|
||||
} else {
|
||||
presentOldAssetPickerSheet()
|
||||
}
|
||||
}
|
||||
|
||||
private func presentOldAssetPickerSheet() {
|
||||
let sheetContainer = AssetPickerSheetContainerViewController()
|
||||
sheetContainer.assetPicker.assetPickerDelegate = self
|
||||
self.present(sheetContainer, animated: true)
|
||||
}
|
||||
|
||||
func presentComposeDrawing() {
|
||||
|
|
|
@ -60,7 +60,9 @@ struct ComposePollView: View {
|
|||
|
||||
HStack {
|
||||
// use .animation(nil) on pickers so frame doesn't have a size change animation when the text changes
|
||||
Picker(selection: $poll.multiple, label: Text(poll.multiple ? "Allow multiple choices" : "Single choice")) {
|
||||
// this is deprecated in iOS 15, but using .animation(nil, value: poll.multiple) does not work (it still animates)
|
||||
// nor does setting that on the Text rather than the Picker
|
||||
Picker(selection: $poll.multiple, label: Text(poll.multiple ? "Allow multiple choice" : "Single choice")) {
|
||||
Text("Allow multiple choices").tag(true)
|
||||
Text("Single choice").tag(false)
|
||||
}
|
||||
|
@ -154,8 +156,7 @@ struct ComposePollOption: View {
|
|||
var body: some View {
|
||||
HStack(spacing: 4) {
|
||||
Checkbox(radiusFraction: poll.multiple ? 0.1 : 0.5, borderWidth: 2)
|
||||
.animation(.default)
|
||||
|
||||
.animation(.default, value: poll.multiple)
|
||||
|
||||
textField
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ extension ComposeUIState {
|
|||
}
|
||||
|
||||
extension ComposeUIState {
|
||||
enum AutocompleteState {
|
||||
enum AutocompleteState: Equatable {
|
||||
case mention(String)
|
||||
case emoji(String)
|
||||
case hashtag(String)
|
||||
|
|
|
@ -119,7 +119,7 @@ struct ComposeView: View {
|
|||
}
|
||||
}
|
||||
.transition(.move(edge: .bottom))
|
||||
.animation(.default)
|
||||
.animation(.default, value: uiState.autocompleteState)
|
||||
}
|
||||
|
||||
func mainStack(outerMinY: CGFloat) -> some View {
|
||||
|
@ -147,7 +147,6 @@ struct ComposeView: View {
|
|||
if let poll = draft.poll {
|
||||
ComposePollView(draft: draft, poll: poll)
|
||||
.transition(.opacity.combined(with: .asymmetric(insertion: .scale(scale: 0.5, anchor: .leading), removal: .scale(scale: 0.5, anchor: .trailing))))
|
||||
.animation(.default)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -124,13 +124,8 @@ class ConversationTableViewController: EnhancedTableViewController {
|
|||
}
|
||||
})
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
visibilityBarButtonItem = UIBarButtonItem(image: ConversationTableViewController.showPostsImage, style: .plain, target: self, action: #selector(toggleVisibilityButtonPressed))
|
||||
visibilityBarButtonItem.isSelected = showStatusesAutomatically
|
||||
} else {
|
||||
let initialImage = showStatusesAutomatically ? ConversationTableViewController.hidePostsImage : ConversationTableViewController.showPostsImage
|
||||
visibilityBarButtonItem = UIBarButtonItem(image: initialImage, style: .plain, target: self, action: #selector(toggleVisibilityButtonPressed))
|
||||
}
|
||||
navigationItem.rightBarButtonItem = visibilityBarButtonItem
|
||||
// disable transparent background when scroll to top because it looks weird when items earlier in the thread load in
|
||||
// (it remains transparent slightly too long, resulting in a flash of the content under the transparent bar)
|
||||
|
@ -396,15 +391,7 @@ class ConversationTableViewController: EnhancedTableViewController {
|
|||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
visibilityBarButtonItem.isSelected = showStatusesAutomatically
|
||||
} else {
|
||||
if showStatusesAutomatically {
|
||||
visibilityBarButtonItem.image = ConversationTableViewController.hidePostsImage
|
||||
} else {
|
||||
visibilityBarButtonItem.image = ConversationTableViewController.showPostsImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,19 +9,20 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class AddSavedHashtagViewController: EnhancedTableViewController {
|
||||
class AddSavedHashtagViewController: UIViewController {
|
||||
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
var resultsController: SearchResultsViewController!
|
||||
var searchController: UISearchController!
|
||||
|
||||
var dataSource: UITableViewDiffableDataSource<Section, Item>!
|
||||
private var collectionView: UICollectionView!
|
||||
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||
|
||||
init(mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
super.init(style: .grouped)
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
@ -33,17 +34,37 @@ class AddSavedHashtagViewController: EnhancedTableViewController {
|
|||
|
||||
title = NSLocalizedString("Search", comment: "search screen title")
|
||||
|
||||
tableView.register(UINib(nibName: "TrendingHashtagTableViewCell", bundle: .main), forCellReuseIdentifier: "trendingTagCell")
|
||||
tableView.rowHeight = 60 // 44 for content + 2 * 8 spacing
|
||||
view.backgroundColor = .systemGroupedBackground
|
||||
|
||||
dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in
|
||||
var config = UICollectionLayoutListConfiguration(appearance: .grouped)
|
||||
config.headerMode = .supplementary
|
||||
let layout = UICollectionViewCompositionalLayout.list(using: config)
|
||||
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
|
||||
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
collectionView.delegate = self
|
||||
view.addSubview(collectionView)
|
||||
|
||||
let sectionHeaderCell = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(elementKind: UICollectionView.elementKindSectionHeader) { (headerView, collectionView, indexPath) in
|
||||
var config = headerView.defaultContentConfiguration()
|
||||
config.text = NSLocalizedString("Trending Hashtags", comment: "trending hashtags section title")
|
||||
headerView.contentConfiguration = config
|
||||
}
|
||||
let registration = UICollectionView.CellRegistration<TrendingHashtagCollectionViewCell, Hashtag> { cell, indexPath, hashtag in
|
||||
cell.updateUI(hashtag: hashtag)
|
||||
}
|
||||
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView, indexPath, item) in
|
||||
switch item {
|
||||
case let .tag(hashtag):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "trendingTagCell", for: indexPath) as! TrendingHashtagTableViewCell
|
||||
cell.updateUI(hashtag: hashtag)
|
||||
return cell
|
||||
return collectionView.dequeueConfiguredReusableCell(using: registration, for: indexPath, item: hashtag)
|
||||
}
|
||||
}
|
||||
dataSource.supplementaryViewProvider = { (collectionView, elementKind, indexPath) in
|
||||
if elementKind == UICollectionView.elementKindSectionHeader {
|
||||
return collectionView.dequeueConfiguredReusableSupplementary(using: sectionHeaderCell, for: indexPath)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
resultsController = HashtagSearchResultsViewController(mastodonController: mastodonController)
|
||||
resultsController.delegate = self
|
||||
|
@ -92,17 +113,6 @@ class AddSavedHashtagViewController: EnhancedTableViewController {
|
|||
presentingViewController!.dismiss(animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Table View Delegate
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
switch dataSource.itemIdentifier(for: indexPath) {
|
||||
case nil:
|
||||
return
|
||||
case let .tag(hashtag):
|
||||
selectHashtag(hashtag)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
||||
@objc func cancelButtonPressed() {
|
||||
|
@ -115,14 +125,23 @@ extension AddSavedHashtagViewController {
|
|||
enum Section {
|
||||
case trendingTags
|
||||
}
|
||||
|
||||
enum Item: Hashable {
|
||||
case tag(Hashtag)
|
||||
}
|
||||
// class DataSource: UITableViewDiffableDataSource<Section, Item> {
|
||||
// override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
class DataSource: UITableViewDiffableDataSource<Section, Item> {
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
return NSLocalizedString("Trending Hashtags", comment: "trending hashtags seciton title")
|
||||
extension AddSavedHashtagViewController: UICollectionViewDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
switch dataSource.itemIdentifier(for: indexPath) {
|
||||
case nil:
|
||||
return
|
||||
case let .tag(hashtag):
|
||||
selectHashtag(hashtag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,18 +9,17 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class TrendingHashtagsViewController: EnhancedTableViewController {
|
||||
class TrendingHashtagsViewController: UIViewController {
|
||||
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
private var dataSource: UITableViewDiffableDataSource<Section, Item>!
|
||||
private var collectionView: UICollectionView!
|
||||
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||
|
||||
init(mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
super.init(style: .grouped)
|
||||
|
||||
dragEnabled = true
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
@ -32,15 +31,24 @@ class TrendingHashtagsViewController: EnhancedTableViewController {
|
|||
|
||||
title = NSLocalizedString("Trending Hashtags", comment: "trending hashtags screen title")
|
||||
|
||||
tableView.register(UINib(nibName: "TrendingHashtagTableViewCell", bundle: .main), forCellReuseIdentifier: "trendingTagCell")
|
||||
tableView.rowHeight = 60 // 44 for content + 2 * 8 spacing
|
||||
view.backgroundColor = .systemGroupedBackground
|
||||
|
||||
dataSource = UITableViewDiffableDataSource<Section, Item>(tableView: tableView) { (tableView, indexPath, item) in
|
||||
let config = UICollectionLayoutListConfiguration(appearance: .grouped)
|
||||
let layout = UICollectionViewCompositionalLayout.list(using: config)
|
||||
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
|
||||
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
collectionView.delegate = self
|
||||
collectionView.dragDelegate = self
|
||||
view.addSubview(collectionView)
|
||||
|
||||
let registration = UICollectionView.CellRegistration<TrendingHashtagCollectionViewCell, Hashtag> { cell, indexPath, hashtag in
|
||||
cell.updateUI(hashtag: hashtag)
|
||||
}
|
||||
|
||||
dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { (collectionView, indexPath, item) in
|
||||
switch item {
|
||||
case let .tag(hashtag):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "trendingTagCell", for: indexPath) as! TrendingHashtagTableViewCell
|
||||
cell.updateUI(hashtag: hashtag)
|
||||
return cell
|
||||
return collectionView.dequeueConfiguredReusableCell(using: registration, for: indexPath, item: hashtag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,9 +68,19 @@ class TrendingHashtagsViewController: EnhancedTableViewController {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Table View Delegate
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
extension TrendingHashtagsViewController {
|
||||
enum Section {
|
||||
case trendingTags
|
||||
}
|
||||
enum Item: Hashable {
|
||||
case tag(Hashtag)
|
||||
}
|
||||
}
|
||||
|
||||
extension TrendingHashtagsViewController: UICollectionViewDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||
case let .tag(hashtag) = item else {
|
||||
return
|
||||
|
@ -71,7 +89,7 @@ class TrendingHashtagsViewController: EnhancedTableViewController {
|
|||
show(HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController), sender: nil)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||
case let .tag(hashtag) = item else {
|
||||
return nil
|
||||
|
@ -79,11 +97,13 @@ class TrendingHashtagsViewController: EnhancedTableViewController {
|
|||
return UIContextMenuConfiguration(identifier: nil) {
|
||||
HashtagTimelineViewController(for: hashtag, mastodonController: self.mastodonController)
|
||||
} actionProvider: { (_) in
|
||||
UIMenu(children: self.actionsForHashtag(hashtag, sourceView: self.tableView.cellForRow(at: indexPath)))
|
||||
UIMenu(children: self.actionsForHashtag(hashtag, sourceView: self.collectionView.cellForItem(at: indexPath)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
extension TrendingHashtagsViewController: UICollectionViewDragDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||
case let .tag(hashtag) = item else {
|
||||
return []
|
||||
|
@ -95,16 +115,6 @@ class TrendingHashtagsViewController: EnhancedTableViewController {
|
|||
}
|
||||
return [UIDragItem(itemProvider: provider)]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension TrendingHashtagsViewController {
|
||||
enum Section {
|
||||
case trendingTags
|
||||
}
|
||||
enum Item: Hashable {
|
||||
case tag(Hashtag)
|
||||
}
|
||||
}
|
||||
|
||||
extension TrendingHashtagsViewController: TuskerNavigationDelegate {
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
//
|
||||
// TrendingLinkCardCollectionViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 6/29/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class TrendingLinkCardCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
private var card: Card?
|
||||
private var isGrayscale = false
|
||||
private var thumbnailRequest: ImageCache.Request?
|
||||
|
||||
@IBOutlet weak var thumbnailView: UIImageView!
|
||||
@IBOutlet weak var titleLabel: UILabel!
|
||||
@IBOutlet weak var providerLabel: UILabel!
|
||||
@IBOutlet weak var activityLabel: UILabel!
|
||||
@IBOutlet weak var historyView: TrendHistoryView!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
layer.shadowOpacity = 0.2
|
||||
layer.shadowRadius = 8
|
||||
layer.shadowOffset = .zero
|
||||
layer.masksToBounds = false
|
||||
updateLayerColors()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
contentView.layer.cornerRadius = 0.05 * bounds.width
|
||||
thumbnailView.layer.cornerRadius = 0.05 * bounds.width
|
||||
}
|
||||
|
||||
func updateUI(card: Card) {
|
||||
self.card = card
|
||||
self.thumbnailView.image = nil
|
||||
|
||||
updateGrayscaleableUI(card: card)
|
||||
updateUIForPreferences()
|
||||
|
||||
let title = card.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
titleLabel.text = title
|
||||
|
||||
let provider = card.providerName!.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
providerLabel.text = provider
|
||||
|
||||
let sorted = card.history!.sorted(by: { $0.day < $1.day })
|
||||
let lastTwo = sorted[(sorted.count - 2)...]
|
||||
let accounts = lastTwo.map(\.accounts).reduce(0, +)
|
||||
let uses = lastTwo.map(\.uses).reduce(0, +)
|
||||
|
||||
// U+2009 THIN SPACE
|
||||
let activityStr = NSMutableAttributedString(string: "\(accounts.formatted())\u{2009}")
|
||||
activityStr.append(NSAttributedString(attachment: NSTextAttachment(image: UIImage(systemName: "person")!)))
|
||||
activityStr.append(NSAttributedString(string: ", \(uses.formatted())\u{2009}"))
|
||||
activityStr.append(NSAttributedString(attachment: NSTextAttachment(image: UIImage(systemName: "square.text.square")!)))
|
||||
activityLabel.attributedText = activityStr
|
||||
|
||||
historyView.setHistory(card.history)
|
||||
historyView.isHidden = card.history == nil || card.history!.count < 2
|
||||
}
|
||||
|
||||
@objc private func updateUIForPreferences() {
|
||||
if isGrayscale != Preferences.shared.grayscaleImages,
|
||||
let card {
|
||||
updateGrayscaleableUI(card: card)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateGrayscaleableUI(card: Card) {
|
||||
isGrayscale = Preferences.shared.grayscaleImages
|
||||
|
||||
if let imageURL = card.image,
|
||||
let url = URL(imageURL) {
|
||||
thumbnailRequest = ImageCache.attachments.get(url, completion: { _, image in
|
||||
guard let image,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: url, image: image) else {
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.thumbnailView.image = transformedImage
|
||||
}
|
||||
})
|
||||
if thumbnailRequest != nil {
|
||||
loadBlurHash(card: card)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadBlurHash(card: Card) {
|
||||
guard let hash = card.blurhash else {
|
||||
return
|
||||
}
|
||||
let imageViewSize = self.thumbnailView.bounds.size
|
||||
AttachmentView.queue.async { [weak self] in
|
||||
let size: CGSize
|
||||
if let width = card.width, let height = card.height {
|
||||
size = CGSize(width: width, height: height)
|
||||
} else {
|
||||
size = imageViewSize
|
||||
}
|
||||
|
||||
guard let preview = UIImage(blurHash: hash, size: size) else {
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self,
|
||||
self.card?.url == card.url,
|
||||
self.thumbnailView.image == nil else {
|
||||
return
|
||||
}
|
||||
self.thumbnailView.image = preview
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
updateLayerColors()
|
||||
}
|
||||
|
||||
private func updateLayerColors() {
|
||||
if traitCollection.userInterfaceStyle == .dark {
|
||||
// clippingView.layer.borderColor = UIColor.darkGray.withAlphaComponent(0.5).cgColor
|
||||
layer.shadowColor = UIColor.darkGray.cgColor
|
||||
} else {
|
||||
// clippingView.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
|
||||
layer.shadowColor = UIColor.black.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21179.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_0" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21169.4"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="collection view cell content view" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="izA-ZZ-g7F" customClass="TrendingLinkCardCollectionViewCell" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="400"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="Zb0-aW-Sen">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="400"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="h3b-Mf-lD6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="225"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="h3b-Mf-lD6" secondAttribute="height" multiplier="4:3" id="QDY-8a-LYC"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ho3-cU-IGi">
|
||||
<rect key="frame" x="16" y="330.66666666666674" width="268" height="20.333333333333314"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Provider" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="O9r-10-LDD">
|
||||
<rect key="frame" x="16.000000000000004" y="355" width="57.333333333333343" height="18"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Activity" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ULe-Gd-t1S">
|
||||
<rect key="frame" x="16" y="377" width="43" height="15"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LZj-Ii-63i" customClass="TrendHistoryView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="200" y="355" width="100" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="100" id="cUc-p7-aLH"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="ULe-Gd-t1S" secondAttribute="bottom" id="6UL-8b-Aia"/>
|
||||
<constraint firstItem="h3b-Mf-lD6" firstAttribute="top" secondItem="Zb0-aW-Sen" secondAttribute="top" id="EFg-Yr-vdt"/>
|
||||
<constraint firstItem="Ho3-cU-IGi" firstAttribute="leading" secondItem="Zb0-aW-Sen" secondAttribute="leadingMargin" id="Ga8-LQ-f4N"/>
|
||||
<constraint firstItem="ULe-Gd-t1S" firstAttribute="top" secondItem="O9r-10-LDD" secondAttribute="bottom" constant="4" id="HPD-qN-k3z"/>
|
||||
<constraint firstAttribute="bottom" secondItem="LZj-Ii-63i" secondAttribute="bottom" constant="1" id="HWu-In-Uem"/>
|
||||
<constraint firstItem="O9r-10-LDD" firstAttribute="leading" secondItem="Zb0-aW-Sen" secondAttribute="leadingMargin" id="Hz8-Bw-jpl"/>
|
||||
<constraint firstAttribute="trailing" secondItem="LZj-Ii-63i" secondAttribute="trailing" id="J9c-CF-3EF"/>
|
||||
<constraint firstItem="ULe-Gd-t1S" firstAttribute="leading" secondItem="Zb0-aW-Sen" secondAttribute="leadingMargin" id="KEj-En-StX"/>
|
||||
<constraint firstItem="Ho3-cU-IGi" firstAttribute="top" secondItem="h3b-Mf-lD6" secondAttribute="bottom" constant="4" id="PjW-V1-oDs"/>
|
||||
<constraint firstItem="LZj-Ii-63i" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="O9r-10-LDD" secondAttribute="trailing" id="WNr-ZP-o9a"/>
|
||||
<constraint firstItem="LZj-Ii-63i" firstAttribute="top" secondItem="Ho3-cU-IGi" secondAttribute="bottom" constant="4" id="fpM-Hp-Oyf"/>
|
||||
<constraint firstAttribute="trailing" secondItem="h3b-Mf-lD6" secondAttribute="trailing" id="kBD-1R-bh7"/>
|
||||
<constraint firstItem="LZj-Ii-63i" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="ULe-Gd-t1S" secondAttribute="trailing" id="ruZ-p8-n0x"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Ho3-cU-IGi" secondAttribute="trailing" id="ubj-f6-bXE"/>
|
||||
<constraint firstItem="h3b-Mf-lD6" firstAttribute="leading" secondItem="Zb0-aW-Sen" secondAttribute="leading" id="wF1-Gm-nVQ"/>
|
||||
<constraint firstItem="O9r-10-LDD" firstAttribute="top" secondItem="Ho3-cU-IGi" secondAttribute="bottom" constant="4" id="yPq-dT-uib"/>
|
||||
</constraints>
|
||||
</collectionViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="activityLabel" destination="ULe-Gd-t1S" id="wqe-G6-IB3"/>
|
||||
<outlet property="historyView" destination="LZj-Ii-63i" id="MVF-az-uyA"/>
|
||||
<outlet property="providerLabel" destination="O9r-10-LDD" id="xAF-NW-ymm"/>
|
||||
<outlet property="thumbnailView" destination="h3b-Mf-lD6" id="4mF-bJ-ALY"/>
|
||||
<outlet property="titleLabel" destination="Ho3-cU-IGi" id="ltu-ey-chT"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="0.0" y="-13.507109004739336"/>
|
||||
</collectionViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
|
@ -113,6 +113,10 @@ class TrendingLinkTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
@objc private func updateUIForPreferences() {
|
||||
if isGrayscale != Preferences.shared.grayscaleImages,
|
||||
let card {
|
||||
updateGrayscaleableUI(card: card)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateGrayscaleableUI(card: Card) {
|
||||
|
|
|
@ -19,8 +19,10 @@ protocol LargeImageContentView: UIView {
|
|||
|
||||
class LargeImageImageContentView: UIImageView, LargeImageContentView {
|
||||
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
@available(iOS 16.0, *)
|
||||
private static let analyzer = ImageAnalyzer()
|
||||
#endif
|
||||
|
||||
var animationImage: UIImage? { image! }
|
||||
|
||||
|
@ -39,6 +41,7 @@ class LargeImageImageContentView: UIImageView, LargeImageContentView {
|
|||
contentMode = .scaleAspectFit
|
||||
isUserInteractionEnabled = true
|
||||
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
if #available(iOS 16.0, *),
|
||||
ImageAnalyzer.isSupported {
|
||||
let interaction = ImageAnalysisInteraction()
|
||||
|
@ -54,6 +57,7 @@ class LargeImageImageContentView: UIImageView, LargeImageContentView {
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
@ -78,12 +82,14 @@ class LargeImageImageContentView: UIImageView, LargeImageContentView {
|
|||
}
|
||||
}
|
||||
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
@available(iOS 16.0, *)
|
||||
extension LargeImageImageContentView: ImageAnalysisInteractionDelegate {
|
||||
func presentingViewController(for interaction: ImageAnalysisInteraction) -> UIViewController? {
|
||||
return owner
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
class LargeImageGifContentView: GIFImageView, LargeImageContentView {
|
||||
var animationImage: UIImage? { image }
|
||||
|
|
|
@ -37,7 +37,7 @@ class MainSidebarViewController: UIViewController {
|
|||
}
|
||||
|
||||
var exploreTabItems: [Item] {
|
||||
var items: [Item] = [.search, .bookmarks, .trendingStatuses, .trendingTags, .trendingLinks, .profileDirectory]
|
||||
var items: [Item] = [.explore, .bookmarks, .trendingStatuses, .profileDirectory]
|
||||
let snapshot = dataSource.snapshot()
|
||||
for case let .list(list) in snapshot.itemIdentifiers(inSection: .lists) {
|
||||
items.append(.list(list))
|
||||
|
@ -154,7 +154,7 @@ class MainSidebarViewController: UIViewController {
|
|||
snapshot.appendItems([
|
||||
.tab(.timelines),
|
||||
.tab(.notifications),
|
||||
.search,
|
||||
.explore,
|
||||
.bookmarks,
|
||||
.tab(.myProfile)
|
||||
], toSection: .tabs)
|
||||
|
@ -177,12 +177,10 @@ class MainSidebarViewController: UIViewController {
|
|||
var discoverSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
||||
discoverSnapshot.append([.discoverHeader])
|
||||
discoverSnapshot.append([
|
||||
.trendingTags,
|
||||
.profileDirectory,
|
||||
], to: .discoverHeader)
|
||||
if mastodonController.instanceFeatures.trendingStatusesAndLinks {
|
||||
discoverSnapshot.insert([.trendingStatuses], before: .trendingTags)
|
||||
discoverSnapshot.insert([.trendingLinks], after: .trendingTags)
|
||||
discoverSnapshot.insert([.trendingStatuses], before: .profileDirectory)
|
||||
}
|
||||
dataSource.apply(discoverSnapshot, to: .discover)
|
||||
}
|
||||
|
@ -345,7 +343,7 @@ class MainSidebarViewController: UIViewController {
|
|||
return UserActivityManager.checkNotificationsActivity(mode: Preferences.shared.defaultNotificationsMode)
|
||||
case .tab(.compose):
|
||||
return UserActivityManager.newPostActivity(accountID: id)
|
||||
case .search:
|
||||
case .explore:
|
||||
return UserActivityManager.searchActivity()
|
||||
case .bookmarks:
|
||||
return UserActivityManager.bookmarksActivity()
|
||||
|
@ -384,8 +382,8 @@ extension MainSidebarViewController {
|
|||
}
|
||||
enum Item: Hashable {
|
||||
case tab(MainTabBarViewController.Tab)
|
||||
case search, bookmarks
|
||||
case discoverHeader, trendingStatuses, trendingTags, trendingLinks, profileDirectory
|
||||
case explore, bookmarks
|
||||
case discoverHeader, trendingStatuses, profileDirectory
|
||||
case listsHeader, list(List), addList
|
||||
case savedHashtagsHeader, savedHashtag(Hashtag), addSavedHashtag
|
||||
case savedInstancesHeader, savedInstance(URL), addSavedInstance
|
||||
|
@ -394,18 +392,14 @@ extension MainSidebarViewController {
|
|||
switch self {
|
||||
case let .tab(tab):
|
||||
return tab.title
|
||||
case .search:
|
||||
return "Search"
|
||||
case .explore:
|
||||
return "Explore"
|
||||
case .bookmarks:
|
||||
return "Bookmarks"
|
||||
case .discoverHeader:
|
||||
return "Discover"
|
||||
case .trendingStatuses:
|
||||
return "Trending Posts"
|
||||
case .trendingTags:
|
||||
return "Trending Hashtags"
|
||||
case .trendingLinks:
|
||||
return "Trending Links"
|
||||
case .profileDirectory:
|
||||
return "Profile Directory"
|
||||
case .listsHeader:
|
||||
|
@ -433,16 +427,12 @@ extension MainSidebarViewController {
|
|||
switch self {
|
||||
case let .tab(tab):
|
||||
return tab.imageName
|
||||
case .search:
|
||||
case .explore:
|
||||
return "magnifyingglass"
|
||||
case .bookmarks:
|
||||
return "bookmark"
|
||||
case .trendingStatuses:
|
||||
return "doc.text.image"
|
||||
case .trendingTags:
|
||||
return "number"
|
||||
case .trendingLinks:
|
||||
return "link"
|
||||
return "square.text.square"
|
||||
case .profileDirectory:
|
||||
return "person.2.fill"
|
||||
case .list(_):
|
||||
|
@ -550,8 +540,7 @@ extension MainSidebarViewController: UICollectionViewDelegate {
|
|||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
guard #available(iOS 15.0, *),
|
||||
let item = dataSource.itemIdentifier(for: indexPath),
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||
let activity = userActivityForItem(item) else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -20,8 +20,11 @@ class MainSplitViewController: UISplitViewController {
|
|||
|
||||
private var tabBarViewController: MainTabBarViewController!
|
||||
|
||||
private var secondaryNavController: UINavigationController! {
|
||||
viewController(for: .secondary) as? UINavigationController
|
||||
// private var secondaryNavController: UINavigationController! {
|
||||
// viewController(for: .secondary) as? UINavigationController
|
||||
// }
|
||||
private var secondaryNavController: SplitNavigationController! {
|
||||
viewController(for: .secondary) as? SplitNavigationController
|
||||
}
|
||||
|
||||
init(mastodonController: MastodonController) {
|
||||
|
@ -46,9 +49,10 @@ class MainSplitViewController: UISplitViewController {
|
|||
setViewController(sidebar, for: .primary)
|
||||
primaryBackgroundStyle = .sidebar
|
||||
|
||||
let secondaryNav = EnhancedNavigationViewController()
|
||||
secondaryNav.useBrowserStyleNavigation = true
|
||||
setViewController(secondaryNav, for: .secondary)
|
||||
// let secondaryNav = EnhancedNavigationViewController()
|
||||
// secondaryNav.useBrowserStyleNavigation = true
|
||||
let splitNav = SplitNavigationController()
|
||||
setViewController(splitNav, for: .secondary)
|
||||
// don't unnecesarily construct a content VC unless the we're in actually split mode
|
||||
// when we change from compact -> split for the first time, the VC will be transferred anyways
|
||||
if traitCollection.horizontalSizeClass != .compact {
|
||||
|
@ -100,7 +104,7 @@ class MainSplitViewController: UISplitViewController {
|
|||
item = .tab(MainTabBarViewController.Tab(rawValue: index)!)
|
||||
} else if let str = command.propertyList as? String {
|
||||
if str == "search" {
|
||||
item = .search
|
||||
item = .explore
|
||||
} else if str == "bookmarks" {
|
||||
item = .bookmarks
|
||||
} else {
|
||||
|
@ -171,7 +175,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
$0.1 > $1.1
|
||||
}
|
||||
if let mostRecentExploreItem = mostRecentExploreItem?.0,
|
||||
mostRecentExploreItem != .search {
|
||||
mostRecentExploreItem != .explore {
|
||||
let exploreNav = tabBarViewController.viewController(for: .explore) as! UINavigationController
|
||||
// Pop back to root, so we're appending to the Explore VC instead of some other VC
|
||||
exploreNav.popToRootViewController(animated: false)
|
||||
|
@ -188,7 +192,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
// sidebar items that map 1 <-> 1 can be transferred directly
|
||||
tabBarViewController.select(tab: tab)
|
||||
|
||||
case .search:
|
||||
case .explore:
|
||||
// Search sidebar item maps to the Explore tab with the search controller/results visible
|
||||
// The nav stack can't be copied directly, since the split VC uses a different SearchViewController
|
||||
// so that explore items aren't shown multiple times.
|
||||
|
@ -217,11 +221,11 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
explore.resultsController.loadResults(from: search.resultsController)
|
||||
|
||||
// Transfer the navigation stack, dropping the search VC, to keep anything the user has opened
|
||||
transferNavigationStack(from: .search, to: exploreNav, dropFirst: true, append: true)
|
||||
transferNavigationStack(from: .explore, to: exploreNav, dropFirst: true, append: true)
|
||||
|
||||
tabBarViewController.select(tab: .explore)
|
||||
|
||||
case .bookmarks, .trendingStatuses, .trendingTags, .trendingLinks, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_):
|
||||
case .bookmarks, .trendingStatuses, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_):
|
||||
tabBarViewController.select(tab: .explore)
|
||||
// Make sure the Explore VC doesn't show it's search bar when it appears, in case the user was previously
|
||||
// in compact mode and performing a search.
|
||||
|
@ -272,7 +276,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
// For other items, the 2nd VC in the nav stack determines which sidebar item they map to.
|
||||
// Search screen has special considerations, all others can be transferred directly.
|
||||
if tabNavigationStack.count == 1 || ((tabNavigationStack.first as? ExploreViewController)?.searchController?.isActive ?? false) {
|
||||
exploreItem = .search
|
||||
exploreItem = .explore
|
||||
let searchVC = SearchViewController(mastodonController: mastodonController)
|
||||
searchVC.loadViewIfNeeded()
|
||||
let explore = tabNavigationStack.first as! ExploreViewController
|
||||
|
@ -300,9 +304,9 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
case is TrendingStatusesViewController:
|
||||
exploreItem = .trendingStatuses
|
||||
case is TrendingHashtagsViewController:
|
||||
exploreItem = .trendingTags
|
||||
exploreItem = .explore
|
||||
case is TrendingLinksViewController:
|
||||
exploreItem = .trendingLinks
|
||||
exploreItem = .explore
|
||||
case is ProfileDirectoryViewController:
|
||||
exploreItem = .profileDirectory
|
||||
default:
|
||||
|
@ -354,16 +358,12 @@ fileprivate extension MainSidebarViewController.Item {
|
|||
switch self {
|
||||
case let .tab(tab):
|
||||
return tab.createViewController(mastodonController)
|
||||
case .search:
|
||||
case .explore:
|
||||
return SearchViewController(mastodonController: mastodonController)
|
||||
case .bookmarks:
|
||||
return BookmarksTableViewController(mastodonController: mastodonController)
|
||||
case .trendingStatuses:
|
||||
return TrendingStatusesViewController(mastodonController: mastodonController)
|
||||
case .trendingTags:
|
||||
return TrendingHashtagsViewController(mastodonController: mastodonController)
|
||||
case .trendingLinks:
|
||||
return TrendingLinksViewController(mastodonController: mastodonController)
|
||||
case .profileDirectory:
|
||||
return ProfileDirectoryViewController(mastodonController: mastodonController)
|
||||
case let .list(list):
|
||||
|
@ -380,7 +380,7 @@ fileprivate extension MainSidebarViewController.Item {
|
|||
|
||||
extension MainSplitViewController: TuskerRootViewController {
|
||||
@objc func presentCompose() {
|
||||
if #available(iOS 15.0, *), UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
let compose = UserActivityManager.newPostActivity(mentioning: nil, accountID: mastodonController.accountInfo!.id)
|
||||
let options = UIWindowScene.ActivationRequestOptions()
|
||||
options.preferredPresentationStyle = .prominent
|
||||
|
@ -435,8 +435,8 @@ extension MainSplitViewController: TuskerRootViewController {
|
|||
return
|
||||
}
|
||||
|
||||
if sidebar.selectedItem != .search {
|
||||
select(item: .search)
|
||||
if sidebar.selectedItem != .explore {
|
||||
select(item: .explore)
|
||||
}
|
||||
|
||||
guard let searchViewController = secondaryNavController.viewControllers.first as? SearchViewController else {
|
||||
|
|
|
@ -142,7 +142,7 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
|||
return vc
|
||||
} else {
|
||||
let nav = EnhancedNavigationViewController(rootViewController: vc)
|
||||
nav.useBrowserStyleNavigation = true
|
||||
// nav.useBrowserStyleNavigation = true
|
||||
return nav
|
||||
}
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ extension MainTabBarViewController: FastAccountSwitcherViewControllerDelegate {
|
|||
|
||||
extension MainTabBarViewController: TuskerRootViewController {
|
||||
@objc func presentCompose() {
|
||||
if #available(iOS 15.0, *), UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
let compose = UserActivityManager.newPostActivity(mentioning: nil, accountID: mastodonController.accountInfo!.id)
|
||||
let options = UIWindowScene.ActivationRequestOptions()
|
||||
options.preferredPresentationStyle = .prominent
|
||||
|
|
|
@ -38,7 +38,7 @@ struct OppositeCollapseKeywordsView: View {
|
|||
FocusableTextField(placeholder: "Add Keyword", text: $valueToAdd, becomeFirstResponder: $makeAddFieldFirstResponder, onCommit: self.addKeyword)
|
||||
}
|
||||
}
|
||||
.animation(.default)
|
||||
.animation(.default, value: keywords.map(\.id))
|
||||
.listStyle(GroupedListStyle())
|
||||
}
|
||||
.onAppear(perform: updateAppearance)
|
||||
|
|
|
@ -16,9 +16,7 @@ struct WellnessPrefsView: View {
|
|||
showFavAndReblogCount
|
||||
notificationsMode
|
||||
grayscaleImages
|
||||
if #available(iOS 15.0, *) {
|
||||
disableInfiniteScrolling
|
||||
}
|
||||
hideDiscover
|
||||
}
|
||||
.listStyle(InsetGroupedListStyle())
|
||||
|
|
|
@ -7,11 +7,16 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import SafariServices
|
||||
|
||||
class SearchViewController: UIViewController {
|
||||
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
private var collectionView: UICollectionView!
|
||||
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||
|
||||
var resultsController: SearchResultsViewController!
|
||||
var searchController: UISearchController!
|
||||
|
||||
|
@ -22,7 +27,7 @@ class SearchViewController: UIViewController {
|
|||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
title = NSLocalizedString("Search", comment: "search tab title")
|
||||
title = NSLocalizedString("Explore", comment: "explore tab title")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
@ -32,12 +37,46 @@ class SearchViewController: UIViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
view.backgroundColor = .systemBackground
|
||||
let layout = UICollectionViewCompositionalLayout { [unowned self] sectionIndex, environment in
|
||||
let sectionIdentifier = self.dataSource.snapshot().sectionIdentifiers[sectionIndex]
|
||||
switch sectionIdentifier {
|
||||
case .trendingHashtags:
|
||||
var listConfig = UICollectionLayoutListConfiguration(appearance: .grouped)
|
||||
listConfig.headerMode = .supplementary
|
||||
return .list(using: listConfig, layoutEnvironment: environment)
|
||||
|
||||
case .trendingLinks:
|
||||
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
|
||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||
// todo: i really wish i could just say the height is automatic and let autolayout figure out what it needs to be
|
||||
// using .estimated(whatever) constrains the height to exactly whatever
|
||||
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(250), heightDimension: .estimated(280))
|
||||
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
|
||||
group.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .fixed(8), top: nil, trailing: .fixed(8), bottom: nil)
|
||||
let section = NSCollectionLayoutSection(group: group)
|
||||
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
|
||||
section.boundarySupplementaryItems = [
|
||||
NSCollectionLayoutBoundarySupplementaryItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(12)), elementKind: UICollectionView.elementKindSectionHeader, alignment: .topLeading)
|
||||
]
|
||||
return section
|
||||
|
||||
default:
|
||||
fatalError("unimplemented")
|
||||
}
|
||||
}
|
||||
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
|
||||
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
collectionView.delegate = self
|
||||
collectionView.dragDelegate = self
|
||||
collectionView.backgroundColor = .secondarySystemBackground
|
||||
view.addSubview(collectionView)
|
||||
|
||||
dataSource = createDataSource()
|
||||
|
||||
resultsController = SearchResultsViewController(mastodonController: mastodonController)
|
||||
resultsController.exploreNavigationController = self.navigationController
|
||||
searchController = UISearchController(searchResultsController: resultsController)
|
||||
searchController.obscuresBackgroundDuringPresentation = false
|
||||
searchController.obscuresBackgroundDuringPresentation = true
|
||||
searchController.searchBar.autocapitalizationType = .none
|
||||
searchController.searchBar.delegate = resultsController
|
||||
searchController.hidesNavigationBarDuringPresentation = false
|
||||
|
@ -48,6 +87,18 @@ class SearchViewController: UIViewController {
|
|||
if #available(iOS 16.0, *) {
|
||||
navigationItem.preferredSearchBarPlacement = .stacked
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
Task(priority: .userInitiated) {
|
||||
if (try? await mastodonController.getOwnInstance()) != nil {
|
||||
await applySnapshot()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
@ -62,4 +113,213 @@ class SearchViewController: UIViewController {
|
|||
}
|
||||
}
|
||||
|
||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||
let sectionHeaderCell = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(elementKind: UICollectionView.elementKindSectionHeader) { [unowned self] (headerView, collectionView, indexPath) in
|
||||
let section = self.dataSource.snapshot().sectionIdentifiers[indexPath.section]
|
||||
var config = UIListContentConfiguration.groupedHeader()
|
||||
config.text = section.title
|
||||
headerView.contentConfiguration = config
|
||||
}
|
||||
|
||||
let trendingHashtagCell = UICollectionView.CellRegistration<TrendingHashtagCollectionViewCell, Hashtag> { (cell, indexPath, hashtag) in
|
||||
cell.updateUI(hashtag: hashtag)
|
||||
}
|
||||
let trendingLinkCell = UICollectionView.CellRegistration<TrendingLinkCardCollectionViewCell, Card>(cellNib: UINib(nibName: "TrendingLinkCardCollectionViewCell", bundle: .main)) { (cell, indexPath, card) in
|
||||
cell.updateUI(card: card)
|
||||
}
|
||||
|
||||
let dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, item in
|
||||
switch item {
|
||||
case let .tag(hashtag):
|
||||
return collectionView.dequeueConfiguredReusableCell(using: trendingHashtagCell, for: indexPath, item: hashtag)
|
||||
|
||||
case let .link(card):
|
||||
return collectionView.dequeueConfiguredReusableCell(using: trendingLinkCell, for: indexPath, item: card)
|
||||
|
||||
default:
|
||||
fatalError("todo")
|
||||
}
|
||||
}
|
||||
dataSource.supplementaryViewProvider = { (collectionView, elementKind, indexPath) in
|
||||
if elementKind == UICollectionView.elementKindSectionHeader {
|
||||
return collectionView.dequeueConfiguredReusableSupplementary(using: sectionHeaderCell, for: indexPath)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return dataSource
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func applySnapshot() async {
|
||||
guard mastodonController.instanceFeatures.trends,
|
||||
!Preferences.shared.hideDiscover else {
|
||||
await dataSource.apply(NSDiffableDataSourceSnapshot())
|
||||
return
|
||||
}
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
|
||||
let hashtagsReq = Client.getTrendingHashtags(limit: 5)
|
||||
async let hashtags = try? mastodonController.run(hashtagsReq).0
|
||||
let linksReq = Client.getTrendingLinks(limit: 10)
|
||||
async let links = try? mastodonController.run(linksReq).0
|
||||
|
||||
if let hashtags = await hashtags {
|
||||
snapshot.appendSections([.trendingHashtags])
|
||||
snapshot.appendItems(hashtags.map { .tag($0) }, toSection: .trendingHashtags)
|
||||
}
|
||||
|
||||
if let links = await links {
|
||||
snapshot.appendSections([.trendingLinks])
|
||||
snapshot.appendItems(links.map { .link($0) }, toSection: .trendingLinks)
|
||||
}
|
||||
|
||||
await dataSource.apply(snapshot)
|
||||
}
|
||||
|
||||
@objc private func preferencesChanged() {
|
||||
Task {
|
||||
await applySnapshot()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SearchViewController {
|
||||
enum Section {
|
||||
case trendingHashtags
|
||||
case trendingLinks
|
||||
case trendingStatuses
|
||||
case profileSuggestions
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .trendingHashtags:
|
||||
return "Trending Hashtags"
|
||||
case .trendingLinks:
|
||||
return "Trending Links"
|
||||
case .trendingStatuses:
|
||||
return "Trending Statuses"
|
||||
case .profileSuggestions:
|
||||
return "Suggested Accounts"
|
||||
}
|
||||
}
|
||||
}
|
||||
enum Item: Equatable, Hashable {
|
||||
case status(String)
|
||||
case tag(Hashtag)
|
||||
case link(Card)
|
||||
|
||||
static func == (lhs: SearchViewController.Item, rhs: SearchViewController.Item) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.status(a), .status(b)):
|
||||
return a == b
|
||||
case let (.tag(a), .tag(b)):
|
||||
return a == b
|
||||
case let (.link(a), .link(b)):
|
||||
return a.url == b.url
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case let .status(id):
|
||||
hasher.combine("status")
|
||||
hasher.combine(id)
|
||||
case let .tag(tag):
|
||||
hasher.combine("tag")
|
||||
hasher.combine(tag.name)
|
||||
case let .link(card):
|
||||
hasher.combine("link")
|
||||
hasher.combine(card.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchViewController: UICollectionViewDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return
|
||||
}
|
||||
switch item {
|
||||
case let .tag(hashtag):
|
||||
show(HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController), sender: nil)
|
||||
|
||||
case let .link(card):
|
||||
if let url = URL(card.url) {
|
||||
selected(url: url)
|
||||
}
|
||||
|
||||
default:
|
||||
fatalError("todo")
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch item {
|
||||
case let .tag(hashtag):
|
||||
return UIContextMenuConfiguration(identifier: nil) {
|
||||
HashtagTimelineViewController(for: hashtag, mastodonController: self.mastodonController)
|
||||
} actionProvider: { (_) in
|
||||
UIMenu(children: self.actionsForHashtag(hashtag, sourceView: self.collectionView.cellForItem(at: indexPath)))
|
||||
}
|
||||
|
||||
case let .link(card):
|
||||
guard let url = URL(card.url) else {
|
||||
return nil
|
||||
}
|
||||
return UIContextMenuConfiguration {
|
||||
SFSafariViewController(url: url)
|
||||
} actionProvider: { _ in
|
||||
UIMenu(children: self.actionsForTrendingLink(card: card))
|
||||
}
|
||||
|
||||
default:
|
||||
fatalError("todo")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchViewController: UICollectionViewDragDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return []
|
||||
}
|
||||
switch item {
|
||||
case let .tag(hashtag):
|
||||
let provider = NSItemProvider(object: hashtag.url as NSURL)
|
||||
if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: mastodonController.accountInfo!.id) {
|
||||
activity.displaysAuxiliaryScene = true
|
||||
provider.registerObject(activity, visibility: .all)
|
||||
}
|
||||
return [UIDragItem(itemProvider: provider)]
|
||||
|
||||
case let .link(card):
|
||||
guard let url = URL(card.url) else {
|
||||
return []
|
||||
}
|
||||
return [UIDragItem(itemProvider: NSItemProvider(object: url as NSURL))]
|
||||
|
||||
default:
|
||||
fatalError("todo")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchViewController: TuskerNavigationDelegate {
|
||||
var apiController: MastodonController { mastodonController }
|
||||
}
|
||||
|
||||
extension SearchViewController: ToastableViewController {
|
||||
}
|
||||
|
||||
extension SearchViewController: MenuActionProvider {
|
||||
}
|
||||
|
|
|
@ -164,8 +164,7 @@ class TimelineTableViewController: DiffableTimelineLikeTableViewController<Timel
|
|||
return
|
||||
}
|
||||
|
||||
if #available(iOS 15.0, *),
|
||||
Preferences.shared.disableInfiniteScrolling && !didConfirmLoadMore {
|
||||
if Preferences.shared.disableInfiniteScrolling && !didConfirmLoadMore {
|
||||
var snapshot = currentSnapshot()
|
||||
guard !snapshot.itemIdentifiers(inSection: .footer).contains(.confirmLoadMore) else {
|
||||
// todo: need something more accurate than "success"/"failure"
|
||||
|
|
|
@ -59,7 +59,7 @@ extension MenuActionProvider {
|
|||
draft.visibility = .direct
|
||||
self.navigationDelegate?.compose(editing: draft)
|
||||
}),
|
||||
UIDeferredMenuElement.uncachedIfPossible({ (elementHandler) in
|
||||
UIDeferredMenuElement.uncached({ (elementHandler) in
|
||||
Task { @MainActor in
|
||||
if let action = await self.followAction(for: accountID, mastodonController: mastodonController) {
|
||||
elementHandler([action])
|
||||
|
@ -358,7 +358,6 @@ extension MenuActionProvider {
|
|||
}
|
||||
|
||||
private func addOpenInNewWindow(actions: inout [UIAction], activity: @escaping @autoclosure () -> NSUserActivity) {
|
||||
if #available(iOS 15.0, *) {
|
||||
let options = UIWindowScene.ActivationRequestOptions()
|
||||
options.preferredPresentationStyle = .automatic
|
||||
actions.append(UIWindowScene.ActivationAction { (_) in
|
||||
|
@ -366,13 +365,6 @@ extension MenuActionProvider {
|
|||
activity.displaysAuxiliaryScene = true
|
||||
return .init(userActivity: activity, options: options, preview: nil)
|
||||
})
|
||||
} else if UIApplication.shared.supportsMultipleScenes {
|
||||
actions.append(createAction(identifier: "new_window", title: "Open in New Window", systemImageName: "rectangle.badge.plus", handler: { (_) in
|
||||
let activity = activity()
|
||||
activity.displaysAuxiliaryScene = true
|
||||
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: nil, errorHandler: nil)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
private func followAction(for accountID: String, mastodonController: MastodonController) async -> UIMenuElement? {
|
||||
|
@ -423,13 +415,3 @@ extension SFSafariViewController: CustomPreviewPresenting {
|
|||
presenter.present(self, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
private extension UIDeferredMenuElement {
|
||||
static func uncachedIfPossible(_ elementProvider: @escaping (@escaping ([UIMenuElement]) -> Void) -> Void) -> UIDeferredMenuElement {
|
||||
if #available(iOS 15.0, *) {
|
||||
return UIDeferredMenuElement.uncached(elementProvider)
|
||||
} else {
|
||||
return UIDeferredMenuElement(elementProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
//
|
||||
// SplitNavigationController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 7/1/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SplitNavigationController: UIViewController {
|
||||
|
||||
private let rootNav = SplitRootNavigationController()
|
||||
private let secondaryNav = SplitSecondaryNavigationController()
|
||||
private let separatorView = UIView()
|
||||
|
||||
private var constraints: [NSLayoutConstraint] = []
|
||||
|
||||
var viewControllers: [UIViewController] {
|
||||
get {
|
||||
return rootNav.viewControllers + secondaryNav.viewControllers
|
||||
}
|
||||
set {
|
||||
if newValue.isEmpty {
|
||||
rootNav.viewControllers = []
|
||||
secondaryNav.viewControllers = []
|
||||
} else if canShowSecondaryNav {
|
||||
var newValue = newValue
|
||||
rootNav.viewControllers = [newValue.removeFirst()]
|
||||
secondaryNav.viewControllers = newValue
|
||||
} else {
|
||||
rootNav.viewControllers = newValue
|
||||
secondaryNav.viewControllers = []
|
||||
}
|
||||
updateSecondaryNavVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
/// This property is only valid after the view has been laid out.
|
||||
private var canShowSecondaryNav: Bool {
|
||||
// minimum of 360pt for each column
|
||||
// this allows split navigation on all ipads in portrait w/ sidebar hidden and in landscape (regardless of sidebar)
|
||||
(viewIfLoaded?.bounds.width ?? 0) >= 720
|
||||
}
|
||||
|
||||
init(rootViewController: UIViewController? = nil) {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
rootNav.showImpl = { [unowned self] vc, sender in
|
||||
if self.canShowSecondaryNav {
|
||||
self.setSecondaryViewControllers([vc], animated: true)
|
||||
|
||||
// the split nav shouldn't really be reaching down into the inner VCs like this,
|
||||
// but I can't think of a cleaner way
|
||||
if let tableVC = sender as? UITableViewController,
|
||||
let selectedIndexPath = tableVC.tableView.indexPathForSelectedRow {
|
||||
tableVC.tableView.deselectRow(at: selectedIndexPath, animated: true)
|
||||
}
|
||||
} else {
|
||||
self.rootNav.pushViewController(vc, animated: true)
|
||||
}
|
||||
}
|
||||
secondaryNav.closeSecondaryImpl = { [unowned self] in
|
||||
self.popToRootViewController(animated: true)
|
||||
}
|
||||
|
||||
if let rootViewController {
|
||||
rootNav.viewControllers = [rootViewController]
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
embedChild(rootNav, layout: false)
|
||||
embedChild(secondaryNav, layout: false)
|
||||
rootNav.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
secondaryNav.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
separatorView.backgroundColor = .separator
|
||||
separatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(separatorView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
rootNav.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
rootNav.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
rootNav.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
|
||||
separatorView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
separatorView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
separatorView.leadingAnchor.constraint(equalTo: rootNav.view.trailingAnchor),
|
||||
separatorView.widthAnchor.constraint(equalToConstant: 0.5),
|
||||
|
||||
secondaryNav.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
secondaryNav.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
secondaryNav.view.leadingAnchor.constraint(equalTo: separatorView.trailingAnchor),
|
||||
])
|
||||
|
||||
updateSecondaryNavVisibility()
|
||||
}
|
||||
|
||||
override func show(_ vc: UIViewController, sender: Any?) {
|
||||
if !canShowSecondaryNav {
|
||||
rootNav.pushViewController(vc, animated: true)
|
||||
} else if rootNav.viewControllers.isEmpty {
|
||||
rootNav.pushViewController(vc, animated: false)
|
||||
} else {
|
||||
secondaryNav.pushViewController(vc, animated: true)
|
||||
}
|
||||
updateSecondaryNavVisibility()
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
super.viewWillLayoutSubviews()
|
||||
|
||||
if !isLayingOutForAnimation {
|
||||
updateSecondaryNavVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateSecondaryNavVisibility() {
|
||||
guard isViewLoaded else {
|
||||
return
|
||||
}
|
||||
|
||||
if canShowSecondaryNav {
|
||||
if rootNav.viewControllers.count > 1 {
|
||||
var vcs = rootNav.viewControllers
|
||||
let root = vcs.removeFirst()
|
||||
rootNav.viewControllers = [root]
|
||||
// this shouldn't be necessary since the vcs are removed from their parent vc by setting rootNav.viewControllers
|
||||
// but it doesn't remove the views from their superview (until the next runloop iteration?)
|
||||
// so we need to do that ourselves before we can set them on the secondary nav (otherwise it raises an exception)
|
||||
vcs.forEach { $0.removeViewAndController() }
|
||||
secondaryNav.viewControllers = vcs
|
||||
}
|
||||
} else {
|
||||
if !secondaryNav.viewControllers.isEmpty {
|
||||
let firstSecondary = secondaryNav.viewControllers.first!
|
||||
// remove the left bar button item so that the builtin Back item shows
|
||||
if firstSecondary.navigationItem.leftBarButtonItem?.tag == ViewTags.splitNavCloseSecondaryButton {
|
||||
firstSecondary.navigationItem.leftBarButtonItem = nil
|
||||
}
|
||||
rootNav.viewControllers.append(contentsOf: secondaryNav.viewControllers)
|
||||
secondaryNav.viewControllers = []
|
||||
}
|
||||
}
|
||||
|
||||
setSecondaryVisible(canShowSecondaryNav && !secondaryNav.viewControllers.isEmpty)
|
||||
}
|
||||
|
||||
private func setSecondaryVisible(_ visible: Bool) {
|
||||
guard isViewLoaded else {
|
||||
return
|
||||
}
|
||||
|
||||
NSLayoutConstraint.deactivate(constraints)
|
||||
if visible {
|
||||
constraints = [
|
||||
rootNav.view.trailingAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
secondaryNav.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
]
|
||||
} else {
|
||||
constraints = [
|
||||
rootNav.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
secondaryNav.view.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
|
||||
]
|
||||
}
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
|
||||
private func setSecondaryViewControllers(_ vcs: [UIViewController], animated: Bool) {
|
||||
if animated {
|
||||
if vcs.isEmpty {
|
||||
popToRootViewController(animated: true)
|
||||
} else {
|
||||
let wasVisible = !secondaryNav.viewControllers.isEmpty
|
||||
secondaryNav.viewControllers = vcs
|
||||
secondaryNav.view.frame = CGRect(x: view.bounds.width, y: 0, width: view.bounds.width / 2, height: view.bounds.height)
|
||||
secondaryNav.view.layoutIfNeeded()
|
||||
if !wasVisible {
|
||||
let animator = UIViewPropertyAnimator(duration: 0.35, curve: .easeInOut) {
|
||||
self.updateSecondaryNavVisibility()
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
animator.startAnimation()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
secondaryNav.viewControllers = vcs
|
||||
updateSecondaryNavVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
private var isLayingOutForAnimation = false
|
||||
|
||||
func popToRootViewController(animated: Bool) {
|
||||
if animated {
|
||||
// we don't update secondaryNav.viewControllers until after the animation is completed
|
||||
// otherwise the secondary nav's contents disappear immediately, rather than sliding off-screen
|
||||
let animator = UIViewPropertyAnimator(duration: 0.35, curve: .easeInOut) {
|
||||
self.isLayingOutForAnimation = true
|
||||
self.setSecondaryVisible(false)
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
animator.addCompletion { _ in
|
||||
self.secondaryNav.viewControllers = []
|
||||
self.isLayingOutForAnimation = false
|
||||
// self.updateSecondaryNavVisibility()
|
||||
}
|
||||
animator.startAnimation()
|
||||
} else {
|
||||
self.secondaryNav.viewControllers = []
|
||||
self.updateSecondaryNavVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class SplitRootNavigationController: UINavigationController {
|
||||
fileprivate var showImpl: ((UIViewController, Any?) -> Void)!
|
||||
|
||||
override func show(_ vc: UIViewController, sender: Any?) {
|
||||
showImpl(vc, sender)
|
||||
}
|
||||
}
|
||||
|
||||
private class SplitSecondaryNavigationController: EnhancedNavigationViewController {
|
||||
fileprivate var closeSecondaryImpl: (() -> Void)!
|
||||
|
||||
override var viewControllers: [UIViewController] {
|
||||
didSet {
|
||||
if let first = viewControllers.first {
|
||||
configureSecondarySplitCloseButton(for: first)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func configureSecondarySplitCloseButton(for viewController: UIViewController) {
|
||||
guard viewController.navigationItem.leftBarButtonItem?.tag != ViewTags.splitNavCloseSecondaryButton else {
|
||||
return
|
||||
}
|
||||
let item = UIBarButtonItem(title: "Close", style: .done, target: self, action: #selector(closeSecondary))
|
||||
item.tag = ViewTags.splitNavCloseSecondaryButton
|
||||
viewController.navigationItem.leftBarButtonItem = item
|
||||
}
|
||||
|
||||
@objc private func closeSecondary() {
|
||||
closeSecondaryImpl()
|
||||
}
|
||||
|
||||
}
|
|
@ -10,7 +10,7 @@ import UIKit
|
|||
|
||||
// Based on MVCTodo by Dave DeLong: https://github.com/davedelong/MVCTodo/blob/841649dd6aa31bacda3ad7ef9a9a836f66281e50/MVCTodo/Extensions/UIViewController.swift
|
||||
extension UIViewController {
|
||||
func embedChild(_ newChild: UIViewController, in container: UIView? = nil) {
|
||||
func embedChild(_ newChild: UIViewController, in container: UIView? = nil, layout: Bool = true) {
|
||||
// if the view controller is already a child of something else, remove it
|
||||
if let oldParent = newChild.parent, oldParent != self {
|
||||
newChild.beginAppearanceTransition(false, animated: false)
|
||||
|
@ -36,7 +36,7 @@ extension UIViewController {
|
|||
newChild.beginAppearanceTransition(true, animated: false)
|
||||
addChild(newChild)
|
||||
newChild.didMove(toParent: self)
|
||||
targetContainer.embedSubview(newChild.view)
|
||||
targetContainer.embedSubview(newChild.view, layout: layout)
|
||||
newChild.endAppearanceTransition()
|
||||
} else {
|
||||
// the view controller is already a child
|
||||
|
@ -45,7 +45,7 @@ extension UIViewController {
|
|||
// we don't do the appearance transition stuff here,
|
||||
// because the vc is already a child, so *presumably*
|
||||
// that transition stuff has already appened
|
||||
targetContainer.embedSubview(newChild.view)
|
||||
targetContainer.embedSubview(newChild.view, layout: layout)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,16 +57,18 @@ extension UIViewController {
|
|||
|
||||
// Based on MVCTodo by Dave DeLong: https://github.com/davedelong/MVCTodo/blob/841649dd6aa31bacda3ad7ef9a9a836f66281e50/MVCTodo/Extensions/UIView.swift
|
||||
extension UIView {
|
||||
func embedSubview(_ subview: UIView) {
|
||||
func embedSubview(_ subview: UIView, layout: Bool = true) {
|
||||
if subview.superview == self { return }
|
||||
|
||||
if subview.superview != nil {
|
||||
subview.removeFromSuperview()
|
||||
}
|
||||
|
||||
subview.frame = bounds
|
||||
addSubview(subview)
|
||||
|
||||
if layout {
|
||||
subview.frame = bounds
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
subview.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
subview.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
|
@ -74,6 +76,7 @@ extension UIView {
|
|||
subview.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
func isContainedWithin(_ other: UIView) -> Bool {
|
||||
var current: UIView? = self
|
||||
|
|
|
@ -89,7 +89,7 @@ extension TuskerNavigationDelegate {
|
|||
}
|
||||
|
||||
func compose(editing draft: Draft) {
|
||||
if #available(iOS 15.0, *), UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
let compose = UserActivityManager.editDraftActivity(id: draft.id, accountID: apiController.accountInfo!.id)
|
||||
let options = UIWindowScene.ActivationRequestOptions()
|
||||
options.preferredPresentationStyle = .prominent
|
||||
|
|
|
@ -16,4 +16,5 @@ struct ViewTags {
|
|||
static let navBackBarButton = 42003
|
||||
static let navForwardBarButton = 42004
|
||||
static let navEmptyTitleView = 42005
|
||||
static let splitNavCloseSecondaryButton = 42006
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ class ConfirmLoadMoreTableViewCell: UITableViewCell {
|
|||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
var config = UIButton.Configuration.tinted()
|
||||
config.title = "Load More"
|
||||
config.showsActivityIndicator = false
|
||||
|
@ -33,23 +32,18 @@ class ConfirmLoadMoreTableViewCell: UITableViewCell {
|
|||
button.configuration?.showsActivityIndicator = self.isLoading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
isLoading = false
|
||||
if #available(iOS 15.0, *) {
|
||||
confirmButton.setNeedsUpdateConfiguration()
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func loadMorePressed(_ sender: Any) {
|
||||
confirmLoadMore?()
|
||||
if #available(iOS 15.0, *) {
|
||||
isLoading = true
|
||||
confirmButton.setNeedsUpdateConfiguration()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
//
|
||||
// TrendingHashtagCollectionViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 6/29/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class TrendingHashtagCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
private let hashtagLabel = UILabel()
|
||||
private let peopleTodayLabel = UILabel()
|
||||
private let historyView = TrendHistoryView()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
backgroundColor = .systemBackground
|
||||
|
||||
hashtagLabel.font = .preferredFont(forTextStyle: .title2)
|
||||
peopleTodayLabel.font = .preferredFont(forTextStyle: .caption1)
|
||||
|
||||
let vStack = UIStackView(arrangedSubviews: [
|
||||
hashtagLabel,
|
||||
peopleTodayLabel,
|
||||
])
|
||||
vStack.axis = .vertical
|
||||
vStack.alignment = .fill
|
||||
vStack.distribution = .fill
|
||||
vStack.spacing = 0
|
||||
|
||||
let hStack = UIStackView(arrangedSubviews: [
|
||||
vStack,
|
||||
historyView,
|
||||
])
|
||||
hStack.axis = .horizontal
|
||||
hStack.alignment = .center
|
||||
hStack.distribution = .fill
|
||||
hStack.spacing = 8
|
||||
hStack.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(hStack)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
hStack.leadingAnchor.constraint(equalToSystemSpacingAfter: leadingAnchor, multiplier: 1),
|
||||
trailingAnchor.constraint(equalToSystemSpacingAfter: hStack.trailingAnchor, multiplier: 1),
|
||||
hStack.topAnchor.constraint(equalTo: topAnchor, constant: 8),
|
||||
hStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),
|
||||
|
||||
historyView.widthAnchor.constraint(equalToConstant: 100),
|
||||
historyView.heightAnchor.constraint(equalToConstant: 44),
|
||||
])
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func updateUI(hashtag: Hashtag) {
|
||||
hashtagLabel.text = "#\(hashtag.name)"
|
||||
historyView.setHistory(hashtag.history)
|
||||
historyView.isHidden = hashtag.history == nil || hashtag.history!.count < 2
|
||||
|
||||
if let history = hashtag.history {
|
||||
let sorted = history.sorted(by: { $0.day < $1.day })
|
||||
let lastTwo = sorted[(sorted.count - 2)...]
|
||||
let accounts = lastTwo.map(\.accounts).reduce(0, +)
|
||||
let uses = lastTwo.map(\.uses).reduce(0, +)
|
||||
|
||||
let format = NSLocalizedString("trending hashtag info", comment: "trending hashtag posts and people")
|
||||
peopleTodayLabel.text = String.localizedStringWithFormat(format, accounts, uses)
|
||||
peopleTodayLabel.isHidden = false
|
||||
} else {
|
||||
peopleTodayLabel.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
//
|
||||
// TrendingHashtagTableViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/24/21.
|
||||
// Copyright © 2021 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class TrendingHashtagTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet weak var hashtagLabel: UILabel!
|
||||
@IBOutlet weak var peopleTodayLabel: UILabel!
|
||||
@IBOutlet weak var historyView: TrendHistoryView!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
}
|
||||
|
||||
func updateUI(hashtag: Hashtag) {
|
||||
hashtagLabel.text = "#\(hashtag.name)"
|
||||
historyView.setHistory(hashtag.history)
|
||||
historyView.isHidden = hashtag.history == nil || hashtag.history!.count < 2
|
||||
|
||||
if let history = hashtag.history {
|
||||
let sorted = history.sorted(by: { $0.day < $1.day })
|
||||
let lastTwo = sorted[(sorted.count - 2)...]
|
||||
let accounts = lastTwo.map(\.accounts).reduce(0, +)
|
||||
let uses = lastTwo.map(\.uses).reduce(0, +)
|
||||
|
||||
let format = NSLocalizedString("trending hashtag info", comment: "trending hashtag posts and people")
|
||||
peopleTodayLabel.text = String.localizedStringWithFormat(format, accounts, uses)
|
||||
peopleTodayLabel.isHidden = false
|
||||
} else {
|
||||
peopleTodayLabel.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="KGk-i7-Jjw" customClass="TrendingHashtagTableViewCell" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="66"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="66"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="tEP-en-vHK">
|
||||
<rect key="frame" x="16" y="0.0" width="288" height="66"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="iCc-do-llt">
|
||||
<rect key="frame" x="0.0" y="15" width="180" height="36.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="#hashtag" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SIS-9e-Paj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="180" height="23"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="6 people today" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Kc5-BL-bmu">
|
||||
<rect key="frame" x="0.0" y="23" width="180" height="13.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Xrw-2v-ybZ" customClass="TrendHistoryView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="188" y="11" width="100" height="44"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="W4C-uw-zWg"/>
|
||||
<constraint firstAttribute="width" constant="100" id="XHb-vd-qNk"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="tEP-en-vHK" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="3Qd-rF-nGk"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="tEP-en-vHK" secondAttribute="trailing" id="Ws6-oZ-9Es"/>
|
||||
<constraint firstItem="tEP-en-vHK" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="if4-Ea-awg"/>
|
||||
<constraint firstAttribute="bottom" secondItem="tEP-en-vHK" secondAttribute="bottom" id="nTV-Ih-vTj"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||
<connections>
|
||||
<outlet property="hashtagLabel" destination="SIS-9e-Paj" id="1UK-Va-3rL"/>
|
||||
<outlet property="historyView" destination="Xrw-2v-ybZ" id="OIh-K9-gSk"/>
|
||||
<outlet property="peopleTodayLabel" destination="Kc5-BL-bmu" id="5L8-aO-zt4"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="132" y="132"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
|
@ -24,11 +24,7 @@ class PublicTimelineDescriptionTableViewCell: UITableViewCell {
|
|||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
contentView.backgroundColor = .tintColor
|
||||
} else {
|
||||
contentView.backgroundColor = .systemBlue
|
||||
}
|
||||
}
|
||||
|
||||
private func updateLabel() {
|
||||
|
|
|
@ -42,7 +42,10 @@ class TrendHistoryView: UIView {
|
|||
|
||||
private func createLayers() {
|
||||
guard let history = history,
|
||||
history.count >= 2 else { return }
|
||||
history.count >= 2,
|
||||
!bounds.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
let maxUses = history.max(by: { $0.uses < $1.uses })!.uses
|
||||
|
||||
|
|
Loading…
Reference in New Issue