mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-21 15:50:59 +00:00
Select multiple statuses to report
This commit is contained in:
parent
5b360c6bc1
commit
7f2f192995
11 changed files with 417 additions and 289 deletions
|
@ -265,11 +265,14 @@
|
|||
"report.target-%@" = "Report %@";
|
||||
"report.forward.hint" = "The account is from another server. Send an anonymized copy of the report there as well?";
|
||||
"report.forward-%@" = "Forward report to %@";
|
||||
"report.select-additional.hint.post" = "Select additional posts to report:";
|
||||
"report.select-additional.hint.toot" = "Select additional toots to report:";
|
||||
"search.scope.all" = "All";
|
||||
"search.scope.accounts" = "People";
|
||||
"search.scope.statuses.post" = "Posts";
|
||||
"search.scope.statuses.toot" = "Toots";
|
||||
"search.scope.tags" = "Hashtags";
|
||||
"selected" = "Selected";
|
||||
"send" = "Send";
|
||||
"share" = "Share";
|
||||
"share-extension-error.no-account-found" = "No account found";
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
D0030982250C6C8500EACB32 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0030981250C6C8500EACB32 /* URL+Extensions.swift */; };
|
||||
D005A1D825EF189A008B2E63 /* ReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005A1D725EF189A008B2E63 /* ReportViewController.swift */; };
|
||||
D005A1E625EF3D11008B2E63 /* ReportHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005A1E525EF3D11008B2E63 /* ReportHeaderView.swift */; };
|
||||
D00702292555E51200F38136 /* ConversationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00702282555E51200F38136 /* ConversationTableViewCell.swift */; };
|
||||
D00702312555F4AE00F38136 /* ConversationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00702302555F4AE00F38136 /* ConversationView.swift */; };
|
||||
D00702362555F4C500F38136 /* ConversationContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */; };
|
||||
|
@ -132,7 +134,6 @@
|
|||
D09D971825C64682007E6394 /* InstanceCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09D971725C64682007E6394 /* InstanceCollectionViewCell.swift */; };
|
||||
D09D972225C65682007E6394 /* SeparatorConfiguredCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09D972125C65682007E6394 /* SeparatorConfiguredCollectionViewListCell.swift */; };
|
||||
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; };
|
||||
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */; };
|
||||
D0B325EB25E88ADC00C24BEA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45724F76169001EBDBB /* Localizable.strings */; };
|
||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
||||
D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */; };
|
||||
|
@ -187,7 +188,6 @@
|
|||
D0D93ED025D9C9ED00C622ED /* AutocompleteItemCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D93ECF25D9C9ED00C622ED /* AutocompleteItemCollectionViewCell.swift */; };
|
||||
D0D93ED925D9CBE200C622ED /* AutocompleteItemCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D93ECF25D9C9ED00C622ED /* AutocompleteItemCollectionViewCell.swift */; };
|
||||
D0D93EDE25DA014700C622ED /* SeparatorConfiguredCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09D972125C65682007E6394 /* SeparatorConfiguredCollectionViewListCell.swift */; };
|
||||
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DD50CA256B1F24004A04F7 /* ReportView.swift */; };
|
||||
D0DDA76B25C5F20800FA0F91 /* ExploreDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DDA76A25C5F20800FA0F91 /* ExploreDataSource.swift */; };
|
||||
D0DDA77525C5F73F00FA0F91 /* TagCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DDA77425C5F73F00FA0F91 /* TagCollectionViewCell.swift */; };
|
||||
D0DDA77F25C6058300FA0F91 /* ExploreSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DDA77E25C6058300FA0F91 /* ExploreSectionHeaderView.swift */; };
|
||||
|
@ -260,6 +260,8 @@
|
|||
|
||||
/* Begin PBXFileReference section */
|
||||
D0030981250C6C8500EACB32 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D005A1D725EF189A008B2E63 /* ReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewController.swift; sourceTree = "<group>"; };
|
||||
D005A1E525EF3D11008B2E63 /* ReportHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportHeaderView.swift; sourceTree = "<group>"; };
|
||||
D00702282555E51200F38136 /* ConversationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D00702302555F4AE00F38136 /* ConversationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationView.swift; sourceTree = "<group>"; };
|
||||
D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationContentConfiguration.swift; sourceTree = "<group>"; };
|
||||
|
@ -353,7 +355,6 @@
|
|||
D09D971725C64682007E6394 /* InstanceCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D09D972125C65682007E6394 /* SeparatorConfiguredCollectionViewListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorConfiguredCollectionViewListCell.swift; sourceTree = "<group>"; };
|
||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
||||
D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = "<group>"; };
|
||||
D0B32F4F250B373600311912 /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = "<group>"; };
|
||||
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileCollection+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D0B8510B25259E56004E0744 /* LoadMoreTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreTableViewCell.swift; sourceTree = "<group>"; };
|
||||
|
@ -403,7 +404,6 @@
|
|||
D0D93EB925D9C70400C622ED /* AutocompleteItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteItemView.swift; sourceTree = "<group>"; };
|
||||
D0D93EBF25D9C71D00C622ED /* AutocompleteItemContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteItemContentConfiguration.swift; sourceTree = "<group>"; };
|
||||
D0D93ECF25D9C9ED00C622ED /* AutocompleteItemCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteItemCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D0DD50CA256B1F24004A04F7 /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = "<group>"; };
|
||||
D0DDA76A25C5F20800FA0F91 /* ExploreDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreDataSource.swift; sourceTree = "<group>"; };
|
||||
D0DDA77425C5F73F00FA0F91 /* TagCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D0DDA77E25C6058300FA0F91 /* ExploreSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreSectionHeaderView.swift; sourceTree = "<group>"; };
|
||||
|
@ -509,6 +509,7 @@
|
|||
D08B8D812544D80000B1EBEF /* PollOptionButton.swift */,
|
||||
D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */,
|
||||
D08B8D71254246E200B1EBEF /* PollView.swift */,
|
||||
D005A1E525EF3D11008B2E63 /* ReportHeaderView.swift */,
|
||||
D08DFAF625CE20EA0005DA98 /* ScrollableToTop.swift */,
|
||||
D035F8C625B96A4000DC75ED /* SecondaryNavigationButton.swift */,
|
||||
D03D87F325C23C44004DCBB2 /* SecondaryNavigationTitleView.swift */,
|
||||
|
@ -535,7 +536,6 @@
|
|||
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */,
|
||||
D0C7D42624F76169001EBDBB /* PreferencesView.swift */,
|
||||
D0B32F4F250B373600311912 /* RegistrationView.swift */,
|
||||
D0DD50CA256B1F24004A04F7 /* ReportView.swift */,
|
||||
D0C7D42724F76169001EBDBB /* RootView.swift */,
|
||||
D0C7D42924F76169001EBDBB /* SecondaryNavigationView.swift */,
|
||||
D021A66925C3E19D008A0C0D /* View Controller Representables */,
|
||||
|
@ -621,7 +621,6 @@
|
|||
D021A67225C3E2C8008A0C0D /* View Repesentables */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */,
|
||||
);
|
||||
path = "View Repesentables";
|
||||
sourceTree = "<group>";
|
||||
|
@ -758,6 +757,7 @@
|
|||
D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */,
|
||||
D097F4C025BFA04C00859F2C /* NotificationsViewController.swift */,
|
||||
D06BC5E525202AD90079541D /* ProfileViewController.swift */,
|
||||
D005A1D725EF189A008B2E63 /* ReportViewController.swift */,
|
||||
D0F0B12D251A97E400942152 /* TableViewController.swift */,
|
||||
D035F87C25B7F61600DC75ED /* TimelinesViewController.swift */,
|
||||
);
|
||||
|
@ -1063,7 +1063,6 @@
|
|||
D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */,
|
||||
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
||||
D0477F2C25C6EBAD005C5368 /* OpenInDefaultBrowserActivity.swift in Sources */,
|
||||
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */,
|
||||
D07F4D9825D493E300F61133 /* MuteView.swift in Sources */,
|
||||
D02D338D25EDA593000A35CC /* CopyableLabel.swift in Sources */,
|
||||
D097F41B25BE3E1A00859F2C /* SearchScope+Extensions.swift in Sources */,
|
||||
|
@ -1075,6 +1074,7 @@
|
|||
D0CEC11025E3462B00FEF5A6 /* AnimatedAttachmentLabel.swift in Sources */,
|
||||
D05E688525B55AE8001FB2C6 /* AVURLAsset+Extensions.swift in Sources */,
|
||||
D0D93EC025D9C71D00C622ED /* AutocompleteItemContentConfiguration.swift in Sources */,
|
||||
D005A1E625EF3D11008B2E63 /* ReportHeaderView.swift in Sources */,
|
||||
D09D970E25C64539007E6394 /* InstanceContentConfiguration.swift in Sources */,
|
||||
D036AA02254B6101009094DF /* NotificationTableViewCell.swift in Sources */,
|
||||
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */,
|
||||
|
@ -1094,6 +1094,7 @@
|
|||
D021A61A25C36C1A008A0C0D /* IdentityContentConfiguration.swift in Sources */,
|
||||
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */,
|
||||
D08B8D602540DE3B00B1EBEF /* ZoomAnimator.swift in Sources */,
|
||||
D005A1D825EF189A008B2E63 /* ReportViewController.swift in Sources */,
|
||||
D08B8D672540DEB200B1EBEF /* ZoomAnimatableView.swift in Sources */,
|
||||
D0CEC0F725E3303200FEF5A6 /* AnimatingLayoutManager.swift in Sources */,
|
||||
D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */,
|
||||
|
@ -1163,7 +1164,6 @@
|
|||
D0FCC105259C4E61000B67DF /* NewStatusViewController.swift in Sources */,
|
||||
D087671625BAA8C0001FDD43 /* ExploreViewController.swift in Sources */,
|
||||
D0D2AC6725BD0484003D5DF2 /* LineChartView.swift in Sources */,
|
||||
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */,
|
||||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
||||
D097F4C125BFA04C00859F2C /* NotificationsViewController.swift in Sources */,
|
||||
D02D33EF25EE04CC000A35CC /* AddRemoveFromListsView.swift in Sources */,
|
||||
|
|
112
View Controllers/ReportViewController.swift
Normal file
112
View Controllers/ReportViewController.swift
Normal file
|
@ -0,0 +1,112 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class ReportViewController: TableViewController {
|
||||
private let reportButton = UIBarButtonItem(
|
||||
title: nil,
|
||||
style: .done,
|
||||
target: nil,
|
||||
action: nil)
|
||||
private let activityIndicatorView = UIActivityIndicatorView(style: .large)
|
||||
private let viewModel: ReportViewModel
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(viewModel: ReportViewModel) {
|
||||
self.viewModel = viewModel
|
||||
|
||||
super.init(viewModel: viewModel)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
navigationItem.title = String.localizedStringWithFormat(
|
||||
NSLocalizedString("report.target-%@", comment: ""),
|
||||
viewModel.accountName)
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
||||
systemItem: .cancel,
|
||||
primaryAction: UIAction { [weak self] _ in self?.presentingViewController?.dismiss(animated: true) })
|
||||
navigationItem.rightBarButtonItem = reportButton
|
||||
reportButton.primaryAction = UIAction(title: NSLocalizedString("report", comment: "")) { [weak self] _ in
|
||||
self?.viewModel.report()
|
||||
}
|
||||
|
||||
tableView.tableHeaderView = ReportHeaderView(viewModel: viewModel)
|
||||
|
||||
view.addSubview(activityIndicatorView)
|
||||
activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||
activityIndicatorView.hidesWhenStopped = true
|
||||
|
||||
viewModel.$reportingState
|
||||
.sink { [weak self] in self?.apply(reportingState: $0) }
|
||||
.store(in: &cancellables)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
activityIndicatorView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
activityIndicatorView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView,
|
||||
willDisplay cell: UITableViewCell,
|
||||
forRowAt indexPath: IndexPath) {
|
||||
super.tableView(tableView, willDisplay: cell, forRowAt: indexPath)
|
||||
|
||||
guard let statusView = cell.contentView as? StatusView else { return }
|
||||
|
||||
statusView.alpha = 0.75
|
||||
statusView.buttonsStackView.isHidden = true
|
||||
statusView.reportSelectionSwitch.isHidden = false
|
||||
|
||||
for subview in statusView.subviews {
|
||||
subview.isUserInteractionEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
override func configureRightBarButtonItem(expandAllState: ExpandAllState) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
guard let statusViewModel = viewModel.viewModel(indexPath: indexPath) as? StatusViewModel else { return }
|
||||
|
||||
if viewModel.elements.statusIds.contains(statusViewModel.id) {
|
||||
viewModel.elements.statusIds.remove(statusViewModel.id)
|
||||
} else {
|
||||
viewModel.elements.statusIds.insert(statusViewModel.id)
|
||||
}
|
||||
|
||||
let selectedForReport = viewModel.elements.statusIds.contains(statusViewModel.id)
|
||||
|
||||
statusViewModel.selectedForReport = selectedForReport
|
||||
|
||||
guard let statusView = tableView.cellForRow(at: indexPath)?.contentView as? StatusView else { return }
|
||||
|
||||
statusView.reportSelectionSwitch.setOn(selectedForReport, animated: true)
|
||||
statusView.refreshAccessibilityLabel()
|
||||
}
|
||||
}
|
||||
|
||||
private extension ReportViewController {
|
||||
func apply(reportingState: ReportViewModel.ReportingState) {
|
||||
switch reportingState {
|
||||
case .composing:
|
||||
activityIndicatorView.stopAnimating()
|
||||
view.isUserInteractionEnabled = true
|
||||
reportButton.isEnabled = true
|
||||
view.alpha = 1
|
||||
case .reporting:
|
||||
activityIndicatorView.startAnimating()
|
||||
view.isUserInteractionEnabled = false
|
||||
reportButton.isEnabled = false
|
||||
view.alpha = 0.5
|
||||
case .done:
|
||||
presentingViewController?.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -179,6 +179,23 @@ class TableViewController: UITableViewController {
|
|||
|
||||
sizeTableHeaderFooterViews()
|
||||
}
|
||||
|
||||
func configureRightBarButtonItem(expandAllState: ExpandAllState) {
|
||||
switch expandAllState {
|
||||
case .hidden:
|
||||
navigationItem.rightBarButtonItem = nil
|
||||
case .expand:
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
title: NSLocalizedString("status.show-more-all-button.accessibilty-label", comment: ""),
|
||||
image: UIImage(systemName: "eye"),
|
||||
primaryAction: UIAction { [weak self] _ in self?.viewModel.toggleExpandAll() })
|
||||
case .collapse:
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
title: NSLocalizedString("status.show-less-all-button.accessibilty-label", comment: ""),
|
||||
image: UIImage(systemName: "eye.slash"),
|
||||
primaryAction: UIAction { [weak self] _ in self?.viewModel.toggleExpandAll() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TableViewController {
|
||||
|
@ -373,7 +390,7 @@ private extension TableViewController {
|
|||
.store(in: &cancellables)
|
||||
|
||||
viewModel.expandAll.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] in self?.set(expandAllState: $0) }
|
||||
.sink { [weak self] in self?.configureRightBarButtonItem(expandAllState: $0) }
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.loading.receive(on: DispatchQueue.main).assign(to: &$loading)
|
||||
|
@ -741,23 +758,6 @@ private extension TableViewController {
|
|||
viewModel.applyAccountListEdit(viewModel: accountViewModel, edit: edit)
|
||||
}
|
||||
|
||||
func set(expandAllState: ExpandAllState) {
|
||||
switch expandAllState {
|
||||
case .hidden:
|
||||
navigationItem.rightBarButtonItem = nil
|
||||
case .expand:
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
title: NSLocalizedString("status.show-more-all-button.accessibilty-label", comment: ""),
|
||||
image: UIImage(systemName: "eye"),
|
||||
primaryAction: UIAction { [weak self] _ in self?.viewModel.toggleExpandAll() })
|
||||
case .collapse:
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
title: NSLocalizedString("status.show-less-all-button.accessibilty-label", comment: ""),
|
||||
image: UIImage(systemName: "eye.slash"),
|
||||
primaryAction: UIAction { [weak self] _ in self?.viewModel.toggleExpandAll() })
|
||||
}
|
||||
}
|
||||
|
||||
func share(url: URL) {
|
||||
let activityViewController = UIActivityViewController(
|
||||
activityItems: [url],
|
||||
|
|
|
@ -95,116 +95,6 @@ public class CollectionItemsViewModel: ObservableObject {
|
|||
|
||||
request(maxId: maxId, minId: nil, search: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension CollectionItemsViewModel: CollectionViewModel {
|
||||
public var title: AnyPublisher<String, Never> { collectionService.title }
|
||||
|
||||
public var titleLocalizationComponents: AnyPublisher<[String], Never> {
|
||||
collectionService.titleLocalizationComponents
|
||||
}
|
||||
|
||||
public var expandAll: AnyPublisher<ExpandAllState, Never> {
|
||||
expandAllSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public var alertItems: AnyPublisher<AlertItem, Never> { $alertItem.compactMap { $0 }.eraseToAnyPublisher() }
|
||||
|
||||
public var loading: AnyPublisher<Bool, Never> { loadingSubject.eraseToAnyPublisher() }
|
||||
|
||||
public var events: AnyPublisher<CollectionItemEvent, Never> {
|
||||
eventsSubject.flatMap { [weak self] eventPublisher -> AnyPublisher<CollectionItemEvent, Never> in
|
||||
guard let self = self else { return Empty().eraseToAnyPublisher() }
|
||||
|
||||
return eventPublisher.assignErrorsToAlertItem(to: \.alertItem, on: self).eraseToAnyPublisher()
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public var searchScopeChanges: AnyPublisher<SearchScope, Never> { searchScopeChangesSubject.eraseToAnyPublisher() }
|
||||
|
||||
public var canRefresh: Bool { collectionService.canRefresh }
|
||||
|
||||
public var announcesNewItems: Bool { collectionService.announcesNewItems }
|
||||
|
||||
public func request(maxId: String? = nil, minId: String? = nil, search: Search?) {
|
||||
collectionService.request(maxId: realMaxId(maxId: maxId), minId: minId, search: search)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.handleEvents(
|
||||
receiveSubscription: { [weak self] _ in self?.loadingSubject.send(true) },
|
||||
receiveCompletion: { [weak self] _ in self?.loadingSubject.send(false) })
|
||||
.sink { _ in }
|
||||
.store(in: &cancellables)
|
||||
collectionService.requestMarkerLastReadId()
|
||||
.sink { _ in } receiveValue: { [weak self] in self?.markerLastReadId = $0 }
|
||||
.store(in: &cancellables)
|
||||
|
||||
}
|
||||
|
||||
public func select(indexPath: IndexPath) {
|
||||
let item = lastUpdate.sections[indexPath.section].items[indexPath.item]
|
||||
|
||||
switch item {
|
||||
case let .status(status, _, _):
|
||||
send(event: .navigation(.collection(collectionService
|
||||
.navigationService
|
||||
.contextService(id: status.displayStatus.id))))
|
||||
case let .loadMore(loadMore):
|
||||
lastSelectedLoadMore = loadMore
|
||||
(viewModel(indexPath: indexPath) as? LoadMoreViewModel)?.loadMore()
|
||||
case let .account(account, _, relationship):
|
||||
send(event: .navigation(.profile(collectionService
|
||||
.navigationService
|
||||
.profileService(account: account, relationship: relationship))))
|
||||
case let .notification(notification, _):
|
||||
if let status = notification.status {
|
||||
send(event: .navigation(.collection(collectionService
|
||||
.navigationService
|
||||
.contextService(id: status.displayStatus.id))))
|
||||
} else {
|
||||
send(event: .navigation(.profile(collectionService
|
||||
.navigationService
|
||||
.profileService(account: notification.account))))
|
||||
}
|
||||
case let .conversation(conversation):
|
||||
guard let status = conversation.lastStatus else { break }
|
||||
|
||||
(collectionService as? ConversationsService)?.markConversationAsRead(id: conversation.id)
|
||||
.sink { _ in } receiveValue: { _ in }
|
||||
.store(in: &cancellables)
|
||||
|
||||
send(event: .navigation(.collection(collectionService
|
||||
.navigationService
|
||||
.contextService(id: status.displayStatus.id))))
|
||||
case let .tag(tag):
|
||||
send(event: .navigation(.collection(collectionService
|
||||
.navigationService
|
||||
.timelineService(timeline: .tag(tag.name)))))
|
||||
case let .moreResults(moreResults):
|
||||
searchScopeChangesSubject.send(moreResults.scope)
|
||||
}
|
||||
}
|
||||
|
||||
public func viewedAtTop(indexPath: IndexPath) {
|
||||
topVisibleIndexPath = indexPath
|
||||
|
||||
if lastUpdate.sections.count > indexPath.section,
|
||||
lastUpdate.sections[indexPath.section].items.count > indexPath.item {
|
||||
lastReadId.send(lastUpdate.sections[indexPath.section].items[indexPath.item].itemId)
|
||||
}
|
||||
}
|
||||
|
||||
public func canSelect(indexPath: IndexPath) -> Bool {
|
||||
switch lastUpdate.sections[indexPath.section].items[indexPath.item] {
|
||||
case let .status(_, configuration, _):
|
||||
return !configuration.isContextParent
|
||||
case .loadMore:
|
||||
return !((viewModel(indexPath: indexPath) as? LoadMoreViewModel)?.loading ?? false)
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_body_length cyclomatic_complexity
|
||||
public func viewModel(indexPath: IndexPath) -> Any {
|
||||
|
@ -317,6 +207,116 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
|||
return viewModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CollectionItemsViewModel: CollectionViewModel {
|
||||
public var title: AnyPublisher<String, Never> { collectionService.title }
|
||||
|
||||
public var titleLocalizationComponents: AnyPublisher<[String], Never> {
|
||||
collectionService.titleLocalizationComponents
|
||||
}
|
||||
|
||||
public var expandAll: AnyPublisher<ExpandAllState, Never> {
|
||||
expandAllSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public var alertItems: AnyPublisher<AlertItem, Never> { $alertItem.compactMap { $0 }.eraseToAnyPublisher() }
|
||||
|
||||
public var loading: AnyPublisher<Bool, Never> { loadingSubject.eraseToAnyPublisher() }
|
||||
|
||||
public var events: AnyPublisher<CollectionItemEvent, Never> {
|
||||
eventsSubject.flatMap { [weak self] eventPublisher -> AnyPublisher<CollectionItemEvent, Never> in
|
||||
guard let self = self else { return Empty().eraseToAnyPublisher() }
|
||||
|
||||
return eventPublisher.assignErrorsToAlertItem(to: \.alertItem, on: self).eraseToAnyPublisher()
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public var searchScopeChanges: AnyPublisher<SearchScope, Never> { searchScopeChangesSubject.eraseToAnyPublisher() }
|
||||
|
||||
public var canRefresh: Bool { collectionService.canRefresh }
|
||||
|
||||
public var announcesNewItems: Bool { collectionService.announcesNewItems }
|
||||
|
||||
public func request(maxId: String? = nil, minId: String? = nil, search: Search?) {
|
||||
collectionService.request(maxId: realMaxId(maxId: maxId), minId: minId, search: search)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.handleEvents(
|
||||
receiveSubscription: { [weak self] _ in self?.loadingSubject.send(true) },
|
||||
receiveCompletion: { [weak self] _ in self?.loadingSubject.send(false) })
|
||||
.sink { _ in }
|
||||
.store(in: &cancellables)
|
||||
collectionService.requestMarkerLastReadId()
|
||||
.sink { _ in } receiveValue: { [weak self] in self?.markerLastReadId = $0 }
|
||||
.store(in: &cancellables)
|
||||
|
||||
}
|
||||
|
||||
public func select(indexPath: IndexPath) {
|
||||
let item = lastUpdate.sections[indexPath.section].items[indexPath.item]
|
||||
|
||||
switch item {
|
||||
case let .status(status, _, _):
|
||||
send(event: .navigation(.collection(collectionService
|
||||
.navigationService
|
||||
.contextService(id: status.displayStatus.id))))
|
||||
case let .loadMore(loadMore):
|
||||
lastSelectedLoadMore = loadMore
|
||||
(viewModel(indexPath: indexPath) as? LoadMoreViewModel)?.loadMore()
|
||||
case let .account(account, _, relationship):
|
||||
send(event: .navigation(.profile(collectionService
|
||||
.navigationService
|
||||
.profileService(account: account, relationship: relationship))))
|
||||
case let .notification(notification, _):
|
||||
if let status = notification.status {
|
||||
send(event: .navigation(.collection(collectionService
|
||||
.navigationService
|
||||
.contextService(id: status.displayStatus.id))))
|
||||
} else {
|
||||
send(event: .navigation(.profile(collectionService
|
||||
.navigationService
|
||||
.profileService(account: notification.account))))
|
||||
}
|
||||
case let .conversation(conversation):
|
||||
guard let status = conversation.lastStatus else { break }
|
||||
|
||||
(collectionService as? ConversationsService)?.markConversationAsRead(id: conversation.id)
|
||||
.sink { _ in } receiveValue: { _ in }
|
||||
.store(in: &cancellables)
|
||||
|
||||
send(event: .navigation(.collection(collectionService
|
||||
.navigationService
|
||||
.contextService(id: status.displayStatus.id))))
|
||||
case let .tag(tag):
|
||||
send(event: .navigation(.collection(collectionService
|
||||
.navigationService
|
||||
.timelineService(timeline: .tag(tag.name)))))
|
||||
case let .moreResults(moreResults):
|
||||
searchScopeChangesSubject.send(moreResults.scope)
|
||||
}
|
||||
}
|
||||
|
||||
public func viewedAtTop(indexPath: IndexPath) {
|
||||
topVisibleIndexPath = indexPath
|
||||
|
||||
if lastUpdate.sections.count > indexPath.section,
|
||||
lastUpdate.sections[indexPath.section].items.count > indexPath.item {
|
||||
lastReadId.send(lastUpdate.sections[indexPath.section].items[indexPath.item].itemId)
|
||||
}
|
||||
}
|
||||
|
||||
public func canSelect(indexPath: IndexPath) -> Bool {
|
||||
switch lastUpdate.sections[indexPath.section].items[indexPath.item] {
|
||||
case let .status(_, configuration, _):
|
||||
return !configuration.isContextParent
|
||||
case .loadMore:
|
||||
return !((viewModel(indexPath: indexPath) as? LoadMoreViewModel)?.loading ?? false)
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public func toggleExpandAll() {
|
||||
let statusIds = Set(lastUpdate.sections.map(\.items).reduce([], +).compactMap { item -> Status.Id? in
|
||||
|
|
|
@ -5,36 +5,44 @@ import Foundation
|
|||
import Mastodon
|
||||
import ServiceLayer
|
||||
|
||||
public final class ReportViewModel: ObservableObject {
|
||||
public final class ReportViewModel: CollectionItemsViewModel {
|
||||
@Published public var elements: ReportElements
|
||||
public let events: AnyPublisher<Event, Never>
|
||||
public let statusViewModel: StatusViewModel?
|
||||
@Published public private(set) var loading = false
|
||||
@Published public var alertItem: AlertItem?
|
||||
@Published public private(set) var reportingState = ReportingState.composing
|
||||
|
||||
private let accountService: AccountService
|
||||
private let eventsSubject = PassthroughSubject<Event, Never>()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
public init(accountService: AccountService, statusService: StatusService? = nil, identityContext: IdentityContext) {
|
||||
public init(accountService: AccountService, statusId: Status.Id? = nil, identityContext: IdentityContext) {
|
||||
self.accountService = accountService
|
||||
elements = ReportElements(accountId: accountService.account.id)
|
||||
events = eventsSubject.eraseToAnyPublisher()
|
||||
|
||||
if let statusService = statusService {
|
||||
statusViewModel = StatusViewModel(statusService: statusService,
|
||||
identityContext: identityContext,
|
||||
eventsSubject: .init())
|
||||
elements.statusIds.insert(statusService.status.displayStatus.id)
|
||||
} else {
|
||||
statusViewModel = nil
|
||||
super.init(
|
||||
collectionService: identityContext.service.navigationService.timelineService(
|
||||
timeline: .profile(accountId: accountService.account.id, profileCollection: .statusesAndReplies)),
|
||||
identityContext: identityContext)
|
||||
|
||||
if let statusId = statusId {
|
||||
elements.statusIds.insert(statusId)
|
||||
}
|
||||
}
|
||||
|
||||
public override func viewModel(indexPath: IndexPath) -> Any {
|
||||
let viewModel = super.viewModel(indexPath: indexPath)
|
||||
|
||||
if let statusViewModel = viewModel as? StatusViewModel {
|
||||
statusViewModel.showReportSelectionToggle = true
|
||||
statusViewModel.selectedForReport = elements.statusIds.contains(statusViewModel.id)
|
||||
}
|
||||
|
||||
return viewModel
|
||||
}
|
||||
}
|
||||
|
||||
public extension ReportViewModel {
|
||||
enum Event {
|
||||
case reported
|
||||
enum ReportingState {
|
||||
case composing
|
||||
case reporting
|
||||
case done
|
||||
}
|
||||
|
||||
var accountName: String { "@".appending(accountService.account.acct) }
|
||||
|
@ -48,15 +56,16 @@ public extension ReportViewModel {
|
|||
func report() {
|
||||
accountService.report(elements)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.handleEvents(receiveSubscription: { [weak self] _ in self?.loading = true })
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.handleEvents(receiveSubscription: { [weak self] _ in self?.reportingState = .reporting })
|
||||
.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.loading = false
|
||||
|
||||
if $0 == .finished {
|
||||
self.eventsSubject.send(.reported)
|
||||
switch $0 {
|
||||
case .finished:
|
||||
self.reportingState = .done
|
||||
case let .failure(error):
|
||||
self.alertItem = AlertItem(error: error)
|
||||
self.reportingState = .composing
|
||||
}
|
||||
} receiveValue: { _ in }
|
||||
.store(in: &cancellables)
|
||||
|
|
|
@ -17,6 +17,8 @@ public final class StatusViewModel: AttachmentsRenderingViewModel, ObservableObj
|
|||
public let pollEmojis: [Emoji]
|
||||
@Published public var pollOptionSelections = Set<Int>()
|
||||
public var configuration = CollectionItem.StatusConfiguration.default
|
||||
public var showReportSelectionToggle = false
|
||||
public var selectedForReport = false
|
||||
public let identityContext: IdentityContext
|
||||
|
||||
private let statusService: StatusService
|
||||
|
@ -349,7 +351,7 @@ public extension StatusViewModel {
|
|||
Just(.report(ReportViewModel(
|
||||
accountService: statusService.navigationService.accountService(
|
||||
account: statusService.status.displayStatus.account),
|
||||
statusService: statusService,
|
||||
statusId: statusService.status.displayStatus.id,
|
||||
identityContext: identityContext)))
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher())
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
import ViewModels
|
||||
|
||||
struct ReportView: View {
|
||||
@StateObject var viewModel: ReportViewModel
|
||||
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
fileprivate var dismissHostingController: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
if let statusViewModel = viewModel.statusViewModel {
|
||||
Section {
|
||||
ReportStatusView(viewModel: statusViewModel)
|
||||
.frame(height: Self.statusHeight)
|
||||
}
|
||||
}
|
||||
Section {
|
||||
Text("report.hint")
|
||||
}
|
||||
Section(header: Text("report.additional-comments")) {
|
||||
TextEditor(text: $viewModel.elements.comment)
|
||||
.accessibility(label: Text("report.additional-comments"))
|
||||
}
|
||||
if !viewModel.isLocalAccount {
|
||||
Section {
|
||||
VStack(alignment: .leading) {
|
||||
Text("report.forward.hint")
|
||||
Toggle("report.forward-\(viewModel.accountHost)", isOn: $viewModel.elements.forward)
|
||||
}
|
||||
}
|
||||
}
|
||||
Section {
|
||||
if viewModel.loading {
|
||||
ProgressView()
|
||||
} else {
|
||||
Button("report.target-\(viewModel.accountName)") {
|
||||
viewModel.report()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.alertItem($viewModel.alertItem)
|
||||
.onReceive(viewModel.events) {
|
||||
switch $0 {
|
||||
case .reported:
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
.navigationTitle("report.target-\(viewModel.accountName)")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("cancel") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
|
||||
private extension ReportView {
|
||||
static let statusHeight: CGFloat = 100
|
||||
|
||||
func dismiss() {
|
||||
if let dismissHostingController = dismissHostingController {
|
||||
dismissHostingController()
|
||||
} else {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class ReportViewController: UIHostingController<ReportView> {
|
||||
init(viewModel: ReportViewModel) {
|
||||
super.init(rootView: ReportView(viewModel: viewModel))
|
||||
|
||||
rootView.dismissHostingController = { [weak self] in self?.dismiss(animated: true) }
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
@objc required dynamic init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct ReportView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
ReportView(viewModel: .preview)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,28 +0,0 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
import ViewModels
|
||||
|
||||
struct ReportStatusView: UIViewRepresentable {
|
||||
private let configuration: StatusContentConfiguration
|
||||
|
||||
init(viewModel: StatusViewModel) {
|
||||
configuration = StatusContentConfiguration(viewModel: viewModel)
|
||||
}
|
||||
|
||||
func makeUIView(context: Context) -> StatusView {
|
||||
let view = StatusView(configuration: configuration)
|
||||
|
||||
view.alpha = 0.5
|
||||
view.buttonsStackView.isHidden = true
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.isUserInteractionEnabled = false
|
||||
view.accessibilityLabel = view.accessibilityAttributedLabel?.string
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: StatusView, context: Context) {
|
||||
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ final class StatusView: UIView {
|
|||
let shareButton = UIButton()
|
||||
let menuButton = UIButton()
|
||||
let buttonsStackView = UIStackView()
|
||||
let reportSelectionSwitch = UISwitch()
|
||||
|
||||
private let containerStackView = UIStackView()
|
||||
private let sideStackView = UIStackView()
|
||||
|
@ -64,7 +65,7 @@ final class StatusView: UIView {
|
|||
}
|
||||
|
||||
override func accessibilityActivate() -> Bool {
|
||||
if !statusConfiguration.viewModel.shouldShowContent {
|
||||
if reportSelectionSwitch.isHidden, !statusConfiguration.viewModel.shouldShowContent {
|
||||
statusConfiguration.viewModel.toggleShowContent()
|
||||
accessibilityAttributedLabel = accessibilityAttributedLabel(forceShowContent: true)
|
||||
|
||||
|
@ -103,6 +104,10 @@ extension StatusView {
|
|||
|
||||
return height
|
||||
}
|
||||
|
||||
func refreshAccessibilityLabel() {
|
||||
accessibilityAttributedLabel = accessibilityAttributedLabel(forceShowContent: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusView: UIContentView {
|
||||
|
@ -377,6 +382,11 @@ private extension StatusView {
|
|||
view.widthAnchor.constraint(equalToConstant: .hairline).isActive = true
|
||||
}
|
||||
|
||||
containerStackView.addArrangedSubview(reportSelectionSwitch)
|
||||
reportSelectionSwitch.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
reportSelectionSwitch.setContentHuggingPriority(.required, for: .horizontal)
|
||||
reportSelectionSwitch.isHidden = true
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
containerStackView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor),
|
||||
containerStackView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
|
||||
|
@ -583,6 +593,8 @@ private extension StatusView {
|
|||
|
||||
menuButton.isEnabled = isAuthenticated
|
||||
|
||||
reportSelectionSwitch.isOn = viewModel.selectedForReport
|
||||
|
||||
isAccessibilityElement = !viewModel.configuration.isContextParent
|
||||
|
||||
accessibilityAttributedLabel = accessibilityAttributedLabel(forceShowContent: false)
|
||||
|
@ -721,6 +733,10 @@ private extension StatusView {
|
|||
func accessibilityAttributedLabel(forceShowContent: Bool) -> NSAttributedString {
|
||||
let accessibilityAttributedLabel = NSMutableAttributedString(string: "")
|
||||
|
||||
if !reportSelectionSwitch.isHidden, reportSelectionSwitch.isOn {
|
||||
accessibilityAttributedLabel.appendWithSeparator(NSLocalizedString("selected", comment: ""))
|
||||
}
|
||||
|
||||
if !infoLabel.isHidden, let infoText = infoLabel.attributedText {
|
||||
accessibilityAttributedLabel.appendWithSeparator(infoText)
|
||||
}
|
||||
|
@ -888,7 +904,7 @@ private extension StatusView {
|
|||
|
||||
// swiftlint:disable:next function_body_length cyclomatic_complexity
|
||||
func accessibilityCustomActions(viewModel: StatusViewModel) -> [UIAccessibilityCustomAction] {
|
||||
guard !viewModel.configuration.isContextParent else {
|
||||
guard !viewModel.configuration.isContextParent, reportSelectionSwitch.isHidden else {
|
||||
return []
|
||||
}
|
||||
|
||||
|
|
113
Views/UIKit/ReportHeaderView.swift
Normal file
113
Views/UIKit/ReportHeaderView.swift
Normal file
|
@ -0,0 +1,113 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class ReportHeaderView: UIView {
|
||||
private let viewModel: ReportViewModel
|
||||
private let textView = UITextView()
|
||||
|
||||
init(viewModel: ReportViewModel) {
|
||||
self.viewModel = viewModel
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
initialSetup()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
extension ReportHeaderView: UITextViewDelegate {
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
viewModel.elements.comment = textView.text
|
||||
}
|
||||
}
|
||||
|
||||
private extension ReportHeaderView {
|
||||
// swiftlint:disable:next function_body_length
|
||||
func initialSetup() {
|
||||
let stackView = UIStackView()
|
||||
|
||||
addSubview(stackView)
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .vertical
|
||||
stackView.spacing = .defaultSpacing
|
||||
|
||||
let hintLabel = UILabel()
|
||||
|
||||
stackView.addArrangedSubview(hintLabel)
|
||||
hintLabel.adjustsFontForContentSizeCategory = true
|
||||
hintLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
hintLabel.text = NSLocalizedString("report.hint", comment: "")
|
||||
hintLabel.numberOfLines = 0
|
||||
|
||||
stackView.addArrangedSubview(textView)
|
||||
textView.adjustsFontForContentSizeCategory = true
|
||||
textView.font = .preferredFont(forTextStyle: .body)
|
||||
textView.layer.borderWidth = .hairline
|
||||
textView.layer.borderColor = UIColor.separator.cgColor
|
||||
textView.layer.cornerRadius = .defaultCornerRadius
|
||||
textView.delegate = self
|
||||
textView.accessibilityLabel = NSLocalizedString("report.additional-comments", comment: "")
|
||||
|
||||
if !viewModel.isLocalAccount {
|
||||
let forwardHintLabel = UILabel()
|
||||
|
||||
stackView.addArrangedSubview(forwardHintLabel)
|
||||
forwardHintLabel.adjustsFontForContentSizeCategory = true
|
||||
forwardHintLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
forwardHintLabel.text = NSLocalizedString("report.forward.hint", comment: "")
|
||||
forwardHintLabel.numberOfLines = 0
|
||||
|
||||
let switchStackView = UIStackView()
|
||||
|
||||
stackView.addArrangedSubview(switchStackView)
|
||||
switchStackView.spacing = .defaultSpacing
|
||||
|
||||
let switchLabel = UILabel()
|
||||
|
||||
switchStackView.addArrangedSubview(switchLabel)
|
||||
switchLabel.adjustsFontForContentSizeCategory = true
|
||||
switchLabel.font = .preferredFont(forTextStyle: .headline)
|
||||
switchLabel.text = String.localizedStringWithFormat(
|
||||
NSLocalizedString("report.forward-%@", comment: ""),
|
||||
viewModel.accountHost)
|
||||
switchLabel.textAlignment = .right
|
||||
switchLabel.numberOfLines = 0
|
||||
|
||||
let forwardSwitch = UISwitch()
|
||||
|
||||
switchStackView.addArrangedSubview(forwardSwitch)
|
||||
forwardSwitch.setContentHuggingPriority(.required, for: .horizontal)
|
||||
forwardSwitch.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
forwardSwitch.addAction(
|
||||
UIAction { [weak self] _ in self?.viewModel.elements.forward = forwardSwitch.isOn },
|
||||
for: .valueChanged)
|
||||
}
|
||||
|
||||
let selectAdditionalHintLabel = UILabel()
|
||||
|
||||
stackView.addArrangedSubview(selectAdditionalHintLabel)
|
||||
selectAdditionalHintLabel.adjustsFontForContentSizeCategory = true
|
||||
selectAdditionalHintLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
selectAdditionalHintLabel.numberOfLines = 0
|
||||
|
||||
switch viewModel.identityContext.appPreferences.statusWord {
|
||||
case .toot:
|
||||
selectAdditionalHintLabel.text = NSLocalizedString("report.select-additional.hint.toot", comment: "")
|
||||
case .post:
|
||||
selectAdditionalHintLabel.text = NSLocalizedString("report.select-additional.hint.post", comment: "")
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
stackView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
|
||||
stackView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: readableContentGuide.bottomAnchor),
|
||||
textView.heightAnchor.constraint(equalToConstant: .minimumButtonDimension * 2)
|
||||
])
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue