feat: add content warning for post media

This commit is contained in:
CMK 2022-01-29 19:51:40 +08:00
parent caaf66286f
commit d332c98a0f
21 changed files with 355 additions and 362 deletions

View File

@ -25,7 +25,6 @@
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33725E6401400AAD544 /* PickServerCell.swift */; };
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 */; };
2D084B8D26258EA3003AA3AF /* NotificationViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D084B8C26258EA3003AA3AF /* NotificationViewModel+Diffable.swift */; };
2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198642261BF09500F0B013 /* SearchResultItem.swift */; };
2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198648261C0B8500F0B013 /* SearchResultSection.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 */; };
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.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 */; };
DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52C25C13561002E6C99 /* DocumentStore.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 */; };
DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.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 */; };
DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; };
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = DBB525072611EAC0002F1F29 /* Tabman */; };
@ -516,7 +515,6 @@
DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */; };
DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D626A54BCB00398BB9 /* MastodonRegex.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 */; };
DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.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>"; };
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>"; };
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>"; };
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>"; };
@ -1180,6 +1177,7 @@
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>"; };
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>"; };
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>"; };
@ -1257,7 +1255,6 @@
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>"; };
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>"; };
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>"; };
@ -1282,7 +1279,6 @@
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>"; };
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>"; };
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>"; };
@ -1690,9 +1686,9 @@
DB6D9F6226357848008423CD /* SettingService.swift */,
DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */,
DB297B1A2679FAE200704C90 /* PlaceholderImageCacheService.swift */,
DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */,
DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */,
DB73BF42271192BB00781945 /* InstanceService.swift */,
DB894CC327A5490600684B74 /* BlurhashImageCacheService.swift */,
);
path = Service;
sourceTree = "<group>";
@ -2724,7 +2720,6 @@
DBCC3B35261440BA0045B23D /* UINavigationController.swift */,
DB73BF4827140BA300781945 /* UICollectionViewDiffableDataSource.swift */,
DB73BF4A27140C0800781945 /* UITableViewDiffableDataSource.swift */,
DBBC50BE278ED0E700AF0CC6 /* Date.swift */,
);
path = Extension;
sourceTree = "<group>";
@ -2794,7 +2789,6 @@
2D35237F26256F470031AF25 /* Cell */,
DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */,
2D607AD726242FC500B70763 /* NotificationViewModel.swift */,
2D084B8C26258EA3003AA3AF /* NotificationViewModel+Diffable.swift */,
);
path = Notification;
sourceTree = "<group>";
@ -3782,7 +3776,6 @@
5DF1054125F886D400D6C0D4 /* VideoPlaybackService.swift in Sources */,
DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */,
0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */,
DBBC50BF278ED0E700AF0CC6 /* Date.swift in Sources */,
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
2D9DB967263A76FB007C1D71 /* BlockDomainService.swift in Sources */,
DB336F43278EB1690031E64B /* MediaView+Configuration.swift in Sources */,
@ -3976,7 +3969,6 @@
DB4F0968269ED8AD00D62E92 /* SearchHistoryTableHeaderView.swift in Sources */,
0FB3D2FE25E4CB6400AAD544 /* OnboardingHeadlineTableViewCell.swift in Sources */,
5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */,
DBAEDE5C267A058D00D25FF5 /* BlurhashImageCacheService.swift in Sources */,
DB1D843026566512000346B3 /* KeyboardPreference.swift in Sources */,
DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */,
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */,
@ -4024,7 +4016,6 @@
DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */,
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
2D084B8D26258EA3003AA3AF /* NotificationViewModel+Diffable.swift in Sources */,
DB3667A1268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift in Sources */,
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,
DBA94434265CBB5300C537E1 /* ProfileFieldSection.swift in Sources */,
@ -4136,6 +4127,7 @@
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */,
DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */,
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */,
DB894CC427A5490600684B74 /* BlurhashImageCacheService.swift in Sources */,
DBFEF07B26A6BCE8006D7ED1 /* APIService+Status+Publish.swift in Sources */,
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */,
5B90C48526259BF10002E742 /* APIService+Subscriptions.swift in Sources */,

