Compare commits
No commits in common. "5b70c713b2829a39332c7e33f01f55566e6f32fb" and "cc10a1378542382d0b5eb85d316c57c1a53ae76f" have entirely different histories.
5b70c713b2
...
cc10a13785
|
@ -39,9 +39,3 @@ extension Emoji: CustomDebugStringConvertible {
|
||||||
return ":\(shortcode):"
|
return ":\(shortcode):"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Emoji: Equatable {
|
|
||||||
public static func ==(lhs: Emoji, rhs: Emoji) -> Bool {
|
|
||||||
return lhs.shortcode == rhs.shortcode && lhs.url == rhs.url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
||||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
||||||
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.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 */; };
|
D6093FB725BE0CF3004811E6 /* TrendHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */; };
|
||||||
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D60CFFDA24A290BA00D00083 /* SwiftSoup */; };
|
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D60CFFDA24A290BA00D00083 /* SwiftSoup */; };
|
||||||
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
|
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
|
||||||
|
@ -219,11 +221,13 @@
|
||||||
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4423216AF800E5038B /* FollowAccountActivity.swift */; };
|
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4423216AF800E5038B /* FollowAccountActivity.swift */; };
|
||||||
D6AEBB4823216B1D00E5038B /* AccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4723216B1D00E5038B /* AccountActivity.swift */; };
|
D6AEBB4823216B1D00E5038B /* AccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4723216B1D00E5038B /* AccountActivity.swift */; };
|
||||||
D6AEBB4A23216F0400E5038B /* UnfollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4923216F0400E5038B /* UnfollowAccountActivity.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 */; };
|
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A123BD2C0600A066FA /* AssetPickerViewController.swift */; };
|
||||||
D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */; };
|
D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */; };
|
||||||
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */; };
|
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */; };
|
||||||
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */; };
|
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */; };
|
||||||
D6B053AC23BD2F1400A066FA /* AssetCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */; };
|
D6B053AC23BD2F1400A066FA /* AssetCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */; };
|
||||||
|
D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053AD23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift */; };
|
||||||
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B17254254F88B800128392 /* OppositeCollapseKeywordsView.swift */; };
|
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B17254254F88B800128392 /* OppositeCollapseKeywordsView.swift */; };
|
||||||
D6B22A0F2560D52D004D82EF /* TabbedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B22A0E2560D52D004D82EF /* TabbedPageViewController.swift */; };
|
D6B22A0F2560D52D004D82EF /* TabbedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B22A0E2560D52D004D82EF /* TabbedPageViewController.swift */; };
|
||||||
D6B30E09254BAF63009CAEE5 /* ImageGrayscalifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */; };
|
D6B30E09254BAF63009CAEE5 /* ImageGrayscalifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */; };
|
||||||
|
@ -278,6 +282,7 @@
|
||||||
D6DFC69E242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DFC69D242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift */; };
|
D6DFC69E242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DFC69D242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift */; };
|
||||||
D6DFC6A0242C4CCC00ACC392 /* WeakArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */; };
|
D6DFC6A0242C4CCC00ACC392 /* WeakArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */; };
|
||||||
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0DC8D216EDF1E00369478 /* Previewing.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 */; };
|
D6E343AB265AAD6B00C4AA01 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AA265AAD6B00C4AA01 /* Media.xcassets */; };
|
||||||
D6E343AD265AAD6B00C4AA01 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */; };
|
D6E343AD265AAD6B00C4AA01 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */; };
|
||||||
D6E343B0265AAD6B00C4AA01 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AE265AAD6B00C4AA01 /* MainInterface.storyboard */; };
|
D6E343B0265AAD6B00C4AA01 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AE265AAD6B00C4AA01 /* MainInterface.storyboard */; };
|
||||||
|
@ -288,10 +293,6 @@
|
||||||
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */; };
|
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */; };
|
||||||
D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */; };
|
D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */; };
|
||||||
D6E57FA325C26FAB00341037 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = D6E57FA525C26FAB00341037 /* Localizable.stringsdict */; };
|
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 */; };
|
D6E9CDA8281A427800BBC98E /* PostService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E9CDA7281A427800BBC98E /* PostService.swift */; };
|
||||||
D6EAE0DB2550CC8A002DB0AC /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.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 */; };
|
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */; };
|
||||||
|
@ -366,6 +367,8 @@
|
||||||
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
||||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
||||||
D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagSearchResultsViewController.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>"; };
|
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>"; };
|
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>"; };
|
D60E2F232442372B005F8713 /* StatusMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMO.swift; sourceTree = "<group>"; };
|
||||||
|
@ -570,6 +573,7 @@
|
||||||
D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewController.swift; sourceTree = "<group>"; };
|
D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewController.swift; sourceTree = "<group>"; };
|
||||||
D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AssetCollectionViewCell.xib; sourceTree = "<group>"; };
|
D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AssetCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||||
|
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>"; };
|
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>"; };
|
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>"; };
|
D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGrayscalifier.swift; sourceTree = "<group>"; };
|
||||||
|
@ -630,6 +634,7 @@
|
||||||
D6DFC69D242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackpadScrollGestureRecognizer.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -643,10 +648,6 @@
|
||||||
D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiImageView.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Scenes.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -666,6 +667,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */,
|
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */,
|
||||||
|
D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */,
|
||||||
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */,
|
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */,
|
||||||
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */,
|
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */,
|
||||||
D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */,
|
D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */,
|
||||||
|
@ -714,7 +716,8 @@
|
||||||
children = (
|
children = (
|
||||||
D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */,
|
D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */,
|
||||||
D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */,
|
D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */,
|
||||||
D6E77D08286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift */,
|
D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */,
|
||||||
|
D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */,
|
||||||
);
|
);
|
||||||
path = "Hashtag Cell";
|
path = "Hashtag Cell";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -800,8 +803,6 @@
|
||||||
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */,
|
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */,
|
||||||
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */,
|
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */,
|
||||||
D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */,
|
D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */,
|
||||||
D6E77D0A286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift */,
|
|
||||||
D6E77D0C286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib */,
|
|
||||||
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */,
|
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */,
|
||||||
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */,
|
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */,
|
||||||
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */,
|
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */,
|
||||||
|
@ -1122,6 +1123,7 @@
|
||||||
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */,
|
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */,
|
||||||
D69693F32585941A00F4E116 /* UIWindowSceneDelegate+Close.swift */,
|
D69693F32585941A00F4E116 /* UIWindowSceneDelegate+Close.swift */,
|
||||||
D62E9984279CA23900C26176 /* URLSession+Development.swift */,
|
D62E9984279CA23900C26176 /* URLSession+Development.swift */,
|
||||||
|
D6E1EEF3285443EF00D20549 /* UIAction+Subtitle.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1240,6 +1242,7 @@
|
||||||
D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */,
|
D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */,
|
||||||
D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */,
|
D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */,
|
||||||
D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */,
|
D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */,
|
||||||
|
D6B053AD23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift */,
|
||||||
);
|
);
|
||||||
path = "Asset Picker";
|
path = "Asset Picker";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1300,7 +1303,6 @@
|
||||||
D653F410267D1E32004E32B1 /* DiffableTimelineLikeTableViewController.swift */,
|
D653F410267D1E32004E32B1 /* DiffableTimelineLikeTableViewController.swift */,
|
||||||
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */,
|
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */,
|
||||||
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */,
|
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */,
|
||||||
D6E77D0E286F773900D8B732 /* SplitNavigationController.swift */,
|
|
||||||
D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */,
|
D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */,
|
||||||
D693DE5823FE24300061E07D /* InteractivePushTransition.swift */,
|
D693DE5823FE24300061E07D /* InteractivePushTransition.swift */,
|
||||||
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */,
|
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */,
|
||||||
|
@ -1502,6 +1504,7 @@
|
||||||
);
|
);
|
||||||
name = Tusker;
|
name = Tusker;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
|
D6B0539E23BD2BA300A066FA /* SheetController */,
|
||||||
D69CCBBE249E6EFD000AF167 /* CrashReporter */,
|
D69CCBBE249E6EFD000AF167 /* CrashReporter */,
|
||||||
D60CFFDA24A290BA00D00083 /* SwiftSoup */,
|
D60CFFDA24A290BA00D00083 /* SwiftSoup */,
|
||||||
D6676CA427A8D0020052936B /* WebURLFoundationExtras */,
|
D6676CA427A8D0020052936B /* WebURLFoundationExtras */,
|
||||||
|
@ -1611,6 +1614,7 @@
|
||||||
);
|
);
|
||||||
mainGroup = D6D4DDC3212518A000E1C4BB;
|
mainGroup = D6D4DDC3212518A000E1C4BB;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
|
D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */,
|
||||||
D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */,
|
D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */,
|
||||||
D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
||||||
D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */,
|
D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */,
|
||||||
|
@ -1633,7 +1637,6 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */,
|
D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */,
|
||||||
D6E77D0D286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib in Resources */,
|
|
||||||
D693A73025CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib in Resources */,
|
D693A73025CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib in Resources */,
|
||||||
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */,
|
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */,
|
||||||
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */,
|
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */,
|
||||||
|
@ -1650,6 +1653,7 @@
|
||||||
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
|
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
|
||||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
|
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
|
||||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
||||||
|
D6093FB125BE0B01004811E6 /* TrendingHashtagTableViewCell.xib in Resources */,
|
||||||
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
||||||
D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */,
|
D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */,
|
||||||
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */,
|
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */,
|
||||||
|
@ -1760,6 +1764,7 @@
|
||||||
D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */,
|
D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */,
|
||||||
D62E9989279DB2D100C26176 /* InstanceFeatures.swift in Sources */,
|
D62E9989279DB2D100C26176 /* InstanceFeatures.swift in Sources */,
|
||||||
D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */,
|
D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */,
|
||||||
|
D6E1EEF4285443EF00D20549 /* UIAction+Subtitle.swift in Sources */,
|
||||||
D69693FA25859A8000F4E116 /* ComposeSceneDelegate.swift in Sources */,
|
D69693FA25859A8000F4E116 /* ComposeSceneDelegate.swift in Sources */,
|
||||||
D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */,
|
D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */,
|
||||||
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */,
|
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */,
|
||||||
|
@ -1807,7 +1812,6 @@
|
||||||
D662AEF2263A4BE10082A153 /* ComposePollView.swift in Sources */,
|
D662AEF2263A4BE10082A153 /* ComposePollView.swift in Sources */,
|
||||||
D677284A24ECBDF400C732D3 /* ComposeCurrentAccount.swift in Sources */,
|
D677284A24ECBDF400C732D3 /* ComposeCurrentAccount.swift in Sources */,
|
||||||
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */,
|
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */,
|
||||||
D6E77D0F286F773900D8B732 /* SplitNavigationController.swift in Sources */,
|
|
||||||
D6B30E09254BAF63009CAEE5 /* ImageGrayscalifier.swift in Sources */,
|
D6B30E09254BAF63009CAEE5 /* ImageGrayscalifier.swift in Sources */,
|
||||||
D6A6C10F25B62D2400298D0F /* DiskCache.swift in Sources */,
|
D6A6C10F25B62D2400298D0F /* DiskCache.swift in Sources */,
|
||||||
D6B81F3C2560365300F6E31D /* RefreshableViewController.swift in Sources */,
|
D6B81F3C2560365300F6E31D /* RefreshableViewController.swift in Sources */,
|
||||||
|
@ -1853,6 +1857,7 @@
|
||||||
D6EAE0DB2550CC8A002DB0AC /* FocusableTextField.swift in Sources */,
|
D6EAE0DB2550CC8A002DB0AC /* FocusableTextField.swift in Sources */,
|
||||||
D623A53D2635F5590095BD04 /* StatusPollView.swift in Sources */,
|
D623A53D2635F5590095BD04 /* StatusPollView.swift in Sources */,
|
||||||
D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */,
|
D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */,
|
||||||
|
D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */,
|
||||||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
||||||
D6E9CDA8281A427800BBC98E /* PostService.swift in Sources */,
|
D6E9CDA8281A427800BBC98E /* PostService.swift in Sources */,
|
||||||
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */,
|
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */,
|
||||||
|
@ -1883,6 +1888,7 @@
|
||||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
||||||
D6945C2F23AC47C3005C403C /* SavedDataManager.swift in Sources */,
|
D6945C2F23AC47C3005C403C /* SavedDataManager.swift in Sources */,
|
||||||
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
||||||
|
D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */,
|
||||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
||||||
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
||||||
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */,
|
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */,
|
||||||
|
@ -1923,7 +1929,6 @@
|
||||||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
||||||
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
|
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
|
||||||
D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */,
|
D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */,
|
||||||
D6E77D0B286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift in Sources */,
|
|
||||||
D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */,
|
D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */,
|
||||||
D670F8B62537DC890046588A /* EmojiPickerWrapper.swift in Sources */,
|
D670F8B62537DC890046588A /* EmojiPickerWrapper.swift in Sources */,
|
||||||
D6B81F442560390300F6E31D /* MenuController.swift in Sources */,
|
D6B81F442560390300F6E31D /* MenuController.swift in Sources */,
|
||||||
|
@ -1967,7 +1972,6 @@
|
||||||
D6C99FCB24FADC91005C74D3 /* MainComposeTextView.swift in Sources */,
|
D6C99FCB24FADC91005C74D3 /* MainComposeTextView.swift in Sources */,
|
||||||
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */,
|
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */,
|
||||||
D6E4267725327FB400C02E1C /* ComposeAutocompleteView.swift in Sources */,
|
D6E4267725327FB400C02E1C /* ComposeAutocompleteView.swift in Sources */,
|
||||||
D6E77D09286D25FA00D8B732 /* TrendingHashtagCollectionViewCell.swift in Sources */,
|
|
||||||
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */,
|
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */,
|
||||||
D677284E24ECC01D00C732D3 /* Draft.swift in Sources */,
|
D677284E24ECC01D00C732D3 /* Draft.swift in Sources */,
|
||||||
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
|
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
|
||||||
|
@ -2201,7 +2205,7 @@
|
||||||
CURRENT_PROJECT_VERSION = 31;
|
CURRENT_PROJECT_VERSION = 31;
|
||||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||||
INFOPLIST_FILE = Tusker/Info.plist;
|
INFOPLIST_FILE = Tusker/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.3;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -2231,7 +2235,7 @@
|
||||||
CURRENT_PROJECT_VERSION = 31;
|
CURRENT_PROJECT_VERSION = 31;
|
||||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||||
INFOPLIST_FILE = Tusker/Info.plist;
|
INFOPLIST_FILE = Tusker/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.3;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -2459,6 +2463,14 @@
|
||||||
minimumVersion = 1.8.0;
|
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 */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
@ -2481,6 +2493,11 @@
|
||||||
package = D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */;
|
package = D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */;
|
||||||
productName = CrashReporter;
|
productName = CrashReporter;
|
||||||
};
|
};
|
||||||
|
D6B0539E23BD2BA300A066FA /* SheetController */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */;
|
||||||
|
productName = SheetController;
|
||||||
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
|
||||||
/* Begin XCVersionGroup section */
|
/* Begin XCVersionGroup section */
|
||||||
|
|
|
@ -99,11 +99,6 @@
|
||||||
value = "1"
|
value = "1"
|
||||||
isEnabled = "NO">
|
isEnabled = "NO">
|
||||||
</EnvironmentVariable>
|
</EnvironmentVariable>
|
||||||
<EnvironmentVariable
|
|
||||||
key = "CG_NUMERICS_SHOW_BACKTRACE"
|
|
||||||
value = ""
|
|
||||||
isEnabled = "NO">
|
|
||||||
</EnvironmentVariable>
|
|
||||||
<EnvironmentVariable
|
<EnvironmentVariable
|
||||||
key = "DEBUG_BLUR_HASH"
|
key = "DEBUG_BLUR_HASH"
|
||||||
value = "1"
|
value = "1"
|
||||||
|
|
|
@ -41,14 +41,20 @@ class ImageCache {
|
||||||
let wrappedCompletion: ((Data?, UIImage?) -> Void)?
|
let wrappedCompletion: ((Data?, UIImage?) -> Void)?
|
||||||
if let completion = completion {
|
if let completion = completion {
|
||||||
wrappedCompletion = { (data, image) in
|
wrappedCompletion = { (data, image) in
|
||||||
if !loadOriginal,
|
if #available(iOS 15.0, *) {
|
||||||
let size = self.desiredPixelSize {
|
if !loadOriginal,
|
||||||
image?.prepareThumbnail(of: size, completionHandler: {
|
let size = self.desiredPixelSize {
|
||||||
completion(data, $0)
|
image?.prepareThumbnail(of: size, completionHandler: {
|
||||||
})
|
completion(data, $0)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
image?.prepareForDisplay {
|
||||||
|
completion(data, $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
image?.prepareForDisplay {
|
self.backgroundQueue.async {
|
||||||
completion(data, $0)
|
completion(data, image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ class MastodonController: ObservableObject {
|
||||||
@Published private(set) var instanceFeatures = InstanceFeatures()
|
@Published private(set) var instanceFeatures = InstanceFeatures()
|
||||||
private(set) var customEmojis: [Emoji]?
|
private(set) var customEmojis: [Emoji]?
|
||||||
|
|
||||||
private var pendingOwnInstanceRequestCallbacks = [(Result<Instance, Client.Error>) -> Void]()
|
private var pendingOwnInstanceRequestCallbacks = [(Instance) -> Void]()
|
||||||
private var ownInstanceRequest: URLSessionTask?
|
private var ownInstanceRequest: URLSessionTask?
|
||||||
|
|
||||||
var loggedIn: Bool {
|
var loggedIn: Bool {
|
||||||
|
@ -159,28 +159,15 @@ class MastodonController: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOwnInstance(completion: ((Instance) -> Void)? = nil) {
|
func getOwnInstance(completion: ((Instance) -> Void)? = nil) {
|
||||||
getOwnInstanceInternal(retryAttempt: 0) {
|
getOwnInstanceInternal(retryAttempt: 0, completion: completion)
|
||||||
if case let .success(instance) = $0 {
|
|
||||||
completion?(instance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
private func getOwnInstanceInternal(retryAttempt: Int, completion: ((Instance) -> Void)?) {
|
||||||
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
|
// this is main thread only to prevent concurrent access to ownInstanceRequest and pendingOwnInstanceRequestCallbacks
|
||||||
assert(Thread.isMainThread)
|
assert(Thread.isMainThread)
|
||||||
|
|
||||||
if let instance = self.instance {
|
if let instance = self.instance {
|
||||||
completion?(.success(instance))
|
completion?(instance)
|
||||||
} else {
|
} else {
|
||||||
if let completion = completion {
|
if let completion = completion {
|
||||||
pendingOwnInstanceRequestCallbacks.append(completion)
|
pendingOwnInstanceRequestCallbacks.append(completion)
|
||||||
|
@ -190,7 +177,7 @@ class MastodonController: ObservableObject {
|
||||||
let request = Client.getInstance()
|
let request = Client.getInstance()
|
||||||
ownInstanceRequest = run(request) { (response) in
|
ownInstanceRequest = run(request) { (response) in
|
||||||
switch response {
|
switch response {
|
||||||
case .failure(let error):
|
case .failure(_):
|
||||||
let delay: DispatchTimeInterval
|
let delay: DispatchTimeInterval
|
||||||
switch retryAttempt {
|
switch retryAttempt {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -203,10 +190,6 @@ class MastodonController: ObservableObject {
|
||||||
delay = .seconds(60)
|
delay = .seconds(60)
|
||||||
default:
|
default:
|
||||||
// if we've failed four times, just give up :/
|
// if we've failed four times, just give up :/
|
||||||
for completion in self.pendingOwnInstanceRequestCallbacks {
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
self.pendingOwnInstanceRequestCallbacks = []
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||||
|
@ -221,7 +204,7 @@ class MastodonController: ObservableObject {
|
||||||
self.instanceFeatures.update(instance: instance, nodeInfo: self.nodeInfo)
|
self.instanceFeatures.update(instance: instance, nodeInfo: self.nodeInfo)
|
||||||
|
|
||||||
for completion in self.pendingOwnInstanceRequestCallbacks {
|
for completion in self.pendingOwnInstanceRequestCallbacks {
|
||||||
completion(.success(instance))
|
completion(instance)
|
||||||
}
|
}
|
||||||
self.pendingOwnInstanceRequestCallbacks = []
|
self.pendingOwnInstanceRequestCallbacks = []
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ struct MenuController {
|
||||||
let data: Any
|
let data: Any
|
||||||
if case let .tab(tab) = item {
|
if case let .tab(tab) = item {
|
||||||
data = tab.rawValue
|
data = tab.rawValue
|
||||||
} else if case .explore = item {
|
} else if case .search = item {
|
||||||
data = "search"
|
data = "search"
|
||||||
} else if case .bookmarks = item {
|
} else if case .bookmarks = item {
|
||||||
data = "bookmarks"
|
data = "bookmarks"
|
||||||
|
@ -42,7 +42,7 @@ struct MenuController {
|
||||||
static let sidebarItemKeyCommands: [UIKeyCommand] = [
|
static let sidebarItemKeyCommands: [UIKeyCommand] = [
|
||||||
sidebarCommand(item: .tab(.timelines), command: "1"),
|
sidebarCommand(item: .tab(.timelines), command: "1"),
|
||||||
sidebarCommand(item: .tab(.notifications), command: "2"),
|
sidebarCommand(item: .tab(.notifications), command: "2"),
|
||||||
sidebarCommand(item: .explore, command: "3"),
|
sidebarCommand(item: .search, command: "3"),
|
||||||
sidebarCommand(item: .bookmarks, command: "4"),
|
sidebarCommand(item: .bookmarks, command: "4"),
|
||||||
sidebarCommand(item: .tab(.myProfile), command: "5"),
|
sidebarCommand(item: .tab(.myProfile), command: "5"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// 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,10 +34,6 @@ struct InstanceFeatures {
|
||||||
instanceType != .pixelfed
|
instanceType != .pixelfed
|
||||||
}
|
}
|
||||||
|
|
||||||
var trends: Bool {
|
|
||||||
instanceType == .mastodon
|
|
||||||
}
|
|
||||||
|
|
||||||
var trendingStatusesAndLinks: Bool {
|
var trendingStatusesAndLinks: Bool {
|
||||||
instanceType == .mastodon && version != nil && version! >= Version(3, 5, 0)
|
instanceType == .mastodon && version != nil && version! >= Version(3, 5, 0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
//
|
||||||
|
// 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,9 +37,15 @@ struct ComposeAttachmentRow: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(action: self.removeAttachment) {
|
if #available(iOS 15.0, *) {
|
||||||
Label("Delete", systemImage: "trash")
|
Button(action: self.removeAttachment) {
|
||||||
}.foregroundStyle(.red)
|
Label("Delete", systemImage: "trash")
|
||||||
|
}.foregroundStyle(.red)
|
||||||
|
} else {
|
||||||
|
Button(action: self.removeAttachment) {
|
||||||
|
Label("Delete", systemImage: "trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
} previewIfAvailable: {
|
} previewIfAvailable: {
|
||||||
ComposeAttachmentImage(attachment: attachment, fullSize: true)
|
ComposeAttachmentImage(attachment: attachment, fullSize: true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,6 +191,16 @@ struct ComposeAttachmentsList: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate extension 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)
|
@available(iOS, obsoleted: 16.0)
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func sheetOrPopover(isPresented: Binding<Bool>, @ViewBuilder content: @escaping () -> some View) -> some View {
|
func sheetOrPopover(isPresented: Binding<Bool>, @ViewBuilder content: @escaping () -> some View) -> some View {
|
||||||
|
|
|
@ -25,6 +25,8 @@ struct ComposeAutocompleteView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
suggestionsView
|
suggestionsView
|
||||||
|
// animate changes of the scroll view items
|
||||||
|
.animation(.default)
|
||||||
.background(backgroundColor)
|
.background(backgroundColor)
|
||||||
.overlay(borderColor.frame(height: 0.5), alignment: .top)
|
.overlay(borderColor.frame(height: 0.5), alignment: .top)
|
||||||
}
|
}
|
||||||
|
@ -83,8 +85,8 @@ struct ComposeAutocompleteMentionsView: View {
|
||||||
}
|
}
|
||||||
.frame(height: 30)
|
.frame(height: 30)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
|
.animation(.linear(duration: 0.1))
|
||||||
}
|
}
|
||||||
.animation(.linear(duration: 0.1), value: accounts)
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
@ -165,7 +167,7 @@ struct ComposeAutocompleteMentionsView: View {
|
||||||
.map(\.0)
|
.map(\.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum EitherAccount: Equatable {
|
private enum EitherAccount {
|
||||||
case pachyderm(Account)
|
case pachyderm(Account)
|
||||||
case coreData(AccountMO)
|
case coreData(AccountMO)
|
||||||
|
|
||||||
|
@ -195,10 +197,6 @@ struct ComposeAutocompleteMentionsView: View {
|
||||||
return account.avatar
|
return account.avatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: EitherAccount, rhs: EitherAccount) -> Bool {
|
|
||||||
return lhs.id == rhs.id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +212,7 @@ struct ComposeAutocompleteEmojisView: View {
|
||||||
HStack(alignment: expanded ? .top : .center, spacing: 0) {
|
HStack(alignment: expanded ? .top : .center, spacing: 0) {
|
||||||
if case let .emoji(query) = uiState.autocompleteState {
|
if case let .emoji(query) = uiState.autocompleteState {
|
||||||
emojiList(query: query)
|
emojiList(query: query)
|
||||||
.animation(.default, value: expanded)
|
.animation(.default)
|
||||||
.transition(.move(edge: .bottom))
|
.transition(.move(edge: .bottom))
|
||||||
} else {
|
} else {
|
||||||
// when the autocomplete view is animating out, the autocomplete state is nil
|
// when the autocomplete view is animating out, the autocomplete state is nil
|
||||||
|
@ -261,8 +259,8 @@ struct ComposeAutocompleteEmojisView: View {
|
||||||
}
|
}
|
||||||
.frame(height: 30)
|
.frame(height: 30)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
|
.animation(.linear(duration: 0.2))
|
||||||
}
|
}
|
||||||
.animation(.linear(duration: 0.2), value: emojis)
|
|
||||||
|
|
||||||
Spacer(minLength: 30)
|
Spacer(minLength: 30)
|
||||||
}
|
}
|
||||||
|
@ -321,8 +319,8 @@ struct ComposeAutocompleteHashtagsView: View {
|
||||||
}
|
}
|
||||||
.frame(height: 30)
|
.frame(height: 30)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
|
.animation(.linear(duration: 0.1))
|
||||||
}
|
}
|
||||||
.animation(.linear(duration: 0.1), value: hashtags)
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
|
@ -339,14 +339,24 @@ extension ComposeHostingController: ComposeUIStateDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentAssetPickerSheet() {
|
func presentAssetPickerSheet() {
|
||||||
let picker = AssetPickerViewController()
|
if #available(iOS 15.0, *) {
|
||||||
picker.assetPickerDelegate = self
|
let picker = AssetPickerViewController()
|
||||||
picker.modalPresentationStyle = .pageSheet
|
picker.assetPickerDelegate = self
|
||||||
picker.overrideUserInterfaceStyle = .dark
|
picker.modalPresentationStyle = .pageSheet
|
||||||
let sheet = picker.sheetPresentationController!
|
picker.overrideUserInterfaceStyle = .dark
|
||||||
sheet.detents = [.medium(), .large()]
|
let sheet = picker.sheetPresentationController!
|
||||||
sheet.prefersEdgeAttachedInCompactHeight = true
|
sheet.detents = [.medium(), .large()]
|
||||||
self.present(picker, animated: true)
|
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() {
|
func presentComposeDrawing() {
|
||||||
|
|
|
@ -60,9 +60,7 @@ struct ComposePollView: View {
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
// use .animation(nil) on pickers so frame doesn't have a size change animation when the text changes
|
// use .animation(nil) on pickers so frame doesn't have a size change animation when the text changes
|
||||||
// this is deprecated in iOS 15, but using .animation(nil, value: poll.multiple) does not work (it still animates)
|
Picker(selection: $poll.multiple, label: Text(poll.multiple ? "Allow multiple choices" : "Single choice")) {
|
||||||
// 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("Allow multiple choices").tag(true)
|
||||||
Text("Single choice").tag(false)
|
Text("Single choice").tag(false)
|
||||||
}
|
}
|
||||||
|
@ -156,7 +154,8 @@ struct ComposePollOption: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
Checkbox(radiusFraction: poll.multiple ? 0.1 : 0.5, borderWidth: 2)
|
Checkbox(radiusFraction: poll.multiple ? 0.1 : 0.5, borderWidth: 2)
|
||||||
.animation(.default, value: poll.multiple)
|
.animation(.default)
|
||||||
|
|
||||||
|
|
||||||
textField
|
textField
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ extension ComposeUIState {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComposeUIState {
|
extension ComposeUIState {
|
||||||
enum AutocompleteState: Equatable {
|
enum AutocompleteState {
|
||||||
case mention(String)
|
case mention(String)
|
||||||
case emoji(String)
|
case emoji(String)
|
||||||
case hashtag(String)
|
case hashtag(String)
|
||||||
|
|
|
@ -119,7 +119,7 @@ struct ComposeView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.transition(.move(edge: .bottom))
|
.transition(.move(edge: .bottom))
|
||||||
.animation(.default, value: uiState.autocompleteState)
|
.animation(.default)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainStack(outerMinY: CGFloat) -> some View {
|
func mainStack(outerMinY: CGFloat) -> some View {
|
||||||
|
@ -147,6 +147,7 @@ struct ComposeView: View {
|
||||||
if let poll = draft.poll {
|
if let poll = draft.poll {
|
||||||
ComposePollView(draft: draft, poll: 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))))
|
.transition(.opacity.combined(with: .asymmetric(insertion: .scale(scale: 0.5, anchor: .leading), removal: .scale(scale: 0.5, anchor: .trailing))))
|
||||||
|
.animation(.default)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,8 +124,13 @@ class ConversationTableViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
visibilityBarButtonItem = UIBarButtonItem(image: ConversationTableViewController.showPostsImage, style: .plain, target: self, action: #selector(toggleVisibilityButtonPressed))
|
if #available(iOS 15.0, *) {
|
||||||
visibilityBarButtonItem.isSelected = showStatusesAutomatically
|
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
|
navigationItem.rightBarButtonItem = visibilityBarButtonItem
|
||||||
// disable transparent background when scroll to top because it looks weird when items earlier in the thread load in
|
// 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)
|
// (it remains transparent slightly too long, resulting in a flash of the content under the transparent bar)
|
||||||
|
@ -391,7 +396,15 @@ class ConversationTableViewController: EnhancedTableViewController {
|
||||||
tableView.beginUpdates()
|
tableView.beginUpdates()
|
||||||
tableView.endUpdates()
|
tableView.endUpdates()
|
||||||
|
|
||||||
visibilityBarButtonItem.isSelected = showStatusesAutomatically
|
if #available(iOS 15.0, *) {
|
||||||
|
visibilityBarButtonItem.isSelected = showStatusesAutomatically
|
||||||
|
} else {
|
||||||
|
if showStatusesAutomatically {
|
||||||
|
visibilityBarButtonItem.image = ConversationTableViewController.hidePostsImage
|
||||||
|
} else {
|
||||||
|
visibilityBarButtonItem.image = ConversationTableViewController.showPostsImage
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,20 +9,19 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
class AddSavedHashtagViewController: UIViewController {
|
class AddSavedHashtagViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
weak var mastodonController: MastodonController!
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
var resultsController: SearchResultsViewController!
|
var resultsController: SearchResultsViewController!
|
||||||
var searchController: UISearchController!
|
var searchController: UISearchController!
|
||||||
|
|
||||||
private var collectionView: UICollectionView!
|
var dataSource: UITableViewDiffableDataSource<Section, Item>!
|
||||||
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
|
||||||
|
|
||||||
init(mastodonController: MastodonController) {
|
init(mastodonController: MastodonController) {
|
||||||
self.mastodonController = mastodonController
|
self.mastodonController = mastodonController
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(style: .grouped)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
|
@ -34,38 +33,18 @@ class AddSavedHashtagViewController: UIViewController {
|
||||||
|
|
||||||
title = NSLocalizedString("Search", comment: "search screen title")
|
title = NSLocalizedString("Search", comment: "search screen title")
|
||||||
|
|
||||||
view.backgroundColor = .systemGroupedBackground
|
tableView.register(UINib(nibName: "TrendingHashtagTableViewCell", bundle: .main), forCellReuseIdentifier: "trendingTagCell")
|
||||||
|
tableView.rowHeight = 60 // 44 for content + 2 * 8 spacing
|
||||||
var config = UICollectionLayoutListConfiguration(appearance: .grouped)
|
|
||||||
config.headerMode = .supplementary
|
dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in
|
||||||
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 {
|
switch item {
|
||||||
case let .tag(hashtag):
|
case let .tag(hashtag):
|
||||||
return collectionView.dequeueConfiguredReusableCell(using: registration, for: indexPath, item: hashtag)
|
let cell = tableView.dequeueReusableCell(withIdentifier: "trendingTagCell", for: indexPath) as! TrendingHashtagTableViewCell
|
||||||
|
cell.updateUI(hashtag: hashtag)
|
||||||
|
return cell
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
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 = HashtagSearchResultsViewController(mastodonController: mastodonController)
|
||||||
resultsController.delegate = self
|
resultsController.delegate = self
|
||||||
resultsController.exploreNavigationController = self.navigationController!
|
resultsController.exploreNavigationController = self.navigationController!
|
||||||
|
@ -113,6 +92,17 @@ class AddSavedHashtagViewController: UIViewController {
|
||||||
presentingViewController!.dismiss(animated: true)
|
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
|
// MARK: - Interaction
|
||||||
|
|
||||||
@objc func cancelButtonPressed() {
|
@objc func cancelButtonPressed() {
|
||||||
|
@ -125,23 +115,14 @@ extension AddSavedHashtagViewController {
|
||||||
enum Section {
|
enum Section {
|
||||||
case trendingTags
|
case trendingTags
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Item: Hashable {
|
enum Item: Hashable {
|
||||||
case tag(Hashtag)
|
case tag(Hashtag)
|
||||||
}
|
}
|
||||||
// class DataSource: UITableViewDiffableDataSource<Section, Item> {
|
|
||||||
// override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
class DataSource: UITableViewDiffableDataSource<Section, Item> {
|
||||||
// return
|
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,17 +9,18 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
class TrendingHashtagsViewController: UIViewController {
|
class TrendingHashtagsViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
weak var mastodonController: MastodonController!
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
private var collectionView: UICollectionView!
|
private var dataSource: UITableViewDiffableDataSource<Section, Item>!
|
||||||
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
|
||||||
|
|
||||||
init(mastodonController: MastodonController) {
|
init(mastodonController: MastodonController) {
|
||||||
self.mastodonController = mastodonController
|
self.mastodonController = mastodonController
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(style: .grouped)
|
||||||
|
|
||||||
|
dragEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
|
@ -31,24 +32,15 @@ class TrendingHashtagsViewController: UIViewController {
|
||||||
|
|
||||||
title = NSLocalizedString("Trending Hashtags", comment: "trending hashtags screen title")
|
title = NSLocalizedString("Trending Hashtags", comment: "trending hashtags screen title")
|
||||||
|
|
||||||
view.backgroundColor = .systemGroupedBackground
|
tableView.register(UINib(nibName: "TrendingHashtagTableViewCell", bundle: .main), forCellReuseIdentifier: "trendingTagCell")
|
||||||
|
tableView.rowHeight = 60 // 44 for content + 2 * 8 spacing
|
||||||
|
|
||||||
let config = UICollectionLayoutListConfiguration(appearance: .grouped)
|
dataSource = UITableViewDiffableDataSource<Section, Item>(tableView: tableView) { (tableView, indexPath, item) in
|
||||||
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 {
|
switch item {
|
||||||
case let .tag(hashtag):
|
case let .tag(hashtag):
|
||||||
return collectionView.dequeueConfiguredReusableCell(using: registration, for: indexPath, item: hashtag)
|
let cell = tableView.dequeueReusableCell(withIdentifier: "trendingTagCell", for: indexPath) as! TrendingHashtagTableViewCell
|
||||||
|
cell.updateUI(hashtag: hashtag)
|
||||||
|
return cell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,6 +59,42 @@ class TrendingHashtagsViewController: UIViewController {
|
||||||
await dataSource.apply(snapshot)
|
await dataSource.apply(snapshot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Table View Delegate
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||||
|
case let .tag(hashtag) = item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
show(HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController), sender: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||||
|
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||||
|
case let .tag(hashtag) = item else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return UIContextMenuConfiguration(identifier: nil) {
|
||||||
|
HashtagTimelineViewController(for: hashtag, mastodonController: self.mastodonController)
|
||||||
|
} actionProvider: { (_) in
|
||||||
|
UIMenu(children: self.actionsForHashtag(hashtag, sourceView: self.tableView.cellForRow(at: indexPath)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||||
|
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||||
|
case let .tag(hashtag) = item else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
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)]
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,44 +107,6 @@ extension TrendingHashtagsViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TrendingHashtagsViewController: UICollectionViewDelegate {
|
|
||||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
||||||
guard let item = dataSource.itemIdentifier(for: indexPath),
|
|
||||||
case let .tag(hashtag) = item else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
show(HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController), sender: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
return UIContextMenuConfiguration(identifier: nil) {
|
|
||||||
HashtagTimelineViewController(for: hashtag, mastodonController: self.mastodonController)
|
|
||||||
} actionProvider: { (_) in
|
|
||||||
UIMenu(children: self.actionsForHashtag(hashtag, sourceView: self.collectionView.cellForItem(at: indexPath)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 []
|
|
||||||
}
|
|
||||||
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)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TrendingHashtagsViewController: TuskerNavigationDelegate {
|
extension TrendingHashtagsViewController: TuskerNavigationDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
<?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,10 +113,6 @@ class TrendingLinkTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func updateUIForPreferences() {
|
@objc private func updateUIForPreferences() {
|
||||||
if isGrayscale != Preferences.shared.grayscaleImages,
|
|
||||||
let card {
|
|
||||||
updateGrayscaleableUI(card: card)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateGrayscaleableUI(card: Card) {
|
private func updateGrayscaleableUI(card: Card) {
|
||||||
|
|
|
@ -19,10 +19,8 @@ protocol LargeImageContentView: UIView {
|
||||||
|
|
||||||
class LargeImageImageContentView: UIImageView, LargeImageContentView {
|
class LargeImageImageContentView: UIImageView, LargeImageContentView {
|
||||||
|
|
||||||
#if !targetEnvironment(macCatalyst)
|
|
||||||
@available(iOS 16.0, *)
|
@available(iOS 16.0, *)
|
||||||
private static let analyzer = ImageAnalyzer()
|
private static let analyzer = ImageAnalyzer()
|
||||||
#endif
|
|
||||||
|
|
||||||
var animationImage: UIImage? { image! }
|
var animationImage: UIImage? { image! }
|
||||||
|
|
||||||
|
@ -41,7 +39,6 @@ class LargeImageImageContentView: UIImageView, LargeImageContentView {
|
||||||
contentMode = .scaleAspectFit
|
contentMode = .scaleAspectFit
|
||||||
isUserInteractionEnabled = true
|
isUserInteractionEnabled = true
|
||||||
|
|
||||||
#if !targetEnvironment(macCatalyst)
|
|
||||||
if #available(iOS 16.0, *),
|
if #available(iOS 16.0, *),
|
||||||
ImageAnalyzer.isSupported {
|
ImageAnalyzer.isSupported {
|
||||||
let interaction = ImageAnalysisInteraction()
|
let interaction = ImageAnalysisInteraction()
|
||||||
|
@ -57,7 +54,6 @@ class LargeImageImageContentView: UIImageView, LargeImageContentView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
|
@ -82,14 +78,12 @@ class LargeImageImageContentView: UIImageView, LargeImageContentView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !targetEnvironment(macCatalyst)
|
|
||||||
@available(iOS 16.0, *)
|
@available(iOS 16.0, *)
|
||||||
extension LargeImageImageContentView: ImageAnalysisInteractionDelegate {
|
extension LargeImageImageContentView: ImageAnalysisInteractionDelegate {
|
||||||
func presentingViewController(for interaction: ImageAnalysisInteraction) -> UIViewController? {
|
func presentingViewController(for interaction: ImageAnalysisInteraction) -> UIViewController? {
|
||||||
return owner
|
return owner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
class LargeImageGifContentView: GIFImageView, LargeImageContentView {
|
class LargeImageGifContentView: GIFImageView, LargeImageContentView {
|
||||||
var animationImage: UIImage? { image }
|
var animationImage: UIImage? { image }
|
||||||
|
|
|
@ -37,7 +37,7 @@ class MainSidebarViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
var exploreTabItems: [Item] {
|
var exploreTabItems: [Item] {
|
||||||
var items: [Item] = [.explore, .bookmarks, .trendingStatuses, .profileDirectory]
|
var items: [Item] = [.search, .bookmarks, .trendingStatuses, .trendingTags, .trendingLinks, .profileDirectory]
|
||||||
let snapshot = dataSource.snapshot()
|
let snapshot = dataSource.snapshot()
|
||||||
for case let .list(list) in snapshot.itemIdentifiers(inSection: .lists) {
|
for case let .list(list) in snapshot.itemIdentifiers(inSection: .lists) {
|
||||||
items.append(.list(list))
|
items.append(.list(list))
|
||||||
|
@ -154,7 +154,7 @@ class MainSidebarViewController: UIViewController {
|
||||||
snapshot.appendItems([
|
snapshot.appendItems([
|
||||||
.tab(.timelines),
|
.tab(.timelines),
|
||||||
.tab(.notifications),
|
.tab(.notifications),
|
||||||
.explore,
|
.search,
|
||||||
.bookmarks,
|
.bookmarks,
|
||||||
.tab(.myProfile)
|
.tab(.myProfile)
|
||||||
], toSection: .tabs)
|
], toSection: .tabs)
|
||||||
|
@ -177,10 +177,12 @@ class MainSidebarViewController: UIViewController {
|
||||||
var discoverSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
var discoverSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
||||||
discoverSnapshot.append([.discoverHeader])
|
discoverSnapshot.append([.discoverHeader])
|
||||||
discoverSnapshot.append([
|
discoverSnapshot.append([
|
||||||
|
.trendingTags,
|
||||||
.profileDirectory,
|
.profileDirectory,
|
||||||
], to: .discoverHeader)
|
], to: .discoverHeader)
|
||||||
if mastodonController.instanceFeatures.trendingStatusesAndLinks {
|
if mastodonController.instanceFeatures.trendingStatusesAndLinks {
|
||||||
discoverSnapshot.insert([.trendingStatuses], before: .profileDirectory)
|
discoverSnapshot.insert([.trendingStatuses], before: .trendingTags)
|
||||||
|
discoverSnapshot.insert([.trendingLinks], after: .trendingTags)
|
||||||
}
|
}
|
||||||
dataSource.apply(discoverSnapshot, to: .discover)
|
dataSource.apply(discoverSnapshot, to: .discover)
|
||||||
}
|
}
|
||||||
|
@ -343,7 +345,7 @@ class MainSidebarViewController: UIViewController {
|
||||||
return UserActivityManager.checkNotificationsActivity(mode: Preferences.shared.defaultNotificationsMode)
|
return UserActivityManager.checkNotificationsActivity(mode: Preferences.shared.defaultNotificationsMode)
|
||||||
case .tab(.compose):
|
case .tab(.compose):
|
||||||
return UserActivityManager.newPostActivity(accountID: id)
|
return UserActivityManager.newPostActivity(accountID: id)
|
||||||
case .explore:
|
case .search:
|
||||||
return UserActivityManager.searchActivity()
|
return UserActivityManager.searchActivity()
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
return UserActivityManager.bookmarksActivity()
|
return UserActivityManager.bookmarksActivity()
|
||||||
|
@ -382,8 +384,8 @@ extension MainSidebarViewController {
|
||||||
}
|
}
|
||||||
enum Item: Hashable {
|
enum Item: Hashable {
|
||||||
case tab(MainTabBarViewController.Tab)
|
case tab(MainTabBarViewController.Tab)
|
||||||
case explore, bookmarks
|
case search, bookmarks
|
||||||
case discoverHeader, trendingStatuses, profileDirectory
|
case discoverHeader, trendingStatuses, trendingTags, trendingLinks, profileDirectory
|
||||||
case listsHeader, list(List), addList
|
case listsHeader, list(List), addList
|
||||||
case savedHashtagsHeader, savedHashtag(Hashtag), addSavedHashtag
|
case savedHashtagsHeader, savedHashtag(Hashtag), addSavedHashtag
|
||||||
case savedInstancesHeader, savedInstance(URL), addSavedInstance
|
case savedInstancesHeader, savedInstance(URL), addSavedInstance
|
||||||
|
@ -392,14 +394,18 @@ extension MainSidebarViewController {
|
||||||
switch self {
|
switch self {
|
||||||
case let .tab(tab):
|
case let .tab(tab):
|
||||||
return tab.title
|
return tab.title
|
||||||
case .explore:
|
case .search:
|
||||||
return "Explore"
|
return "Search"
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
return "Bookmarks"
|
return "Bookmarks"
|
||||||
case .discoverHeader:
|
case .discoverHeader:
|
||||||
return "Discover"
|
return "Discover"
|
||||||
case .trendingStatuses:
|
case .trendingStatuses:
|
||||||
return "Trending Posts"
|
return "Trending Posts"
|
||||||
|
case .trendingTags:
|
||||||
|
return "Trending Hashtags"
|
||||||
|
case .trendingLinks:
|
||||||
|
return "Trending Links"
|
||||||
case .profileDirectory:
|
case .profileDirectory:
|
||||||
return "Profile Directory"
|
return "Profile Directory"
|
||||||
case .listsHeader:
|
case .listsHeader:
|
||||||
|
@ -427,12 +433,16 @@ extension MainSidebarViewController {
|
||||||
switch self {
|
switch self {
|
||||||
case let .tab(tab):
|
case let .tab(tab):
|
||||||
return tab.imageName
|
return tab.imageName
|
||||||
case .explore:
|
case .search:
|
||||||
return "magnifyingglass"
|
return "magnifyingglass"
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
return "bookmark"
|
return "bookmark"
|
||||||
case .trendingStatuses:
|
case .trendingStatuses:
|
||||||
return "square.text.square"
|
return "doc.text.image"
|
||||||
|
case .trendingTags:
|
||||||
|
return "number"
|
||||||
|
case .trendingLinks:
|
||||||
|
return "link"
|
||||||
case .profileDirectory:
|
case .profileDirectory:
|
||||||
return "person.2.fill"
|
return "person.2.fill"
|
||||||
case .list(_):
|
case .list(_):
|
||||||
|
@ -540,7 +550,8 @@ extension MainSidebarViewController: UICollectionViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||||
guard let item = dataSource.itemIdentifier(for: indexPath),
|
guard #available(iOS 15.0, *),
|
||||||
|
let item = dataSource.itemIdentifier(for: indexPath),
|
||||||
let activity = userActivityForItem(item) else {
|
let activity = userActivityForItem(item) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,8 @@ class MainSplitViewController: UISplitViewController {
|
||||||
|
|
||||||
private var tabBarViewController: MainTabBarViewController!
|
private var tabBarViewController: MainTabBarViewController!
|
||||||
|
|
||||||
// private var secondaryNavController: UINavigationController! {
|
private var secondaryNavController: UINavigationController! {
|
||||||
// viewController(for: .secondary) as? UINavigationController
|
viewController(for: .secondary) as? UINavigationController
|
||||||
// }
|
|
||||||
private var secondaryNavController: SplitNavigationController! {
|
|
||||||
viewController(for: .secondary) as? SplitNavigationController
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init(mastodonController: MastodonController) {
|
init(mastodonController: MastodonController) {
|
||||||
|
@ -49,10 +46,9 @@ class MainSplitViewController: UISplitViewController {
|
||||||
setViewController(sidebar, for: .primary)
|
setViewController(sidebar, for: .primary)
|
||||||
primaryBackgroundStyle = .sidebar
|
primaryBackgroundStyle = .sidebar
|
||||||
|
|
||||||
// let secondaryNav = EnhancedNavigationViewController()
|
let secondaryNav = EnhancedNavigationViewController()
|
||||||
// secondaryNav.useBrowserStyleNavigation = true
|
secondaryNav.useBrowserStyleNavigation = true
|
||||||
let splitNav = SplitNavigationController()
|
setViewController(secondaryNav, for: .secondary)
|
||||||
setViewController(splitNav, for: .secondary)
|
|
||||||
// don't unnecesarily construct a content VC unless the we're in actually split mode
|
// 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
|
// when we change from compact -> split for the first time, the VC will be transferred anyways
|
||||||
if traitCollection.horizontalSizeClass != .compact {
|
if traitCollection.horizontalSizeClass != .compact {
|
||||||
|
@ -104,7 +100,7 @@ class MainSplitViewController: UISplitViewController {
|
||||||
item = .tab(MainTabBarViewController.Tab(rawValue: index)!)
|
item = .tab(MainTabBarViewController.Tab(rawValue: index)!)
|
||||||
} else if let str = command.propertyList as? String {
|
} else if let str = command.propertyList as? String {
|
||||||
if str == "search" {
|
if str == "search" {
|
||||||
item = .explore
|
item = .search
|
||||||
} else if str == "bookmarks" {
|
} else if str == "bookmarks" {
|
||||||
item = .bookmarks
|
item = .bookmarks
|
||||||
} else {
|
} else {
|
||||||
|
@ -175,7 +171,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
||||||
$0.1 > $1.1
|
$0.1 > $1.1
|
||||||
}
|
}
|
||||||
if let mostRecentExploreItem = mostRecentExploreItem?.0,
|
if let mostRecentExploreItem = mostRecentExploreItem?.0,
|
||||||
mostRecentExploreItem != .explore {
|
mostRecentExploreItem != .search {
|
||||||
let exploreNav = tabBarViewController.viewController(for: .explore) as! UINavigationController
|
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
|
// Pop back to root, so we're appending to the Explore VC instead of some other VC
|
||||||
exploreNav.popToRootViewController(animated: false)
|
exploreNav.popToRootViewController(animated: false)
|
||||||
|
@ -192,7 +188,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
||||||
// sidebar items that map 1 <-> 1 can be transferred directly
|
// sidebar items that map 1 <-> 1 can be transferred directly
|
||||||
tabBarViewController.select(tab: tab)
|
tabBarViewController.select(tab: tab)
|
||||||
|
|
||||||
case .explore:
|
case .search:
|
||||||
// Search sidebar item maps to the Explore tab with the search controller/results visible
|
// 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
|
// 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.
|
// so that explore items aren't shown multiple times.
|
||||||
|
@ -221,11 +217,11 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
||||||
explore.resultsController.loadResults(from: search.resultsController)
|
explore.resultsController.loadResults(from: search.resultsController)
|
||||||
|
|
||||||
// Transfer the navigation stack, dropping the search VC, to keep anything the user has opened
|
// Transfer the navigation stack, dropping the search VC, to keep anything the user has opened
|
||||||
transferNavigationStack(from: .explore, to: exploreNav, dropFirst: true, append: true)
|
transferNavigationStack(from: .search, to: exploreNav, dropFirst: true, append: true)
|
||||||
|
|
||||||
tabBarViewController.select(tab: .explore)
|
tabBarViewController.select(tab: .explore)
|
||||||
|
|
||||||
case .bookmarks, .trendingStatuses, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_):
|
case .bookmarks, .trendingStatuses, .trendingTags, .trendingLinks, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_):
|
||||||
tabBarViewController.select(tab: .explore)
|
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
|
// 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.
|
// in compact mode and performing a search.
|
||||||
|
@ -276,7 +272,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
||||||
// For other items, the 2nd VC in the nav stack determines which sidebar item they map to.
|
// 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.
|
// Search screen has special considerations, all others can be transferred directly.
|
||||||
if tabNavigationStack.count == 1 || ((tabNavigationStack.first as? ExploreViewController)?.searchController?.isActive ?? false) {
|
if tabNavigationStack.count == 1 || ((tabNavigationStack.first as? ExploreViewController)?.searchController?.isActive ?? false) {
|
||||||
exploreItem = .explore
|
exploreItem = .search
|
||||||
let searchVC = SearchViewController(mastodonController: mastodonController)
|
let searchVC = SearchViewController(mastodonController: mastodonController)
|
||||||
searchVC.loadViewIfNeeded()
|
searchVC.loadViewIfNeeded()
|
||||||
let explore = tabNavigationStack.first as! ExploreViewController
|
let explore = tabNavigationStack.first as! ExploreViewController
|
||||||
|
@ -304,9 +300,9 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
||||||
case is TrendingStatusesViewController:
|
case is TrendingStatusesViewController:
|
||||||
exploreItem = .trendingStatuses
|
exploreItem = .trendingStatuses
|
||||||
case is TrendingHashtagsViewController:
|
case is TrendingHashtagsViewController:
|
||||||
exploreItem = .explore
|
exploreItem = .trendingTags
|
||||||
case is TrendingLinksViewController:
|
case is TrendingLinksViewController:
|
||||||
exploreItem = .explore
|
exploreItem = .trendingLinks
|
||||||
case is ProfileDirectoryViewController:
|
case is ProfileDirectoryViewController:
|
||||||
exploreItem = .profileDirectory
|
exploreItem = .profileDirectory
|
||||||
default:
|
default:
|
||||||
|
@ -358,12 +354,16 @@ fileprivate extension MainSidebarViewController.Item {
|
||||||
switch self {
|
switch self {
|
||||||
case let .tab(tab):
|
case let .tab(tab):
|
||||||
return tab.createViewController(mastodonController)
|
return tab.createViewController(mastodonController)
|
||||||
case .explore:
|
case .search:
|
||||||
return SearchViewController(mastodonController: mastodonController)
|
return SearchViewController(mastodonController: mastodonController)
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
return BookmarksTableViewController(mastodonController: mastodonController)
|
return BookmarksTableViewController(mastodonController: mastodonController)
|
||||||
case .trendingStatuses:
|
case .trendingStatuses:
|
||||||
return TrendingStatusesViewController(mastodonController: mastodonController)
|
return TrendingStatusesViewController(mastodonController: mastodonController)
|
||||||
|
case .trendingTags:
|
||||||
|
return TrendingHashtagsViewController(mastodonController: mastodonController)
|
||||||
|
case .trendingLinks:
|
||||||
|
return TrendingLinksViewController(mastodonController: mastodonController)
|
||||||
case .profileDirectory:
|
case .profileDirectory:
|
||||||
return ProfileDirectoryViewController(mastodonController: mastodonController)
|
return ProfileDirectoryViewController(mastodonController: mastodonController)
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
|
@ -380,7 +380,7 @@ fileprivate extension MainSidebarViewController.Item {
|
||||||
|
|
||||||
extension MainSplitViewController: TuskerRootViewController {
|
extension MainSplitViewController: TuskerRootViewController {
|
||||||
@objc func presentCompose() {
|
@objc func presentCompose() {
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
if #available(iOS 15.0, *), UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||||
let compose = UserActivityManager.newPostActivity(mentioning: nil, accountID: mastodonController.accountInfo!.id)
|
let compose = UserActivityManager.newPostActivity(mentioning: nil, accountID: mastodonController.accountInfo!.id)
|
||||||
let options = UIWindowScene.ActivationRequestOptions()
|
let options = UIWindowScene.ActivationRequestOptions()
|
||||||
options.preferredPresentationStyle = .prominent
|
options.preferredPresentationStyle = .prominent
|
||||||
|
@ -435,8 +435,8 @@ extension MainSplitViewController: TuskerRootViewController {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if sidebar.selectedItem != .explore {
|
if sidebar.selectedItem != .search {
|
||||||
select(item: .explore)
|
select(item: .search)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let searchViewController = secondaryNavController.viewControllers.first as? SearchViewController else {
|
guard let searchViewController = secondaryNavController.viewControllers.first as? SearchViewController else {
|
||||||
|
|
|
@ -142,7 +142,7 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
return vc
|
return vc
|
||||||
} else {
|
} else {
|
||||||
let nav = EnhancedNavigationViewController(rootViewController: vc)
|
let nav = EnhancedNavigationViewController(rootViewController: vc)
|
||||||
// nav.useBrowserStyleNavigation = true
|
nav.useBrowserStyleNavigation = true
|
||||||
return nav
|
return nav
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,7 +230,7 @@ extension MainTabBarViewController: FastAccountSwitcherViewControllerDelegate {
|
||||||
|
|
||||||
extension MainTabBarViewController: TuskerRootViewController {
|
extension MainTabBarViewController: TuskerRootViewController {
|
||||||
@objc func presentCompose() {
|
@objc func presentCompose() {
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
if #available(iOS 15.0, *), UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||||
let compose = UserActivityManager.newPostActivity(mentioning: nil, accountID: mastodonController.accountInfo!.id)
|
let compose = UserActivityManager.newPostActivity(mentioning: nil, accountID: mastodonController.accountInfo!.id)
|
||||||
let options = UIWindowScene.ActivationRequestOptions()
|
let options = UIWindowScene.ActivationRequestOptions()
|
||||||
options.preferredPresentationStyle = .prominent
|
options.preferredPresentationStyle = .prominent
|
||||||
|
|
|
@ -38,7 +38,7 @@ struct OppositeCollapseKeywordsView: View {
|
||||||
FocusableTextField(placeholder: "Add Keyword", text: $valueToAdd, becomeFirstResponder: $makeAddFieldFirstResponder, onCommit: self.addKeyword)
|
FocusableTextField(placeholder: "Add Keyword", text: $valueToAdd, becomeFirstResponder: $makeAddFieldFirstResponder, onCommit: self.addKeyword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animation(.default, value: keywords.map(\.id))
|
.animation(.default)
|
||||||
.listStyle(GroupedListStyle())
|
.listStyle(GroupedListStyle())
|
||||||
}
|
}
|
||||||
.onAppear(perform: updateAppearance)
|
.onAppear(perform: updateAppearance)
|
||||||
|
|
|
@ -16,7 +16,9 @@ struct WellnessPrefsView: View {
|
||||||
showFavAndReblogCount
|
showFavAndReblogCount
|
||||||
notificationsMode
|
notificationsMode
|
||||||
grayscaleImages
|
grayscaleImages
|
||||||
disableInfiniteScrolling
|
if #available(iOS 15.0, *) {
|
||||||
|
disableInfiniteScrolling
|
||||||
|
}
|
||||||
hideDiscover
|
hideDiscover
|
||||||
}
|
}
|
||||||
.listStyle(InsetGroupedListStyle())
|
.listStyle(InsetGroupedListStyle())
|
||||||
|
|
|
@ -7,16 +7,11 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
|
||||||
import SafariServices
|
|
||||||
|
|
||||||
class SearchViewController: UIViewController {
|
class SearchViewController: UIViewController {
|
||||||
|
|
||||||
weak var mastodonController: MastodonController!
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
private var collectionView: UICollectionView!
|
|
||||||
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
|
||||||
|
|
||||||
var resultsController: SearchResultsViewController!
|
var resultsController: SearchResultsViewController!
|
||||||
var searchController: UISearchController!
|
var searchController: UISearchController!
|
||||||
|
|
||||||
|
@ -27,7 +22,7 @@ class SearchViewController: UIViewController {
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
title = NSLocalizedString("Explore", comment: "explore tab title")
|
title = NSLocalizedString("Search", comment: "search tab title")
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
|
@ -37,46 +32,12 @@ class SearchViewController: UIViewController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
let layout = UICollectionViewCompositionalLayout { [unowned self] sectionIndex, environment in
|
view.backgroundColor = .systemBackground
|
||||||
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 = SearchResultsViewController(mastodonController: mastodonController)
|
||||||
resultsController.exploreNavigationController = self.navigationController
|
resultsController.exploreNavigationController = self.navigationController
|
||||||
searchController = UISearchController(searchResultsController: resultsController)
|
searchController = UISearchController(searchResultsController: resultsController)
|
||||||
searchController.obscuresBackgroundDuringPresentation = true
|
searchController.obscuresBackgroundDuringPresentation = false
|
||||||
searchController.searchBar.autocapitalizationType = .none
|
searchController.searchBar.autocapitalizationType = .none
|
||||||
searchController.searchBar.delegate = resultsController
|
searchController.searchBar.delegate = resultsController
|
||||||
searchController.hidesNavigationBarDuringPresentation = false
|
searchController.hidesNavigationBarDuringPresentation = false
|
||||||
|
@ -87,18 +48,6 @@ class SearchViewController: UIViewController {
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
navigationItem.preferredSearchBarPlacement = .stacked
|
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) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
@ -112,214 +61,5 @@ class SearchViewController: UIViewController {
|
||||||
searchControllerStatusOnAppearance = nil
|
searchControllerStatusOnAppearance = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,7 +164,8 @@ class TimelineTableViewController: DiffableTimelineLikeTableViewController<Timel
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if Preferences.shared.disableInfiniteScrolling && !didConfirmLoadMore {
|
if #available(iOS 15.0, *),
|
||||||
|
Preferences.shared.disableInfiniteScrolling && !didConfirmLoadMore {
|
||||||
var snapshot = currentSnapshot()
|
var snapshot = currentSnapshot()
|
||||||
guard !snapshot.itemIdentifiers(inSection: .footer).contains(.confirmLoadMore) else {
|
guard !snapshot.itemIdentifiers(inSection: .footer).contains(.confirmLoadMore) else {
|
||||||
// todo: need something more accurate than "success"/"failure"
|
// todo: need something more accurate than "success"/"failure"
|
||||||
|
|
|
@ -59,7 +59,7 @@ extension MenuActionProvider {
|
||||||
draft.visibility = .direct
|
draft.visibility = .direct
|
||||||
self.navigationDelegate?.compose(editing: draft)
|
self.navigationDelegate?.compose(editing: draft)
|
||||||
}),
|
}),
|
||||||
UIDeferredMenuElement.uncached({ (elementHandler) in
|
UIDeferredMenuElement.uncachedIfPossible({ (elementHandler) in
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
if let action = await self.followAction(for: accountID, mastodonController: mastodonController) {
|
if let action = await self.followAction(for: accountID, mastodonController: mastodonController) {
|
||||||
elementHandler([action])
|
elementHandler([action])
|
||||||
|
@ -358,13 +358,21 @@ extension MenuActionProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func addOpenInNewWindow(actions: inout [UIAction], activity: @escaping @autoclosure () -> NSUserActivity) {
|
private func addOpenInNewWindow(actions: inout [UIAction], activity: @escaping @autoclosure () -> NSUserActivity) {
|
||||||
let options = UIWindowScene.ActivationRequestOptions()
|
if #available(iOS 15.0, *) {
|
||||||
options.preferredPresentationStyle = .automatic
|
let options = UIWindowScene.ActivationRequestOptions()
|
||||||
actions.append(UIWindowScene.ActivationAction { (_) in
|
options.preferredPresentationStyle = .automatic
|
||||||
let activity = activity()
|
actions.append(UIWindowScene.ActivationAction { (_) in
|
||||||
activity.displaysAuxiliaryScene = true
|
let activity = activity()
|
||||||
return .init(userActivity: activity, options: options, preview: nil)
|
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? {
|
private func followAction(for accountID: String, mastodonController: MastodonController) async -> UIMenuElement? {
|
||||||
|
@ -415,3 +423,13 @@ extension SFSafariViewController: CustomPreviewPresenting {
|
||||||
presenter.present(self, animated: true)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,262 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
// Based on MVCTodo by Dave DeLong: https://github.com/davedelong/MVCTodo/blob/841649dd6aa31bacda3ad7ef9a9a836f66281e50/MVCTodo/Extensions/UIViewController.swift
|
||||||
extension UIViewController {
|
extension UIViewController {
|
||||||
func embedChild(_ newChild: UIViewController, in container: UIView? = nil, layout: Bool = true) {
|
func embedChild(_ newChild: UIViewController, in container: UIView? = nil) {
|
||||||
// if the view controller is already a child of something else, remove it
|
// if the view controller is already a child of something else, remove it
|
||||||
if let oldParent = newChild.parent, oldParent != self {
|
if let oldParent = newChild.parent, oldParent != self {
|
||||||
newChild.beginAppearanceTransition(false, animated: false)
|
newChild.beginAppearanceTransition(false, animated: false)
|
||||||
|
@ -36,7 +36,7 @@ extension UIViewController {
|
||||||
newChild.beginAppearanceTransition(true, animated: false)
|
newChild.beginAppearanceTransition(true, animated: false)
|
||||||
addChild(newChild)
|
addChild(newChild)
|
||||||
newChild.didMove(toParent: self)
|
newChild.didMove(toParent: self)
|
||||||
targetContainer.embedSubview(newChild.view, layout: layout)
|
targetContainer.embedSubview(newChild.view)
|
||||||
newChild.endAppearanceTransition()
|
newChild.endAppearanceTransition()
|
||||||
} else {
|
} else {
|
||||||
// the view controller is already a child
|
// the view controller is already a child
|
||||||
|
@ -45,7 +45,7 @@ extension UIViewController {
|
||||||
// we don't do the appearance transition stuff here,
|
// we don't do the appearance transition stuff here,
|
||||||
// because the vc is already a child, so *presumably*
|
// because the vc is already a child, so *presumably*
|
||||||
// that transition stuff has already appened
|
// that transition stuff has already appened
|
||||||
targetContainer.embedSubview(newChild.view, layout: layout)
|
targetContainer.embedSubview(newChild.view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,25 +57,22 @@ extension UIViewController {
|
||||||
|
|
||||||
// Based on MVCTodo by Dave DeLong: https://github.com/davedelong/MVCTodo/blob/841649dd6aa31bacda3ad7ef9a9a836f66281e50/MVCTodo/Extensions/UIView.swift
|
// Based on MVCTodo by Dave DeLong: https://github.com/davedelong/MVCTodo/blob/841649dd6aa31bacda3ad7ef9a9a836f66281e50/MVCTodo/Extensions/UIView.swift
|
||||||
extension UIView {
|
extension UIView {
|
||||||
func embedSubview(_ subview: UIView, layout: Bool = true) {
|
func embedSubview(_ subview: UIView) {
|
||||||
if subview.superview == self { return }
|
if subview.superview == self { return }
|
||||||
|
|
||||||
if subview.superview != nil {
|
if subview.superview != nil {
|
||||||
subview.removeFromSuperview()
|
subview.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subview.frame = bounds
|
||||||
addSubview(subview)
|
addSubview(subview)
|
||||||
|
|
||||||
if layout {
|
NSLayoutConstraint.activate([
|
||||||
subview.frame = bounds
|
subview.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
subview.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
NSLayoutConstraint.activate([
|
subview.topAnchor.constraint(equalTo: topAnchor),
|
||||||
subview.leadingAnchor.constraint(equalTo: leadingAnchor),
|
subview.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||||
subview.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
||||||
subview.topAnchor.constraint(equalTo: topAnchor),
|
|
||||||
subview.bottomAnchor.constraint(equalTo: bottomAnchor)
|
|
||||||
])
|
])
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isContainedWithin(_ other: UIView) -> Bool {
|
func isContainedWithin(_ other: UIView) -> Bool {
|
||||||
|
|
|
@ -89,7 +89,7 @@ extension TuskerNavigationDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func compose(editing draft: Draft) {
|
func compose(editing draft: Draft) {
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
if #available(iOS 15.0, *), UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||||
let compose = UserActivityManager.editDraftActivity(id: draft.id, accountID: apiController.accountInfo!.id)
|
let compose = UserActivityManager.editDraftActivity(id: draft.id, accountID: apiController.accountInfo!.id)
|
||||||
let options = UIWindowScene.ActivationRequestOptions()
|
let options = UIWindowScene.ActivationRequestOptions()
|
||||||
options.preferredPresentationStyle = .prominent
|
options.preferredPresentationStyle = .prominent
|
||||||
|
|
|
@ -16,5 +16,4 @@ struct ViewTags {
|
||||||
static let navBackBarButton = 42003
|
static let navBackBarButton = 42003
|
||||||
static let navForwardBarButton = 42004
|
static let navForwardBarButton = 42004
|
||||||
static let navEmptyTitleView = 42005
|
static let navEmptyTitleView = 42005
|
||||||
static let splitNavCloseSecondaryButton = 42006
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,13 +23,15 @@ class ConfirmLoadMoreTableViewCell: UITableViewCell {
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|
||||||
var config = UIButton.Configuration.tinted()
|
if #available(iOS 15.0, *) {
|
||||||
config.title = "Load More"
|
var config = UIButton.Configuration.tinted()
|
||||||
config.showsActivityIndicator = false
|
config.title = "Load More"
|
||||||
config.imagePadding = 4
|
config.showsActivityIndicator = false
|
||||||
confirmButton.configuration = config
|
config.imagePadding = 4
|
||||||
confirmButton.configurationUpdateHandler = { [unowned self] button in
|
confirmButton.configuration = config
|
||||||
button.configuration?.showsActivityIndicator = self.isLoading
|
confirmButton.configurationUpdateHandler = { [unowned self] button in
|
||||||
|
button.configuration?.showsActivityIndicator = self.isLoading
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,13 +39,17 @@ class ConfirmLoadMoreTableViewCell: UITableViewCell {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
|
|
||||||
isLoading = false
|
isLoading = false
|
||||||
confirmButton.setNeedsUpdateConfiguration()
|
if #available(iOS 15.0, *) {
|
||||||
|
confirmButton.setNeedsUpdateConfiguration()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func loadMorePressed(_ sender: Any) {
|
@IBAction func loadMorePressed(_ sender: Any) {
|
||||||
confirmLoadMore?()
|
confirmLoadMore?()
|
||||||
isLoading = true
|
if #available(iOS 15.0, *) {
|
||||||
confirmButton.setNeedsUpdateConfiguration()
|
isLoading = true
|
||||||
|
confirmButton.setNeedsUpdateConfiguration()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?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,7 +24,11 @@ class PublicTimelineDescriptionTableViewCell: UITableViewCell {
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|
||||||
contentView.backgroundColor = .tintColor
|
if #available(iOS 15.0, *) {
|
||||||
|
contentView.backgroundColor = .tintColor
|
||||||
|
} else {
|
||||||
|
contentView.backgroundColor = .systemBlue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateLabel() {
|
private func updateLabel() {
|
||||||
|
|
|
@ -42,10 +42,7 @@ class TrendHistoryView: UIView {
|
||||||
|
|
||||||
private func createLayers() {
|
private func createLayers() {
|
||||||
guard let history = history,
|
guard let history = history,
|
||||||
history.count >= 2,
|
history.count >= 2 else { return }
|
||||||
!bounds.isEmpty else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let maxUses = history.max(by: { $0.uses < $1.uses })!.uses
|
let maxUses = history.max(by: { $0.uses < $1.uses })!.uses
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue