Skip to content

Commit

Permalink
MBL-1456: Stub PPO view and container
Browse files Browse the repository at this point in the history
  • Loading branch information
amy-at-kickstarter committed Jun 20, 2024
1 parent 87add1a commit 0ba8143
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation
import Library
import SwiftUI

public class PPOContainerViewController: PagedContainerViewController {
public override func viewDidLoad() {
super.viewDidLoad()

// TODO: Translate these strings
self.title = "Activity"
let ppoViewController = UIHostingController(rootView: PPOView())
ppoViewController.title = "Project Alerts"

let activitiesViewController = ActivitiesViewController.instantiate()
activitiesViewController.title = "Activity Feed"

self.setPagedViewControllers([ppoViewController, activitiesViewController])
}
}
12 changes: 12 additions & 0 deletions Kickstarter-iOS/Features/PostPledgeOverview/PPOView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import SwiftUI

struct PPOView: View {
@StateObject private var viewModel = PPOViewModel()
var body: some View {
Text(self.viewModel.greeting)
}
}

#Preview {
PPOView()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Combine
import Foundation

public class PPOViewModel: ObservableObject {
let greeting = "Hello, PPO"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation
@testable import Kickstarter_Framework
import XCTest

class PPOViewModelTests: XCTestCase {
func test_stub() {
let vm = PPOViewModel()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ public final class RootTabBarViewController: UITabBarController, MessageBannerVi
return DiscoveryViewController.instantiate()
case .activities:
return ActivitiesViewController.instantiate()
case .pledgedProjectsAndActivities:
return PPOContainerViewController.instantiate()
case .search:
return SearchViewController.instantiate()
case let .profile(isLoggedIn):
Expand Down
40 changes: 40 additions & 0 deletions Kickstarter.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1520,6 +1520,8 @@
E135005C2C07ABA600A30161 /* PledgePaymentMethodsAndSelectionData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E135005B2C07ABA600A30161 /* PledgePaymentMethodsAndSelectionData.swift */; };
E16794282B7EAA5200064063 /* OAuthTokenExchange.swift in Sources */ = {isa = PBXBuildFile; fileRef = E16794272B7EAA5200064063 /* OAuthTokenExchange.swift */; };
E167942A2B85136900064063 /* OAuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E16794292B85136900064063 /* OAuthTests.swift */; };
E16ECA702C245A34002A1D25 /* PagedContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E16ECA6F2C245A34002A1D25 /* PagedContainerViewController.swift */; };
E16ECA722C245A51002A1D25 /* PagedContainerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E16ECA712C245A51002A1D25 /* PagedContainerViewModel.swift */; };
E170B9112B20E83B001BEDD7 /* MockGraphQLClient+CombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E170B9102B20E83B001BEDD7 /* MockGraphQLClient+CombineTests.swift */; };
E17611E02B7287CF00DF2F50 /* PaginationExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11CFE4E2B7162A400497375 /* PaginationExampleView.swift */; };
E17611E22B73D9A400DF2F50 /* Data+PKCE.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17611E12B73D9A400DF2F50 /* Data+PKCE.swift */; };
Expand All @@ -1539,6 +1541,10 @@
E1B813C52BC8340700DF33CF /* FetchMySavedProjectsQuery.graphql in Resources */ = {isa = PBXBuildFile; fileRef = E1B813C42BC8340700DF33CF /* FetchMySavedProjectsQuery.graphql */; };
E1B813C72BC851CB00DF33CF /* FetchMyBackedProjectsQueryRequestForTests.graphql_test in Resources */ = {isa = PBXBuildFile; fileRef = E1B813C62BC851CB00DF33CF /* FetchMyBackedProjectsQueryRequestForTests.graphql_test */; };
E1B813C92BC851E100DF33CF /* FetchMyBackedProjectsQuery.json in Resources */ = {isa = PBXBuildFile; fileRef = E1B813C82BC851E100DF33CF /* FetchMyBackedProjectsQuery.json */; };
E1BAA03A2C1A1CCD004F8B06 /* PPOViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BAA0362C1A1B13004F8B06 /* PPOViewModel.swift */; };
E1BAA03B2C1A1CCD004F8B06 /* PPOView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BAA0342C1A1907004F8B06 /* PPOView.swift */; };
E1BAA03C2C1A1CCD004F8B06 /* PPOContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BAA0382C1A1C56004F8B06 /* PPOContainerViewController.swift */; };
E1BAA03E2C1A1CE4004F8B06 /* PPOViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BAA03D2C1A1CE4004F8B06 /* PPOViewModelTests.swift */; };
E1BB25642B1E81AA000BD2D6 /* Publisher+Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BB25632B1E81AA000BD2D6 /* Publisher+Service.swift */; };
E1C880AF2BBC6CDA008B9612 /* GraphQLSelectionSet+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C880AE2BBC6CDA008B9612 /* GraphQLSelectionSet+String.swift */; };
E1EEED292B684AA7009976D9 /* PKCE.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EEED282B684AA7009976D9 /* PKCE.swift */; };
Expand Down Expand Up @@ -3151,6 +3157,8 @@
E135005B2C07ABA600A30161 /* PledgePaymentMethodsAndSelectionData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PledgePaymentMethodsAndSelectionData.swift; sourceTree = "<group>"; };
E16794272B7EAA5200064063 /* OAuthTokenExchange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenExchange.swift; sourceTree = "<group>"; };
E16794292B85136900064063 /* OAuthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTests.swift; sourceTree = "<group>"; };
E16ECA6F2C245A34002A1D25 /* PagedContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedContainerViewController.swift; sourceTree = "<group>"; };
E16ECA712C245A51002A1D25 /* PagedContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedContainerViewModel.swift; sourceTree = "<group>"; };
E170B9102B20E83B001BEDD7 /* MockGraphQLClient+CombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockGraphQLClient+CombineTests.swift"; sourceTree = "<group>"; };
E17611E12B73D9A400DF2F50 /* Data+PKCE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+PKCE.swift"; sourceTree = "<group>"; };
E17611E32B751E8100DF2F50 /* Paginator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Paginator.swift; sourceTree = "<group>"; };
Expand All @@ -3167,6 +3175,10 @@
E1B813C42BC8340700DF33CF /* FetchMySavedProjectsQuery.graphql */ = {isa = PBXFileReference; lastKnownFileType = text; path = FetchMySavedProjectsQuery.graphql; sourceTree = "<group>"; };
E1B813C62BC851CB00DF33CF /* FetchMyBackedProjectsQueryRequestForTests.graphql_test */ = {isa = PBXFileReference; lastKnownFileType = text; path = FetchMyBackedProjectsQueryRequestForTests.graphql_test; sourceTree = "<group>"; };
E1B813C82BC851E100DF33CF /* FetchMyBackedProjectsQuery.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = FetchMyBackedProjectsQuery.json; sourceTree = "<group>"; };
E1BAA0342C1A1907004F8B06 /* PPOView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PPOView.swift; sourceTree = "<group>"; };
E1BAA0362C1A1B13004F8B06 /* PPOViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PPOViewModel.swift; sourceTree = "<group>"; };
E1BAA0382C1A1C56004F8B06 /* PPOContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PPOContainerViewController.swift; sourceTree = "<group>"; };
E1BAA03D2C1A1CE4004F8B06 /* PPOViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PPOViewModelTests.swift; sourceTree = "<group>"; };
E1BB25632B1E81AA000BD2D6 /* Publisher+Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+Service.swift"; sourceTree = "<group>"; };
E1C880AE2BBC6CDA008B9612 /* GraphQLSelectionSet+String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GraphQLSelectionSet+String.swift"; sourceTree = "<group>"; };
E1EA34EE2AE1B28400942A04 /* Signal+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Signal+Combine.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4768,6 +4780,7 @@
1937A6F028C92F0000DD732D /* PledgeAmountSummary */,
19A97D3928C802230031B857 /* PledgePaymentMethods */,
1937A70428C9392600DD732D /* PledgeShippingLocation */,
E1BAA0332C1A18DA004F8B06 /* PostPledgeOverview */,
1937A70528C9399D00DD732D /* PledgeView */,
1965436E28C810CC00457EC6 /* ProjectNotifications */,
1965437528C812F100457EC6 /* ProjectPamphletContentDataSource_DEPRECATED_09_06_2022 */,
Expand Down Expand Up @@ -6143,6 +6156,7 @@
E11CFE492B6C41B400497375 /* OAuth.swift */,
E16794292B85136900064063 /* OAuthTests.swift */,
94C92E7B2659EDBF00A96818 /* PaddingLabel.swift */,
E16ECA6E2C245A12002A1D25 /* PagedContainer */,
A77D7B061CBAAF5D0077586B /* Paginate.swift */,
A7ED1F1C1E830FDC00BFFA01 /* PaginateTests.swift */,
E17611E32B751E8100DF2F50 /* Paginator.swift */,
Expand Down Expand Up @@ -7049,6 +7063,15 @@
path = PaginationExample;
sourceTree = "<group>";
};
E16ECA6E2C245A12002A1D25 /* PagedContainer */ = {
isa = PBXGroup;
children = (
E16ECA6F2C245A34002A1D25 /* PagedContainerViewController.swift */,
E16ECA712C245A51002A1D25 /* PagedContainerViewModel.swift */,
);
path = PagedContainer;
sourceTree = "<group>";
};
E1A149252ACE060E00F49709 /* templates */ = {
isa = PBXGroup;
children = (
Expand All @@ -7071,6 +7094,17 @@
path = combine;
sourceTree = "<group>";
};
E1BAA0332C1A18DA004F8B06 /* PostPledgeOverview */ = {
isa = PBXGroup;
children = (
E1BAA0382C1A1C56004F8B06 /* PPOContainerViewController.swift */,
E1BAA0342C1A1907004F8B06 /* PPOView.swift */,
E1BAA0362C1A1B13004F8B06 /* PPOViewModel.swift */,
E1BAA03D2C1A1CE4004F8B06 /* PPOViewModelTests.swift */,
);
path = PostPledgeOverview;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
Expand Down Expand Up @@ -7861,6 +7895,7 @@
A7169BF61DDD064200480C0D /* UIScrollView+Extensions.swift in Sources */,
597582E31D5D12AE008765DE /* SettingsStyles.swift in Sources */,
A7F441AF1D005A9400FE6FC5 /* ActivityFriendBackingViewModel.swift in Sources */,
E16ECA702C245A34002A1D25 /* PagedContainerViewController.swift in Sources */,
8AAA9BD822F49EC200F12976 /* UIColor+Mixing.swift in Sources */,
8AC3E13A269F781D00168BF8 /* ErrorEnvelope+LocalizedDescription.swift in Sources */,
D04AAC34218BB70D00CF713E /* SettingsAccountPickerCellViewModel.swift in Sources */,
Expand Down Expand Up @@ -7922,6 +7957,7 @@
9DD1E3881D50035E00D4829E /* ProjectActivityData.swift in Sources */,
7061848D29BE577B008F9941 /* MessageBannerViewViewModel.swift in Sources */,
778215E820F6922100F3D09F /* HelpType.swift in Sources */,
E16ECA722C245A51002A1D25 /* PagedContainerViewModel.swift in Sources */,
01940B261D42DC1A0074FCE3 /* HelpViewModel.swift in Sources */,
D6B6766520FE85010082717D /* SettingsNewslettersCellViewModel.swift in Sources */,
39B5E10E2B86C56600FFB720 /* RefInfo.swift in Sources */,
Expand Down Expand Up @@ -8392,6 +8428,7 @@
0634C2F727CFEE40003A6D6E /* ExternalSourceViewElementCell.swift in Sources */,
47F95ED72672C594001365B2 /* ViewRepliesView.swift in Sources */,
37CA16AD23300376006044F9 /* ManageViewPledgeRewardReceivedViewController.swift in Sources */,
E1BAA03B2C1A1CCD004F8B06 /* PPOView.swift in Sources */,
6049D0242AA7940E0015BB0D /* DemoCTAContainerView.swift in Sources */,
778CCC5222822B5D00FB8D35 /* SheetOverlayTransitionAnimator.swift in Sources */,
D04F48D41E0313FB00EDC98A /* ActivityProjectStatusCell.swift in Sources */,
Expand All @@ -8402,6 +8439,7 @@
94BA168E2667C37F0034CC3F /* CommentTableViewFooterView.swift in Sources */,
37E9E2A0225EABB000D29DD7 /* AmountInputView.swift in Sources */,
A77352ED1D5E70FC0017E239 /* MostPopularCell.swift in Sources */,
E1BAA03C2C1A1CCD004F8B06 /* PPOContainerViewController.swift in Sources */,
D79F0F3721028C2600D3B32C /* SettingsPrivacyRecommendationCell.swift in Sources */,
D6B6766920FF8D850082717D /* SettingsNewslettersDataSource.swift in Sources */,
4751A675272B317500F81DD5 /* ProjectTabCategoryDescriptionCell.swift in Sources */,
Expand All @@ -8413,6 +8451,7 @@
D764370A224040D100DAFC9E /* SharedFunctions.swift in Sources */,
77C5E252214182CA002E1670 /* SettingsPrivacySwitchCell.swift in Sources */,
A7C795B41C873AC90081977F /* DiscoveryViewController.swift in Sources */,
E1BAA03A2C1A1CCD004F8B06 /* PPOViewModel.swift in Sources */,
3767EDAB22CFFED40088E8E4 /* ShippingRuleCell.swift in Sources */,
47C500712696481300BB4BF2 /* CommentViewMoreRepliesFailedCell.swift in Sources */,
8ACB32A824ABC2DB00A03968 /* RewardAddOnSelectionViewController.swift in Sources */,
Expand Down Expand Up @@ -8586,6 +8625,7 @@
A7ED20171E83229E00BFFA01 /* DiscoveryProjectsDataSourceTest.swift in Sources */,
776989AA242E747200AAC48D /* CuratedProjectsViewControllerTests.swift in Sources */,
19A97D1A28C7F0EC0031B857 /* DiscoveryNavigationHeaderViewControllerTests.swift in Sources */,
E1BAA03E2C1A1CE4004F8B06 /* PPOViewModelTests.swift in Sources */,
8A23EF0822F11470001262E1 /* RewardCardContainerViewTests.swift in Sources */,
1965436D28C807FB00457EC6 /* PledgePaymentMethodsViewControllerTests.swift in Sources */,
D764377C224174B700DAFC9E /* SharedFunctionsTests.swift in Sources */,
Expand Down
138 changes: 138 additions & 0 deletions Library/PagedContainer/PagedContainerViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import Combine
import Foundation
import UIKit

open class PagedContainerViewController: UIViewController {
private weak var activeController: UIViewController? = nil
private var subscriptions = Set<AnyCancellable>()
private let viewModel = PagedContainerViewModel()

// TODO: Use the correct page control, per the designs.
// This may exist already in SortPagerViewController, or we can write one in SwiftUI.
private lazy var toggle = UISegmentedControl(
frame: .zero,
actions: []
)

open override func viewDidLoad() {
super.viewDidLoad()

self.view.backgroundColor = .white
self.view.addSubview(self.toggle)
self.toggle.translatesAutoresizingMaskIntoConstraints = false
self.toggle.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
self.toggle.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true

self.viewModel.pageTitles
.sink { [weak self] titles in
self?.configureSegmentedControl(withTitles: titles)
}.store(in: &self.subscriptions)

self.viewModel.displayChildViewControllerAtIndex.receive(on: RunLoop.main)
.sink { [weak self] controller, index in
self?.showChildController(controller, atIndex: index)
}.store(in: &self.subscriptions)
}

open override var shouldAutomaticallyForwardAppearanceMethods: Bool {
return false
}

public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

self.viewModel.viewWillAppear()

if let activeController = self.activeController {
activeController.beginAppearanceTransition(true, animated: animated)
}
}

public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

if let activeController = self.activeController {
activeController.endAppearanceTransition()
}
}

override open func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)