View File

@ -86,6 +86,13 @@
ReferencedContainer = "container:Mastodon.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@ -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)
}
}
}
}

View File

@ -47,7 +47,7 @@ extension DataSourceFacade {
switch target {
case .status:
return status.reblog ?? status
case .repost:
case .reblog:
return status
}
}

View File

@ -10,7 +10,7 @@ import Foundation
enum DataSourceFacade {
enum StatusTarget {
case status // remove repost wrapper
case repost // keep repost wrapper
case status // remove reblog wrapper
case reblog // keep reblog wrapper
}
}

View File

@ -30,7 +30,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
}
await DataSourceFacade.coordinateToProfileScene(
provider: self,
target: .status, // without reblog header
target: .reblog, // keep the wrapper for header author
status: status
)
}
@ -117,6 +117,24 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & MediaPrev
assertionFailure("only works for status data provider")
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(
dependency: self,
status: status,

View File

@ -374,7 +374,7 @@ extension HomeTimelineViewController {
@objc private func findPeopleButtonPressed(_ sender: PrimaryActionButton) {
// TODO:
let viewModel = SuggestionAccountViewModel(context: context)
// let viewModel = SuggestionAccountViewModel(context: context)
// viewModel.delegate = self.viewModel
// 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) {
// 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
extension HomeTimelineViewController: ContentOffsetAdjustableTimelineViewControllerDelegate {
func navigationBar() -> UINavigationBar? {
@ -613,24 +582,23 @@ extension HomeTimelineViewController: ScrollViewContainer {
var scrollView: UIScrollView { return tableView }
func scrollToTop(animated: Bool) {
// TODO:
// if scrollView.contentOffset.y < scrollView.frame.height,
// viewModel.loadLatestStateMachine.canEnterState(HomeTimelineViewModel.LoadLatestState.Loading.self),
// (scrollView.contentOffset.y + scrollView.adjustedContentInset.top) == 0.0,
// !refreshControl.isRefreshing {
// 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
// guard let self = self else { return }
// self.refreshControl.beginRefreshing()
// self.refreshControl.sendActions(for: .valueChanged)
// }
// } else {
// let indexPath = IndexPath(row: 0, section: 0)
// guard viewModel.diffableDataSource?.itemIdentifier(for: indexPath) != nil else { return }
// // save position
// savePositionBeforeScrollToTop()
// tableView.scrollToRow(at: indexPath, at: .top, animated: true)
// }
if scrollView.contentOffset.y < scrollView.frame.height,
viewModel.loadLatestStateMachine.canEnterState(HomeTimelineViewModel.LoadLatestState.Loading.self),
(scrollView.contentOffset.y + scrollView.adjustedContentInset.top) == 0.0,
!refreshControl.isRefreshing {
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
guard let self = self else { return }
self.refreshControl.beginRefreshing()
self.refreshControl.sendActions(for: .valueChanged)
}
} else {
let indexPath = IndexPath(row: 0, section: 0)
guard viewModel.diffableDataSource?.itemIdentifier(for: indexPath) != nil else { return }
// save position
savePositionBeforeScrollToTop()
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
}
}

View File

@ -42,7 +42,7 @@ extension NotificationTableViewCell {
case .feed(let feed):
notificationView.configure(feed: feed)
}
//
self.delegate = delegate
}

View File

@ -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()
// }
// }
// }
// }
//
//}

View File

@ -25,24 +25,54 @@ extension MediaView {
return status.publisher(for: \.attachments)
.map { attachments -> [MediaView.Configuration] in
return attachments.map { attachment -> MediaView.Configuration in
switch attachment.kind {
case .image:
let info = MediaView.Configuration.ImageInfo(
aspectRadio: attachment.size,
assetURL: attachment.assetURL
let configuration: MediaView.Configuration = {
switch attachment.kind {
case .image:
let info = MediaView.Configuration.ImageInfo(
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)
case .video:
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)
.assign(to: \.blurhashImage, on: configuration)
.store(in: &configuration.blurhashImageDisposeBag)
}
configuration.isReveal = status.sensitive ? status.isMediaSensitiveToggled : true
return configuration
}
}
.eraseToAnyPublisher()

View File

@ -92,13 +92,7 @@ extension NotificationView {
.assign(to: \.authorUsername, on: viewModel)
.store(in: &disposeBag)
// timestamp
viewModel.timestampFormatter = { (date: Date) in
date.localizedSlowedTimeAgoSinceNow
}
notification.publisher(for: \.createAt)
.map { $0 as Date? }
.assign(to: \.timestamp, on: viewModel)
.store(in: &disposeBag)
viewModel.timestamp = notification.createAt
// notification type indicator
Publishers.CombineLatest3(
notification.publisher(for: \.typeRaw),
@ -111,7 +105,7 @@ extension NotificationView {
self.viewModel.notificationIndicatorText = nil
return
}
func createMetaContent(text: String, emojis: MastodonContent.Emojis) -> MetaContent {
let content = MastodonContent(content: text, emojis: emojis)
guard let metaContent = try? MastodonMetaContent.convert(document: content) else {
@ -119,7 +113,7 @@ extension NotificationView {
}
return metaContent
}
// TODO: fix the i18n. The subject should assert place at the string beginning
switch type {
case .follow:

View File

@ -173,14 +173,10 @@ extension StatusView {
.map { $0 as String? }
.assign(to: \.authorUsername, on: viewModel)
.store(in: &disposeBag)
// // protected
// author.publisher(for: \.locked)
// .assign(to: \.protected, on: viewModel)
// .store(in: &disposeBag)
// // visibility
// viewModel.visibility = status.visibility.asStatusVisibility
// locked
author.publisher(for: \.locked)
.assign(to: \.locked, on: viewModel)
.store(in: &disposeBag)
// isMuting
Publishers.CombineLatest(
viewModel.$userIdentifier,
@ -267,42 +263,22 @@ extension StatusView {
status.publisher(for: \.isContentSensitiveToggled)
.assign(to: \.isContentSensitiveToggled, on: viewModel)
.store(in: &disposeBag)
status.publisher(for: \.isMediaSensitiveToggled)
.assign(to: \.isMediaSensitiveToggled, on: viewModel)
.store(in: &disposeBag)
// viewModel.source = status.source
}
private func configureMedia(status: Status) {
let status = status.reblog ?? status
// mediaGridContainerView.viewModel.resetContentWarningOverlay()
// viewModel.isMediaSensitiveSwitchable = true
viewModel.isMediaSensitive = status.sensitive
viewModel.isMediaSensitive = status.sensitive && !status.attachments.isEmpty // some servers set media sensitive even empty attachments
MediaView.configuration(status: status)
.assign(to: \.mediaViewConfigurations, on: viewModel)
.store(in: &disposeBag)
// // set directly without delay
// viewModel.isMediaSensitiveToggled = status.isMediaSensitiveToggled
// viewModel.isMediaSensitive = status.isMediaSensitive
// 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)
status.publisher(for: \.isMediaSensitiveToggled)
.assign(to: \.isMediaSensitiveToggled, on: viewModel)
.store(in: &disposeBag)
}
private func configurePoll(status: Status) {

View File

@ -8,13 +8,19 @@
import UIKit
import Combine
final class BlurhashImageCacheService {
public final class BlurhashImageCacheService {
static let edgeMaxLength: CGFloat = 20
let cache = NSCache<Key, UIImage>()
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)
if let image = self.cache.object(forKey: key) {
@ -23,7 +29,7 @@ final class BlurhashImageCacheService {
return Future { promise in
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))
return
}
@ -33,27 +39,25 @@ final class BlurhashImageCacheService {
}
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
static func blurhashImage(blurhash: String, size: CGSize, url: URL) -> UIImage? {
fatalError()
// let imageSize: CGSize = {
// let aspectRadio = size.width / size.height
// if size.width > size.height {
// let width: CGFloat = MosaicMeta.edgeMaxLength
// let height = width / aspectRadio
// return CGSize(width: width, height: height)
// } else {
// let height: CGFloat = MosaicMeta.edgeMaxLength
// let width = height * aspectRadio
// return CGSize(width: width, height: height)
// }
// }()
//
// let image = UIImage(blurHash: blurhash, size: imageSize)
//
// return image
static func blurhashImage(blurhash: String, size: CGSize) -> UIImage? {
let imageSize: CGSize = {
let aspectRadio = size.width / size.height
if size.width > size.height {
let width: CGFloat = BlurhashImageCacheService.edgeMaxLength
let height = width / aspectRadio
return CGSize(width: width, height: height)
} else {
let height: CGFloat = BlurhashImageCacheService.edgeMaxLength
let width = height * aspectRadio
return CGSize(width: width, height: height)
}
}()
let image = UIImage(blurHash: blurhash, size: imageSize)
return image
}
}
@ -62,9 +66,9 @@ extension BlurhashImageCacheService {
class Key: NSObject {
let blurhash: String
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.size = size
self.url = url
@ -83,6 +87,5 @@ extension BlurhashImageCacheService {
size.height.hashValue ^
url.hashValue
}
}
}

View File

@ -129,11 +129,6 @@
<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="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="notifications" inverseEntity="Status"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="id"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="Poll" representedClassName="CoreDataStack.Poll" syncable="YES">
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>

View 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?
}

View File

@ -9,6 +9,40 @@ 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)
}
}
}
}
extension Date {
public func localizedShortTimeAgo(since date: Date) -> String {

View File

@ -12,13 +12,25 @@ import CoreData
import Photos
extension MediaView {
public enum Configuration: Hashable {
case image(info: ImageInfo)
case gif(info: VideoInfo)
case video(info: VideoInfo)
public class Configuration: Hashable {
public let info: Info
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 {
switch self {
switch info {
case .image(let info): return info.aspectRadio
case .gif(let info): return info.aspectRadio
case .video(let info): return info.aspectRadio
@ -26,7 +38,7 @@ extension MediaView {
}
public var assetURL: String? {
switch self {
switch info {
case .image(let info):
return info.assetURL
case .gif(let info):
@ -37,7 +49,7 @@ extension MediaView {
}
public var resourceType: PHAssetResourceType {
switch self {
switch info {
case .image:
return .photo
case .gif:
@ -47,51 +59,72 @@ extension MediaView {
}
}
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 static func == (lhs: MediaView.Configuration, rhs: MediaView.Configuration) -> Bool {
return lhs.info == rhs.info
&& lhs.blurhash == rhs.blurhash
&& lhs.isReveal == rhs.isReveal
}
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) }
}
public func hash(into hasher: inout Hasher) {
hasher.combine(info)
hasher.combine(blurhash)
}
}
}
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) }
}
}
}

View File

@ -8,9 +8,12 @@
import AVKit
import UIKit
import Combine
public final class MediaView: UIView {
var _disposeBag = Set<AnyCancellable>()
public static let cornerRadius: CGFloat = 0
public static let durationFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
@ -23,6 +26,14 @@ public final class MediaView: UIView {
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 = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
@ -91,7 +102,7 @@ extension MediaView {
setupContainerViewHierarchy()
switch configuration {
switch configuration.info {
case .image(let info):
configure(image: info)
case .gif(let info):
@ -99,6 +110,31 @@ extension MediaView {
case .video(let 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) {
@ -122,7 +158,7 @@ extension MediaView {
placeholderImage: placeholder
)
}
private func configure(gif info: Configuration.VideoInfo) {
// use view controller as View here
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() {
_disposeBag.removeAll()
// reset appearance
alpha = 1
@ -207,6 +258,11 @@ extension MediaView {
playerViewController.player = nil
playerLooper = nil
// blurhash
blurhashImageView.removeFromSuperview()
blurhashImageView.removeConstraints(blurhashImageView.constraints)
blurhashImageView.image = nil
// reset indicator
indicatorBlurEffectView.removeFromSuperview()

View File

@ -34,7 +34,6 @@ extension NotificationView {
@Published public var isBlocking = false
@Published public var timestamp: Date?
public var timestampFormatter: ((_ date: Date) -> String)?
let timestampUpdatePublisher = Timer.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
@ -100,13 +99,12 @@ extension NotificationView.ViewModel {
)
.sink { [weak self] timestamp, _ in
guard let self = self else { return }
guard let timestamp = timestamp,
let text = self.timestampFormatter?(timestamp)
else {
guard let timestamp = timestamp else {
notificationView.dateLabel.configure(content: PlaintextMetaContent(string: ""))
return
}
let text = timestamp.localizedTimeAgoSinceNow
notificationView.dateLabel.configure(content: PlaintextMetaContent(string: text))
}
.store(in: &disposeBag)

View File

@ -35,6 +35,8 @@ extension StatusView {
@Published public var authorName: MetaContent?
@Published public var authorUsername: String?
@Published public var locked = false
@Published public var isMyself = false
@Published public var isMuting = false
@Published public var isBlocking = false
@ -125,6 +127,10 @@ extension StatusView {
}
init() {
// isReblogEnabled
$locked
.map { !$0 }
.assign(to: &$isReblogEnabled)
// isContentSensitive
$spoilerContent
.map { $0 != nil }
@ -141,14 +147,14 @@ extension StatusView {
$isContentSensitive,
$isContentSensitiveToggled
)
.map { $1 ? $0 : !$0 }
.map { $0 ? $1 : true }
.assign(to: &$isContentReveal)
// $isMediaReveal
Publishers.CombineLatest(
$isMediaSensitive,
$isMediaSensitiveToggled
)
.map { $1 ? !$0 : $0}
.map { $0 ? $1 : true }
.assign(to: &$isMediaReveal)
}
}
@ -300,19 +306,16 @@ extension StatusView.ViewModel {
}
}
.store(in: &disposeBag)
Publishers.CombineLatest(
$isContentSensitive,
$isMediaSensitive
)
.sink { isContentSensitive, isMediaSensitive in
if isContentSensitive || isMediaSensitive {
let image = Asset.Human.eyeCircleFill.image
statusView.contentWarningToggleButton.setImage(image, for: .normal)
statusView.contentWarningToggleButton.tintColor = .systemGray
statusView.setContentWarningToggleButtonDisplay()
$isSensitive
.sink { isSensitive in
if isSensitive {
let image = Asset.Human.eyeCircleFill.image
statusView.contentWarningToggleButton.setImage(image, for: .normal)
statusView.contentWarningToggleButton.tintColor = .systemGray
statusView.setContentWarningToggleButtonDisplay()
}
}
}
.store(in: &disposeBag)
.store(in: &disposeBag)
// $spoilerContent
// .sink { metaContent in
// guard let metaContent = metaContent else {
@ -411,6 +414,17 @@ extension StatusView.ViewModel {
}
.store(in: &disposeBag)
Publishers.CombineLatest(
$mediaViewConfigurations,
$isMediaReveal
)
.sink { configurations, isMediaReveal in
for configuration in configurations {
configuration.isReveal = isMediaReveal
}
}
.store(in: &disposeBag)
// FIXME:
statusView.mediaGridContainerView.viewModel.isContentWarningOverlayDisplay = false
// $isMediaReveal

View File

@ -559,6 +559,7 @@ extension StatusView.Style {
statusView.usernameTrialingDotLabel.removeFromSuperview()
statusView.dateLabel.removeFromSuperview()
statusView.contentContainer.removeFromSuperview()
statusView.spoilerOverlayView.removeFromSuperview()
statusView.mediaContainerView.removeFromSuperview()
statusView.pollContainerView.removeFromSuperview()
statusView.statusVisibilityView.removeFromSuperview()