Sensitive attachments

This commit is contained in:
Justin Mazzocchi 2020-10-13 17:03:01 -07:00
parent 40b795cd7e
commit cc370af881
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
25 changed files with 250 additions and 136 deletions

View file

@ -62,7 +62,11 @@ extension ContentDatabase {
t.column("pinned", .boolean)
}
try db.create(table: "statusShowMoreToggle") { t in
try db.create(table: "statusShowContentToggle") { t in
t.column("statusId", .text).primaryKey().references("statusRecord", onDelete: .cascade)
}
try db.create(table: "statusShowAttachmentsToggle") { t in
t.column("statusId", .text).primaryKey().references("statusRecord", onDelete: .cascade)
}

View file

@ -149,37 +149,56 @@ public extension ContentDatabase {
.eraseToAnyPublisher()
}
func toggleShowMore(id: Status.Id) -> AnyPublisher<Never, Error> {
func toggleShowContent(id: Status.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher {
if let toggle = try StatusShowMoreToggle
.filter(StatusShowMoreToggle.Columns.statusId == id)
if let toggle = try StatusShowContentToggle
.filter(StatusShowContentToggle.Columns.statusId == id)
.fetchOne($0) {
try toggle.delete($0)
} else {
try StatusShowMoreToggle(statusId: id).save($0)
try StatusShowContentToggle(statusId: id).save($0)
}
}
.ignoreOutput()
.eraseToAnyPublisher()
}
func showMore(ids: Set<Status.Id>) -> AnyPublisher<Never, Error> {
func toggleShowAttachments(id: Status.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher {
if let toggle = try StatusShowAttachmentsToggle
.filter(StatusShowAttachmentsToggle.Columns.statusId == id)
.fetchOne($0) {
try toggle.delete($0)
} else {
try StatusShowAttachmentsToggle(statusId: id).save($0)
}
}
.ignoreOutput()
.eraseToAnyPublisher()
}
func expand(ids: Set<Status.Id>) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher {
for id in ids {
try StatusShowMoreToggle(statusId: id).save($0)
try StatusShowContentToggle(statusId: id).save($0)
try StatusShowAttachmentsToggle(statusId: id).save($0)
}
}
.ignoreOutput()
.eraseToAnyPublisher()
}
func showLess(ids: Set<Status.Id>) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher(
updates: StatusShowMoreToggle
.filter(ids.contains(StatusShowMoreToggle.Columns.statusId))
.deleteAll)
.ignoreOutput()
.eraseToAnyPublisher()
func collapse(ids: Set<Status.Id>) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher {
try StatusShowContentToggle
.filter(ids.contains(StatusShowContentToggle.Columns.statusId))
.deleteAll($0)
try StatusShowAttachmentsToggle
.filter(ids.contains(StatusShowContentToggle.Columns.statusId))
.deleteAll($0)
}
.ignoreOutput()
.eraseToAnyPublisher()
}
func append(accounts: [Account], toList list: AccountList) -> AnyPublisher<Never, Error> {

View file

@ -35,7 +35,8 @@ extension ContextItemsInfo {
return .status(
.init(info: statusInfo),
.init(showMoreToggled: statusInfo.showMoreToggled,
.init(showContentToggled: statusInfo.showContentToggled,
showAttachmentsToggled: statusInfo.showAttachmentsToggled,
isContextParent: statusInfo.record.id == parent.record.id,
isReplyInContext: isReplyInContext,
hasReplyFollowing: hasReplyFollowing))

View file

@ -8,8 +8,10 @@ struct StatusInfo: Codable, Hashable, FetchableRecord {
let accountInfo: AccountInfo
let reblogAccountInfo: AccountInfo?
let reblogRecord: StatusRecord?
let showMoreToggle: StatusShowMoreToggle?
let reblogShowMoreToggle: StatusShowMoreToggle?
let showContentToggle: StatusShowContentToggle?
let reblogShowContentToggle: StatusShowContentToggle?
let showAttachmentsToggle: StatusShowAttachmentsToggle?
let reblogShowAttachmentsToggle: StatusShowAttachmentsToggle?
}
extension StatusInfo {
@ -18,9 +20,11 @@ extension StatusInfo {
.including(optional: AccountInfo.addingIncludes(StatusRecord.reblogAccount)
.forKey(CodingKeys.reblogAccountInfo))
.including(optional: StatusRecord.reblog.forKey(CodingKeys.reblogRecord))
.including(optional: StatusRecord.showMoreToggle.forKey(CodingKeys.showMoreToggle))
.including(optional: StatusRecord.reblogShowMoreToggle
.forKey(CodingKeys.reblogShowMoreToggle))
.including(optional: StatusRecord.showContentToggle.forKey(CodingKeys.showContentToggle))
.including(optional: StatusRecord.reblogShowContentToggle.forKey(CodingKeys.reblogShowContentToggle))
.including(optional: StatusRecord.showAttachmentsToggle.forKey(CodingKeys.showAttachmentsToggle))
.including(optional: StatusRecord.reblogShowAttachmentsToggle
.forKey(CodingKeys.reblogShowAttachmentsToggle))
}
static func request(_ request: QueryInterfaceRequest<StatusRecord>) -> QueryInterfaceRequest<Self> {
@ -31,7 +35,11 @@ extension StatusInfo {
(record.filterableContent + (reblogRecord?.filterableContent ?? [])).joined(separator: " ")
}
var showMoreToggled: Bool {
showMoreToggle != nil || reblogShowMoreToggle != nil
var showContentToggled: Bool {
showContentToggle != nil || reblogShowContentToggle != nil
}
var showAttachmentsToggled: Bool {
showAttachmentsToggle != nil || reblogShowAttachmentsToggle != nil
}
}

View file

@ -92,11 +92,16 @@ extension StatusRecord {
through: Self.reblogAccount,
using: AccountRecord.moved)
static let reblog = belongsTo(StatusRecord.self)
static let showMoreToggle = hasOne(StatusShowMoreToggle.self)
static let reblogShowMoreToggle = hasOne(
StatusShowMoreToggle.self,
static let showContentToggle = hasOne(StatusShowContentToggle.self)
static let reblogShowContentToggle = hasOne(
StatusShowContentToggle.self,
through: Self.reblog,
using: Self.showMoreToggle)
using: Self.showContentToggle)
static let showAttachmentsToggle = hasOne(StatusShowAttachmentsToggle.self)
static let reblogShowAttachmentsToggle = hasOne(
StatusShowAttachmentsToggle.self,
through: Self.reblog,
using: Self.showAttachmentsToggle)
static let ancestorJoins = hasMany(
StatusAncestorJoin.self,
using: ForeignKey([StatusAncestorJoin.Columns.parentId]))

View file

@ -0,0 +1,25 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import GRDB
import Mastodon
struct StatusShowAttachmentsToggle: Codable, Hashable {
let statusId: Status.Id
}
extension StatusShowAttachmentsToggle {
enum Columns {
static let statusId = Column(StatusShowAttachmentsToggle.CodingKeys.statusId)
}
}
extension StatusShowAttachmentsToggle: FetchableRecord, PersistableRecord {
static func databaseJSONDecoder(for column: String) -> JSONDecoder {
MastodonDecoder()
}
static func databaseJSONEncoder(for column: String) -> JSONEncoder {
MastodonEncoder()
}
}

View file

@ -4,17 +4,17 @@ import Foundation
import GRDB
import Mastodon
struct StatusShowMoreToggle: Codable, Hashable {
struct StatusShowContentToggle: Codable, Hashable {
let statusId: Status.Id
}
extension StatusShowMoreToggle {
extension StatusShowContentToggle {
enum Columns {
static let statusId = Column(StatusShowMoreToggle.CodingKeys.statusId)
static let statusId = Column(StatusShowContentToggle.CodingKeys.statusId)
}
}
extension StatusShowMoreToggle: FetchableRecord, PersistableRecord {
extension StatusShowContentToggle: FetchableRecord, PersistableRecord {
static func databaseJSONDecoder(for column: String) -> JSONDecoder {
MastodonDecoder()
}

View file

@ -35,7 +35,8 @@ extension TimelineItemsInfo {
.map {
CollectionItem.status(
.init(info: $0),
.init(showMoreToggled: $0.showMoreToggled))
.init(showContentToggled: $0.showContentToggled,
showAttachmentsToggled: $0.showAttachmentsToggled))
}
for loadMoreRecord in loadMoreRecords {
@ -58,7 +59,9 @@ extension TimelineItemsInfo {
.map {
CollectionItem.status(
.init(info: $0),
.init(showMoreToggled: $0.showMoreToggled, isPinned: true))
.init(showContentToggled: $0.showContentToggled,
showAttachmentsToggled: $0.showAttachmentsToggled,
isPinned: true))
},
timelineItems]
} else {

View file

@ -10,18 +10,21 @@ public enum CollectionItem: Hashable {
public extension CollectionItem {
struct StatusConfiguration: Hashable {
public let showMoreToggled: Bool
public let showContentToggled: Bool
public let showAttachmentsToggled: Bool
public let isContextParent: Bool
public let isPinned: Bool
public let isReplyInContext: Bool
public let hasReplyFollowing: Bool
init(showMoreToggled: Bool,
init(showContentToggled: Bool,
showAttachmentsToggled: Bool,
isContextParent: Bool = false,
isPinned: Bool = false,
isReplyInContext: Bool = false,
hasReplyFollowing: Bool = false) {
self.showMoreToggled = showMoreToggled
self.showContentToggled = showContentToggled
self.showAttachmentsToggled = showAttachmentsToggled
self.isContextParent = isContextParent
self.isPinned = isPinned
self.isReplyInContext = isReplyInContext
@ -31,5 +34,5 @@ public extension CollectionItem {
}
public extension CollectionItem.StatusConfiguration {
static let `default` = Self(showMoreToggled: false)
static let `default` = Self(showContentToggled: false, showAttachmentsToggled: false)
}

View file

@ -30,20 +30,5 @@ class TableViewDataSource: UITableViewDiffableDataSource<Int, CollectionItemIden
return cell
}
defaultRowAnimation = .none
}
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<Int, CollectionItemIdentifier>,
animatingDifferences: Bool = true,
completion: (() -> Void)? = nil) {
let differenceExceptShowMoreToggled = self.snapshot().itemIdentifiers.difference(
from: snapshot.itemIdentifiers,
by: CollectionItemIdentifier.isSameExceptShowMoreToggled(lhs:rhs:))
let animated = snapshot.itemIdentifiers.count > 0 && differenceExceptShowMoreToggled.count == 0
updateQueue.async {
super.apply(snapshot, animatingDifferences: animated, completion: completion)
}
}
}

View file

@ -11,6 +11,8 @@
"add-identity.join" = "Join";
"add-identity.request-invite" = "Request an invite";
"add-identity.unable-to-connect-to-instance" = "Unable to connect to instance";
"attachment.sensitive-content" = "Sensitive content";
"attachment.media-hidden" = "Media hidden";
"registration.review-terms-of-use-and-privacy-policy-%@" = "Please review %@'s Terms of Use and Privacy Policy to continue";
"registration.username" = "Username";
"registration.email" = "Email";

View file

@ -41,8 +41,4 @@ extension AccountListService: CollectionService {
.flatMap { contentDatabase.append(accounts: $0.result, toList: list) }
.eraseToAnyPublisher()
}
public func toggleShowMore(id: Status.Id) -> AnyPublisher<Never, Error> {
contentDatabase.toggleShowMore(id: id)
}
}

View file

@ -9,7 +9,6 @@ public protocol CollectionService {
var title: AnyPublisher<String, Never> { get }
var navigationService: NavigationService { get }
func request(maxId: String?, minId: String?) -> AnyPublisher<Never, Error>
func toggleShowMore(id: Status.Id) -> AnyPublisher<Never, Error>
}
extension CollectionService {

View file

@ -32,15 +32,11 @@ extension ContextService: CollectionService {
.eraseToAnyPublisher()
}
public func toggleShowMore(id: Status.Id) -> AnyPublisher<Never, Error> {
contentDatabase.toggleShowMore(id: id)
public func expand(ids: Set<Status.Id>) -> AnyPublisher<Never, Error> {
contentDatabase.expand(ids: ids)
}
public func showMore(ids: Set<Status.Id>) -> AnyPublisher<Never, Error> {
contentDatabase.showMore(ids: ids)
}
public func showLess(ids: Set<Status.Id>) -> AnyPublisher<Never, Error> {
contentDatabase.showLess(ids: ids)
public func collapse(ids: Set<Status.Id>) -> AnyPublisher<Never, Error> {
contentDatabase.collapse(ids: ids)
}
}

View file

@ -24,8 +24,12 @@ public struct StatusService {
}
public extension StatusService {
func toggleShowMore() -> AnyPublisher<Never, Error> {
contentDatabase.toggleShowMore(id: status.displayStatus.id)
func toggleShowContent() -> AnyPublisher<Never, Error> {
contentDatabase.toggleShowContent(id: status.displayStatus.id)
}
func toggleShowAttachments() -> AnyPublisher<Never, Error> {
contentDatabase.toggleShowAttachments(id: status.displayStatus.id)
}
func toggleFavorited() -> AnyPublisher<Never, Error> {

View file

@ -44,8 +44,4 @@ extension TimelineService: CollectionService {
.flatMap { contentDatabase.insert(statuses: $0.result, timeline: timeline) }
.eraseToAnyPublisher()
}
public func toggleShowMore(id: Status.Id) -> AnyPublisher<Never, Error> {
contentDatabase.toggleShowMore(id: id)
}
}

View file

@ -164,8 +164,8 @@ private extension TableViewController {
.sink { [weak self] in self?.handle(event: $0) }
.store(in: &cancellables)
viewModel.showMoreForAll.receive(on: DispatchQueue.main)
.sink { [weak self] in self?.set(showMoreForAllState: $0) }
viewModel.expandAll.receive(on: DispatchQueue.main)
.sink { [weak self] in self?.set(expandAllState: $0) }
.store(in: &cancellables)
viewModel.loading.receive(on: RunLoop.main).sink { [weak self] in
@ -194,7 +194,7 @@ private extension TableViewController {
offsetFromNavigationBar = tableView.rectForRow(at: indexPath).origin.y - navigationBarMaxY
}
self.dataSource.apply(update.items.snapshot()) { [weak self] in
self.dataSource.apply(update.items.snapshot(), animatingDifferences: false) { [weak self] in
guard let self = self else { return }
if
@ -241,20 +241,20 @@ private extension TableViewController {
}
}
func set(showMoreForAllState: ShowMoreForAllState) {
switch showMoreForAllState {
func set(expandAllState: ExpandAllState) {
switch expandAllState {
case .hidden:
navigationItem.rightBarButtonItem = nil
case .showMore:
case .expand:
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: NSLocalizedString("status.show-more", comment: ""),
image: UIImage(systemName: "eye.slash"),
primaryAction: UIAction { [weak self] _ in self?.viewModel.toggleShowMoreForAll() })
case .showLess:
image: UIImage(systemName: "eye"),
primaryAction: UIAction { [weak self] _ in self?.viewModel.toggleExpandAll() })
case .collapse:
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: NSLocalizedString("status.show-less", comment: ""),
image: UIImage(systemName: "eye"),
primaryAction: UIAction { [weak self] _ in self?.viewModel.toggleShowMoreForAll() })
image: UIImage(systemName: "eye.slash"),
primaryAction: UIAction { [weak self] _ in self?.viewModel.toggleExpandAll() })
}
}

View file

@ -15,7 +15,7 @@ final public class CollectionItemsViewModel: ObservableObject {
private var viewModelCache = [CollectionItem: (viewModel: CollectionItemViewModel, events: AnyCancellable)]()
private let eventsSubject = PassthroughSubject<CollectionItemEvent, Never>()
private let loadingSubject = PassthroughSubject<Bool, Never>()
private let showMoreForAllSubject: CurrentValueSubject<ShowMoreForAllState, Never>
private let expandAllSubject: CurrentValueSubject<ExpandAllState, Never>
private var maintainScrollPosition: CollectionItemIdentifier?
private var topVisibleIndexPath = IndexPath(item: 0, section: 0)
private var lastSelectedLoadMore: LoadMore?
@ -24,9 +24,9 @@ final public class CollectionItemsViewModel: ObservableObject {
public init(collectionService: CollectionService, identification: Identification) {
self.collectionService = collectionService
self.identification = identification
showMoreForAllSubject = CurrentValueSubject(
expandAllSubject = CurrentValueSubject(
collectionService is ContextService && !identification.identity.preferences.readingExpandSpoilers
? .showMore : .hidden)
? .expand : .hidden)
collectionService.sections
.handleEvents(receiveOutput: { [weak self] in self?.process(items: $0) })
@ -52,8 +52,8 @@ extension CollectionItemsViewModel: CollectionViewModel {
public var title: AnyPublisher<String, Never> { collectionService.title }
public var showMoreForAll: AnyPublisher<ShowMoreForAllState, Never> {
showMoreForAllSubject.eraseToAnyPublisher()
public var expandAll: AnyPublisher<ExpandAllState, Never> {
expandAllSubject.eraseToAnyPublisher()
}
public var alertItems: AnyPublisher<AlertItem, Never> { $alertItem.compactMap { $0 }.eraseToAnyPublisher() }
@ -153,27 +153,27 @@ extension CollectionItemsViewModel: CollectionViewModel {
}
}
public func toggleShowMoreForAll() {
public func toggleExpandAll() {
let statusIds = Set(items.value.reduce([], +).compactMap { item -> Status.Id? in
guard case let .status(status, _) = item else { return nil }
return status.id
})
switch showMoreForAllSubject.value {
switch expandAllSubject.value {
case .hidden:
break
case .showMore:
(collectionService as? ContextService)?.showMore(ids: statusIds)
case .expand:
(collectionService as? ContextService)?.expand(ids: statusIds)
.assignErrorsToAlertItem(to: \.alertItem, on: self)
.collect()
.sink { [weak self] _ in self?.showMoreForAllSubject.send(.showLess) }
.sink { [weak self] _ in self?.expandAllSubject.send(.collapse) }
.store(in: &cancellables)
case .showLess:
(collectionService as? ContextService)?.showLess(ids: statusIds)
case .collapse:
(collectionService as? ContextService)?.collapse(ids: statusIds)
.assignErrorsToAlertItem(to: \.alertItem, on: self)
.collect()
.sink { [weak self] _ in self?.showMoreForAllSubject.send(.showMore) }
.sink { [weak self] _ in self?.expandAllSubject.send(.expand) }
.store(in: &cancellables)
}
}

View file

@ -6,7 +6,7 @@ import Foundation
public protocol CollectionViewModel {
var updates: AnyPublisher<CollectionUpdate, Never> { get }
var title: AnyPublisher<String, Never> { get }
var showMoreForAll: AnyPublisher<ShowMoreForAllState, Never> { get }
var expandAll: AnyPublisher<ExpandAllState, Never> { get }
var alertItems: AnyPublisher<AlertItem, Never> { get }
var loading: AnyPublisher<Bool, Never> { get }
var events: AnyPublisher<CollectionItemEvent, Never> { get }
@ -16,5 +16,5 @@ public protocol CollectionViewModel {
func select(indexPath: IndexPath)
func canSelect(indexPath: IndexPath) -> Bool
func viewModel(indexPath: IndexPath) -> CollectionItemViewModel
func toggleShowMoreForAll()
func toggleExpandAll()
}

View file

@ -29,17 +29,3 @@ public extension CollectionItemIdentifier {
}
}
}
extension CollectionItemIdentifier {
public static func isSameExceptShowMoreToggled(lhs: Self, rhs: Self) -> Bool {
guard case let .status(lhsStatus, lhsConfiguration) = lhs.item,
case let .status(rhsStatus, rhsConfiguration) = rhs.item,
lhsStatus == rhsStatus
else { return false }
return lhsConfiguration.isContextParent == rhsConfiguration.isContextParent
&& lhsConfiguration.isPinned == rhsConfiguration.isPinned
&& lhsConfiguration.isReplyInContext == rhsConfiguration.isReplyInContext
&& lhsConfiguration.hasReplyFollowing == rhsConfiguration.hasReplyFollowing
}
}

View file

@ -1,7 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
public enum ShowMoreForAllState {
public enum ExpandAllState {
case hidden
case showMore
case showLess
case expand
case collapse
}

View file

@ -49,8 +49,8 @@ extension ProfileViewModel: CollectionViewModel {
$accountViewModel.compactMap { $0?.accountName }.eraseToAnyPublisher()
}
public var showMoreForAll: AnyPublisher<ShowMoreForAllState, Never> {
collectionViewModel.flatMap(\.showMoreForAll).eraseToAnyPublisher()
public var expandAll: AnyPublisher<ExpandAllState, Never> {
collectionViewModel.flatMap(\.expandAll).eraseToAnyPublisher()
}
public var alertItems: AnyPublisher<AlertItem, Never> {
@ -101,7 +101,7 @@ extension ProfileViewModel: CollectionViewModel {
collectionViewModel.value.viewModel(indexPath: indexPath)
}
public func toggleShowMoreForAll() {
collectionViewModel.value.toggleShowMoreForAll()
public func toggleExpandAll() {
collectionViewModel.value.toggleExpandAll()
}
}

View file

@ -48,16 +48,31 @@ public struct StatusViewModel: CollectionItemViewModel {
}
public extension StatusViewModel {
var shouldShowMore: Bool {
var shouldShowContent: Bool {
guard spoilerText != "" else { return true }
if identification.identity.preferences.readingExpandSpoilers {
return !configuration.showMoreToggled
return !configuration.showContentToggled
} else {
return configuration.showMoreToggled
return configuration.showContentToggled
}
}
var shouldShowAttachments: Bool {
switch identification.identity.preferences.readingExpandMedia {
case .default, .unknown:
return !sensitive || configuration.showAttachmentsToggled
case .showAll:
return !configuration.showAttachmentsToggled
case .hideAll:
return configuration.showAttachmentsToggled
}
}
var shouldShowHideAttachmentsButton: Bool {
sensitive || identification.identity.preferences.readingExpandMedia == .hideAll
}
var accountName: String { "@" + statusService.status.displayStatus.account.acct }
var avatarURL: URL { statusService.status.displayStatus.account.avatar }
@ -107,9 +122,16 @@ public extension StatusViewModel {
}
}
func toggleShowMore() {
func toggleShowContent() {
eventsSubject.send(
statusService.toggleShowMore()
statusService.toggleShowContent()
.map { _ in CollectionItemEvent.ignorableOutput }
.eraseToAnyPublisher())
}
func toggleShowAttachments() {
eventsSubject.send(
statusService.toggleShowAttachments()
.map { _ in CollectionItemEvent.ignorableOutput }
.eraseToAnyPublisher())
}

View file

@ -7,6 +7,10 @@ final class StatusAttachmentsView: UIView {
private let containerStackView = UIStackView()
private let leftStackView = UIStackView()
private let rightStackView = UIStackView()
private let curtain = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial))
private let curtainButton = UIButton(type: .system)
private let hideButtonBackground = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial))
private let hideButton = UIButton()
private var aspectRatioConstraint: NSLayoutConstraint?
var viewModel: StatusViewModel? {
@ -47,6 +51,15 @@ final class StatusAttachmentsView: UIView {
aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: newAspectRatio)
aspectRatioConstraint?.priority = .justBelowMax
aspectRatioConstraint?.isActive = true
curtain.isHidden = viewModel?.shouldShowAttachments ?? false
curtainButton.setTitle(
NSLocalizedString((viewModel?.sensitive ?? false)
? "attachment.sensitive-content"
: "attachment.media-hidden",
comment: ""),
for: .normal)
hideButtonBackground.isHidden = !(viewModel?.shouldShowHideAttachmentsButton ?? false)
}
}
@ -63,6 +76,7 @@ final class StatusAttachmentsView: UIView {
}
private extension StatusAttachmentsView {
// swiftlint:disable:next function_body_length
func initialSetup() {
backgroundColor = .clear
layoutMargins = .zero
@ -81,11 +95,57 @@ private extension StatusAttachmentsView {
containerStackView.addArrangedSubview(leftStackView)
containerStackView.addArrangedSubview(rightStackView)
let toggleShowAttachmentsAction = UIAction { [weak self] _ in
self?.viewModel?.toggleShowAttachments()
}
addSubview(hideButtonBackground)
hideButtonBackground.translatesAutoresizingMaskIntoConstraints = false
hideButtonBackground.clipsToBounds = true
hideButtonBackground.layer.cornerRadius = .defaultCornerRadius
hideButton.addAction(toggleShowAttachmentsAction, for: .touchUpInside)
hideButtonBackground.contentView.addSubview(hideButton)
hideButton.translatesAutoresizingMaskIntoConstraints = false
hideButton.setImage(
UIImage(systemName: "eye.slash", withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
for: .normal)
addSubview(curtain)
curtain.translatesAutoresizingMaskIntoConstraints = false
curtain.contentView.addSubview(curtainButton)
curtainButton.addAction(toggleShowAttachmentsAction, for: .touchUpInside)
curtainButton.translatesAutoresizingMaskIntoConstraints = false
curtainButton.titleLabel?.font = .preferredFont(forTextStyle: .headline)
curtainButton.titleLabel?.adjustsFontForContentSizeCategory = true
NSLayoutConstraint.activate([
containerStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
containerStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
containerStackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
containerStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
containerStackView.topAnchor.constraint(equalTo: topAnchor),
containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
hideButtonBackground.topAnchor.constraint(equalTo: topAnchor, constant: .defaultSpacing),
hideButtonBackground.leadingAnchor.constraint(equalTo: leadingAnchor, constant: .defaultSpacing),
hideButton.topAnchor.constraint(
equalTo: hideButtonBackground.contentView.topAnchor,
constant: .compactSpacing),
hideButton.leadingAnchor.constraint(
equalTo: hideButtonBackground.contentView.leadingAnchor,
constant: .compactSpacing),
hideButtonBackground.contentView.trailingAnchor.constraint(
equalTo: hideButton.trailingAnchor,
constant: .compactSpacing),
hideButtonBackground.contentView.bottomAnchor.constraint(
equalTo: hideButton.bottomAnchor,
constant: .compactSpacing),
curtain.topAnchor.constraint(equalTo: topAnchor),
curtain.leadingAnchor.constraint(equalTo: leadingAnchor),
curtain.trailingAnchor.constraint(equalTo: trailingAnchor),
curtain.bottomAnchor.constraint(equalTo: bottomAnchor),
curtainButton.topAnchor.constraint(equalTo: curtain.contentView.topAnchor),
curtainButton.leadingAnchor.constraint(equalTo: curtain.contentView.leadingAnchor),
curtainButton.trailingAnchor.constraint(equalTo: curtain.contentView.trailingAnchor),
curtainButton.bottomAnchor.constraint(equalTo: curtain.contentView.bottomAnchor)
])
}
}

View file

@ -13,7 +13,7 @@ final class StatusView: UIView {
let accountLabel = UILabel()
let timeLabel = UILabel()
let spoilerTextLabel = UILabel()
let toggleShowMoreButton = UIButton(type: .system)
let toggleShowContentButton = UIButton(type: .system)
let contentTextView = TouchFallthroughTextView()
let attachmentsView = StatusAttachmentsView()
let cardView = CardView()
@ -148,12 +148,12 @@ private extension StatusView {
spoilerTextLabel.adjustsFontForContentSizeCategory = true
mainStackView.addArrangedSubview(spoilerTextLabel)
toggleShowMoreButton.titleLabel?.font = .preferredFont(forTextStyle: .headline)
toggleShowMoreButton.titleLabel?.adjustsFontForContentSizeCategory = true
toggleShowMoreButton.addAction(
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.toggleShowMore() },
toggleShowContentButton.titleLabel?.font = .preferredFont(forTextStyle: .headline)
toggleShowContentButton.titleLabel?.adjustsFontForContentSizeCategory = true
toggleShowContentButton.addAction(
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.toggleShowContent() },
for: .touchUpInside)
mainStackView.addArrangedSubview(toggleShowMoreButton)
mainStackView.addArrangedSubview(toggleShowContentButton)
contentTextView.adjustsFontForContentSizeCategory = true
contentTextView.isScrollEnabled = false
@ -365,14 +365,14 @@ private extension StatusView {
spoilerTextLabel.font = contentFont
spoilerTextLabel.attributedText = mutableSpoilerText
spoilerTextLabel.isHidden = spoilerTextLabel.text == ""
toggleShowMoreButton.setTitle(
viewModel.shouldShowMore
toggleShowContentButton.setTitle(
viewModel.shouldShowContent
? NSLocalizedString("status.show-less", comment: "")
: NSLocalizedString("status.show-more", comment: ""),
for: .normal)
toggleShowMoreButton.isHidden = viewModel.spoilerText == ""
toggleShowContentButton.isHidden = viewModel.spoilerText == ""
contentTextView.isHidden = !viewModel.shouldShowMore
contentTextView.isHidden = !viewModel.shouldShowContent
nameAccountTimeStackView.axis = isContextParent ? .vertical : .horizontal
nameAccountTimeStackView.alignment = isContextParent ? .leading : .fill