if let activeController = self.activeController {
activeController.beginAppearanceTransition(false, animated: animated)
}
}

open override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)

if let activeController = self.activeController {
activeController.endAppearanceTransition()
}
}

public func setPagedViewControllers(_ controllers: [UIViewController]) {
self.viewModel.configure(withChildren: controllers)
}

private func configureSegmentedControl(withTitles titles: [String]) {
self.toggle.removeAllSegments()

for (idx, title) in titles.enumerated() {
let action = UIAction(
title: title,
handler: { [weak self] _ in self?.viewModel.didSelectPage(atIndex: idx) }
)
self.toggle.insertSegment(action: action, at: idx, animated: false)
}
}

private func showChildController(_ controller: UIViewController, atIndex index: Int) {
if self.toggle.selectedSegmentIndex == UISegmentedControl.noSegment {
self.toggle.selectedSegmentIndex = index
}

if let activeController = self.activeController {
self.stopDisplayingChildViewController(activeController)
}

self.displayChildViewController(controller)
self.activeController = controller
}

func displayChildViewController(_ controller: UIViewController) {
guard let childView = controller.view else {
return
}

controller.beginAppearanceTransition(true, animated: true)

addChild(controller)

self.view.addSubview(childView)

childView.translatesAutoresizingMaskIntoConstraints = false
childView.topAnchor.constraint(equalTo: self.toggle.bottomAnchor).isActive = true
childView.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor).isActive = true
childView.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor).isActive = true
childView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor).isActive = true

