Compare commits
6 Commits
7edf0fdb93
...
1fda4248ec
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 1fda4248ec | |
Shadowfacts | 7781c5252b | |
Shadowfacts | 7f4bf52050 | |
Shadowfacts | ba0d179de5 | |
Shadowfacts | 71b6f1bdf0 | |
Shadowfacts | 09ec4a920c |
|
@ -141,6 +141,9 @@
|
||||||
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; };
|
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; };
|
||||||
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */; };
|
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */; };
|
||||||
D647D92824257BEB0005044F /* AttachmentPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */; };
|
D647D92824257BEB0005044F /* AttachmentPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */; };
|
||||||
|
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9026C80DC600FC57FB /* ToastView.swift */; };
|
||||||
|
D64AAE9526C88C5000FC57FB /* ToastableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */; };
|
||||||
|
D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9626C88DC400FC57FB /* ToastConfiguration.swift */; };
|
||||||
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */; };
|
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */; };
|
||||||
D64BC18823C1640A000D0238 /* PinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18723C1640A000D0238 /* PinStatusActivity.swift */; };
|
D64BC18823C1640A000D0238 /* PinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18723C1640A000D0238 /* PinStatusActivity.swift */; };
|
||||||
D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */; };
|
D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */; };
|
||||||
|
@ -544,6 +547,9 @@
|
||||||
D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageShrinkAnimationController.swift; sourceTree = "<group>"; };
|
D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageShrinkAnimationController.swift; sourceTree = "<group>"; };
|
||||||
D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = "<group>"; };
|
D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = "<group>"; };
|
||||||
D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentPreviewViewController.swift; sourceTree = "<group>"; };
|
D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentPreviewViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D64AAE9026C80DC600FC57FB /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
|
||||||
|
D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastableViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D64AAE9626C88DC400FC57FB /* ToastConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastConfiguration.swift; sourceTree = "<group>"; };
|
||||||
D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPreviewViewController.swift; sourceTree = "<group>"; };
|
D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPreviewViewController.swift; sourceTree = "<group>"; };
|
||||||
D64BC18723C1640A000D0238 /* PinStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinStatusActivity.swift; sourceTree = "<group>"; };
|
D64BC18723C1640A000D0238 /* PinStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinStatusActivity.swift; sourceTree = "<group>"; };
|
||||||
D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnpinStatusActivity.swift; sourceTree = "<group>"; };
|
D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnpinStatusActivity.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1062,27 +1068,27 @@
|
||||||
D641C780213DD7C4004B4513 /* Screens */ = {
|
D641C780213DD7C4004B4513 /* Screens */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D6C693FA2162FE5D007D6A6D /* Utilities */,
|
D6A3BC822321F69400FD64D5 /* Account List */,
|
||||||
D6F2E960249E772F005846BB /* Crash Reporter */,
|
|
||||||
D641C782213DD7F0004B4513 /* Main */,
|
|
||||||
D641C783213DD7FE004B4513 /* Onboarding */,
|
|
||||||
D6A4DCC92553666600D9DE31 /* Fast Account Switcher */,
|
|
||||||
D641C781213DD7DD004B4513 /* Timeline */,
|
|
||||||
D641C784213DD819004B4513 /* Profile */,
|
|
||||||
D641C785213DD83B004B4513 /* Conversation */,
|
|
||||||
D641C786213DD852004B4513 /* Notifications */,
|
|
||||||
D641C787213DD862004B4513 /* Compose */,
|
|
||||||
D6B053A023BD2BED00A066FA /* Asset Picker */,
|
D6B053A023BD2BED00A066FA /* Asset Picker */,
|
||||||
|
0411610522B457290030A9B7 /* Attachment Gallery */,
|
||||||
|
D627944823A6AD5100D38C68 /* Bookmarks */,
|
||||||
|
D641C787213DD862004B4513 /* Compose */,
|
||||||
|
D641C785213DD83B004B4513 /* Conversation */,
|
||||||
|
D6F2E960249E772F005846BB /* Crash Reporter */,
|
||||||
D627FF77217E94F200CC0648 /* Drafts */,
|
D627FF77217E94F200CC0648 /* Drafts */,
|
||||||
D627943C23A5635D00D38C68 /* Explore */,
|
D627943C23A5635D00D38C68 /* Explore */,
|
||||||
D6BC9DD8232D8BCA002CA326 /* Search */,
|
D6A4DCC92553666600D9DE31 /* Fast Account Switcher */,
|
||||||
D627944B23A9A02400D38C68 /* Lists */,
|
|
||||||
D641C788213DD86D004B4513 /* Large Image */,
|
D641C788213DD86D004B4513 /* Large Image */,
|
||||||
0411610522B457290030A9B7 /* Attachment Gallery */,
|
D627944B23A9A02400D38C68 /* Lists */,
|
||||||
D6A3BC822321F69400FD64D5 /* Account List */,
|
D641C782213DD7F0004B4513 /* Main */,
|
||||||
D6A3BC8C2321FF9B00FD64D5 /* Status Action Account List */,
|
D641C786213DD852004B4513 /* Notifications */,
|
||||||
D627944823A6AD5100D38C68 /* Bookmarks */,
|
D641C783213DD7FE004B4513 /* Onboarding */,
|
||||||
D641C789213DD87E004B4513 /* Preferences */,
|
D641C789213DD87E004B4513 /* Preferences */,
|
||||||
|
D641C784213DD819004B4513 /* Profile */,
|
||||||
|
D6BC9DD8232D8BCA002CA326 /* Search */,
|
||||||
|
D6A3BC8C2321FF9B00FD64D5 /* Status Action Account List */,
|
||||||
|
D641C781213DD7DD004B4513 /* Timeline */,
|
||||||
|
D6C693FA2162FE5D007D6A6D /* Utilities */,
|
||||||
);
|
);
|
||||||
path = Screens;
|
path = Screens;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1263,6 +1269,16 @@
|
||||||
path = Transitions;
|
path = Transitions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D64AAE8F26C80DB600FC57FB /* Toast */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D64AAE9026C80DC600FC57FB /* ToastView.swift */,
|
||||||
|
D64AAE9626C88DC400FC57FB /* ToastConfiguration.swift */,
|
||||||
|
D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */,
|
||||||
|
);
|
||||||
|
path = Toast;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D65A37F221472F300087646E /* Frameworks */ = {
|
D65A37F221472F300087646E /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1454,35 +1470,36 @@
|
||||||
D6BED1722126661300F02DA0 /* Views */ = {
|
D6BED1722126661300F02DA0 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D620483323D3801D008A63EF /* LinkTextView.swift */,
|
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */,
|
||||||
D620483523D38075008A63EF /* ContentTextView.swift */,
|
|
||||||
D620483723D38190008A63EF /* StatusContentTextView.swift */,
|
|
||||||
D68E6F5E253C9B2D001A1B4C /* BaseEmojiLabel.swift */,
|
D68E6F5E253C9B2D001A1B4C /* BaseEmojiLabel.swift */,
|
||||||
|
D620483523D38075008A63EF /* ContentTextView.swift */,
|
||||||
|
D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */,
|
||||||
D6969E9F240C8384002843CE /* EmojiLabel.swift */,
|
D6969E9F240C8384002843CE /* EmojiLabel.swift */,
|
||||||
D68E6F58253C9969001A1B4C /* MultiSourceEmojiLabel.swift */,
|
D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */,
|
||||||
|
D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */,
|
||||||
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */,
|
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */,
|
||||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
|
D620483323D3801D008A63EF /* LinkTextView.swift */,
|
||||||
|
D68E6F58253C9969001A1B4C /* MultiSourceEmojiLabel.swift */,
|
||||||
D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */,
|
D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */,
|
||||||
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */,
|
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */,
|
||||||
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */,
|
D620483723D38190008A63EF /* StatusContentTextView.swift */,
|
||||||
|
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
|
||||||
D6403CC124A6B72D00E81C55 /* VisualEffectImageButton.swift */,
|
D6403CC124A6B72D00E81C55 /* VisualEffectImageButton.swift */,
|
||||||
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
|
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
|
||||||
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */,
|
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */,
|
||||||
D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */,
|
D6A3BC872321F78000FD64D5 /* Account Cell */,
|
||||||
D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */,
|
|
||||||
D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */,
|
|
||||||
D67C57A721E2649B00C3118B /* Account Detail */,
|
D67C57A721E2649B00C3118B /* Account Detail */,
|
||||||
D626494023C122C800612E6E /* Asset Picker */,
|
D626494023C122C800612E6E /* Asset Picker */,
|
||||||
D61959D0241E842400A37B8E /* Draft Cell */,
|
|
||||||
D641C78A213DD926004B4513 /* Status */,
|
|
||||||
D6C7D27B22B6EBE200071952 /* Attachments */,
|
D6C7D27B22B6EBE200071952 /* Attachments */,
|
||||||
D623A53B2635F4E20095BD04 /* Poll */,
|
D6DEA0DB268400AF00FE896A /* Confirm Load More Cell */,
|
||||||
D641C78B213DD92F004B4513 /* Profile Header */,
|
D61959D0241E842400A37B8E /* Draft Cell */,
|
||||||
D641C78C213DD937004B4513 /* Notifications */,
|
|
||||||
D6A3BC872321F78000FD64D5 /* Account Cell */,
|
|
||||||
D611C2CC232DC5FC00C86A49 /* Hashtag Cell */,
|
D611C2CC232DC5FC00C86A49 /* Hashtag Cell */,
|
||||||
D61AC1DA232EA43100C54D2D /* Instance Cell */,
|
D61AC1DA232EA43100C54D2D /* Instance Cell */,
|
||||||
D6DEA0DB268400AF00FE896A /* Confirm Load More Cell */,
|
D641C78C213DD937004B4513 /* Notifications */,
|
||||||
|
D623A53B2635F4E20095BD04 /* Poll */,
|
||||||
|
D641C78B213DD92F004B4513 /* Profile Header */,
|
||||||
|
D641C78A213DD926004B4513 /* Status */,
|
||||||
|
D64AAE8F26C80DB600FC57FB /* Toast */,
|
||||||
D6420AEB26BED17500ED8175 /* Timeline Description Cell */,
|
D6420AEB26BED17500ED8175 /* Timeline Description Cell */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
|
@ -1491,24 +1508,24 @@
|
||||||
D6C693FA2162FE5D007D6A6D /* Utilities */ = {
|
D6C693FA2162FE5D007D6A6D /* Utilities */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */,
|
|
||||||
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */,
|
|
||||||
D6E0DC8D216EDF1E00369478 /* Previewing.swift */,
|
|
||||||
D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */,
|
|
||||||
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */,
|
|
||||||
D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */,
|
|
||||||
D6B22A0E2560D52D004D82EF /* TabbedPageViewController.swift */,
|
|
||||||
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */,
|
|
||||||
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */,
|
|
||||||
D693DE5823FE24300061E07D /* InteractivePushTransition.swift */,
|
|
||||||
D6DFC69D242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift */,
|
|
||||||
D6412B0224AFF6A600F5412E /* TabBarScrollableViewController.swift */,
|
|
||||||
D65C6BF425478A9C00A6E89C /* BackgroundableViewController.swift */,
|
D65C6BF425478A9C00A6E89C /* BackgroundableViewController.swift */,
|
||||||
D6B81F3B2560365300F6E31D /* RefreshableViewController.swift */,
|
|
||||||
D65234C8256189D0001AF9CF /* TimelineLikeTableViewController.swift */,
|
|
||||||
D653F410267D1E32004E32B1 /* DiffableTimelineLikeTableViewController.swift */,
|
D653F410267D1E32004E32B1 /* DiffableTimelineLikeTableViewController.swift */,
|
||||||
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */,
|
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */,
|
||||||
|
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */,
|
||||||
|
D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */,
|
||||||
|
D693DE5823FE24300061E07D /* InteractivePushTransition.swift */,
|
||||||
|
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */,
|
||||||
|
D6E0DC8D216EDF1E00369478 /* Previewing.swift */,
|
||||||
|
D6B81F3B2560365300F6E31D /* RefreshableViewController.swift */,
|
||||||
|
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */,
|
||||||
D6A6C10425B6138A00298D0F /* StatusTablePrefetching.swift */,
|
D6A6C10425B6138A00298D0F /* StatusTablePrefetching.swift */,
|
||||||
|
D6412B0224AFF6A600F5412E /* TabBarScrollableViewController.swift */,
|
||||||
|
D6B22A0E2560D52D004D82EF /* TabbedPageViewController.swift */,
|
||||||
|
D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */,
|
||||||
|
D65234C8256189D0001AF9CF /* TimelineLikeTableViewController.swift */,
|
||||||
|
D6DFC69D242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift */,
|
||||||
|
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */,
|
||||||
|
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */,
|
||||||
);
|
);
|
||||||
path = Utilities;
|
path = Utilities;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1555,36 +1572,36 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D6E4885C24A2890C0011C13E /* Tusker.entitlements */,
|
D6E4885C24A2890C0011C13E /* Tusker.entitlements */,
|
||||||
|
D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */,
|
||||||
|
D6D4DDDB212518A200E1C4BB /* Info.plist */,
|
||||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
||||||
D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */,
|
D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */,
|
||||||
D68C2AE225869BAB00548EFF /* AuxiliarySceneDelegate.swift */,
|
D68C2AE225869BAB00548EFF /* AuxiliarySceneDelegate.swift */,
|
||||||
D69693F925859A8000F4E116 /* ComposeSceneDelegate.swift */,
|
D69693F925859A8000F4E116 /* ComposeSceneDelegate.swift */,
|
||||||
|
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */,
|
||||||
|
D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */,
|
||||||
|
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */,
|
||||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
||||||
|
D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */,
|
||||||
|
D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */,
|
||||||
D6945C2E23AC47C3005C403C /* SavedDataManager.swift */,
|
D6945C2E23AC47C3005C403C /* SavedDataManager.swift */,
|
||||||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */,
|
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */,
|
||||||
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */,
|
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */,
|
||||||
D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */,
|
|
||||||
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */,
|
|
||||||
D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */,
|
|
||||||
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */,
|
|
||||||
D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */,
|
|
||||||
D6E57FA525C26FAB00341037 /* Localizable.stringsdict */,
|
|
||||||
D67B506B250B28FF00FAECFB /* Vendor */,
|
|
||||||
D6F1F84E2193B9BE00F5FE67 /* Caching */,
|
|
||||||
D6757A7A2157E00100721E32 /* XCallbackURL */,
|
|
||||||
D62D241E217AA46B005076CC /* Shortcuts */,
|
|
||||||
D663626021360A9600C9CBA2 /* Preferences */,
|
|
||||||
D6AEBB3F2321640F00E5038B /* Activities */,
|
|
||||||
D667E5F62135C2ED0057A976 /* Extensions */,
|
|
||||||
D61959D2241E846D00A37B8E /* Models */,
|
|
||||||
D6370B9924421FE00092A7FF /* CoreData */,
|
|
||||||
D6F953F121251A2F00CF0F2B /* Controllers */,
|
|
||||||
D641C780213DD7C4004B4513 /* Screens */,
|
|
||||||
D6BED1722126661300F02DA0 /* Views */,
|
|
||||||
D6D4DDD6212518A200E1C4BB /* Assets.xcassets */,
|
D6D4DDD6212518A200E1C4BB /* Assets.xcassets */,
|
||||||
|
D6AEBB3F2321640F00E5038B /* Activities */,
|
||||||
|
D6F1F84E2193B9BE00F5FE67 /* Caching */,
|
||||||
|
D6F953F121251A2F00CF0F2B /* Controllers */,
|
||||||
|
D6370B9924421FE00092A7FF /* CoreData */,
|
||||||
|
D667E5F62135C2ED0057A976 /* Extensions */,
|
||||||
D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */,
|
D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */,
|
||||||
D6D4DDDB212518A200E1C4BB /* Info.plist */,
|
D6E57FA525C26FAB00341037 /* Localizable.stringsdict */,
|
||||||
D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */,
|
D61959D2241E846D00A37B8E /* Models */,
|
||||||
|
D663626021360A9600C9CBA2 /* Preferences */,
|
||||||
|
D641C780213DD7C4004B4513 /* Screens */,
|
||||||
|
D62D241E217AA46B005076CC /* Shortcuts */,
|
||||||
|
D67B506B250B28FF00FAECFB /* Vendor */,
|
||||||
|
D6BED1722126661300F02DA0 /* Views */,
|
||||||
|
D6757A7A2157E00100721E32 /* XCallbackURL */,
|
||||||
);
|
);
|
||||||
path = Tusker;
|
path = Tusker;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2067,6 +2084,7 @@
|
||||||
D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */,
|
D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */,
|
||||||
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */,
|
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */,
|
||||||
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */,
|
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */,
|
||||||
|
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */,
|
||||||
D62275AA24F1E01C00B82A16 /* ComposeTextView.swift in Sources */,
|
D62275AA24F1E01C00B82A16 /* ComposeTextView.swift in Sources */,
|
||||||
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */,
|
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */,
|
||||||
D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */,
|
D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */,
|
||||||
|
@ -2148,6 +2166,7 @@
|
||||||
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */,
|
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */,
|
||||||
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 */,
|
||||||
D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */,
|
D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */,
|
||||||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
||||||
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */,
|
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */,
|
||||||
|
@ -2162,6 +2181,7 @@
|
||||||
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */,
|
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */,
|
||||||
D6F0B17524A3A1AA001E48C3 /* MainSidebarViewController.swift in Sources */,
|
D6F0B17524A3A1AA001E48C3 /* MainSidebarViewController.swift in Sources */,
|
||||||
D622757424EDF1CD00B82A16 /* ComposeAttachmentsList.swift in Sources */,
|
D622757424EDF1CD00B82A16 /* ComposeAttachmentsList.swift in Sources */,
|
||||||
|
D64AAE9526C88C5000FC57FB /* ToastableViewController.swift in Sources */,
|
||||||
D6420AEE26BED18B00ED8175 /* PublicTimelineDescriptionTableViewCell.swift in Sources */,
|
D6420AEE26BED18B00ED8175 /* PublicTimelineDescriptionTableViewCell.swift in Sources */,
|
||||||
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
|
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
|
||||||
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */,
|
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */,
|
||||||
|
|
|
@ -89,3 +89,11 @@ extension AccountSwitchingContainerViewController: TuskerRootViewController {
|
||||||
root.performSearch(query: query)
|
root.performSearch(query: query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AccountSwitchingContainerViewController: BackgroundableViewController {
|
||||||
|
func sceneDidEnterBackground() {
|
||||||
|
if let backgroundable = root as? BackgroundableViewController {
|
||||||
|
backgroundable.sceneDidEnterBackground()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ class InstanceSelectorTableViewController: UITableViewController {
|
||||||
var urlHandler: AnyCancellable?
|
var urlHandler: AnyCancellable?
|
||||||
var currentQuery: String?
|
var currentQuery: String?
|
||||||
|
|
||||||
|
private var activityIndicator: UIActivityIndicatorView!
|
||||||
|
|
||||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||||
return .portrait
|
return .portrait
|
||||||
|
@ -56,9 +58,9 @@ class InstanceSelectorTableViewController: UITableViewController {
|
||||||
navigationItem.scrollEdgeAppearance = appearance
|
navigationItem.scrollEdgeAppearance = appearance
|
||||||
|
|
||||||
tableView.register(UINib(nibName: "InstanceTableViewCell", bundle: .main), forCellReuseIdentifier: instanceCell)
|
tableView.register(UINib(nibName: "InstanceTableViewCell", bundle: .main), forCellReuseIdentifier: instanceCell)
|
||||||
|
|
||||||
tableView.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
tableView.estimatedRowHeight = 120
|
tableView.estimatedRowHeight = 120
|
||||||
|
createActivityIndicatorHeader()
|
||||||
|
|
||||||
dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in
|
dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in
|
||||||
switch item {
|
switch item {
|
||||||
|
@ -78,12 +80,19 @@ class InstanceSelectorTableViewController: UITableViewController {
|
||||||
searchController.obscuresBackgroundDuringPresentation = false
|
searchController.obscuresBackgroundDuringPresentation = false
|
||||||
searchController.searchBar.searchTextField.autocapitalizationType = .none
|
searchController.searchBar.searchTextField.autocapitalizationType = .none
|
||||||
navigationItem.searchController = searchController
|
navigationItem.searchController = searchController
|
||||||
|
navigationItem.hidesSearchBarWhenScrolling = false
|
||||||
definesPresentationContext = true
|
definesPresentationContext = true
|
||||||
|
|
||||||
urlHandler = urlCheckerSubject
|
urlHandler = urlCheckerSubject
|
||||||
.debounce(for: .seconds(1), scheduler: RunLoop.main)
|
|
||||||
.compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) }
|
.compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||||
.sink(receiveValue: updateSpecificInstance)
|
.map { [weak self] (s) -> String in
|
||||||
|
if !s.isEmpty {
|
||||||
|
self?.activityIndicator.startAnimating()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
.debounce(for: .seconds(1), scheduler: RunLoop.main)
|
||||||
|
.sink { [weak self] in self?.updateSpecificInstance(domain: $0) }
|
||||||
|
|
||||||
loadRecommendedInstances()
|
loadRecommendedInstances()
|
||||||
}
|
}
|
||||||
|
@ -117,6 +126,8 @@ class InstanceSelectorTableViewController: UITableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateSpecificInstance(domain: String) {
|
private func updateSpecificInstance(domain: String) {
|
||||||
|
activityIndicator.startAnimating()
|
||||||
|
|
||||||
let components = parseURLComponents(input: domain)
|
let components = parseURLComponents(input: domain)
|
||||||
let url = components.url!
|
let url = components.url!
|
||||||
|
|
||||||
|
@ -138,7 +149,13 @@ class InstanceSelectorTableViewController: UITableViewController {
|
||||||
snapshot.appendItems([.selected(url, instance)], toSection: .selected)
|
snapshot.appendItems([.selected(url, instance)], toSection: .selected)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.dataSource.apply(snapshot)
|
self.dataSource.apply(snapshot) {
|
||||||
|
self.activityIndicator.stopAnimating()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.activityIndicator.stopAnimating()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,6 +175,28 @@ class InstanceSelectorTableViewController: UITableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func createActivityIndicatorHeader() {
|
||||||
|
let header = UITableViewHeaderFooterView()
|
||||||
|
header.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
header.contentView.backgroundColor = .secondarySystemBackground
|
||||||
|
|
||||||
|
activityIndicator = UIActivityIndicatorView(style: .large)
|
||||||
|
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
header.contentView.addSubview(activityIndicator)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
activityIndicator.centerXAnchor.constraint(equalTo: header.contentView.centerXAnchor),
|
||||||
|
activityIndicator.topAnchor.constraint(equalTo: header.contentView.topAnchor, constant: 4),
|
||||||
|
activityIndicator.bottomAnchor.constraint(equalTo: header.contentView.bottomAnchor, constant: -4),
|
||||||
|
])
|
||||||
|
|
||||||
|
let fittingSize = CGSize(width: tableView.bounds.width - (tableView.safeAreaInsets.left + tableView.safeAreaInsets.right), height: 0)
|
||||||
|
let size = header.systemLayoutSizeFitting(fittingSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
|
||||||
|
header.frame = CGRect(origin: .zero, size: size)
|
||||||
|
|
||||||
|
tableView.tableHeaderView = header
|
||||||
|
}
|
||||||
|
|
||||||
private func showRecommendationsError(_ error: Client.Error) {
|
private func showRecommendationsError(_ error: Client.Error) {
|
||||||
let footer = UITableViewHeaderFooterView()
|
let footer = UITableViewHeaderFooterView()
|
||||||
footer.translatesAutoresizingMaskIntoConstraints = false
|
footer.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
|
@ -70,8 +70,8 @@ class ProfileViewController: UIPageViewController {
|
||||||
|
|
||||||
let composeButton = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(composeMentioning))
|
let composeButton = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(composeMentioning))
|
||||||
composeButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: [
|
composeButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: [
|
||||||
UIAction(title: "Direct Message", image: UIImage(systemName: Status.Visibility.direct.unfilledImageName), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
|
UIAction(title: "Direct Message", image: UIImage(systemName: Status.Visibility.direct.unfilledImageName), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { [weak self] (_) in
|
||||||
self.composeDirectMentioning()
|
self?.composeDirectMentioning()
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
composeButton.isEnabled = mastodonController.loggedIn
|
composeButton.isEnabled = mastodonController.loggedIn
|
||||||
|
|
|
@ -59,6 +59,7 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
|
||||||
super.viewDidDisappear(animated)
|
super.viewDidDisappear(animated)
|
||||||
|
|
||||||
pruneOffscreenRows()
|
pruneOffscreenRows()
|
||||||
|
currentToast?.dismissToast(animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
class func refreshCommandTitle() -> String {
|
class func refreshCommandTitle() -> String {
|
||||||
|
@ -111,13 +112,26 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
|
||||||
state = .loadingInitial
|
state = .loadingInitial
|
||||||
|
|
||||||
loadInitialItems() { result in
|
loadInitialItems() { result in
|
||||||
guard case let .success(snapshot) = result else {
|
|
||||||
self.state = .unloaded
|
|
||||||
return
|
|
||||||
}
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
switch result {
|
||||||
|
case let .success(snapshot):
|
||||||
self.dataSource.apply(snapshot, animatingDifferences: false)
|
self.dataSource.apply(snapshot, animatingDifferences: false)
|
||||||
self.state = .loaded
|
self.state = .loaded
|
||||||
|
case let .failure(.client(error)):
|
||||||
|
self.state = .unloaded
|
||||||
|
var config = ToastConfiguration(title: "Error Loading")
|
||||||
|
config.subtitle = error.localizedDescription
|
||||||
|
config.systemImageName = error.systemImageName
|
||||||
|
config.actionTitle = "Retry"
|
||||||
|
config.action = { [weak self] (toast) in
|
||||||
|
toast.dismissToast(animated: true)
|
||||||
|
self?.loadInitial()
|
||||||
|
}
|
||||||
|
self.showToast(configuration: config, animated: true)
|
||||||
|
|
||||||
|
default:
|
||||||
|
self.state = .unloaded
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,13 +147,27 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
|
||||||
state = .loadingOlder
|
state = .loadingOlder
|
||||||
|
|
||||||
loadOlderItems(currentSnapshot: dataSource.snapshot()) { result in
|
loadOlderItems(currentSnapshot: dataSource.snapshot()) { result in
|
||||||
guard case let .success(snapshot) = result else {
|
|
||||||
self.state = .loaded
|
|
||||||
return
|
|
||||||
}
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.dataSource.apply(snapshot, animatingDifferences: false)
|
|
||||||
self.state = .loaded
|
self.state = .loaded
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case let .success(snapshot):
|
||||||
|
self.dataSource.apply(snapshot, animatingDifferences: false)
|
||||||
|
|
||||||
|
case let .failure(.client(error)):
|
||||||
|
var config = ToastConfiguration(title: "Error Loading Older")
|
||||||
|
config.subtitle = error.localizedDescription
|
||||||
|
config.systemImageName = error.systemImageName
|
||||||
|
config.actionTitle = "Retry"
|
||||||
|
config.action = { [weak self] (toast) in
|
||||||
|
toast.dismissToast(animated: true)
|
||||||
|
self?.loadOlder()
|
||||||
|
}
|
||||||
|
self.showToast(configuration: config, animated: true)
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,24 +224,33 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNewerItems(currentSnapshot: snapshot) { result in
|
loadNewerItems(currentSnapshot: snapshot) { result in
|
||||||
guard case let .success(snapshot) = result else {
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.refreshControl?.endRefreshing()
|
self.refreshControl?.endRefreshing()
|
||||||
self.state = .loaded
|
self.state = .loaded
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
switch result {
|
||||||
self.refreshControl?.endRefreshing()
|
case let .success(snapshot):
|
||||||
self.dataSource.apply(snapshot, animatingDifferences: false)
|
self.dataSource.apply(snapshot, animatingDifferences: false)
|
||||||
self.state = .loaded
|
|
||||||
|
|
||||||
if let item = item,
|
if let item = item,
|
||||||
let indexPath = self.dataSource.indexPath(for: item) {
|
let indexPath = self.dataSource.indexPath(for: item) {
|
||||||
// maintain the current position in the list (don't scroll to top)
|
// maintain the current position in the list (don't scroll to top)
|
||||||
self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
|
self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case let .failure(.client(error)):
|
||||||
|
var config = ToastConfiguration(title: "Error Loading Newer")
|
||||||
|
config.subtitle = error.localizedDescription
|
||||||
|
config.systemImageName = error.systemImageName
|
||||||
|
config.actionTitle = "Retry"
|
||||||
|
config.action = { [weak self] (toast) in
|
||||||
|
toast.dismissToast(animated: true)
|
||||||
|
self?.refresh()
|
||||||
|
}
|
||||||
|
self.showToast(configuration: config, animated: true)
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,5 +304,20 @@ extension DiffableTimelineLikeTableViewController {
|
||||||
extension DiffableTimelineLikeTableViewController: BackgroundableViewController {
|
extension DiffableTimelineLikeTableViewController: BackgroundableViewController {
|
||||||
func sceneDidEnterBackground() {
|
func sceneDidEnterBackground() {
|
||||||
pruneOffscreenRows()
|
pruneOffscreenRows()
|
||||||
|
currentToast?.dismissToast(animated: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DiffableTimelineLikeTableViewController: ToastableViewController {
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension Client.Error {
|
||||||
|
var systemImageName: String {
|
||||||
|
switch self {
|
||||||
|
case .networkError(_):
|
||||||
|
return "wifi.exclamationmark"
|
||||||
|
default:
|
||||||
|
return "exclamationmark.triangle"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// ToastConfiguration.swift
|
||||||
|
// ToastConfiguration
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 8/14/21.
|
||||||
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct ToastConfiguration {
|
||||||
|
var systemImageName: String?
|
||||||
|
var title: String
|
||||||
|
var subtitle: String?
|
||||||
|
var actionTitle: String?
|
||||||
|
var action: ((ToastView) -> Void)?
|
||||||
|
var edgeSpacing: CGFloat = 8
|
||||||
|
var edge: Edge = .automatic
|
||||||
|
var dismissOnScroll = true
|
||||||
|
|
||||||
|
init(title: String) {
|
||||||
|
self.title = title
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Edge: Equatable {
|
||||||
|
case top
|
||||||
|
case bottom
|
||||||
|
/// Determines edge based on the current device. Bottom on iPhone, top on iPad/Mac.
|
||||||
|
case automatic
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
//
|
||||||
|
// ToastView.swift
|
||||||
|
// ToastView
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 8/14/21.
|
||||||
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class ToastView: UIView {
|
||||||
|
|
||||||
|
let configuration: ToastConfiguration
|
||||||
|
|
||||||
|
private var shrinkAnimator: UIViewPropertyAnimator?
|
||||||
|
private var recognizedGesture = false
|
||||||
|
|
||||||
|
private var offscreenTranslation: CGFloat {
|
||||||
|
var translation = bounds.height + configuration.edgeSpacing
|
||||||
|
if configuration.edge == .bottom {
|
||||||
|
translation += superview?.safeAreaInsets.bottom ?? 0
|
||||||
|
} else {
|
||||||
|
translation += superview?.safeAreaInsets.top ?? 0
|
||||||
|
translation *= -1
|
||||||
|
}
|
||||||
|
return translation
|
||||||
|
}
|
||||||
|
|
||||||
|
init(configuration: ToastConfiguration) {
|
||||||
|
precondition(configuration.edge != .automatic)
|
||||||
|
self.configuration = configuration
|
||||||
|
|
||||||
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
setupView()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupView() {
|
||||||
|
backgroundColor = .systemBlue
|
||||||
|
layer.shadowColor = UIColor.black.cgColor
|
||||||
|
layer.shadowRadius = 5
|
||||||
|
layer.shadowOffset = CGSize(width: 0, height: 2.5)
|
||||||
|
layer.shadowOpacity = 0.5
|
||||||
|
layer.masksToBounds = false
|
||||||
|
|
||||||
|
let stack = UIStackView()
|
||||||
|
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
stack.axis = .horizontal
|
||||||
|
stack.spacing = 8
|
||||||
|
|
||||||
|
if let name = configuration.systemImageName {
|
||||||
|
let imageView = UIImageView(image: UIImage(systemName: name))
|
||||||
|
imageView.tintColor = .white
|
||||||
|
imageView.contentMode = .scaleAspectFit
|
||||||
|
stack.addArrangedSubview(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleLabel = UILabel()
|
||||||
|
titleLabel.text = configuration.title
|
||||||
|
titleLabel.textColor = .white
|
||||||
|
titleLabel.font = .boldSystemFont(ofSize: 14)
|
||||||
|
titleLabel.adjustsFontSizeToFitWidth = true
|
||||||
|
|
||||||
|
if let subtitle = configuration.subtitle {
|
||||||
|
let subtitleLabel = UILabel()
|
||||||
|
subtitleLabel.text = subtitle
|
||||||
|
subtitleLabel.textColor = .white
|
||||||
|
subtitleLabel.numberOfLines = 0
|
||||||
|
subtitleLabel.font = .systemFont(ofSize: 14)
|
||||||
|
let vStack = UIStackView(arrangedSubviews: [
|
||||||
|
titleLabel,
|
||||||
|
subtitleLabel
|
||||||
|
])
|
||||||
|
vStack.axis = .vertical
|
||||||
|
vStack.spacing = 4
|
||||||
|
stack.addArrangedSubview(vStack)
|
||||||
|
} else {
|
||||||
|
stack.addArrangedSubview(titleLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let actionTitle = configuration.actionTitle {
|
||||||
|
let actionLabel = UILabel()
|
||||||
|
actionLabel.text = actionTitle
|
||||||
|
actionLabel.font = .boldSystemFont(ofSize: 16)
|
||||||
|
actionLabel.textColor = .white
|
||||||
|
stack.addArrangedSubview(actionLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
addSubview(stack)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
stack.leadingAnchor.constraint(equalToSystemSpacingAfter: leadingAnchor, multiplier: 1),
|
||||||
|
trailingAnchor.constraint(equalToSystemSpacingAfter: stack.trailingAnchor, multiplier: 1),
|
||||||
|
stack.topAnchor.constraint(equalTo: topAnchor, constant: 4),
|
||||||
|
stack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
|
||||||
|
])
|
||||||
|
|
||||||
|
let pan = UIPanGestureRecognizer(target: self, action: #selector(panRecognized))
|
||||||
|
addGestureRecognizer(pan)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
layer.cornerRadius = min(32, bounds.height / 2)
|
||||||
|
layer.shadowPath = CGPath(roundedRect: bounds, cornerWidth: layer.cornerRadius, cornerHeight: layer.cornerRadius, transform: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
super.touchesBegan(touches, with: event)
|
||||||
|
|
||||||
|
recognizedGesture = false
|
||||||
|
shrinkAnimator = UIViewPropertyAnimator(duration: 0.1, curve: .easeInOut) {
|
||||||
|
self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
|
||||||
|
}
|
||||||
|
shrinkAnimator?.startAnimation(afterDelay: 0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
super.touchesEnded(touches, with: event)
|
||||||
|
|
||||||
|
if !recognizedGesture {
|
||||||
|
guard let shrinkAnimator = shrinkAnimator else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shrinkAnimator.stopAnimation(true)
|
||||||
|
UIView.animate(withDuration: 0.1, delay: 0, options: .curveEaseInOut) {
|
||||||
|
self.transform = .identity
|
||||||
|
}
|
||||||
|
configuration.action?(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
shrinkAnimator = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func panRecognized(_ recognizer: UIPanGestureRecognizer) {
|
||||||
|
let translation = recognizer.translation(in: self).y
|
||||||
|
|
||||||
|
let isDraggingAwayFromDismissalEdge = (configuration.edge == .top && translation > 0) || (configuration.edge == .bottom && translation < 0)
|
||||||
|
|
||||||
|
switch recognizer.state {
|
||||||
|
case .began:
|
||||||
|
recognizedGesture = true
|
||||||
|
UIView.animate(withDuration: 0.1, delay: 0, options: [.curveEaseInOut, .allowUserInteraction]) {
|
||||||
|
self.transform = .identity
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case .changed:
|
||||||
|
var distance: CGFloat
|
||||||
|
if isDraggingAwayFromDismissalEdge {
|
||||||
|
distance = sqrt(abs(translation))
|
||||||
|
if configuration.edge == .bottom {
|
||||||
|
distance *= -1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
distance = translation
|
||||||
|
}
|
||||||
|
transform = CGAffineTransform(translationX: 0, y: distance)
|
||||||
|
|
||||||
|
case .ended, .cancelled:
|
||||||
|
let velocity = recognizer.velocity(in: self).y
|
||||||
|
let distance = isDraggingAwayFromDismissalEdge ? sqrt(abs(translation)) : translation
|
||||||
|
|
||||||
|
let minDismissalDistance = configuration.edgeSpacing + bounds.height / 2
|
||||||
|
let dismissDueToDistance = configuration.edge == .bottom ? distance > minDismissalDistance : -distance > minDismissalDistance
|
||||||
|
|
||||||
|
let minDismissalVelocity: CGFloat = 250
|
||||||
|
let dismissDueToVelocity = configuration.edge == .bottom ? velocity > minDismissalDistance : velocity < -minDismissalVelocity
|
||||||
|
if dismissDueToDistance || dismissDueToVelocity {
|
||||||
|
|
||||||
|
if abs(translation) < abs(offscreenTranslation) {
|
||||||
|
let distanceLeft = offscreenTranslation - translation
|
||||||
|
let duration = 1 / TimeInterval(max(velocity, minDismissalVelocity) / distanceLeft)
|
||||||
|
|
||||||
|
UIView.animate(withDuration: duration, delay: 0, options: .allowUserInteraction) {
|
||||||
|
self.transform = CGAffineTransform(translationX: 0, y: self.offscreenTranslation)
|
||||||
|
} completion: { (_) in
|
||||||
|
self.removeFromSuperview()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
let duration = 0.5
|
||||||
|
let velocity = distance * duration
|
||||||
|
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: velocity, options: .allowUserInteraction) {
|
||||||
|
self.transform = .identity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dismissToast(animated: Bool) {
|
||||||
|
guard animated else {
|
||||||
|
removeFromSuperview()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut) {
|
||||||
|
self.transform = CGAffineTransform(translationX: 0, y: self.offscreenTranslation)
|
||||||
|
} completion: { (_) in
|
||||||
|
self.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateAppearance() {
|
||||||
|
self.transform = CGAffineTransform(translationX: 0, y: offscreenTranslation)
|
||||||
|
let duration = 0.5
|
||||||
|
let velocity = 0.5
|
||||||
|
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: velocity, options: []) {
|
||||||
|
self.transform = .identity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
//
|
||||||
|
// ToastableViewController.swift
|
||||||
|
// ToastableViewController
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 8/14/21.
|
||||||
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
protocol ToastableViewController: UIViewController {
|
||||||
|
|
||||||
|
var toastParentView: UIView { get }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private var currentToastKey = "Tusker_currentToast"
|
||||||
|
|
||||||
|
extension ToastableViewController {
|
||||||
|
|
||||||
|
private(set) var currentToast: ToastView? {
|
||||||
|
get {
|
||||||
|
let holder = objc_getAssociatedObject(self, ¤tToastKey) as? WeakHolder<ToastView>
|
||||||
|
return holder?.object
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if let newValue = newValue {
|
||||||
|
objc_setAssociatedObject(self, ¤tToastKey, WeakHolder(object: newValue), .OBJC_ASSOCIATION_RETAIN)
|
||||||
|
} else {
|
||||||
|
objc_setAssociatedObject(self, ¤tToastKey, nil, .OBJC_ASSOCIATION_RETAIN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var toastParentView: UIView { view }
|
||||||
|
|
||||||
|
func showToast(configuration config: ToastConfiguration, animated: Bool) {
|
||||||
|
currentToast?.dismissToast(animated: false)
|
||||||
|
|
||||||
|
var config = config
|
||||||
|
config.edge = effectiveEdge(edge: config.edge)
|
||||||
|
|
||||||
|
let toast = ToastView(configuration: config)
|
||||||
|
currentToast = toast
|
||||||
|
|
||||||
|
let parentSafeArea = toastParentView.safeAreaLayoutGuide
|
||||||
|
toast.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
toastParentView.addSubview(toast)
|
||||||
|
|
||||||
|
let yConstraint: NSLayoutConstraint
|
||||||
|
switch config.edge {
|
||||||
|
case .top:
|
||||||
|
yConstraint = toast.topAnchor.constraint(equalTo: parentSafeArea.topAnchor, constant: config.edgeSpacing)
|
||||||
|
case .bottom:
|
||||||
|
yConstraint = parentSafeArea.bottomAnchor.constraint(equalTo: toast.bottomAnchor, constant: config.edgeSpacing)
|
||||||
|
case .automatic:
|
||||||
|
fatalError("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
toast.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: parentSafeArea.leadingAnchor, multiplier: 1),
|
||||||
|
parentSafeArea.trailingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: toast.trailingAnchor, multiplier: 1),
|
||||||
|
parentSafeArea.centerXAnchor.constraint(equalTo: toast.centerXAnchor),
|
||||||
|
yConstraint,
|
||||||
|
])
|
||||||
|
|
||||||
|
if animated {
|
||||||
|
toast.animateAppearance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func effectiveEdge(edge: ToastConfiguration.Edge) -> ToastConfiguration.Edge {
|
||||||
|
guard case .automatic = edge else {
|
||||||
|
return edge
|
||||||
|
}
|
||||||
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||||
|
return .bottom
|
||||||
|
} else {
|
||||||
|
return .top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate class WeakHolder<T: AnyObject> {
|
||||||
|
weak var object: T?
|
||||||
|
|
||||||
|
init(object: T) {
|
||||||
|
self.object = object
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue