mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-22 08:10:59 +00:00
Sensitive attachments
This commit is contained in:
parent
40b795cd7e
commit
cc370af881
25 changed files with 250 additions and 136 deletions
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -149,35 +149,54 @@ 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)
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]))
|
||||
|
|
25
DB/Sources/DB/Content/StatusShowAttachmentsToggle.swift
Normal file
25
DB/Sources/DB/Content/StatusShowAttachmentsToggle.swift
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue