mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2025-02-02 10:27:08 +01:00
feat: add content warning for post media
This commit is contained in:
parent
caaf66286f
commit
d332c98a0f
@ -25,7 +25,6 @@
|
|||||||
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33725E6401400AAD544 /* PickServerCell.swift */; };
|
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33725E6401400AAD544 /* PickServerCell.swift */; };
|
||||||
164F0EBC267D4FE400249499 /* BoopSound.caf in Resources */ = {isa = PBXBuildFile; fileRef = 164F0EBB267D4FE400249499 /* BoopSound.caf */; };
|
164F0EBC267D4FE400249499 /* BoopSound.caf in Resources */ = {isa = PBXBuildFile; fileRef = 164F0EBB267D4FE400249499 /* BoopSound.caf */; };
|
||||||
18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; };
|
18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; };
|
||||||
2D084B8D26258EA3003AA3AF /* NotificationViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D084B8C26258EA3003AA3AF /* NotificationViewModel+Diffable.swift */; };
|
|
||||||
2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198642261BF09500F0B013 /* SearchResultItem.swift */; };
|
2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198642261BF09500F0B013 /* SearchResultItem.swift */; };
|
||||||
2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198648261C0B8500F0B013 /* SearchResultSection.swift */; };
|
2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198648261C0B8500F0B013 /* SearchResultSection.swift */; };
|
||||||
2D206B7225F5D27F00143C56 /* AudioContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B7125F5D27F00143C56 /* AudioContainerView.swift */; };
|
2D206B7225F5D27F00143C56 /* AudioContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B7125F5D27F00143C56 /* AudioContainerView.swift */; };
|
||||||
@ -413,6 +412,7 @@
|
|||||||
DB852D1F26FB037800FC9D81 /* SidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */; };
|
DB852D1F26FB037800FC9D81 /* SidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */; };
|
||||||
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */; };
|
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */; };
|
||||||
DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */; };
|
DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */; };
|
||||||
|
DB894CC427A5490600684B74 /* BlurhashImageCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB894CC327A5490600684B74 /* BlurhashImageCacheService.swift */; };
|
||||||
DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52B25C13561002E6C99 /* ViewStateStore.swift */; };
|
DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52B25C13561002E6C99 /* ViewStateStore.swift */; };
|
||||||
DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52C25C13561002E6C99 /* DocumentStore.swift */; };
|
DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52C25C13561002E6C99 /* DocumentStore.swift */; };
|
||||||
DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52D25C13561002E6C99 /* AppContext.swift */; };
|
DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52D25C13561002E6C99 /* AppContext.swift */; };
|
||||||
@ -477,7 +477,6 @@
|
|||||||
DBAE3F942616E28B004B8251 /* APIService+Follow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F932616E28B004B8251 /* APIService+Follow.swift */; };
|
DBAE3F942616E28B004B8251 /* APIService+Follow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F932616E28B004B8251 /* APIService+Follow.swift */; };
|
||||||
DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; };
|
DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; };
|
||||||
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; };
|
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; };
|
||||||
DBAEDE5C267A058D00D25FF5 /* BlurhashImageCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */; };
|
|
||||||
DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; };
|
DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; };
|
||||||
DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; };
|
DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; };
|
||||||
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = DBB525072611EAC0002F1F29 /* Tabman */; };
|
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = DBB525072611EAC0002F1F29 /* Tabman */; };
|
||||||
@ -516,7 +515,6 @@
|
|||||||
DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */; };
|
DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */; };
|
||||||
DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */; };
|
DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */; };
|
||||||
DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */; };
|
DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */; };
|
||||||
DBBC50BF278ED0E700AF0CC6 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC50BE278ED0E700AF0CC6 /* Date.swift */; };
|
|
||||||
DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; };
|
DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; };
|
||||||
DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */; };
|
DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */; };
|
||||||
DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */; };
|
DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */; };
|
||||||
@ -750,7 +748,6 @@
|
|||||||
159AC43EFE0A1F95FCB358A4 /* Pods-MastodonIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonIntent.release.xcconfig"; path = "Target Support Files/Pods-MastodonIntent/Pods-MastodonIntent.release.xcconfig"; sourceTree = "<group>"; };
|
159AC43EFE0A1F95FCB358A4 /* Pods-MastodonIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonIntent.release.xcconfig"; path = "Target Support Files/Pods-MastodonIntent/Pods-MastodonIntent.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
164F0EBB267D4FE400249499 /* BoopSound.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = BoopSound.caf; sourceTree = "<group>"; };
|
164F0EBB267D4FE400249499 /* BoopSound.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = BoopSound.caf; sourceTree = "<group>"; };
|
||||||
1D6D967E77A5357E2C6110D9 /* Pods-Mastodon.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk - debug.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk - debug.xcconfig"; sourceTree = "<group>"; };
|
1D6D967E77A5357E2C6110D9 /* Pods-Mastodon.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk - debug.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk - debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
2D084B8C26258EA3003AA3AF /* NotificationViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationViewModel+Diffable.swift"; sourceTree = "<group>"; };
|
|
||||||
2D198642261BF09500F0B013 /* SearchResultItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultItem.swift; sourceTree = "<group>"; };
|
2D198642261BF09500F0B013 /* SearchResultItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultItem.swift; sourceTree = "<group>"; };
|
||||||
2D198648261C0B8500F0B013 /* SearchResultSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultSection.swift; sourceTree = "<group>"; };
|
2D198648261C0B8500F0B013 /* SearchResultSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultSection.swift; sourceTree = "<group>"; };
|
||||||
2D206B7125F5D27F00143C56 /* AudioContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioContainerView.swift; sourceTree = "<group>"; };
|
2D206B7125F5D27F00143C56 /* AudioContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioContainerView.swift; sourceTree = "<group>"; };
|
||||||
@ -1180,6 +1177,7 @@
|
|||||||
DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarViewModel.swift; sourceTree = "<group>"; };
|
DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarViewModel.swift; sourceTree = "<group>"; };
|
||||||
DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollOptionCollectionViewCell.swift; sourceTree = "<group>"; };
|
DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollOptionCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollOptionAppendEntryCollectionViewCell.swift; sourceTree = "<group>"; };
|
DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollOptionAppendEntryCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
DB894CC327A5490600684B74 /* BlurhashImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurhashImageCacheService.swift; sourceTree = "<group>"; };
|
||||||
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Mastodon.entitlements; sourceTree = "<group>"; };
|
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Mastodon.entitlements; sourceTree = "<group>"; };
|
||||||
DB8AF52B25C13561002E6C99 /* ViewStateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewStateStore.swift; sourceTree = "<group>"; };
|
DB8AF52B25C13561002E6C99 /* ViewStateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewStateStore.swift; sourceTree = "<group>"; };
|
||||||
DB8AF52C25C13561002E6C99 /* DocumentStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentStore.swift; sourceTree = "<group>"; };
|
DB8AF52C25C13561002E6C99 /* DocumentStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentStore.swift; sourceTree = "<group>"; };
|
||||||
@ -1257,7 +1255,6 @@
|
|||||||
DBAE3F932616E28B004B8251 /* APIService+Follow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Follow.swift"; sourceTree = "<group>"; };
|
DBAE3F932616E28B004B8251 /* APIService+Follow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Follow.swift"; sourceTree = "<group>"; };
|
||||||
DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Mute.swift"; sourceTree = "<group>"; };
|
DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Mute.swift"; sourceTree = "<group>"; };
|
||||||
DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = "<group>"; };
|
DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = "<group>"; };
|
||||||
DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurhashImageCacheService.swift; sourceTree = "<group>"; };
|
|
||||||
DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLAnimatedImageView.swift; sourceTree = "<group>"; };
|
DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLAnimatedImageView.swift; sourceTree = "<group>"; };
|
||||||
DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = "<group>"; };
|
DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = "<group>"; };
|
||||||
DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewController.swift; sourceTree = "<group>"; };
|
DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewController.swift; sourceTree = "<group>"; };
|
||||||
@ -1282,7 +1279,6 @@
|
|||||||
DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThemeService+Appearance.swift"; sourceTree = "<group>"; };
|
DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThemeService+Appearance.swift"; sourceTree = "<group>"; };
|
||||||
DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = "<group>"; };
|
DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = "<group>"; };
|
||||||
DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = "<group>"; };
|
DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = "<group>"; };
|
||||||
DBBC50BE278ED0E700AF0CC6 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
|
|
||||||
DBBC50C0278ED49200AF0CC6 /* MastodonAuthenticationBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationBox.swift; sourceTree = "<group>"; };
|
DBBC50C0278ED49200AF0CC6 /* MastodonAuthenticationBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationBox.swift; sourceTree = "<group>"; };
|
||||||
DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = "<group>"; };
|
DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = "<group>"; };
|
||||||
DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewModel.swift; sourceTree = "<group>"; };
|
DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewModel.swift; sourceTree = "<group>"; };
|
||||||
@ -1690,9 +1686,9 @@
|
|||||||
DB6D9F6226357848008423CD /* SettingService.swift */,
|
DB6D9F6226357848008423CD /* SettingService.swift */,
|
||||||
DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */,
|
DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */,
|
||||||
DB297B1A2679FAE200704C90 /* PlaceholderImageCacheService.swift */,
|
DB297B1A2679FAE200704C90 /* PlaceholderImageCacheService.swift */,
|
||||||
DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */,
|
|
||||||
DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */,
|
DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */,
|
||||||
DB73BF42271192BB00781945 /* InstanceService.swift */,
|
DB73BF42271192BB00781945 /* InstanceService.swift */,
|
||||||
|
DB894CC327A5490600684B74 /* BlurhashImageCacheService.swift */,
|
||||||
);
|
);
|
||||||
path = Service;
|
path = Service;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2724,7 +2720,6 @@
|
|||||||
DBCC3B35261440BA0045B23D /* UINavigationController.swift */,
|
DBCC3B35261440BA0045B23D /* UINavigationController.swift */,
|
||||||
DB73BF4827140BA300781945 /* UICollectionViewDiffableDataSource.swift */,
|
DB73BF4827140BA300781945 /* UICollectionViewDiffableDataSource.swift */,
|
||||||
DB73BF4A27140C0800781945 /* UITableViewDiffableDataSource.swift */,
|
DB73BF4A27140C0800781945 /* UITableViewDiffableDataSource.swift */,
|
||||||
DBBC50BE278ED0E700AF0CC6 /* Date.swift */,
|
|
||||||
);
|
);
|
||||||
path = Extension;
|
path = Extension;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2794,7 +2789,6 @@
|
|||||||
2D35237F26256F470031AF25 /* Cell */,
|
2D35237F26256F470031AF25 /* Cell */,
|
||||||
DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */,
|
DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */,
|
||||||
2D607AD726242FC500B70763 /* NotificationViewModel.swift */,
|
2D607AD726242FC500B70763 /* NotificationViewModel.swift */,
|
||||||
2D084B8C26258EA3003AA3AF /* NotificationViewModel+Diffable.swift */,
|
|
||||||
);
|
);
|
||||||
path = Notification;
|
path = Notification;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -3782,7 +3776,6 @@
|
|||||||
5DF1054125F886D400D6C0D4 /* VideoPlaybackService.swift in Sources */,
|
5DF1054125F886D400D6C0D4 /* VideoPlaybackService.swift in Sources */,
|
||||||
DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */,
|
DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */,
|
||||||
0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */,
|
0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */,
|
||||||
DBBC50BF278ED0E700AF0CC6 /* Date.swift in Sources */,
|
|
||||||
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
|
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
|
||||||
2D9DB967263A76FB007C1D71 /* BlockDomainService.swift in Sources */,
|
2D9DB967263A76FB007C1D71 /* BlockDomainService.swift in Sources */,
|
||||||
DB336F43278EB1690031E64B /* MediaView+Configuration.swift in Sources */,
|
DB336F43278EB1690031E64B /* MediaView+Configuration.swift in Sources */,
|
||||||
@ -3976,7 +3969,6 @@
|
|||||||
DB4F0968269ED8AD00D62E92 /* SearchHistoryTableHeaderView.swift in Sources */,
|
DB4F0968269ED8AD00D62E92 /* SearchHistoryTableHeaderView.swift in Sources */,
|
||||||
0FB3D2FE25E4CB6400AAD544 /* OnboardingHeadlineTableViewCell.swift in Sources */,
|
0FB3D2FE25E4CB6400AAD544 /* OnboardingHeadlineTableViewCell.swift in Sources */,
|
||||||
5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */,
|
5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */,
|
||||||
DBAEDE5C267A058D00D25FF5 /* BlurhashImageCacheService.swift in Sources */,
|
|
||||||
DB1D843026566512000346B3 /* KeyboardPreference.swift in Sources */,
|
DB1D843026566512000346B3 /* KeyboardPreference.swift in Sources */,
|
||||||
DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */,
|
DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */,
|
||||||
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */,
|
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */,
|
||||||
@ -4024,7 +4016,6 @@
|
|||||||
DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */,
|
DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */,
|
||||||
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
|
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
|
||||||
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
|
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
|
||||||
2D084B8D26258EA3003AA3AF /* NotificationViewModel+Diffable.swift in Sources */,
|
|
||||||
DB3667A1268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift in Sources */,
|
DB3667A1268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift in Sources */,
|
||||||
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,
|
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,
|
||||||
DBA94434265CBB5300C537E1 /* ProfileFieldSection.swift in Sources */,
|
DBA94434265CBB5300C537E1 /* ProfileFieldSection.swift in Sources */,
|
||||||
@ -4136,6 +4127,7 @@
|
|||||||
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */,
|
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */,
|
||||||
DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */,
|
DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */,
|
||||||
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */,
|
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */,
|
||||||
|
DB894CC427A5490600684B74 /* BlurhashImageCacheService.swift in Sources */,
|
||||||
DBFEF07B26A6BCE8006D7ED1 /* APIService+Status+Publish.swift in Sources */,
|
DBFEF07B26A6BCE8006D7ED1 /* APIService+Status+Publish.swift in Sources */,
|
||||||
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */,
|
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */,
|
||||||
5B90C48526259BF10002E742 /* APIService+Subscriptions.swift in Sources */,
|
5B90C48526259BF10002E742 /* APIService+Subscriptions.swift in Sources */,
|
||||||
|
@ -86,6 +86,13 @@
|
|||||||
ReferencedContainer = "container:Mastodon.xcodeproj">
|
ReferencedContainer = "container:Mastodon.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
|
<AdditionalOptions>
|
||||||
|
<AdditionalOption
|
||||||
|
key = "NSZombieEnabled"
|
||||||
|
value = "YES"
|
||||||
|
isEnabled = "YES">
|
||||||
|
</AdditionalOption>
|
||||||
|
</AdditionalOptions>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
//
|
|
||||||
// Date.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by MainasuK on 2022-1-12.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import MastodonAsset
|
|
||||||
import MastodonLocalization
|
|
||||||
|
|
||||||
extension Date {
|
|
||||||
|
|
||||||
public static let relativeTimestampFormatter: RelativeDateTimeFormatter = {
|
|
||||||
let formatter = RelativeDateTimeFormatter()
|
|
||||||
formatter.dateTimeStyle = .numeric
|
|
||||||
formatter.unitsStyle = .full
|
|
||||||
return formatter
|
|
||||||
}()
|
|
||||||
|
|
||||||
public var localizedSlowedTimeAgoSinceNow: String {
|
|
||||||
return self.localizedTimeAgo(since: Date(), isSlowed: true, isAbbreviated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
public var localizedTimeAgoSinceNow: String {
|
|
||||||
return self.localizedTimeAgo(since: Date(), isSlowed: false, isAbbreviated: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func localizedTimeAgo(since date: Date, isSlowed: Bool, isAbbreviated: Bool) -> String {
|
|
||||||
let earlierDate = date < self ? date : self
|
|
||||||
let latestDate = earlierDate == date ? self : date
|
|
||||||
|
|
||||||
if isSlowed, earlierDate.timeIntervalSince(latestDate) >= -60 {
|
|
||||||
return L10n.Common.Controls.Timeline.Timestamp.now
|
|
||||||
} else {
|
|
||||||
if isAbbreviated {
|
|
||||||
return latestDate.localizedShortTimeAgo(since: earlierDate)
|
|
||||||
} else {
|
|
||||||
return Date.relativeTimestampFormatter.localizedString(for: earlierDate, relativeTo: latestDate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -47,7 +47,7 @@ extension DataSourceFacade {
|
|||||||
switch target {
|
switch target {
|
||||||
case .status:
|
case .status:
|
||||||
return status.reblog ?? status
|
return status.reblog ?? status
|
||||||
case .repost:
|
case .reblog:
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import Foundation
|
|||||||
|
|
||||||
enum DataSourceFacade {
|
enum DataSourceFacade {
|
||||||
enum StatusTarget {
|
enum StatusTarget {
|
||||||
case status // remove repost wrapper
|
case status // remove reblog wrapper
|
||||||
case repost // keep repost wrapper
|
case reblog // keep reblog wrapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||||||
}
|
}
|
||||||
await DataSourceFacade.coordinateToProfileScene(
|
await DataSourceFacade.coordinateToProfileScene(
|
||||||
provider: self,
|
provider: self,
|
||||||
target: .status, // without reblog header
|
target: .reblog, // keep the wrapper for header author
|
||||||
status: status
|
status: status
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -117,6 +117,24 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & MediaPrev
|
|||||||
assertionFailure("only works for status data provider")
|
assertionFailure("only works for status data provider")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let managedObjectContext = self.context.managedObjectContext
|
||||||
|
let needsToggleMediaSensitive: Bool = try await managedObjectContext.perform {
|
||||||
|
guard let _status = status.object(in: managedObjectContext) else { return false }
|
||||||
|
let status = _status.reblog ?? _status
|
||||||
|
guard status.sensitive else { return false }
|
||||||
|
guard status.isMediaSensitiveToggled else { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
guard !needsToggleMediaSensitive else {
|
||||||
|
try await DataSourceFacade.responseToToggleMediaSensitiveAction(
|
||||||
|
dependency: self,
|
||||||
|
status: status
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try await DataSourceFacade.coordinateToMediaPreviewScene(
|
try await DataSourceFacade.coordinateToMediaPreviewScene(
|
||||||
dependency: self,
|
dependency: self,
|
||||||
status: status,
|
status: status,
|
||||||
|
@ -374,7 +374,7 @@ extension HomeTimelineViewController {
|
|||||||
|
|
||||||
@objc private func findPeopleButtonPressed(_ sender: PrimaryActionButton) {
|
@objc private func findPeopleButtonPressed(_ sender: PrimaryActionButton) {
|
||||||
// TODO:
|
// TODO:
|
||||||
let viewModel = SuggestionAccountViewModel(context: context)
|
// let viewModel = SuggestionAccountViewModel(context: context)
|
||||||
// viewModel.delegate = self.viewModel
|
// viewModel.delegate = self.viewModel
|
||||||
// coordinator.present(scene: .suggestionAccount(viewModel: viewModel), from: self, transition: .modal(animated: true, completion: nil))
|
// coordinator.present(scene: .suggestionAccount(viewModel: viewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||||
}
|
}
|
||||||
@ -553,40 +553,9 @@ extension HomeTimelineViewController: UITableViewDelegate, AutoGenerateTableView
|
|||||||
// func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
// func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||||
// aspectTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath)
|
// aspectTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath)
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
||||||
// aspectTableView(tableView, didSelectRowAt: indexPath)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
|
||||||
// return aspectTableView(tableView, contextMenuConfigurationForRowAt: indexPath, point: point)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
|
|
||||||
// return aspectTableView(tableView, previewForHighlightingContextMenuWithConfiguration: configuration)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func tableView(_ tableView: UITableView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
|
|
||||||
// return aspectTableView(tableView, previewForDismissingContextMenuWithConfiguration: configuration)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
|
||||||
// aspectTableView(tableView, willPerformPreviewActionForMenuWith: configuration, animator: animator)
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDataSourcePrefetching
|
|
||||||
//extension HomeTimelineViewController: UITableViewDataSourcePrefetching {
|
|
||||||
// func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
|
||||||
// aspectTableView(tableView, prefetchRowsAt: indexPaths)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
|
||||||
// aspectTableView(tableView, cancelPrefetchingForRowsAt: indexPaths)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
// MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate
|
// MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate
|
||||||
extension HomeTimelineViewController: ContentOffsetAdjustableTimelineViewControllerDelegate {
|
extension HomeTimelineViewController: ContentOffsetAdjustableTimelineViewControllerDelegate {
|
||||||
func navigationBar() -> UINavigationBar? {
|
func navigationBar() -> UINavigationBar? {
|
||||||
@ -613,24 +582,23 @@ extension HomeTimelineViewController: ScrollViewContainer {
|
|||||||
var scrollView: UIScrollView { return tableView }
|
var scrollView: UIScrollView { return tableView }
|
||||||
|
|
||||||
func scrollToTop(animated: Bool) {
|
func scrollToTop(animated: Bool) {
|
||||||
// TODO:
|
if scrollView.contentOffset.y < scrollView.frame.height,
|
||||||
// if scrollView.contentOffset.y < scrollView.frame.height,
|
viewModel.loadLatestStateMachine.canEnterState(HomeTimelineViewModel.LoadLatestState.Loading.self),
|
||||||
// viewModel.loadLatestStateMachine.canEnterState(HomeTimelineViewModel.LoadLatestState.Loading.self),
|
(scrollView.contentOffset.y + scrollView.adjustedContentInset.top) == 0.0,
|
||||||
// (scrollView.contentOffset.y + scrollView.adjustedContentInset.top) == 0.0,
|
!refreshControl.isRefreshing {
|
||||||
// !refreshControl.isRefreshing {
|
scrollView.scrollRectToVisible(CGRect(origin: CGPoint(x: 0, y: -refreshControl.frame.height), size: CGSize(width: 1, height: 1)), animated: animated)
|
||||||
// scrollView.scrollRectToVisible(CGRect(origin: CGPoint(x: 0, y: -refreshControl.frame.height), size: CGSize(width: 1, height: 1)), animated: animated)
|
DispatchQueue.main.async { [weak self] in
|
||||||
// DispatchQueue.main.async { [weak self] in
|
guard let self = self else { return }
|
||||||
// guard let self = self else { return }
|
self.refreshControl.beginRefreshing()
|
||||||
// self.refreshControl.beginRefreshing()
|
self.refreshControl.sendActions(for: .valueChanged)
|
||||||
// self.refreshControl.sendActions(for: .valueChanged)
|
}
|
||||||
// }
|
} else {
|
||||||
// } else {
|
let indexPath = IndexPath(row: 0, section: 0)
|
||||||
// let indexPath = IndexPath(row: 0, section: 0)
|
guard viewModel.diffableDataSource?.itemIdentifier(for: indexPath) != nil else { return }
|
||||||
// guard viewModel.diffableDataSource?.itemIdentifier(for: indexPath) != nil else { return }
|
// save position
|
||||||
// // save position
|
savePositionBeforeScrollToTop()
|
||||||
// savePositionBeforeScrollToTop()
|
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
|
||||||
// tableView.scrollToRow(at: indexPath, at: .top, animated: true)
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ extension NotificationTableViewCell {
|
|||||||
case .feed(let feed):
|
case .feed(let feed):
|
||||||
notificationView.configure(feed: feed)
|
notificationView.configure(feed: feed)
|
||||||
}
|
}
|
||||||
|
//
|
||||||
self.delegate = delegate
|
self.delegate = delegate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,94 +0,0 @@
|
|||||||
//
|
|
||||||
// NotificationViewModel+Diffable.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by sxiaojian on 2021/4/13.
|
|
||||||
//
|
|
||||||
|
|
||||||
import CoreData
|
|
||||||
import CoreDataStack
|
|
||||||
import os.log
|
|
||||||
import UIKit
|
|
||||||
import MastodonSDK
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//extension NotificationViewModel: NSFetchedResultsControllerDelegate {
|
|
||||||
// func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
|
||||||
// os_log("%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
|
|
||||||
// os_log("%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
|
|
||||||
//
|
|
||||||
// guard let tableView = self.tableView else { return }
|
|
||||||
// guard let navigationBar = contentOffsetAdjustableTimelineViewControllerDelegate?.navigationBar() else { return }
|
|
||||||
//
|
|
||||||
// guard let diffableDataSource = self.diffableDataSource else { return }
|
|
||||||
//
|
|
||||||
// let predicate: NSPredicate = {
|
|
||||||
// let notificationTypePredicate = MastodonNotification.predicate(
|
|
||||||
// validTypesRaws: Mastodon.Entity.Notification.NotificationType.knownCases.map { $0.rawValue }
|
|
||||||
// )
|
|
||||||
// return fetchedResultsController.fetchRequest.predicate.flatMap {
|
|
||||||
// NSCompoundPredicate(andPredicateWithSubpredicates: [$0, notificationTypePredicate])
|
|
||||||
// } ?? notificationTypePredicate
|
|
||||||
// }()
|
|
||||||
// let parentManagedObjectContext = fetchedResultsController.managedObjectContext
|
|
||||||
// let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
|
||||||
// managedObjectContext.parent = parentManagedObjectContext
|
|
||||||
//
|
|
||||||
// managedObjectContext.perform {
|
|
||||||
// let notifications: [MastodonNotification] = {
|
|
||||||
// let request = MastodonNotification.sortedFetchRequest
|
|
||||||
// request.returnsObjectsAsFaults = false
|
|
||||||
// request.predicate = predicate
|
|
||||||
// do {
|
|
||||||
// return try managedObjectContext.fetch(request)
|
|
||||||
// } catch {
|
|
||||||
// assertionFailure(error.localizedDescription)
|
|
||||||
// return []
|
|
||||||
// }
|
|
||||||
// }()
|
|
||||||
//
|
|
||||||
// DispatchQueue.main.async {
|
|
||||||
// let oldSnapshot = diffableDataSource.snapshot()
|
|
||||||
// var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusAttribute] = [:]
|
|
||||||
// for item in oldSnapshot.itemIdentifiers {
|
|
||||||
// guard case let .notification(objectID, attribute) = item else { continue }
|
|
||||||
// oldSnapshotAttributeDict[objectID] = attribute
|
|
||||||
// }
|
|
||||||
// var newSnapshot = NSDiffableDataSourceSnapshot<NotificationSection, NotificationItem>()
|
|
||||||
// newSnapshot.appendSections([.main])
|
|
||||||
//
|
|
||||||
// let segment = self.selectedIndex.value
|
|
||||||
// switch segment {
|
|
||||||
// case .everyThing:
|
|
||||||
// let items: [NotificationItem] = notifications.map { notification in
|
|
||||||
// let attribute: Item.StatusAttribute = oldSnapshotAttributeDict[notification.objectID] ?? Item.StatusAttribute()
|
|
||||||
// return NotificationItem.notification(objectID: notification.objectID, attribute: attribute)
|
|
||||||
// }
|
|
||||||
// newSnapshot.appendItems(items, toSection: .main)
|
|
||||||
// case .mentions:
|
|
||||||
// let items: [NotificationItem] = notifications.map { notification in
|
|
||||||
// let attribute: Item.StatusAttribute = oldSnapshotAttributeDict[notification.objectID] ?? Item.StatusAttribute()
|
|
||||||
// return NotificationItem.notificationStatus(objectID: notification.objectID, attribute: attribute)
|
|
||||||
// }
|
|
||||||
// newSnapshot.appendItems(items, toSection: .main)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if !notifications.isEmpty, self.noMoreNotification.value == false {
|
|
||||||
// newSnapshot.appendItems([.bottomLoader], toSection: .main)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// self.isFetchingLatestNotification.value = false
|
|
||||||
//
|
|
||||||
// diffableDataSource.apply(newSnapshot, animatingDifferences: false) { [weak self] in
|
|
||||||
// guard let self = self else { return }
|
|
||||||
// self.dataSourceDidUpdated.send()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//}
|
|
@ -25,24 +25,54 @@ extension MediaView {
|
|||||||
return status.publisher(for: \.attachments)
|
return status.publisher(for: \.attachments)
|
||||||
.map { attachments -> [MediaView.Configuration] in
|
.map { attachments -> [MediaView.Configuration] in
|
||||||
return attachments.map { attachment -> MediaView.Configuration in
|
return attachments.map { attachment -> MediaView.Configuration in
|
||||||
switch attachment.kind {
|
let configuration: MediaView.Configuration = {
|
||||||
case .image:
|
switch attachment.kind {
|
||||||
let info = MediaView.Configuration.ImageInfo(
|
case .image:
|
||||||
aspectRadio: attachment.size,
|
let info = MediaView.Configuration.ImageInfo(
|
||||||
assetURL: attachment.assetURL
|
aspectRadio: attachment.size,
|
||||||
|
assetURL: attachment.assetURL
|
||||||
|
)
|
||||||
|
return .init(
|
||||||
|
info: .image(info: info),
|
||||||
|
blurhash: attachment.blurhash
|
||||||
|
)
|
||||||
|
case .video:
|
||||||
|
let info = videoInfo(from: attachment)
|
||||||
|
return .init(
|
||||||
|
info: .video(info: info),
|
||||||
|
blurhash: attachment.blurhash
|
||||||
|
)
|
||||||
|
case .gifv:
|
||||||
|
let info = videoInfo(from: attachment)
|
||||||
|
return .init(
|
||||||
|
info: .gif(info: info),
|
||||||
|
blurhash: attachment.blurhash
|
||||||
|
)
|
||||||
|
case .audio:
|
||||||
|
// TODO:
|
||||||
|
let info = videoInfo(from: attachment)
|
||||||
|
return .init(
|
||||||
|
info: .video(info: info),
|
||||||
|
blurhash: attachment.blurhash
|
||||||
|
)
|
||||||
|
} // end switch
|
||||||
|
}()
|
||||||
|
|
||||||
|
if let assetURL = configuration.assetURL,
|
||||||
|
let blurhash = configuration.blurhash
|
||||||
|
{
|
||||||
|
AppContext.shared.blurhashImageCacheService.image(
|
||||||
|
blurhash: blurhash,
|
||||||
|
size: configuration.aspectRadio,
|
||||||
|
url: assetURL
|
||||||
)
|
)
|
||||||
return .image(info: info)
|
.assign(to: \.blurhashImage, on: configuration)
|
||||||
case .video:
|
.store(in: &configuration.blurhashImageDisposeBag)
|
||||||
let info = videoInfo(from: attachment)
|
|
||||||
return .video(info: info)
|
|
||||||
case .gifv:
|
|
||||||
let info = videoInfo(from: attachment)
|
|
||||||
return .gif(info: info)
|
|
||||||
case .audio:
|
|
||||||
// TODO:
|
|
||||||
let info = videoInfo(from: attachment)
|
|
||||||
return .video(info: info)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configuration.isReveal = status.sensitive ? status.isMediaSensitiveToggled : true
|
||||||
|
|
||||||
|
return configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
@ -92,13 +92,7 @@ extension NotificationView {
|
|||||||
.assign(to: \.authorUsername, on: viewModel)
|
.assign(to: \.authorUsername, on: viewModel)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
// timestamp
|
// timestamp
|
||||||
viewModel.timestampFormatter = { (date: Date) in
|
viewModel.timestamp = notification.createAt
|
||||||
date.localizedSlowedTimeAgoSinceNow
|
|
||||||
}
|
|
||||||
notification.publisher(for: \.createAt)
|
|
||||||
.map { $0 as Date? }
|
|
||||||
.assign(to: \.timestamp, on: viewModel)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
// notification type indicator
|
// notification type indicator
|
||||||
Publishers.CombineLatest3(
|
Publishers.CombineLatest3(
|
||||||
notification.publisher(for: \.typeRaw),
|
notification.publisher(for: \.typeRaw),
|
||||||
@ -111,7 +105,7 @@ extension NotificationView {
|
|||||||
self.viewModel.notificationIndicatorText = nil
|
self.viewModel.notificationIndicatorText = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMetaContent(text: String, emojis: MastodonContent.Emojis) -> MetaContent {
|
func createMetaContent(text: String, emojis: MastodonContent.Emojis) -> MetaContent {
|
||||||
let content = MastodonContent(content: text, emojis: emojis)
|
let content = MastodonContent(content: text, emojis: emojis)
|
||||||
guard let metaContent = try? MastodonMetaContent.convert(document: content) else {
|
guard let metaContent = try? MastodonMetaContent.convert(document: content) else {
|
||||||
@ -119,7 +113,7 @@ extension NotificationView {
|
|||||||
}
|
}
|
||||||
return metaContent
|
return metaContent
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: fix the i18n. The subject should assert place at the string beginning
|
// TODO: fix the i18n. The subject should assert place at the string beginning
|
||||||
switch type {
|
switch type {
|
||||||
case .follow:
|
case .follow:
|
||||||
|
@ -173,14 +173,10 @@ extension StatusView {
|
|||||||
.map { $0 as String? }
|
.map { $0 as String? }
|
||||||
.assign(to: \.authorUsername, on: viewModel)
|
.assign(to: \.authorUsername, on: viewModel)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
// locked
|
||||||
// // protected
|
author.publisher(for: \.locked)
|
||||||
// author.publisher(for: \.locked)
|
.assign(to: \.locked, on: viewModel)
|
||||||
// .assign(to: \.protected, on: viewModel)
|
.store(in: &disposeBag)
|
||||||
// .store(in: &disposeBag)
|
|
||||||
// // visibility
|
|
||||||
// viewModel.visibility = status.visibility.asStatusVisibility
|
|
||||||
|
|
||||||
// isMuting
|
// isMuting
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
viewModel.$userIdentifier,
|
viewModel.$userIdentifier,
|
||||||
@ -267,42 +263,22 @@ extension StatusView {
|
|||||||
status.publisher(for: \.isContentSensitiveToggled)
|
status.publisher(for: \.isContentSensitiveToggled)
|
||||||
.assign(to: \.isContentSensitiveToggled, on: viewModel)
|
.assign(to: \.isContentSensitiveToggled, on: viewModel)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
status.publisher(for: \.isMediaSensitiveToggled)
|
|
||||||
.assign(to: \.isMediaSensitiveToggled, on: viewModel)
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
// viewModel.source = status.source
|
// viewModel.source = status.source
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureMedia(status: Status) {
|
private func configureMedia(status: Status) {
|
||||||
let status = status.reblog ?? status
|
let status = status.reblog ?? status
|
||||||
|
|
||||||
// mediaGridContainerView.viewModel.resetContentWarningOverlay()
|
viewModel.isMediaSensitive = status.sensitive && !status.attachments.isEmpty // some servers set media sensitive even empty attachments
|
||||||
// viewModel.isMediaSensitiveSwitchable = true
|
|
||||||
|
|
||||||
viewModel.isMediaSensitive = status.sensitive
|
|
||||||
|
|
||||||
MediaView.configuration(status: status)
|
MediaView.configuration(status: status)
|
||||||
.assign(to: \.mediaViewConfigurations, on: viewModel)
|
.assign(to: \.mediaViewConfigurations, on: viewModel)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
// // set directly without delay
|
status.publisher(for: \.isMediaSensitiveToggled)
|
||||||
// viewModel.isMediaSensitiveToggled = status.isMediaSensitiveToggled
|
.assign(to: \.isMediaSensitiveToggled, on: viewModel)
|
||||||
// viewModel.isMediaSensitive = status.isMediaSensitive
|
.store(in: &disposeBag)
|
||||||
// mediaGridContainerView.configureOverlayDisplay(
|
|
||||||
// isDisplay: status.isMediaSensitiveToggled ? !status.isMediaSensitive : !status.isMediaSensitive,
|
|
||||||
// animated: false
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// status.publisher(for: \.isMediaSensitive)
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .assign(to: \.isMediaSensitive, on: viewModel)
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// status.publisher(for: \.isMediaSensitiveToggled)
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .assign(to: \.isMediaSensitiveToggled, on: viewModel)
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configurePoll(status: Status) {
|
private func configurePoll(status: Status) {
|
||||||
|
@ -8,13 +8,19 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
final class BlurhashImageCacheService {
|
public final class BlurhashImageCacheService {
|
||||||
|
|
||||||
|
static let edgeMaxLength: CGFloat = 20
|
||||||
|
|
||||||
let cache = NSCache<Key, UIImage>()
|
let cache = NSCache<Key, UIImage>()
|
||||||
|
|
||||||
let workingQueue = DispatchQueue(label: "org.joinmastodon.app.BlurhashImageCacheService.working-queue", qos: .userInitiated, attributes: .concurrent)
|
let workingQueue = DispatchQueue(label: "org.joinmastodon.app.BlurhashImageCacheService.working-queue", qos: .userInitiated, attributes: .concurrent)
|
||||||
|
|
||||||
func image(blurhash: String, size: CGSize, url: URL) -> AnyPublisher<UIImage?, Never> {
|
public func image(
|
||||||
|
blurhash: String,
|
||||||
|
size: CGSize,
|
||||||
|
url: String
|
||||||
|
) -> AnyPublisher<UIImage?, Never> {
|
||||||
let key = Key(blurhash: blurhash, size: size, url: url)
|
let key = Key(blurhash: blurhash, size: size, url: url)
|
||||||
|
|
||||||
if let image = self.cache.object(forKey: key) {
|
if let image = self.cache.object(forKey: key) {
|
||||||
@ -23,7 +29,7 @@ final class BlurhashImageCacheService {
|
|||||||
|
|
||||||
return Future { promise in
|
return Future { promise in
|
||||||
self.workingQueue.async {
|
self.workingQueue.async {
|
||||||
guard let image = BlurhashImageCacheService.blurhashImage(blurhash: blurhash, size: size, url: url) else {
|
guard let image = BlurhashImageCacheService.blurhashImage(blurhash: blurhash, size: size) else {
|
||||||
promise(.success(nil))
|
promise(.success(nil))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -33,27 +39,25 @@ final class BlurhashImageCacheService {
|
|||||||
}
|
}
|
||||||
.receive(on: RunLoop.main)
|
.receive(on: RunLoop.main)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func blurhashImage(blurhash: String, size: CGSize, url: URL) -> UIImage? {
|
static func blurhashImage(blurhash: String, size: CGSize) -> UIImage? {
|
||||||
fatalError()
|
let imageSize: CGSize = {
|
||||||
// let imageSize: CGSize = {
|
let aspectRadio = size.width / size.height
|
||||||
// let aspectRadio = size.width / size.height
|
if size.width > size.height {
|
||||||
// if size.width > size.height {
|
let width: CGFloat = BlurhashImageCacheService.edgeMaxLength
|
||||||
// let width: CGFloat = MosaicMeta.edgeMaxLength
|
let height = width / aspectRadio
|
||||||
// let height = width / aspectRadio
|
return CGSize(width: width, height: height)
|
||||||
// return CGSize(width: width, height: height)
|
} else {
|
||||||
// } else {
|
let height: CGFloat = BlurhashImageCacheService.edgeMaxLength
|
||||||
// let height: CGFloat = MosaicMeta.edgeMaxLength
|
let width = height * aspectRadio
|
||||||
// let width = height * aspectRadio
|
return CGSize(width: width, height: height)
|
||||||
// return CGSize(width: width, height: height)
|
}
|
||||||
// }
|
}()
|
||||||
// }()
|
|
||||||
//
|
let image = UIImage(blurHash: blurhash, size: imageSize)
|
||||||
// let image = UIImage(blurHash: blurhash, size: imageSize)
|
|
||||||
//
|
return image
|
||||||
// return image
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -62,9 +66,9 @@ extension BlurhashImageCacheService {
|
|||||||
class Key: NSObject {
|
class Key: NSObject {
|
||||||
let blurhash: String
|
let blurhash: String
|
||||||
let size: CGSize
|
let size: CGSize
|
||||||
let url: URL
|
let url: String
|
||||||
|
|
||||||
init(blurhash: String, size: CGSize, url: URL) {
|
init(blurhash: String, size: CGSize, url: String) {
|
||||||
self.blurhash = blurhash
|
self.blurhash = blurhash
|
||||||
self.size = size
|
self.size = size
|
||||||
self.url = url
|
self.url = url
|
||||||
@ -83,6 +87,5 @@ extension BlurhashImageCacheService {
|
|||||||
size.height.hashValue ^
|
size.height.hashValue ^
|
||||||
url.hashValue
|
url.hashValue
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,11 +129,6 @@
|
|||||||
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="notifications" inverseEntity="MastodonUser"/>
|
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="notifications" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="feeds" toMany="YES" deletionRule="Cascade" destinationEntity="Feed" inverseName="notification" inverseEntity="Feed"/>
|
<relationship name="feeds" toMany="YES" deletionRule="Cascade" destinationEntity="Feed" inverseName="notification" inverseEntity="Feed"/>
|
||||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="notifications" inverseEntity="Status"/>
|
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="notifications" inverseEntity="Status"/>
|
||||||
<uniquenessConstraints>
|
|
||||||
<uniquenessConstraint>
|
|
||||||
<constraint value="id"/>
|
|
||||||
</uniquenessConstraint>
|
|
||||||
</uniquenessConstraints>
|
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="Poll" representedClassName="CoreDataStack.Poll" syncable="YES">
|
<entity name="Poll" representedClassName="CoreDataStack.Poll" syncable="YES">
|
||||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
12
MastodonSDK/Sources/MastodonUI/DateTimeProvider.swift
Normal file
12
MastodonSDK/Sources/MastodonUI/DateTimeProvider.swift
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
//
|
||||||
|
// DateTimeProvider.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by MainasuK on 2022-1-29.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public protocol DateTimeProvider {
|
||||||
|
func shortTimeAgoSinceNow(to date: Date?) -> String?
|
||||||
|
}
|
@ -9,6 +9,40 @@ import Foundation
|
|||||||
import MastodonAsset
|
import MastodonAsset
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
|
|
||||||
|
extension Date {
|
||||||
|
|
||||||
|
public static let relativeTimestampFormatter: RelativeDateTimeFormatter = {
|
||||||
|
let formatter = RelativeDateTimeFormatter()
|
||||||
|
formatter.dateTimeStyle = .numeric
|
||||||
|
formatter.unitsStyle = .full
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
|
||||||
|
public var localizedSlowedTimeAgoSinceNow: String {
|
||||||
|
return self.localizedTimeAgo(since: Date(), isSlowed: true, isAbbreviated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var localizedTimeAgoSinceNow: String {
|
||||||
|
return self.localizedTimeAgo(since: Date(), isSlowed: false, isAbbreviated: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func localizedTimeAgo(since date: Date, isSlowed: Bool, isAbbreviated: Bool) -> String {
|
||||||
|
let earlierDate = date < self ? date : self
|
||||||
|
let latestDate = earlierDate == date ? self : date
|
||||||
|
|
||||||
|
if isSlowed, earlierDate.timeIntervalSince(latestDate) >= -60 {
|
||||||
|
return L10n.Common.Controls.Timeline.Timestamp.now
|
||||||
|
} else {
|
||||||
|
if isAbbreviated {
|
||||||
|
return latestDate.localizedShortTimeAgo(since: earlierDate)
|
||||||
|
} else {
|
||||||
|
return Date.relativeTimestampFormatter.localizedString(for: earlierDate, relativeTo: latestDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
extension Date {
|
extension Date {
|
||||||
|
|
||||||
public func localizedShortTimeAgo(since date: Date) -> String {
|
public func localizedShortTimeAgo(since date: Date) -> String {
|
||||||
|
@ -12,13 +12,25 @@ import CoreData
|
|||||||
import Photos
|
import Photos
|
||||||
|
|
||||||
extension MediaView {
|
extension MediaView {
|
||||||
public enum Configuration: Hashable {
|
public class Configuration: Hashable {
|
||||||
case image(info: ImageInfo)
|
|
||||||
case gif(info: VideoInfo)
|
public let info: Info
|
||||||
case video(info: VideoInfo)
|
public let blurhash: String?
|
||||||
|
|
||||||
|
@Published public var isReveal = true
|
||||||
|
@Published public var blurhashImage: UIImage?
|
||||||
|
public var blurhashImageDisposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
public init(
|
||||||
|
info: MediaView.Configuration.Info,
|
||||||
|
blurhash: String?
|
||||||
|
) {
|
||||||
|
self.info = info
|
||||||
|
self.blurhash = blurhash
|
||||||
|
}
|
||||||
|
|
||||||
public var aspectRadio: CGSize {
|
public var aspectRadio: CGSize {
|
||||||
switch self {
|
switch info {
|
||||||
case .image(let info): return info.aspectRadio
|
case .image(let info): return info.aspectRadio
|
||||||
case .gif(let info): return info.aspectRadio
|
case .gif(let info): return info.aspectRadio
|
||||||
case .video(let info): return info.aspectRadio
|
case .video(let info): return info.aspectRadio
|
||||||
@ -26,7 +38,7 @@ extension MediaView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var assetURL: String? {
|
public var assetURL: String? {
|
||||||
switch self {
|
switch info {
|
||||||
case .image(let info):
|
case .image(let info):
|
||||||
return info.assetURL
|
return info.assetURL
|
||||||
case .gif(let info):
|
case .gif(let info):
|
||||||
@ -37,7 +49,7 @@ extension MediaView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var resourceType: PHAssetResourceType {
|
public var resourceType: PHAssetResourceType {
|
||||||
switch self {
|
switch info {
|
||||||
case .image:
|
case .image:
|
||||||
return .photo
|
return .photo
|
||||||
case .gif:
|
case .gif:
|
||||||
@ -47,51 +59,72 @@ extension MediaView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ImageInfo: Hashable {
|
public static func == (lhs: MediaView.Configuration, rhs: MediaView.Configuration) -> Bool {
|
||||||
public let aspectRadio: CGSize
|
return lhs.info == rhs.info
|
||||||
public let assetURL: String?
|
&& lhs.blurhash == rhs.blurhash
|
||||||
|
&& lhs.isReveal == rhs.isReveal
|
||||||
public init(
|
|
||||||
aspectRadio: CGSize,
|
|
||||||
assetURL: String?
|
|
||||||
) {
|
|
||||||
self.aspectRadio = aspectRadio
|
|
||||||
self.assetURL = assetURL
|
|
||||||
}
|
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
|
||||||
hasher.combine(aspectRadio.width)
|
|
||||||
hasher.combine(aspectRadio.height)
|
|
||||||
assetURL.flatMap { hasher.combine($0) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct VideoInfo: Hashable {
|
public func hash(into hasher: inout Hasher) {
|
||||||
public let aspectRadio: CGSize
|
hasher.combine(info)
|
||||||
public let assetURL: String?
|
hasher.combine(blurhash)
|
||||||
public let previewURL: String?
|
|
||||||
public let durationMS: Int?
|
|
||||||
|
|
||||||
public init(
|
|
||||||
aspectRadio: CGSize,
|
|
||||||
assetURL: String?,
|
|
||||||
previewURL: String?,
|
|
||||||
durationMS: Int?
|
|
||||||
) {
|
|
||||||
self.aspectRadio = aspectRadio
|
|
||||||
self.assetURL = assetURL
|
|
||||||
self.previewURL = previewURL
|
|
||||||
self.durationMS = durationMS
|
|
||||||
}
|
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
|
||||||
hasher.combine(aspectRadio.width)
|
|
||||||
hasher.combine(aspectRadio.height)
|
|
||||||
assetURL.flatMap { hasher.combine($0) }
|
|
||||||
previewURL.flatMap { hasher.combine($0) }
|
|
||||||
durationMS.flatMap { hasher.combine($0) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension MediaView.Configuration {
|
||||||
|
|
||||||
|
public enum Info: Hashable {
|
||||||
|
case image(info: ImageInfo)
|
||||||
|
case gif(info: VideoInfo)
|
||||||
|
case video(info: VideoInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ImageInfo: Hashable {
|
||||||
|
public let aspectRadio: CGSize
|
||||||
|
public let assetURL: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
aspectRadio: CGSize,
|
||||||
|
assetURL: String?
|
||||||
|
) {
|
||||||
|
self.aspectRadio = aspectRadio
|
||||||
|
self.assetURL = assetURL
|
||||||
|
}
|
||||||
|
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(aspectRadio.width)
|
||||||
|
hasher.combine(aspectRadio.height)
|
||||||
|
assetURL.flatMap { hasher.combine($0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct VideoInfo: Hashable {
|
||||||
|
public let aspectRadio: CGSize
|
||||||
|
public let assetURL: String?
|
||||||
|
public let previewURL: String?
|
||||||
|
public let durationMS: Int?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
aspectRadio: CGSize,
|
||||||
|
assetURL: String?,
|
||||||
|
previewURL: String?,
|
||||||
|
durationMS: Int?
|
||||||
|
) {
|
||||||
|
self.aspectRadio = aspectRadio
|
||||||
|
self.assetURL = assetURL
|
||||||
|
self.previewURL = previewURL
|
||||||
|
self.durationMS = durationMS
|
||||||
|
}
|
||||||
|
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(aspectRadio.width)
|
||||||
|
hasher.combine(aspectRadio.height)
|
||||||
|
assetURL.flatMap { hasher.combine($0) }
|
||||||
|
previewURL.flatMap { hasher.combine($0) }
|
||||||
|
durationMS.flatMap { hasher.combine($0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -8,9 +8,12 @@
|
|||||||
|
|
||||||
import AVKit
|
import AVKit
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
public final class MediaView: UIView {
|
public final class MediaView: UIView {
|
||||||
|
|
||||||
|
var _disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
public static let cornerRadius: CGFloat = 0
|
public static let cornerRadius: CGFloat = 0
|
||||||
public static let durationFormatter: DateComponentsFormatter = {
|
public static let durationFormatter: DateComponentsFormatter = {
|
||||||
let formatter = DateComponentsFormatter()
|
let formatter = DateComponentsFormatter()
|
||||||
@ -23,6 +26,14 @@ public final class MediaView: UIView {
|
|||||||
|
|
||||||
public private(set) var configuration: Configuration?
|
public private(set) var configuration: Configuration?
|
||||||
|
|
||||||
|
private(set) lazy var blurhashImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView()
|
||||||
|
imageView.contentMode = .scaleAspectFill
|
||||||
|
imageView.isUserInteractionEnabled = false
|
||||||
|
imageView.layer.masksToBounds = true // clip overflow
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
private(set) lazy var imageView: UIImageView = {
|
private(set) lazy var imageView: UIImageView = {
|
||||||
let imageView = UIImageView()
|
let imageView = UIImageView()
|
||||||
imageView.contentMode = .scaleAspectFill
|
imageView.contentMode = .scaleAspectFill
|
||||||
@ -91,7 +102,7 @@ extension MediaView {
|
|||||||
|
|
||||||
setupContainerViewHierarchy()
|
setupContainerViewHierarchy()
|
||||||
|
|
||||||
switch configuration {
|
switch configuration.info {
|
||||||
case .image(let info):
|
case .image(let info):
|
||||||
configure(image: info)
|
configure(image: info)
|
||||||
case .gif(let info):
|
case .gif(let info):
|
||||||
@ -99,6 +110,31 @@ extension MediaView {
|
|||||||
case .video(let info):
|
case .video(let info):
|
||||||
configure(video: info)
|
configure(video: info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let blurhash = configuration.blurhash {
|
||||||
|
configure(blurhash: blurhash)
|
||||||
|
|
||||||
|
configuration.$blurhashImage
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.assign(to: \.image, on: blurhashImageView)
|
||||||
|
.store(in: &_disposeBag)
|
||||||
|
|
||||||
|
blurhashImageView.alpha = configuration.isReveal ? 0 : 1
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration.$isReveal
|
||||||
|
.dropFirst()
|
||||||
|
.removeDuplicates()
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] isReveal in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let animator = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut)
|
||||||
|
animator.addAnimations {
|
||||||
|
self.blurhashImageView.alpha = isReveal ? 0 : 1
|
||||||
|
}
|
||||||
|
animator.startAnimation()
|
||||||
|
}
|
||||||
|
.store(in: &_disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configure(image info: Configuration.ImageInfo) {
|
private func configure(image info: Configuration.ImageInfo) {
|
||||||
@ -122,7 +158,7 @@ extension MediaView {
|
|||||||
placeholderImage: placeholder
|
placeholderImage: placeholder
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configure(gif info: Configuration.VideoInfo) {
|
private func configure(gif info: Configuration.VideoInfo) {
|
||||||
// use view controller as View here
|
// use view controller as View here
|
||||||
playerViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
playerViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
@ -188,7 +224,22 @@ extension MediaView {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func configure(blurhash: String) {
|
||||||
|
blurhashImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
container.addSubview(blurhashImageView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
blurhashImageView.topAnchor.constraint(equalTo: container.topAnchor),
|
||||||
|
blurhashImageView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
|
||||||
|
blurhashImageView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
|
||||||
|
blurhashImageView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
blurhashImageView.backgroundColor = .systemGray
|
||||||
|
}
|
||||||
|
|
||||||
public func prepareForReuse() {
|
public func prepareForReuse() {
|
||||||
|
_disposeBag.removeAll()
|
||||||
|
|
||||||
// reset appearance
|
// reset appearance
|
||||||
alpha = 1
|
alpha = 1
|
||||||
|
|
||||||
@ -207,6 +258,11 @@ extension MediaView {
|
|||||||
playerViewController.player = nil
|
playerViewController.player = nil
|
||||||
playerLooper = nil
|
playerLooper = nil
|
||||||
|
|
||||||
|
// blurhash
|
||||||
|
blurhashImageView.removeFromSuperview()
|
||||||
|
blurhashImageView.removeConstraints(blurhashImageView.constraints)
|
||||||
|
blurhashImageView.image = nil
|
||||||
|
|
||||||
// reset indicator
|
// reset indicator
|
||||||
indicatorBlurEffectView.removeFromSuperview()
|
indicatorBlurEffectView.removeFromSuperview()
|
||||||
|
|
||||||
|
@ -34,7 +34,6 @@ extension NotificationView {
|
|||||||
@Published public var isBlocking = false
|
@Published public var isBlocking = false
|
||||||
|
|
||||||
@Published public var timestamp: Date?
|
@Published public var timestamp: Date?
|
||||||
public var timestampFormatter: ((_ date: Date) -> String)?
|
|
||||||
|
|
||||||
let timestampUpdatePublisher = Timer.publish(every: 1.0, on: .main, in: .common)
|
let timestampUpdatePublisher = Timer.publish(every: 1.0, on: .main, in: .common)
|
||||||
.autoconnect()
|
.autoconnect()
|
||||||
@ -100,13 +99,12 @@ extension NotificationView.ViewModel {
|
|||||||
)
|
)
|
||||||
.sink { [weak self] timestamp, _ in
|
.sink { [weak self] timestamp, _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard let timestamp = timestamp,
|
guard let timestamp = timestamp else {
|
||||||
let text = self.timestampFormatter?(timestamp)
|
|
||||||
else {
|
|
||||||
notificationView.dateLabel.configure(content: PlaintextMetaContent(string: ""))
|
notificationView.dateLabel.configure(content: PlaintextMetaContent(string: ""))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let text = timestamp.localizedTimeAgoSinceNow
|
||||||
notificationView.dateLabel.configure(content: PlaintextMetaContent(string: text))
|
notificationView.dateLabel.configure(content: PlaintextMetaContent(string: text))
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
@ -35,6 +35,8 @@ extension StatusView {
|
|||||||
@Published public var authorName: MetaContent?
|
@Published public var authorName: MetaContent?
|
||||||
@Published public var authorUsername: String?
|
@Published public var authorUsername: String?
|
||||||
|
|
||||||
|
@Published public var locked = false
|
||||||
|
|
||||||
@Published public var isMyself = false
|
@Published public var isMyself = false
|
||||||
@Published public var isMuting = false
|
@Published public var isMuting = false
|
||||||
@Published public var isBlocking = false
|
@Published public var isBlocking = false
|
||||||
@ -125,6 +127,10 @@ extension StatusView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
// isReblogEnabled
|
||||||
|
$locked
|
||||||
|
.map { !$0 }
|
||||||
|
.assign(to: &$isReblogEnabled)
|
||||||
// isContentSensitive
|
// isContentSensitive
|
||||||
$spoilerContent
|
$spoilerContent
|
||||||
.map { $0 != nil }
|
.map { $0 != nil }
|
||||||
@ -141,14 +147,14 @@ extension StatusView {
|
|||||||
$isContentSensitive,
|
$isContentSensitive,
|
||||||
$isContentSensitiveToggled
|
$isContentSensitiveToggled
|
||||||
)
|
)
|
||||||
.map { $1 ? $0 : !$0 }
|
.map { $0 ? $1 : true }
|
||||||
.assign(to: &$isContentReveal)
|
.assign(to: &$isContentReveal)
|
||||||
// $isMediaReveal
|
// $isMediaReveal
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
$isMediaSensitive,
|
$isMediaSensitive,
|
||||||
$isMediaSensitiveToggled
|
$isMediaSensitiveToggled
|
||||||
)
|
)
|
||||||
.map { $1 ? !$0 : $0}
|
.map { $0 ? $1 : true }
|
||||||
.assign(to: &$isMediaReveal)
|
.assign(to: &$isMediaReveal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,19 +306,16 @@ extension StatusView.ViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
Publishers.CombineLatest(
|
$isSensitive
|
||||||
$isContentSensitive,
|
.sink { isSensitive in
|
||||||
$isMediaSensitive
|
if isSensitive {
|
||||||
)
|
let image = Asset.Human.eyeCircleFill.image
|
||||||
.sink { isContentSensitive, isMediaSensitive in
|
statusView.contentWarningToggleButton.setImage(image, for: .normal)
|
||||||
if isContentSensitive || isMediaSensitive {
|
statusView.contentWarningToggleButton.tintColor = .systemGray
|
||||||
let image = Asset.Human.eyeCircleFill.image
|
statusView.setContentWarningToggleButtonDisplay()
|
||||||
statusView.contentWarningToggleButton.setImage(image, for: .normal)
|
}
|
||||||
statusView.contentWarningToggleButton.tintColor = .systemGray
|
|
||||||
statusView.setContentWarningToggleButtonDisplay()
|
|
||||||
}
|
}
|
||||||
}
|
.store(in: &disposeBag)
|
||||||
.store(in: &disposeBag)
|
|
||||||
// $spoilerContent
|
// $spoilerContent
|
||||||
// .sink { metaContent in
|
// .sink { metaContent in
|
||||||
// guard let metaContent = metaContent else {
|
// guard let metaContent = metaContent else {
|
||||||
@ -411,6 +414,17 @@ extension StatusView.ViewModel {
|
|||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
Publishers.CombineLatest(
|
||||||
|
$mediaViewConfigurations,
|
||||||
|
$isMediaReveal
|
||||||
|
)
|
||||||
|
.sink { configurations, isMediaReveal in
|
||||||
|
for configuration in configurations {
|
||||||
|
configuration.isReveal = isMediaReveal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
// FIXME:
|
// FIXME:
|
||||||
statusView.mediaGridContainerView.viewModel.isContentWarningOverlayDisplay = false
|
statusView.mediaGridContainerView.viewModel.isContentWarningOverlayDisplay = false
|
||||||
// $isMediaReveal
|
// $isMediaReveal
|
||||||
|
@ -559,6 +559,7 @@ extension StatusView.Style {
|
|||||||
statusView.usernameTrialingDotLabel.removeFromSuperview()
|
statusView.usernameTrialingDotLabel.removeFromSuperview()
|
||||||
statusView.dateLabel.removeFromSuperview()
|
statusView.dateLabel.removeFromSuperview()
|
||||||
statusView.contentContainer.removeFromSuperview()
|
statusView.contentContainer.removeFromSuperview()
|
||||||
|
statusView.spoilerOverlayView.removeFromSuperview()
|
||||||
statusView.mediaContainerView.removeFromSuperview()
|
statusView.mediaContainerView.removeFromSuperview()
|
||||||
statusView.pollContainerView.removeFromSuperview()
|
statusView.pollContainerView.removeFromSuperview()
|
||||||
statusView.statusVisibilityView.removeFromSuperview()
|
statusView.statusVisibilityView.removeFromSuperview()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user