From 8f40c4c24262b9296b1e72ff0c0b7b5b799e0f20 Mon Sep 17 00:00:00 2001 From: Scott Clampet <110618242+scottkicks@users.noreply.github.com> Date: Thu, 28 Sep 2023 11:51:10 -0500 Subject: [PATCH] [MBL-983] Already Reported Project Label (#1857) * create ReportProjectCell to display swiftui view * UITableView cell will be added to the Project Page Table View * Used to display a new swiftui view for the report this project label and already reported label * add new cell below overviewSubpages and pass in project's flagging prop * rename label view * fix precondition failure: cyclic graph ReportProjectLabelView (SwiftUI View) re-renders when its text labels contain hyperlinks. Since we're rendering hyperlinks with the helper in Text+HTML.swift, this seems to cause a `precondition failure: cyclic graph` crash. This check prevents that by making sure the cell is only configured once on dequeue. * layout tableview if needed needs to be called because we're adding a SwiftUI view to the cell's contentView * conditionally render labels using `flagged` property * cleanup * pass project flagged property along with projectURL to viewcontroller so we know which label can be tappable * remove temp fix for cyclic graph crash * update tests * updateProjectPageViewControllerDataSourceTests tests * fix pamphletsubpage cell divider we don't need to hide it now that there is a new section below it. * fix view styling on iPad * formatting * support more than one hyperlink in a string * undo change in deprecated file * update hyperlink urls * set accessibility traits and cell's traitCollection * pop views on successful submissionupdate contraint margins --- .../ProjectPageViewController.swift | 14 ++-- .../ProjectPageViewControllerDataSource.swift | 16 +++-- ...ectPageViewControllerDataSourceTests.swift | 61 +++++++---------- .../Views/Cells/ReportProjectCell.swift | 61 +++++++++++++++++ .../Views/ReportProjectLabelView.swift | 67 +++++++++++++++++++ .../ReportProject/ReportProjectInfoView.swift | 5 +- Kickstarter.xcodeproj/project.pbxproj | 8 +++ Library/HelpType.swift | 2 +- Library/SwiftUI+Extensions/Text+HTML.swift | 8 ++- Library/ViewModels/ProjectPageViewModel.swift | 8 +-- .../ProjectPageViewModelTests.swift | 5 +- .../ProjectPamphletSubpageCellViewModel.swift | 9 +-- 12 files changed, 199 insertions(+), 65 deletions(-) create mode 100644 Kickstarter-iOS/Features/ProjectPage/Views/Cells/ReportProjectCell.swift create mode 100644 Kickstarter-iOS/Features/ProjectPage/Views/ReportProjectLabelView.swift diff --git a/Kickstarter-iOS/Features/ProjectPage/Controller/ProjectPageViewController.swift b/Kickstarter-iOS/Features/ProjectPage/Controller/ProjectPageViewController.swift index 28f9a4c9b6..4c6d2ad7f6 100644 --- a/Kickstarter-iOS/Features/ProjectPage/Controller/ProjectPageViewController.swift +++ b/Kickstarter-iOS/Features/ProjectPage/Controller/ProjectPageViewController.swift @@ -95,6 +95,7 @@ public final class ProjectPageViewController: UIViewController, MessageBannerVie self.tableView.registerCellClass(ImageViewElementCell.self) self.tableView.registerCellClass(AudioVideoViewElementCell.self) self.tableView.registerCellClass(ExternalSourceViewElementCell.self) + self.tableView.registerCellClass(ReportProjectCell.self) self.tableView.register(nib: .ProjectPamphletMainCell) self.tableView.register(nib: .ProjectPamphletSubpageCell) self.tableView.registerCellClass(ProjectRisksCell.self) @@ -382,8 +383,9 @@ public final class ProjectPageViewController: UIViewController, MessageBannerVie self.viewModel.outputs.goToReportProject .observeForControllerAction() - .observeValues { [weak self] in - self?.goToReportProject(projectUrl: $0) + .observeValues { [weak self] flagged, projectUrl in + guard !flagged else { return } + self?.goToReportProject(projectUrl: projectUrl) } self.viewModel.outputs.goToUpdates @@ -829,9 +831,9 @@ extension ProjectPageViewController: UITableViewDelegate { self.viewModel.inputs.tappedComments() } else if self.dataSource.indexPathIsUpdatesSubpage(indexPath) { self.viewModel.inputs.tappedUpdates() - } else if self.dataSource.indexPathIsReportProject(indexPath) { - self.viewModel.inputs.tappedReportProject() } + case ProjectPageViewControllerDataSource.Section.overviewReportProject.rawValue: + self.viewModel.inputs.tappedReportProject() case ProjectPageViewControllerDataSource.Section.faqsAskAQuestion.rawValue: self.viewModel.inputs.askAQuestionCellTapped() case ProjectPageViewControllerDataSource.Section.faqs.rawValue: @@ -863,7 +865,9 @@ extension ProjectPageViewController: UITableViewDelegate { /// If we are displaying the `ProjectPamphletSubpageCell` we do not want to show the cells separator. self.tableView.separatorStyle = indexPath.section == ProjectPageViewControllerDataSource.Section - .overviewSubpages.rawValue ? .none : .singleLine + .overviewReportProject.rawValue ? .none : .singleLine + + self.tableView.layoutIfNeeded() } public func tableView( diff --git a/Kickstarter-iOS/Features/ProjectPage/Datasource/Datasource/ProjectPageViewControllerDataSource.swift b/Kickstarter-iOS/Features/ProjectPage/Datasource/Datasource/ProjectPageViewControllerDataSource.swift index 2a4d0fc477..6be429dd07 100644 --- a/Kickstarter-iOS/Features/ProjectPage/Datasource/Datasource/ProjectPageViewControllerDataSource.swift +++ b/Kickstarter-iOS/Features/ProjectPage/Datasource/Datasource/ProjectPageViewControllerDataSource.swift @@ -9,6 +9,7 @@ internal final class ProjectPageViewControllerDataSource: ValueCellDataSource { case overviewCreatorHeader case overview case overviewSubpages + case overviewReportProject case campaignHeader case campaign case faqsHeader @@ -104,8 +105,7 @@ internal final class ProjectPageViewControllerDataSource: ValueCellDataSource { let values: [ProjectPamphletSubpage] = [ .comments(project.stats.commentsCount as Int?, .first), - .updates(project.stats.updatesCount as Int?, .middle), - .reportProject(.last) + .updates(project.stats.updatesCount as Int?, .middle) ] self.set( @@ -113,6 +113,12 @@ internal final class ProjectPageViewControllerDataSource: ValueCellDataSource { cellClass: ProjectPamphletSubpageCell.self, inSection: Section.overviewSubpages.rawValue ) + + self.set( + values: [project.flagging ?? false], + cellClass: ReportProjectCell.self, + inSection: Section.overviewReportProject.rawValue + ) case .campaign: self.set( values: [HeaderValue.campaign.description], @@ -339,6 +345,8 @@ internal final class ProjectPageViewControllerDataSource: ValueCellDataSource { cell.configureWith(value: value) case let (cell as ExternalSourceViewElementCell, value as ExternalSourceViewElement): cell.configureWith(value: value) + case let (cell as ReportProjectCell, value as Bool): + cell.configureWith(value: value) default: assertionFailure("Unrecognized combo: \(cell), \(value)") } @@ -449,10 +457,6 @@ internal final class ProjectPageViewControllerDataSource: ValueCellDataSource { return (self[indexPath] as? ProjectPamphletSubpage)?.isUpdates == true } - internal func indexPathIsReportProject(_ indexPath: IndexPath) -> Bool { - return (self[indexPath] as? ProjectPamphletSubpage)?.isReportProject == true - } - internal func isExpandedValuesForFAQsSection() -> [Bool]? { guard let values = self[section: Section.faqs.rawValue] as? [(ProjectFAQ, Bool)] else { return nil } return values.map { _, isExpanded in isExpanded } diff --git a/Kickstarter-iOS/Features/ProjectPage/Datasource/Datasource/ProjectPageViewControllerDataSourceTests.swift b/Kickstarter-iOS/Features/ProjectPage/Datasource/Datasource/ProjectPageViewControllerDataSourceTests.swift index fbc73c768c..f9bc34c910 100644 --- a/Kickstarter-iOS/Features/ProjectPage/Datasource/Datasource/ProjectPageViewControllerDataSourceTests.swift +++ b/Kickstarter-iOS/Features/ProjectPage/Datasource/Datasource/ProjectPageViewControllerDataSourceTests.swift @@ -96,6 +96,8 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { .rawValue private let overviewSection = ProjectPageViewControllerDataSource.Section.overview.rawValue private let overviewSubpagesSection = ProjectPageViewControllerDataSource.Section.overviewSubpages.rawValue + private let overviewReportProject = ProjectPageViewControllerDataSource.Section.overviewReportProject + .rawValue private let faqsHeaderSection = ProjectPageViewControllerDataSource.Section.faqsHeader.rawValue private let faqsEmptySection = ProjectPageViewControllerDataSource.Section.faqsEmpty.rawValue private let faqsSection = ProjectPageViewControllerDataSource.Section.faqs.rawValue @@ -144,7 +146,7 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { refTag: nil, isExpandedStates: [false, false, false, false] ) - XCTAssertEqual(9, self.dataSource.numberOfSections(in: self.tableView)) + XCTAssertEqual(10, self.dataSource.numberOfSections(in: self.tableView)) // faqsHeader XCTAssertEqual( @@ -200,7 +202,7 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { refTag: nil, isExpandedStates: [false, false, false, false] ) - XCTAssertEqual(8, self.dataSource.numberOfSections(in: self.tableView)) + XCTAssertEqual(9, self.dataSource.numberOfSections(in: self.tableView)) // faqsHeader XCTAssertEqual( @@ -245,7 +247,7 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { project: project, refTag: nil ) - XCTAssertEqual(9, self.dataSource.numberOfSections(in: self.tableView)) + XCTAssertEqual(10, self.dataSource.numberOfSections(in: self.tableView)) // faqsHeader XCTAssertEqual( @@ -300,7 +302,7 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { project: project, refTag: nil ) - XCTAssertEqual(7, self.dataSource.numberOfSections(in: self.tableView)) + XCTAssertEqual(8, self.dataSource.numberOfSections(in: self.tableView)) // faqsHeader XCTAssertEqual( @@ -343,7 +345,7 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { refTag: nil, isExpandedStates: nil ) - XCTAssertEqual(12, self.dataSource.numberOfSections(in: self.tableView)) + XCTAssertEqual(13, self.dataSource.numberOfSections(in: self.tableView)) // risksHeader XCTAssertEqual( @@ -396,7 +398,7 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { refTag: nil, isExpandedStates: nil ) - XCTAssertEqual(17, self.dataSource.numberOfSections(in: self.tableView)) + XCTAssertEqual(18, self.dataSource.numberOfSections(in: self.tableView)) XCTAssertEqual( 1, @@ -477,7 +479,7 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { refTag: nil, isExpandedStates: nil ) - XCTAssertEqual(17, self.dataSource.numberOfSections(in: self.tableView)) + XCTAssertEqual(18, self.dataSource.numberOfSections(in: self.tableView)) XCTAssertEqual( 1, @@ -551,7 +553,7 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { refTag: nil, isExpandedStates: nil ) - XCTAssertEqual(20, self.dataSource.numberOfSections(in: self.tableView)) + XCTAssertEqual(21, self.dataSource.numberOfSections(in: self.tableView)) // environmentCommitmentsHeader XCTAssertEqual( @@ -607,7 +609,7 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { refTag: nil, isExpandedStates: nil ) - XCTAssertEqual(20, self.dataSource.numberOfSections(in: self.tableView)) + XCTAssertEqual(21, self.dataSource.numberOfSections(in: self.tableView)) // environmentCommitmentsHeader XCTAssertEqual( @@ -659,7 +661,7 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { refTag: nil, isExpandedStates: nil ) - XCTAssertEqual(5, self.dataSource.numberOfSections(in: self.tableView)) + XCTAssertEqual(6, self.dataSource.numberOfSections(in: self.tableView)) // campaign header section XCTAssertEqual( @@ -715,7 +717,7 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { refTag: nil, isExpandedStates: nil ) - XCTAssertEqual(3, self.dataSource.numberOfSections(in: self.tableView)) + XCTAssertEqual(4, self.dataSource.numberOfSections(in: self.tableView)) // overviewCreatorHeader XCTAssertEqual( @@ -731,10 +733,15 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { // overviewSubpages XCTAssertEqual( - 3, + 2, self.dataSource.tableView(self.tableView, numberOfRowsInSection: self.overviewSubpagesSection) ) + XCTAssertEqual( + 1, + self.dataSource.tableView(self.tableView, numberOfRowsInSection: self.overviewReportProject) + ) + XCTAssertEqual( "ProjectPamphletMainCell", self.dataSource.reusableId(item: 0, section: self.overviewSection) @@ -743,6 +750,11 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { "ProjectPamphletSubpageCell", self.dataSource.reusableId(item: 0, section: self.overviewSubpagesSection) ) + + XCTAssertEqual( + "ReportProjectCell", + self.dataSource.reusableId(item: 0, section: self.overviewReportProject) + ) } } @@ -818,31 +830,6 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase { ) } - func testIndexPathIsReportProjectSubpage() { - let project = Project.template - |> \.displayPrelaunch .~ false - |> \.extendedProjectProperties .~ ExtendedProjectProperties( - environmentalCommitments: [], - faqs: [], - aiDisclosure: nil, - risks: "", - story: self.storyViewableElements, - minimumPledgeAmount: 1 - ) - - self.dataSource.load( - navigationSection: .overview, - project: project, - refTag: nil - ) - - XCTAssertEqual( - self.dataSource - .indexPathIsReportProject(IndexPath(row: 2, section: self.overviewSubpagesSection)), - true - ) - } - func testUpdatingCampaign_WithImageViewElementImage_Success() { let project = Project.template |> \.extendedProjectProperties .~ ExtendedProjectProperties( diff --git a/Kickstarter-iOS/Features/ProjectPage/Views/Cells/ReportProjectCell.swift b/Kickstarter-iOS/Features/ProjectPage/Views/Cells/ReportProjectCell.swift new file mode 100644 index 0000000000..670628a09c --- /dev/null +++ b/Kickstarter-iOS/Features/ProjectPage/Views/Cells/ReportProjectCell.swift @@ -0,0 +1,61 @@ +import Library +import Prelude +import SwiftUI +import UIKit + +internal final class ReportProjectCell: UITableViewCell, ValueCell { + internal func configureWith(value projectFlagged: Bool) { + self.setupTableViewCellStyle(projectFlagged: projectFlagged) + self.setupReportProjectLabelView(projectFlagged: projectFlagged) + } + + internal override func layoutSubviews() { + super.layoutSubviews() + } + + internal override func bindStyles() { + super.bindStyles() + + self.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: .greatestFiniteMagnitude) + self.setNeedsLayout() + } + + // MARK: - Private Methods + + private func setupTableViewCellStyle(projectFlagged: Bool) { + let accessibilityTraits = projectFlagged + ? UIAccessibilityTraits.staticText + : UIAccessibilityTraits.button + + _ = self + |> baseTableViewCellStyle() + |> ReportProjectCell.lens.accessibilityTraits .~ accessibilityTraits + } + + private func setupReportProjectLabelView(projectFlagged: Bool) { + if #available(iOS 15.0, *) { + DispatchQueue.main.async { + let hostingController = + UIHostingController(rootView: ReportProjectLabelView(flagged: projectFlagged)) + + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + hostingController.view.backgroundColor = .clear + + self.contentView.addSubview(hostingController.view) + + let leftRightInset = self.traitCollection.isRegularRegular ? Styles.grid(16) : Styles.gridHalf(5) + + NSLayoutConstraint.activate([ + hostingController.view.topAnchor + .constraint(equalTo: self.contentView.topAnchor, constant: Styles.gridHalf(5)), + hostingController.view.bottomAnchor + .constraint(equalTo: self.contentView.bottomAnchor, constant: -Styles.gridHalf(5)), + hostingController.view.leadingAnchor + .constraint(equalTo: self.contentView.leadingAnchor, constant: leftRightInset), + hostingController.view.trailingAnchor + .constraint(equalTo: self.contentView.trailingAnchor, constant: -leftRightInset) + ]) + } + } + } +} diff --git a/Kickstarter-iOS/Features/ProjectPage/Views/ReportProjectLabelView.swift b/Kickstarter-iOS/Features/ProjectPage/Views/ReportProjectLabelView.swift new file mode 100644 index 0000000000..bce15280b3 --- /dev/null +++ b/Kickstarter-iOS/Features/ProjectPage/Views/ReportProjectLabelView.swift @@ -0,0 +1,67 @@ +import Library +import SwiftUI + +@available(iOS 15.0, *) +struct ReportProjectLabelView: View { + let flagged: Bool + + @SwiftUI.Environment(\.horizontalSizeClass) private var horizontalSizeClass + + var body: some View { + if flagged { + AlreadyReportedView() + } else { + HStack { + Text(Strings.Report_this_project_to()) + .font(Font(UIFont.ksr_body(size: 14))) + .foregroundColor(Color(.ksr_support_700)) + + Spacer() + + Image("chevron-right") + .resizable() + .scaledToFit() + .frame(width: 10, height: 10) + } + .padding(self.horizontalSizeClass == .regular ? 0 : 10) + } + } + + private struct AlreadyReportedView: View { + var body: some View { + HStack(alignment: .top, spacing: 10) { + Image("info") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .foregroundColor(Color(.ksr_support_500)) + + Text( + html: Strings.It_looks( + our_rules: HelpType.prohibitedItems + .url(withBaseUrl: AppEnvironment.current.apiService.serverConfig.webBaseUrl)? + .absoluteString ?? "", + community_guidelines: HelpType.community + .url(withBaseUrl: AppEnvironment.current.apiService.serverConfig.webBaseUrl)? + .absoluteString ?? "" + ), + with: [ + ReportProjectHyperLinkType.ourRules.stringLiteral(), + ReportProjectHyperLinkType.communityGuidelines.stringLiteral() + ] + ) + .font(Font(UIFont.ksr_caption1())) + } + .padding() + .background(Color(.ksr_support_100)) + .cornerRadius(15) + } + } +} + +@available(iOS 15.0, *) +struct ReportProjectView_Previews: PreviewProvider { + static var previews: some View { + ReportProjectLabelView(flagged: false) + } +} diff --git a/Kickstarter-iOS/Features/ReportProject/ReportProjectInfoView.swift b/Kickstarter-iOS/Features/ReportProject/ReportProjectInfoView.swift index 0e32e3952a..00edec0a1c 100644 --- a/Kickstarter-iOS/Features/ReportProject/ReportProjectInfoView.swift +++ b/Kickstarter-iOS/Features/ReportProject/ReportProjectInfoView.swift @@ -5,6 +5,7 @@ import SwiftUI enum ReportProjectHyperLinkType: String, CaseIterable { case prohibitedItems case communityGuidelines + case ourRules func stringLiteral() -> String { switch self { @@ -12,6 +13,8 @@ enum ReportProjectHyperLinkType: String, CaseIterable { return Strings.Prohibited_items() case .communityGuidelines: return "community guidelines" + case .ourRules: + return "our rules" } } } @@ -65,7 +68,7 @@ private struct BaseRowView: View { .frame(maxWidth: .infinity, alignment: .leading) if let hyperLink = hyperLink(in: item.subtitle) { - Text(html: item.subtitle, with: hyperLink.stringLiteral()) + Text(html: item.subtitle, with: [hyperLink.stringLiteral()]) .font(item.type == .parent ? Font(UIFont.ksr_subhead()) : Font(UIFont.ksr_footnote())) .frame(maxWidth: .infinity, alignment: .leading) .multilineTextAlignment(.leading) diff --git a/Kickstarter.xcodeproj/project.pbxproj b/Kickstarter.xcodeproj/project.pbxproj index bf351f474b..1cd586388e 100644 --- a/Kickstarter.xcodeproj/project.pbxproj +++ b/Kickstarter.xcodeproj/project.pbxproj @@ -505,6 +505,8 @@ 609309912A60555F004297AF /* TriggerThirdPartyEventInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 609309902A60555F004297AF /* TriggerThirdPartyEventInput.swift */; }; 609309952A6055A5004297AF /* TriggerThirdPartyEvent.graphql in Resources */ = {isa = PBXBuildFile; fileRef = 609309942A6055A5004297AF /* TriggerThirdPartyEvent.graphql */; }; 60AE9F062ABB897900FB3A96 /* ReportProjectInfoListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60AE9F022ABB822300FB3A96 /* ReportProjectInfoListItem.swift */; }; + 60C996E42ABCA5E5006BE4F4 /* ReportProjectLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C996E32ABCA5E5006BE4F4 /* ReportProjectLabelView.swift */; }; + 60C996E62ABCC002006BE4F4 /* ReportProjectCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C996E52ABCC002006BE4F4 /* ReportProjectCell.swift */; }; 60C996E82AC1FDD8006BE4F4 /* GraphAPI.CreateFlaggingInput+CreateFlaggingInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C996E72AC1FDD8006BE4F4 /* GraphAPI.CreateFlaggingInput+CreateFlaggingInput.swift */; }; 60C996EC2AC1FF0C006BE4F4 /* CreateFlagging.graphql in Resources */ = {isa = PBXBuildFile; fileRef = 60C996EB2AC1FF0C006BE4F4 /* CreateFlagging.graphql */; }; 60C996EE2AC2030C006BE4F4 /* CreateFlaggingInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C996ED2AC2030C006BE4F4 /* CreateFlaggingInput.swift */; }; @@ -2092,6 +2094,8 @@ 609309922A605563004297AF /* TriggerThirdPartyEventInputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TriggerThirdPartyEventInputTests.swift; sourceTree = ""; }; 609309942A6055A5004297AF /* TriggerThirdPartyEvent.graphql */ = {isa = PBXFileReference; lastKnownFileType = text; path = TriggerThirdPartyEvent.graphql; sourceTree = ""; }; 60AE9F022ABB822300FB3A96 /* ReportProjectInfoListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportProjectInfoListItem.swift; sourceTree = ""; }; + 60C996E32ABCA5E5006BE4F4 /* ReportProjectLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportProjectLabelView.swift; sourceTree = ""; }; + 60C996E52ABCC002006BE4F4 /* ReportProjectCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportProjectCell.swift; sourceTree = ""; }; 60C996E72AC1FDD8006BE4F4 /* GraphAPI.CreateFlaggingInput+CreateFlaggingInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GraphAPI.CreateFlaggingInput+CreateFlaggingInput.swift"; sourceTree = ""; }; 60C996E92AC1FDE3006BE4F4 /* GraphAPI.CreateFlaggingInput+CreateFlaggingInputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GraphAPI.CreateFlaggingInput+CreateFlaggingInputTests.swift"; sourceTree = ""; }; 60C996EB2AC1FF0C006BE4F4 /* CreateFlagging.graphql */ = {isa = PBXFileReference; lastKnownFileType = text; path = CreateFlagging.graphql; sourceTree = ""; }; @@ -4646,6 +4650,7 @@ 06E3296A270E39B300216306 /* ProjectPageNavigationBarView.swift */, 06AF78772710DB57009587F1 /* ProjectPageNavigationBarViewTests.swift */, 477239742710FA1900D26CDA /* ProjectNavigationSelectorView.swift */, + 60C996E32ABCA5E5006BE4F4 /* ReportProjectLabelView.swift */, 1965437928C814D300457EC6 /* Cells */, ); path = Views; @@ -4668,6 +4673,7 @@ D668509E236B38E000EE9AC2 /* ProjectPamphletCreatorHeaderCell.swift */, A7CA8BB61D8F14260086A3E9 /* ProjectPamphletMainCell.swift */, A79F52FF1D8F50CE00C051B8 /* ProjectPamphletSubpageCell.swift */, + 60C996E52ABCC002006BE4F4 /* ReportProjectCell.swift */, 191E601E2A93F318001413B2 /* ProjectTabAIGenerationCell.swift */, 473DE013273C551C0033331D /* ProjectRisksDisclaimerCell.swift */, 473DE00F273C4BA90033331D /* ProjectRisksCell.swift */, @@ -8398,6 +8404,7 @@ D63BBD392180BE5D007E01F0 /* PaymentMethodsFooterView.swift in Sources */, 774F8D5B22B1B0B300A1ACD5 /* FeatureFlagToolsViewController.swift in Sources */, 7754A0AE215A8361003AA36D /* ChangePasswordViewController.swift in Sources */, + 60C996E62ABCC002006BE4F4 /* ReportProjectCell.swift in Sources */, 608E7A5328ABDBAE00289E92 /* SetYourPasswordViewController.swift in Sources */, A72C3AB71D00FB1F0075227E /* DiscoveryExpandedSelectableRow.swift in Sources */, 063D2D0A2846767F00CEDE33 /* PledgeLocalPickupView.swift in Sources */, @@ -8451,6 +8458,7 @@ 4751A675272B317500F81DD5 /* ProjectTabCategoryDescriptionCell.swift in Sources */, 59D1E6261D1865AC00896A4C /* DashboardVideoCell.swift in Sources */, 06EB4E3127B5D32000D8BFCC /* PinchToZoom.swift in Sources */, + 60C996E42ABCA5E5006BE4F4 /* ReportProjectLabelView.swift in Sources */, A75CBDE81C8A26F800758C55 /* AppDelegateViewModel.swift in Sources */, A72C3AA71D00F7A30075227E /* SortPagerViewController.swift in Sources */, D79F0F6F2102944500D3B32C /* SettingsPrivacyRequestDataCell.swift in Sources */, diff --git a/Library/HelpType.swift b/Library/HelpType.swift index aaa9971335..c0cac70d74 100644 --- a/Library/HelpType.swift +++ b/Library/HelpType.swift @@ -101,7 +101,7 @@ public enum HelpType: SettingsCellTypeProtocol, CaseIterable, Equatable { case .aiDisclosure: return baseUrl.appendingPathComponent("hc/en-us/articles/16848396410267") case .prohibitedItems: - return baseUrl.appendingPathComponent("rules/prohibited?ref=project-page-report") + return baseUrl.appendingPathComponent("rules/prohibited") } } } diff --git a/Library/SwiftUI+Extensions/Text+HTML.swift b/Library/SwiftUI+Extensions/Text+HTML.swift index 2d83b1722c..e99c126ecd 100644 --- a/Library/SwiftUI+Extensions/Text+HTML.swift +++ b/Library/SwiftUI+Extensions/Text+HTML.swift @@ -4,13 +4,15 @@ import SwiftUI extension Text { /// Allows Text to be initialized with a string that has html. Option to sepcify a portion of the string that should be a hyperlink. @available(iOS 15, *) - init(html: String, with hyperlink: String) { + init(html: String, with hyperlinks: [String]) { do { var attrString = try html.htmlToAttributedString() attrString.font = .ksr_subhead() - if let range = attrString.range(of: hyperlink, options: .caseInsensitive) { - attrString[range].foregroundColor = .green + for hyperlink in hyperlinks { + if let range = attrString.range(of: hyperlink, options: .caseInsensitive) { + attrString[range].foregroundColor = .green + } } self.init(attrString) diff --git a/Library/ViewModels/ProjectPageViewModel.swift b/Library/ViewModels/ProjectPageViewModel.swift index 1461e5f1ca..d8b6dd7a52 100644 --- a/Library/ViewModels/ProjectPageViewModel.swift +++ b/Library/ViewModels/ProjectPageViewModel.swift @@ -102,8 +102,8 @@ public protocol ProjectPageViewModelOutputs { /// Emits a `Project` when the updates are to be rendered. var goToUpdates: Signal { get } - /// Emits a project URL `String` when the report project view is to be rendered. - var goToReportProject: Signal { get } + /// Emits a `Bool` to show if the project has been flagged and a project URL `String` when the report project view is to be rendered. + var goToReportProject: Signal<(Bool, String), Never> { get } /// Emits a project and refTag to be used to navigate to the reward selection screen. var goToRewards: Signal<(Project, RefTag?), Never> { get } @@ -357,7 +357,7 @@ public final class ProjectPageViewModel: ProjectPageViewModelType, ProjectPageVi .takeWhen(self.tappedUpdatesProperty.signal) self.goToReportProject = project.signal - .map { $0.urls.web.project } + .map { ($0.flagging ?? false, $0.urls.web.project) } .takeWhen(self.tappedReportProjectProperty.signal) // Hide the custom navigation bar when pushing a new view controller @@ -632,7 +632,7 @@ public final class ProjectPageViewModel: ProjectPageViewModelType, ProjectPageVi public let goToManagePledge: Signal public let goToRewards: Signal<(Project, RefTag?), Never> public let goToUpdates: Signal - public let goToReportProject: Signal + public let goToReportProject: Signal<(Bool, String), Never> public let goToURL: Signal public let navigationBarIsHidden: Signal public let pauseMedia: Signal diff --git a/Library/ViewModels/ProjectPageViewModelTests.swift b/Library/ViewModels/ProjectPageViewModelTests.swift index 571548a48c..a66ef9355c 100644 --- a/Library/ViewModels/ProjectPageViewModelTests.swift +++ b/Library/ViewModels/ProjectPageViewModelTests.swift @@ -38,7 +38,7 @@ final class ProjectPageViewModelTests: TestCase { private let goToDashboard = TestObserver() private let goToManagePledgeProjectParam = TestObserver() private let goToManagePledgeBackingParam = TestObserver() - private let goToReportProject = TestObserver() + private let goToReportProject = TestObserver<(Bool, String), Never>() private let goToRewardsProject = TestObserver() private let goToRewardsRefTag = TestObserver() private let goToUpdates = TestObserver() @@ -765,7 +765,8 @@ final class ProjectPageViewModelTests: TestCase { self.vm.inputs.tappedReportProject() - self.goToReportProject.assertValues([project.urls.web.project]) + XCTAssertEqual(self.goToReportProject.lastValue?.0, false) + XCTAssertEqual(self.goToReportProject.lastValue?.1, project.urls.web.project) } func testGoToDashboard() { diff --git a/Library/ViewModels/ProjectPamphletSubpageCellViewModel.swift b/Library/ViewModels/ProjectPamphletSubpageCellViewModel.swift index 537a95993d..e671f7598d 100644 --- a/Library/ViewModels/ProjectPamphletSubpageCellViewModel.swift +++ b/Library/ViewModels/ProjectPamphletSubpageCellViewModel.swift @@ -44,18 +44,15 @@ public final class ProjectPamphletSubpageCellViewModel: ProjectPamphletSubpageCe public init() { let commentsSubpage = self.subpageProperty.signal.skipNil().filter { $0.isComments } let updatesSubpage = self.subpageProperty.signal.skipNil().filter { $0.isUpdates } - let reportProjectSubpage = self.subpageProperty.signal.skipNil().filter { $0.isReportProject } self.labelText = Signal.merge( commentsSubpage.mapConst(Strings.project_menu_buttons_comments()), - updatesSubpage.mapConst(Strings.project_menu_buttons_updates()), - reportProjectSubpage.mapConst(Strings.Report_this_project_to()) + updatesSubpage.mapConst(Strings.project_menu_buttons_updates()) ) self.labelTextColor = Signal.merge( commentsSubpage.mapConst(.ksr_support_700), - updatesSubpage.mapConst(.ksr_support_700), - reportProjectSubpage.mapConst(.ksr_support_700) + updatesSubpage.mapConst(.ksr_support_700) ) self.topSeparatorViewHidden = self.subpageProperty.signal.skipNil() @@ -68,7 +65,7 @@ public final class ProjectPamphletSubpageCellViewModel: ProjectPamphletSubpageCe .map { Format.wholeNumber($0.count ?? 0) } self.countLabelTextColor = Signal.merge(commentsSubpage, updatesSubpage).mapConst(.ksr_support_700) - self.countLabelBorderColor = Signal.merge(commentsSubpage, updatesSubpage, reportProjectSubpage) + self.countLabelBorderColor = Signal.merge(commentsSubpage, updatesSubpage) .mapConst(.clear) self.countLabelBackgroundColor = Signal.merge(commentsSubpage, updatesSubpage).mapConst(.ksr_support_100) }