controller.didMove(toParent: self)

controller.endAppearanceTransition()
}

func stopDisplayingChildViewController(_ controller: UIViewController) {
controller.beginAppearanceTransition(false, animated: true)

controller.willMove(toParent: nil)
for constraint in controller.view.constraints {
constraint.isActive = false
}
controller.view.removeFromSuperview()
controller.removeFromParent()

controller.endAppearanceTransition()
}
}
48 changes: 48 additions & 0 deletions Library/PagedContainer/PagedContainerViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Combine
import Foundation
import UIKit

public class PagedContainerViewModel {
// Internal
private var subscriptions = Set<AnyCancellable>()

init() {
self.pageTitles = self.configureWithChildrenSubject.map { controllers in
controllers.map { $0.title ?? "Page " }
}.eraseToAnyPublisher()

self.displayChildViewControllerAtIndex = Publishers.CombineLatest(
self.configureWithChildrenSubject,
self.selectedIndex.compactMap { $0 }
)
.map { (controllers: [UIViewController], index: Int) -> (UIViewController, Int)? in
if index < controllers.count {
return (controllers[index], index)
} else {
return nil
}
}.compactMap { $0 }
.eraseToAnyPublisher()
}

// Inputs
func viewWillAppear() {
if self.selectedIndex.value == nil {
self.didSelectPage(atIndex: 0)
}
}

private let configureWithChildrenSubject = CurrentValueSubject<[UIViewController], Never>([])
func configure(withChildren children: [UIViewController]) {
self.configureWithChildrenSubject.send(children)
}

private let selectedIndex = CurrentValueSubject<Int?, Never>(nil)
func didSelectPage(atIndex index: Int) {
self.selectedIndex.send(index)
}

// Outputs
public let displayChildViewControllerAtIndex: AnyPublisher<(UIViewController, Int), Never>
public let pageTitles: AnyPublisher<[String], Never>
}
Loading

0 comments on commit 0ba8143

Please sign in to comment.