diff --git a/Mastodon/Diffiable/Notification/NotificationSection.swift b/Mastodon/Diffiable/Notification/NotificationSection.swift index 7cca0c0e..97cf8ada 100644 --- a/Mastodon/Diffiable/Notification/NotificationSection.swift +++ b/Mastodon/Diffiable/Notification/NotificationSection.swift @@ -24,6 +24,8 @@ extension NotificationSection { struct Configuration { weak var notificationTableViewCellDelegate: NotificationTableViewCellDelegate? + let filterContext: Mastodon.Entity.Filter.Context? + let activeFilters: Published<[Mastodon.Entity.Filter]>.Publisher? } static func diffableDataSource( @@ -58,57 +60,6 @@ extension NotificationSection { cell.activityIndicatorView.startAnimating() return cell } -// switch notificationItem { -// case .notification(let objectID, let attribute): -// guard let notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification, -// !notification.isDeleted -// else { return UITableViewCell() } -// -// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationStatusTableViewCell.self), for: indexPath) as! NotificationStatusTableViewCell -// configure( -// tableView: tableView, -// cell: cell, -// notification: notification, -// dependency: dependency, -// attribute: attribute -// ) -// cell.delegate = delegate -// cell.isAccessibilityElement = true -// NotificationSection.configureStatusAccessibilityLabel(cell: cell) -// return cell -// -// case .notificationStatus(objectID: let objectID, attribute: let attribute): -// guard let notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification, -// !notification.isDeleted, -// let status = notification.status, -// let requestUserID = dependency.context.authenticationService.activeMastodonAuthenticationBox.value?.userID -// else { return UITableViewCell() } -// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell -// -// // configure cell -// StatusSection.configureStatusTableViewCell( -// cell: cell, -// tableView: tableView, -// timelineContext: .notifications, -// dependency: dependency, -// readableLayoutFrame: tableView.readableContentGuide.layoutFrame, -// status: status, -// requestUserID: requestUserID, -// statusItemAttribute: attribute -// ) -// cell.statusView.headerContainerView.isHidden = true // set header hide -// cell.statusView.actionToolbarContainer.isHidden = true // set toolbar hide -// cell.statusView.actionToolbarPlaceholderPaddingView.isHidden = false -// cell.delegate = statusTableViewCellDelegate -// cell.isAccessibilityElement = true -// StatusSection.configureStatusAccessibilityLabel(cell: cell) -// return cell -// -// case .bottomLoader: -// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) as! TimelineBottomLoaderTableViewCell -// cell.startAnimating() -// return cell -// } } } } @@ -142,163 +93,17 @@ extension NotificationSection { viewModel: viewModel, delegate: configuration.notificationTableViewCellDelegate ) + + cell.notificationView.statusView.viewModel.filterContext = configuration.filterContext + cell.notificationView.quoteStatusView.viewModel.filterContext = configuration.filterContext + + configuration.activeFilters? + .assign(to: \.activeFilters, on: cell.notificationView.statusView.viewModel) + .store(in: &cell.disposeBag) + configuration.activeFilters? + .assign(to: \.activeFilters, on: cell.notificationView.quoteStatusView.viewModel) + .store(in: &cell.disposeBag) } -// static func configure( -// tableView: UITableView, -// cell: NotificationStatusTableViewCell, -// notification: MastodonNotification, -// dependency: NeedsDependency, -// attribute: Item.StatusAttribute -// ) { -// // configure author -// cell.configure( -// with: AvatarConfigurableViewConfiguration( -// avatarImageURL: notification.account.avatarImageURL() -// ) -// ) -// -// func createActionImage() -> UIImage? { -// return UIImage( -// systemName: notification.notificationType.actionImageName, -// withConfiguration: UIImage.SymbolConfiguration( -// pointSize: 12, weight: .semibold -// ) -// )? -// .withTintColor(.systemBackground) -// .af.imageAspectScaled(toFit: CGSize(width: 14, height: 14)) -// } -// -// cell.avatarButton.badgeImageView.backgroundColor = notification.notificationType.color -// cell.avatarButton.badgeImageView.image = createActionImage() -// cell.traitCollectionDidChange -// .receive(on: DispatchQueue.main) -// .sink { [weak cell] in -// guard let cell = cell else { return } -// cell.avatarButton.badgeImageView.image = createActionImage() -// } -// .store(in: &cell.disposeBag) -// -// // configure author name, notification description, timestamp -// let nameText = notification.account.displayNameWithFallback -// let titleLabelText: String = { -// switch notification.notificationType { -// case .favourite: return L10n.Scene.Notification.userFavoritedYourPost(nameText) -// case .follow: return L10n.Scene.Notification.userFollowedYou(nameText) -// case .followRequest: return L10n.Scene.Notification.userRequestedToFollowYou(nameText) -// case .mention: return L10n.Scene.Notification.userMentionedYou(nameText) -// case .poll: return L10n.Scene.Notification.userYourPollHasEnded(nameText) -// case .reblog: return L10n.Scene.Notification.userRebloggedYourPost(nameText) -// default: return "" -// } -// }() -// -// do { -// let nameContent = MastodonContent(content: nameText, emojis: notification.account.emojiMeta) -// let nameMetaContent = try MastodonMetaContent.convert(document: nameContent) -// -// let mastodonContent = MastodonContent(content: titleLabelText, emojis: notification.account.emojiMeta) -// let metaContent = try MastodonMetaContent.convert(document: mastodonContent) -// -// cell.titleLabel.configure(content: metaContent) -// -// if let nameRange = metaContent.string.range(of: nameMetaContent.string) { -// let nsRange = NSRange(nameRange, in: metaContent.string) -// cell.titleLabel.textStorage.addAttributes([ -// .font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20), -// .foregroundColor: Asset.Colors.brandBlue.color, -// ], range: nsRange) -// } -// -// } catch { -// let metaContent = PlaintextMetaContent(string: titleLabelText) -// cell.titleLabel.configure(content: metaContent) -// } -// -// let createAt = notification.createAt -// cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow -// AppContext.shared.timestampUpdatePublisher -// .receive(on: DispatchQueue.main) -// .sink { [weak cell] _ in -// guard let cell = cell else { return } -// cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow -// } -// .store(in: &cell.disposeBag) -// -// // configure follow request (if exist) -// if case .followRequest = notification.notificationType { -// cell.acceptButton.publisher(for: .touchUpInside) -// .sink { [weak cell] _ in -// guard let cell = cell else { return } -// cell.delegate?.notificationTableViewCell(cell, notification: notification, acceptButtonDidPressed: cell.acceptButton) -// } -// .store(in: &cell.disposeBag) -// cell.rejectButton.publisher(for: .touchUpInside) -// .sink { [weak cell] _ in -// guard let cell = cell else { return } -// cell.delegate?.notificationTableViewCell(cell, notification: notification, rejectButtonDidPressed: cell.rejectButton) -// } -// .store(in: &cell.disposeBag) -// cell.buttonStackView.isHidden = false -// } else { -// cell.buttonStackView.isHidden = true -// } -// -// // configure status (if exist) -// if let status = notification.status { -// let frame = CGRect( -// x: 0, -// y: 0, -// width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right, -// height: tableView.readableContentGuide.layoutFrame.height -// ) -// StatusSection.configure( -// cell: cell, -// tableView: tableView, -// timelineContext: .notifications, -// dependency: dependency, -// readableLayoutFrame: frame, -// status: status, -// requestUserID: notification.userID, -// statusItemAttribute: attribute -// ) -// cell.statusContainerView.isHidden = false -// cell.containerStackView.alignment = .top -// cell.containerStackViewBottomLayoutConstraint.constant = 0 -// } else { -// if case .followRequest = notification.notificationType { -// cell.containerStackView.alignment = .top -// } else { -// cell.containerStackView.alignment = .center -// } -// cell.statusContainerView.isHidden = true -// cell.containerStackViewBottomLayoutConstraint.constant = 5 // 5pt margin when no status view -// } -// } -// -// static func configureStatusAccessibilityLabel(cell: NotificationStatusTableViewCell) { -// // FIXME: -// cell.accessibilityLabel = { -// var accessibilityViews: [UIView?] = [] -// accessibilityViews.append(contentsOf: [ -// cell.titleLabel, -// cell.timestampLabel, -// cell.statusView -// ]) -// if !cell.statusContainerView.isHidden { -// if !cell.statusView.headerContainerView.isHidden { -// accessibilityViews.append(cell.statusView.headerInfoLabel) -// } -// accessibilityViews.append(contentsOf: [ -// cell.statusView.nameMetaLabel, -// cell.statusView.dateLabel, -// cell.statusView.contentMetaText.textView, -// ]) -// } -// return accessibilityViews -// .compactMap { $0?.accessibilityLabel } -// .joined(separator: " ") -// }() -// } } diff --git a/Mastodon/Diffiable/Status/StatusSection.swift b/Mastodon/Diffiable/Status/StatusSection.swift index 6eefdef7..40b7e535 100644 --- a/Mastodon/Diffiable/Status/StatusSection.swift +++ b/Mastodon/Diffiable/Status/StatusSection.swift @@ -28,6 +28,8 @@ extension StatusSection { struct Configuration { weak var statusTableViewCellDelegate: StatusTableViewCellDelegate? weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate? + let filterContext: Mastodon.Entity.Filter.Context? + let activeFilters: Published<[Mastodon.Entity.Filter]>.Publisher? } static func diffableDataSource( @@ -258,6 +260,11 @@ extension StatusSection { viewModel: viewModel, delegate: configuration.statusTableViewCellDelegate ) + + cell.statusView.viewModel.filterContext = configuration.filterContext + configuration.activeFilters? + .assign(to: \.activeFilters, on: cell.statusView.viewModel) + .store(in: &cell.disposeBag) } static func configure( @@ -282,6 +289,11 @@ extension StatusSection { viewModel: viewModel, delegate: configuration.statusTableViewCellDelegate ) + + cell.statusView.viewModel.filterContext = configuration.filterContext + configuration.activeFilters? + .assign(to: \.activeFilters, on: cell.statusView.viewModel) + .store(in: &cell.disposeBag) } static func configure( @@ -296,133 +308,3 @@ extension StatusSection { } } - -extension StatusSection { - - enum TimelineContext { - case home - case notifications - case `public` - case thread - case account - - case favorite - case hashtag - case report - case search - - var filterContext: Mastodon.Entity.Filter.Context? { - switch self { - case .home: return .home - case .notifications: return .notifications - case .public: return .public - case .thread: return .thread - case .account: return .account - default: return nil - } - } - } - - private static func needsFilterStatus( - content: MastodonMetaContent?, - filters: [Mastodon.Entity.Filter], - timelineContext: TimelineContext - ) -> AnyPublisher { - guard let content = content, - let currentFilterContext = timelineContext.filterContext, - !filters.isEmpty else { - return Just(false).eraseToAnyPublisher() - } - - return Future { promise in - DispatchQueue.global(qos: .userInteractive).async { - var wordFilters: [Mastodon.Entity.Filter] = [] - var nonWordFilters: [Mastodon.Entity.Filter] = [] - for filter in filters { - guard filter.context.contains(where: { $0 == currentFilterContext }) else { continue } - if filter.wholeWord { - wordFilters.append(filter) - } else { - nonWordFilters.append(filter) - } - } - - let text = content.original.lowercased() - - var needsFilter = false - for filter in nonWordFilters { - guard text.contains(filter.phrase.lowercased()) else { continue } - needsFilter = true - break - } - - if needsFilter { - DispatchQueue.main.async { - promise(.success(true)) - } - return - } - - let tokenizer = NLTokenizer(unit: .word) - tokenizer.string = text - let phraseWords = wordFilters.map { $0.phrase.lowercased() } - tokenizer.enumerateTokens(in: text.startIndex..? - - init( - statusObjectID: NSManagedObjectID, - mastodonContent: MastodonContent - ) { - self.statusObjectID = statusObjectID - self.mastodonContent = mastodonContent - super.init() - } - - override func main() { - guard !isCancelled else { return } - // logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): prcoess \(self.statusObjectID)…") - - do { - let content = try MastodonMetaContent.convert(document: mastodonContent) - result = .success(content) - // logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): process success \(self.statusObjectID)") - } catch { - result = .failure(error) - // logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): process fail \(self.statusObjectID)") - } - - } - - override func cancel() { - // logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): cancel \(self.statusObjectID.debugDescription)") - super.cancel() - } -} diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift index 43add2d2..c71d195c 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift @@ -21,7 +21,9 @@ extension HashtagTimelineViewModel { context: context, configuration: StatusSection.Configuration( statusTableViewCellDelegate: statusTableViewCellDelegate, - timelineMiddleLoaderTableViewCellDelegate: nil + timelineMiddleLoaderTableViewCellDelegate: nil, + filterContext: .none, + activeFilters: nil ) ) diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift index 67f9e5b5..756a4b60 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift @@ -22,7 +22,9 @@ extension HomeTimelineViewModel { context: context, configuration: StatusSection.Configuration( statusTableViewCellDelegate: statusTableViewCellDelegate, - timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate + timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate, + filterContext: .home, + activeFilters: context.statusFilterService.$activeFilters ) ) diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift index 1476ef2e..b32eae76 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift @@ -20,7 +20,9 @@ extension NotificationTimelineViewModel { tableView: tableView, context: context, configuration: NotificationSection.Configuration( - notificationTableViewCellDelegate: notificationTableViewCellDelegate + notificationTableViewCellDelegate: notificationTableViewCellDelegate, + filterContext: .notifications, + activeFilters: context.statusFilterService.$activeFilters ) ) diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift index f74d3de7..58109247 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift @@ -18,7 +18,9 @@ extension FavoriteViewModel { context: context, configuration: StatusSection.Configuration( statusTableViewCellDelegate: statusTableViewCellDelegate, - timelineMiddleLoaderTableViewCellDelegate: nil + timelineMiddleLoaderTableViewCellDelegate: nil, + filterContext: .none, + activeFilters: nil ) ) // set empty section to make update animation top-to-bottom style diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift index 56d0973e..a0a1f52c 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift @@ -19,7 +19,9 @@ extension UserTimelineViewModel { context: context, configuration: StatusSection.Configuration( statusTableViewCellDelegate: statusTableViewCellDelegate, - timelineMiddleLoaderTableViewCellDelegate: nil + timelineMiddleLoaderTableViewCellDelegate: nil, + filterContext: .none, + activeFilters: nil ) ) diff --git a/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift b/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift index 5df29402..1a90c69a 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift @@ -9,11 +9,16 @@ import UIKit import Combine import MastodonUI import CoreDataStack +import MastodonSDK import MastodonLocalization import MastodonMeta import Meta +import NaturalLanguage extension StatusView { + + static let statusFilterWorkingQueue = DispatchQueue(label: "StatusFilterWorkingQueue") + public func configure(feed: Feed) { switch feed.kind { case .home: @@ -48,7 +53,8 @@ extension StatusView { configureContent(status: status) configureMedia(status: status) configurePoll(status: status) - configureToolbar(status: status) + configureToolbar(status: status) + configureFilter(status: status) } } @@ -397,5 +403,58 @@ extension StatusView { .assign(to: \.isFavorite, on: viewModel) .store(in: &disposeBag) } + + private func configureFilter(status: Status) { + let status = status.reblog ?? status + + let content = status.content.lowercased() + + Publishers.CombineLatest( + viewModel.$activeFilters, + viewModel.$filterContext + ) + .receive(on: StatusView.statusFilterWorkingQueue) + .map { filters, filterContext in + var wordFilters: [Mastodon.Entity.Filter] = [] + var nonWordFilters: [Mastodon.Entity.Filter] = [] + for filter in filters { + guard filter.context.contains(where: { $0 == filterContext }) else { continue } + if filter.wholeWord { + wordFilters.append(filter) + } else { + nonWordFilters.append(filter) + } + } + + var needsFilter = false + for filter in nonWordFilters { + guard content.contains(filter.phrase.lowercased()) else { continue } + needsFilter = true + break + } + + if needsFilter { + return true + } + + let tokenizer = NLTokenizer(unit: .word) + tokenizer.string = content + let phraseWords = wordFilters.map { $0.phrase.lowercased() } + tokenizer.enumerateTokens(in: content.startIndex..() // output - let activeFilters = CurrentValueSubject<[Mastodon.Entity.Filter], Never>([]) + @Published var activeFilters: [Mastodon.Entity.Filter] = [] init( apiService: APIService, @@ -57,7 +57,14 @@ final class StatusFilterService { .map { response in let now = Date() let newResponse = response.map { filters in - return filters.filter { $0.expiresAt > now } // filter out expired rules + return filters.filter { filter in + if let expiresAt = filter.expiresAt { + // filter out expired rules + return expiresAt > now + } else { + return true + } + } } return Result, Error>.success(newResponse) } @@ -70,7 +77,7 @@ final class StatusFilterService { switch result { case .success(let response): os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: fetch account filters success. %ld items", ((#file as NSString).lastPathComponent), #line, #function, response.value.count) - self.activeFilters.value = response.value + self.activeFilters = response.value case .failure(let error): os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: fetch account filters fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) diff --git a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 3.xcdatamodel/contents b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 3.xcdatamodel/contents index 3e2fcc13..a6f0ee0c 100644 --- a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 3.xcdatamodel/contents +++ b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 3.xcdatamodel/contents @@ -1,5 +1,5 @@ - + diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Filter.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Filter.swift index e52dd36b..00a06ccf 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Filter.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Filter.swift @@ -22,7 +22,7 @@ extension Mastodon.Entity { public let id: ID public let phrase: String public let context: [Context] - public let expiresAt: Date + public let expiresAt: Date? public let irreversible: Bool public let wholeWord: Bool @@ -38,7 +38,7 @@ extension Mastodon.Entity { } extension Mastodon.Entity.Filter { - public enum Context: RawRepresentable, Codable { + public enum Context: RawRepresentable, Codable, Hashable { case home case notifications case `public` diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index cc162a94..ceaf103c 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -89,6 +89,11 @@ extension StatusView { @Published public var replyCount: Int = 0 @Published public var reblogCount: Int = 0 @Published public var favoriteCount: Int = 0 + + // Filter + @Published public var activeFilters: [Mastodon.Entity.Filter] = [] + @Published public var filterContext: Mastodon.Entity.Filter.Context? + @Published public var isFiltered = false @Published public var groupedAccessibilityLabel = "" @@ -128,9 +133,8 @@ extension StatusView { isMediaSensitive = false isMediaSensitiveToggled = false -// isSensitive = false -// isContentReveal = false -// isMediaReveal = false + activeFilters = [] + filterContext = nil } init() { @@ -192,6 +196,7 @@ extension StatusView.ViewModel { bindToolbar(statusView: statusView) bindMetric(statusView: statusView) bindMenu(statusView: statusView) + bindFilter(statusView: statusView) bindAccessibility(statusView: statusView) } @@ -611,6 +616,17 @@ extension StatusView.ViewModel { .store(in: &disposeBag) } + private func bindFilter(statusView: StatusView) { + $isFiltered + .sink { isFiltered in + statusView.containerStackView.isHidden = isFiltered + if isFiltered { + statusView.setFilterHintLabelDisplay() + } + } + .store(in: &disposeBag) + } + private func bindAccessibility(statusView: StatusView) { let authorAccessibilityLabel = Publishers.CombineLatest3( $header, diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift index 8a1c02df..2b9f936b 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift @@ -226,6 +226,15 @@ public final class StatusView: UIView { // metric public let statusMetricView = StatusMetricView() + // filter hint + public let filterHintLabel: UILabel = { + let label = UILabel() + label.textColor = Asset.Colors.Label.secondary.color + label.text = L10n.Common.Controls.Timeline.filtered + label.font = .systemFont(ofSize: 17, weight: .regular) + return label + }() + public func prepareForReuse() { disposeBag.removeAll() @@ -249,7 +258,7 @@ public final class StatusView: UIView { mediaContainerView.isHidden = true pollContainerView.isHidden = true statusVisibilityView.isHidden = true - // setSpoilerBannerViewHidden(isHidden: true) + filterHintLabel.isHidden = true } public override init(frame: CGRect) { @@ -570,6 +579,14 @@ extension StatusView.Style { statusView.actionToolbarContainer.configure(for: .inline) statusView.actionToolbarContainer.preservesSuperviewLayoutMargins = true statusView.containerStackView.addArrangedSubview(statusView.actionToolbarContainer) + + // filterHintLabel + statusView.filterHintLabel.translatesAutoresizingMaskIntoConstraints = false + statusView.addSubview(statusView.filterHintLabel) + NSLayoutConstraint.activate([ + statusView.filterHintLabel.centerXAnchor.constraint(equalTo: statusView.containerStackView.centerXAnchor), + statusView.filterHintLabel.centerYAnchor.constraint(equalTo: statusView.containerStackView.centerYAnchor), + ]) } func inline(statusView: StatusView) { @@ -673,9 +690,9 @@ extension StatusView { statusVisibilityView.isHidden = false } - // func setSpoilerBannerViewHidden(isHidden: Bool) { - // spoilerBannerView.isHidden = isHidden - // } + func setFilterHintLabelDisplay() { + filterHintLabel.isHidden = false + } // content text Width public var contentMaxLayoutWidth: CGFloat {