Skip to content

Commit

Permalink
[MBL-970] Report Project Info View (#1852)
Browse files Browse the repository at this point in the history
* add new "Report This Project" row to Project overviewSubpages tableview section

* navigate to ReportThisProjectInfoView

* formatting

* create ReportProjectInfoView

* displays different reporting categories in a nested dropdown style list
* some text needs to contain hyperlinks so I added helpers to make this easier.

* create ReportProjectInfoView

* displays different reporting categories in a nested dropdown style list
* some text needs to contain hyperlinks so I added helpers to make this easier.

* formatting and cleanup

* make Text extension more generic

* tests

* formatting

* add goToReportProject output test

* format

* only pass project url to ReportProjectInfoView

* revert .pbxproj change

* move model into KsApi target and replace hardcoded strings

* refactor List view

* List view has a bug in iOS 16 that causes expandable rows to have poor animations. My solution uses a ScrollView and a manual Expanded property instead
* Also addresses the double navigation bar issues

* formatting
  • Loading branch information
scottkicks committed Sep 23, 2023
1 parent e0657bf commit ae5ef71
Show file tree
Hide file tree
Showing 190 changed files with 417 additions and 7 deletions.
Expand Up @@ -3,6 +3,7 @@ import Combine
import KsApi
import Library
import Prelude
import SwiftUI
import UIKit

public enum ProjectPageViewControllerStyles {
Expand Down Expand Up @@ -379,6 +380,12 @@ public final class ProjectPageViewController: UIViewController, MessageBannerVie
self?.goToDashboard(param: param)
}

self.viewModel.outputs.goToReportProject
.observeForControllerAction()
.observeValues { [weak self] in
self?.goToReportProject(projectUrl: $0)
}

self.viewModel.outputs.goToUpdates
.observeForControllerAction()
.observeValues { [weak self] in
Expand Down Expand Up @@ -650,6 +657,15 @@ public final class ProjectPageViewController: UIViewController, MessageBannerVie
}
}

private func goToReportProject(projectUrl: String) {
if #available(iOS 15, *) {
let reportProjectInfoView = ReportProjectInfoView(projectUrl: projectUrl)
self.viewModel.inputs.showNavigationBar(false)
self.navigationController?
.pushViewController(UIHostingController(rootView: reportProjectInfoView), animated: true)
}
}

private func goToDashboard(param: Param) {
self.view.window?.rootViewController
.flatMap { $0 as? RootTabBarViewController }
Expand Down Expand Up @@ -813,6 +829,8 @@ 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.faqsAskAQuestion.rawValue:
self.viewModel.inputs.askAQuestionCellTapped()
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Expand Up @@ -104,7 +104,8 @@ internal final class ProjectPageViewControllerDataSource: ValueCellDataSource {

let values: [ProjectPamphletSubpage] = [
.comments(project.stats.commentsCount as Int?, .first),
.updates(project.stats.updatesCount as Int?, .last)
.updates(project.stats.updatesCount as Int?, .middle),
.reportProject(.last)
]

self.set(
Expand Down Expand Up @@ -448,6 +449,10 @@ 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 }
Expand Down
Expand Up @@ -731,7 +731,7 @@ final class ProjectPageViewControllerDataSourceTests: XCTestCase {

// overviewSubpages
XCTAssertEqual(
2,
3,
self.dataSource.tableView(self.tableView, numberOfRowsInSection: self.overviewSubpagesSection)
)

Expand Down Expand Up @@ -818,6 +818,31 @@ 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(
Expand Down
152 changes: 152 additions & 0 deletions Kickstarter-iOS/Features/ReportProject/ReportProjectInfoView.swift
@@ -0,0 +1,152 @@
import KsApi
import Library
import SwiftUI

enum ReportProjectHyperLinkType: String, CaseIterable {
case prohibitedItems
case communityGuidelines

func stringLiteral() -> String {
switch self {
case .prohibitedItems:
return Strings.Prohibited_items()
case .communityGuidelines:
return "community guidelines"
}
}
}

@available(iOS 15, *)
struct ReportProjectInfoView: View {
let projectUrl: String

@State private var selection: Set<ReportProjectInfoListItem> = []

var body: some View {
ScrollView {
ForEach(listItems) { item in
RowView(item: item, isExpanded: self.selection.contains(item))
.modifier(ListRowModifier())
.onTapGesture {
withAnimation {
self.selectDeselect(item)
}
}
.padding(5)
.animation(.linear(duration: 0.3))
}
}
.navigationTitle(Strings.Report_this_project())
.navigationBarTitleDisplayMode(.inline)
}

private func selectDeselect(_ item: ReportProjectInfoListItem) {
if self.selection.contains(item) {
self.selection.remove(item)
} else {
self.selection.insert(item)
}
}
}

// MARK: - Views

@available(iOS 15, *)
private struct BaseRowView: View {
var item: ReportProjectInfoListItem
var isExpanded: Bool = false

var body: some View {
HStack {
VStack(spacing: 5) {
Text(item.title)
.font(item.type == .parent ? Font(UIFont.ksr_body()) : Font(UIFont.ksr_callout()))
.bold()
.frame(maxWidth: .infinity, alignment: .leading)

if let hyperLink = hyperLink(in: item.subtitle) {
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)
} else {
Text(item.subtitle)
.font(item.type == .parent ? Font(UIFont.ksr_subhead()) : Font(UIFont.ksr_footnote()))
.frame(maxWidth: .infinity, alignment: .leading)
.multilineTextAlignment(.leading)
}
}

Spacer()

Image(isExpanded ? "arrow-down" : "chevron-right")
.resizable()
.scaledToFit()
.frame(width: 15, height: 15)
.foregroundColor(item.type == .parent ? Color(.ksr_create_700) : Color(.ksr_support_400))
}
}
}

@available(iOS 15, *)
struct RowView: View {
var item: ReportProjectInfoListItem
let isExpanded: Bool

private let contentSpacing = 10.0
private let contentPadding = 12.0

var body: some View {
HStack {
VStack(alignment: .leading) {
BaseRowView(item: item, isExpanded: isExpanded)

if isExpanded {
ForEach(item.subItems ?? []) { item in
VStack(alignment: .leading, spacing: contentSpacing) {
// TODO: Push Submission Form View In MBL-971(https://kickstarter.atlassian.net/browse/MBL-971)
NavigationLink(destination: { Text("submit report view") }, label: { BaseRowView(item: item) })
.buttonStyle(PlainButtonStyle())
}
.padding(.vertical, 5)
.padding(.leading, self.contentPadding)
}
}
}
}
.padding(.trailing, 30)
}
}

// MARK: - Private Methods

/// Returns a ReportProjectHyperLinkType if the given string contains a type's string literal
private func hyperLink(in string: String) -> ReportProjectHyperLinkType? {
for linkType in ReportProjectHyperLinkType.allCases {
if string.lowercased().contains(linkType.stringLiteral().lowercased()) {
return linkType
}
}

return nil
}

// MARK: - Modifiers

struct ListRowModifier: ViewModifier {
func body(content: Content) -> some View {
Group {
content
Divider()
}.offset(x: 20)
}
}

// MARK: - Preview

@available(iOS 15, *)
struct ReportProjectInfoView_Previews: PreviewProvider {
static var previews: some View {
ReportProjectInfoView(projectUrl: "")
}
}

0 comments on commit ae5ef71

Please sign in to comment.