main 0caee426f2ca cached
212 files
359.2 KB
104.7k tokens
1 requests
Download .txt
Showing preview only (422K chars total). Download the full file or copy to clipboard to get everything.
Repository: nsoojin/MiniSuperApp-fastcampus
Branch: main
Commit: 0caee426f2ca
Files: 212
Total size: 359.2 KB

Directory structure:
gitextract_mzfutfxp/

├── .gitignore
├── MiniSuperApp/
│   ├── AppDelegate/
│   │   ├── AppComponent.swift
│   │   └── AppDelegate.swift
│   ├── AppHome/
│   │   ├── AppHomeBuilder.swift
│   │   ├── AppHomeInteractor.swift
│   │   ├── AppHomeRouter.swift
│   │   ├── AppHomeViewController.swift
│   │   ├── HomeWidgetModel.swift
│   │   └── Views/
│   │       └── HomeWidgetView.swift
│   ├── AppRoot/
│   │   ├── AppRootBuilder.swift
│   │   ├── AppRootInteractor.swift
│   │   ├── AppRootRouter.swift
│   │   └── RootTabBarController.swift
│   ├── Assets.xcassets/
│   │   ├── AccentColor.colorset/
│   │   │   └── Contents.json
│   │   ├── AppIcon.appiconset/
│   │   │   └── Contents.json
│   │   ├── Contents.json
│   │   └── map_seoul.imageset/
│   │       └── Contents.json
│   ├── Base.lproj/
│   │   └── LaunchScreen.storyboard
│   ├── FinanceHome/
│   │   ├── FinanceHomeBuilder.swift
│   │   ├── FinanceHomeInteractor.swift
│   │   ├── FinanceHomeRouter.swift
│   │   └── FinanceHomeViewController.swift
│   ├── Info.plist
│   ├── ProfileHome/
│   │   ├── ProfileHomeBuilder.swift
│   │   ├── ProfileHomeInteractor.swift
│   │   ├── ProfileHomeRouter.swift
│   │   └── ProfileHomeViewController.swift
│   ├── TransportHome/
│   │   ├── TransportHomeBuilder.swift
│   │   ├── TransportHomeInteractor.swift
│   │   ├── TransportHomeRouter.swift
│   │   ├── TransportHomeViewController.swift
│   │   └── Views/
│   │       ├── RideTypeView.swift
│   │       └── SuperPayView.swift
│   └── Utils/
│       ├── Array+Utils.swift
│       ├── PushModalPresentationController.swift
│       ├── RIBs+Utils.swift
│       ├── UIColor+Super.swift
│       ├── UIColor+Utils.swift
│       ├── UIImage+Utils.swift
│       ├── UITableView+Utils.swift
│       └── UIView+Utils.swift
├── MiniSuperApp.xcodeproj/
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   ├── contents.xcworkspacedata
│   │   └── xcshareddata/
│   │       ├── IDEWorkspaceChecks.plist
│   │       └── swiftpm/
│   │           └── Package.resolved
│   └── xcshareddata/
│       └── xcschemes/
│           └── MiniSuperApp.xcscheme
├── README.md
├── Samples/
│   ├── DefaultsStore/
│   │   └── DefaultsStore.swift
│   ├── Network/
│   │   ├── HTTPMethod.swift
│   │   ├── Network.swift
│   │   └── NetworkError.swift
│   ├── NetworkImp/
│   │   └── NetworkImp.swift
│   ├── RIBsTestSupport/
│   │   ├── RoutingMock.swift
│   │   ├── ViewControllableMock.swift
│   │   └── ViewableRoutingMock.swift
│   ├── TestUtil.swift
│   ├── Topup/
│   │   ├── CardOnFileCell.swift
│   │   ├── CardOnFileViewController.swift
│   │   ├── EnterAmountViewController.swift
│   │   └── Views/
│   │       ├── EnterAmountWidget.swift
│   │       └── SelectedPaymentMethodView.swift
│   └── TopupDependencyMock.swift
└── completed/
    └── MiniSuperApp/
        ├── .gitignore
        ├── AddPaymentMethodIntegrationTests/
        │   └── AddPaymentMethodIntegrationTests.swift
        ├── CX/
        │   ├── .gitignore
        │   ├── Package.swift
        │   ├── README.md
        │   └── Sources/
        │       └── AppHome/
        │           ├── AppHomeBuilder.swift
        │           ├── AppHomeInteractor.swift
        │           ├── AppHomeRouter.swift
        │           ├── AppHomeViewController.swift
        │           ├── HomeWidgetModel.swift
        │           └── Views/
        │               └── HomeWidgetView.swift
        ├── Finance/
        │   ├── .gitignore
        │   ├── .swiftpm/
        │   │   └── xcode/
        │   │       └── xcshareddata/
        │   │           └── xcschemes/
        │   │               ├── TopupImp.xcscheme
        │   │               └── TopupImpTests.xcscheme
        │   ├── Package.swift
        │   ├── README.md
        │   ├── Sources/
        │   │   ├── AddPaymentMethod/
        │   │   │   └── AddPaymentMethodInterface.swift
        │   │   ├── AddPaymentMethodImp/
        │   │   │   ├── AddPaymentMethodBuilder.swift
        │   │   │   ├── AddPaymentMethodInteractor.swift
        │   │   │   ├── AddPaymentMethodRouter.swift
        │   │   │   └── AddPaymentMethodViewController.swift
        │   │   ├── AddPaymentMethodTestSupport/
        │   │   │   └── AddPaymentMethodTestSupport.swift
        │   │   ├── FinanceEntity/
        │   │   │   ├── AddPaymentMethodInfo.swift
        │   │   │   └── PaymentMethod.swift
        │   │   ├── FinanceHome/
        │   │   │   ├── CardOnFileDashboard/
        │   │   │   │   ├── CardOnFileDashboardBuilder.swift
        │   │   │   │   ├── CardOnFileDashboardInteractor.swift
        │   │   │   │   ├── CardOnFileDashboardRouter.swift
        │   │   │   │   ├── CardOnFileDashboardViewController.swift
        │   │   │   │   ├── PaymentMethodViewModel.swift
        │   │   │   │   └── Views/
        │   │   │   │       ├── AddPaymentMethodButton.swift
        │   │   │   │       └── PaymentMethodView.swift
        │   │   │   ├── FinanceHomeBuilder.swift
        │   │   │   ├── FinanceHomeInteractor.swift
        │   │   │   ├── FinanceHomeRouter.swift
        │   │   │   ├── FinanceHomeViewController.swift
        │   │   │   └── SuperPayDashboard/
        │   │   │       ├── Formatter.swift
        │   │   │       ├── SuperPayDashboardBuilder.swift
        │   │   │       ├── SuperPayDashboardInteractor.swift
        │   │   │       ├── SuperPayDashboardRouter.swift
        │   │   │       └── SuperPayDashboardViewController.swift
        │   │   ├── FinanceRepository/
        │   │   │   ├── AddCardRequest.swift
        │   │   │   ├── CardOnFileRepository.swift
        │   │   │   ├── CardOnFileRequest.swift
        │   │   │   ├── SuperPayRepository.swift
        │   │   │   └── TopupRequest.swift
        │   │   ├── FinanceRepositoryTestSupport/
        │   │   │   ├── CardOnFileRepositoryMock.swift
        │   │   │   └── SuperPayRepositoryMock.swift
        │   │   ├── Topup/
        │   │   │   └── TopupInterface.swift
        │   │   ├── TopupImp/
        │   │   │   ├── Array+Utils.swift
        │   │   │   ├── CardOnFile/
        │   │   │   │   ├── CardOnFileBuilder.swift
        │   │   │   │   ├── CardOnFileCell.swift
        │   │   │   │   ├── CardOnFileInteractor.swift
        │   │   │   │   ├── CardOnFileRouter.swift
        │   │   │   │   └── CardOnFileViewController.swift
        │   │   │   ├── EnterAmount/
        │   │   │   │   ├── EnterAmountBuilder.swift
        │   │   │   │   ├── EnterAmountInteractor.swift
        │   │   │   │   ├── EnterAmountRouter.swift
        │   │   │   │   ├── EnterAmountViewController.swift
        │   │   │   │   ├── EnterAmountWidget.swift
        │   │   │   │   └── SelectedPaymentMethodView.swift
        │   │   │   ├── Models/
        │   │   │   │   └── PaymentMethodViewModel.swift
        │   │   │   ├── TopupBuilder.swift
        │   │   │   ├── TopupInteractor.swift
        │   │   │   └── TopupRouter.swift
        │   │   └── TopupTestSupport/
        │   │       └── TopupMock.swift
        │   └── Tests/
        │       └── TopupImpTests/
        │           ├── CardOnFile/
        │           │   ├── CardOnFileMock.swift
        │           │   └── CardOnFileViewTests.swift
        │           ├── EnterAmount/
        │           │   ├── EnterAmountInteractorTests.swift
        │           │   ├── EnterAmountMock.swift
        │           │   ├── EnterAmountRouterTests.swift
        │           │   └── EnterAmountViewTests.swift
        │           └── Topup/
        │               ├── TopupInteractorTests.swift
        │               ├── TopupMock.swift
        │               └── TopupRouterTests.swift
        ├── MiniSuperApp/
        │   ├── AppDelegate/
        │   │   ├── AppComponent.swift
        │   │   └── AppDelegate.swift
        │   ├── AppRoot/
        │   │   ├── AppRootBuilder.swift
        │   │   ├── AppRootComponent.swift
        │   │   ├── AppRootInteractor.swift
        │   │   ├── AppRootRouter.swift
        │   │   ├── BaseURL.swift
        │   │   ├── RootTabBarController.swift
        │   │   ├── SetupURLProtocol.swift
        │   │   └── SuperAppURLProtocol.swift
        │   ├── Assets.xcassets/
        │   │   ├── AccentColor.colorset/
        │   │   │   └── Contents.json
        │   │   ├── AppIcon.appiconset/
        │   │   │   └── Contents.json
        │   │   └── Contents.json
        │   ├── Base.lproj/
        │   │   └── LaunchScreen.storyboard
        │   ├── Info.plist
        │   └── Utils/
        │       └── Array+Utils.swift
        ├── MiniSuperApp.xcodeproj/
        │   ├── project.pbxproj
        │   ├── project.xcworkspace/
        │   │   ├── contents.xcworkspacedata
        │   │   └── xcshareddata/
        │   │       ├── IDEWorkspaceChecks.plist
        │   │       └── swiftpm/
        │   │           └── Package.resolved
        │   └── xcshareddata/
        │       └── xcschemes/
        │           ├── AddPaymentMethodIntegrationTests.xcscheme
        │           ├── MiniSuperApp.xcscheme
        │           ├── MiniSuperAppUITests.xcscheme
        │           └── TestHost.xcscheme
        ├── MiniSuperAppUITests/
        │   ├── Response/
        │   │   ├── cardOnFile.json
        │   │   └── topupSuccessResponse.json
        │   ├── TestUtil.swift
        │   └── TopupImpUITests.swift
        ├── Platform/
        │   ├── .gitignore
        │   ├── Package.swift
        │   ├── README.md
        │   └── Sources/
        │       ├── CombineUtil/
        │       │   └── Combine+Utils.swift
        │       ├── DefaultsStore/
        │       │   └── DefaultsStore.swift
        │       ├── Network/
        │       │   ├── HTTPMethod.swift
        │       │   ├── Network.swift
        │       │   └── NetworkError.swift
        │       ├── NetworkImp/
        │       │   └── NetworkImp.swift
        │       ├── PlatformTestSupport/
        │       │   └── PlatformTestSupport.swift
        │       ├── RIBsTestSupport/
        │       │   ├── RoutingMock.swift
        │       │   ├── ViewControllableMock.swift
        │       │   └── ViewableRoutingMock.swift
        │       ├── RIBsUtil/
        │       │   └── RIBs+Util.swift
        │       └── SuperUI/
        │           ├── AdaptivePresentationControllerDelegate.swift
        │           ├── PushModalPresentationController.swift
        │           ├── UIColor+Super.swift
        │           ├── UIColor+Utils.swift
        │           ├── UIImage+Utils.swift
        │           ├── UITableView+Utils.swift
        │           ├── UIView+Utils.swift
        │           └── UIViewController+Utils.swift
        ├── Profile/
        │   ├── .gitignore
        │   ├── Package.swift
        │   ├── README.md
        │   └── Sources/
        │       └── ProfileHome/
        │           ├── ProfileHomeBuilder.swift
        │           ├── ProfileHomeInteractor.swift
        │           ├── ProfileHomeRouter.swift
        │           └── ProfileHomeViewController.swift
        ├── Samples/
        │   ├── TestUtil.swift
        │   └── TopupDependencyMock.swift
        ├── TestHost/
        │   ├── AppDelegate.swift
        │   ├── Assets.xcassets/
        │   │   ├── AccentColor.colorset/
        │   │   │   └── Contents.json
        │   │   ├── AppIcon.appiconset/
        │   │   │   └── Contents.json
        │   │   └── Contents.json
        │   ├── Base.lproj/
        │   │   └── LaunchScreen.storyboard
        │   └── Info.plist
        └── Transport/
            ├── .gitignore
            ├── Package.swift
            ├── README.md
            └── Sources/
                ├── TransportHome/
                │   └── TransportHomeInterface.swift
                └── TransportHomeImp/
                    ├── Formatter.swift
                    ├── TransportHomeBuilder.swift
                    ├── TransportHomeInteractor.swift
                    ├── TransportHomeRouter.swift
                    ├── TransportHomeViewController.swift
                    └── Views/
                        ├── RideTypeView.swift
                        └── SuperPayView.swift

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
xcuserdata/


================================================
FILE: MiniSuperApp/AppDelegate/AppComponent.swift
================================================
import Foundation
import ModernRIBs

final class AppComponent: Component<EmptyDependency>, AppRootDependency {
  
  init() {
    super.init(dependency: EmptyComponent())
  }
  
}


================================================
FILE: MiniSuperApp/AppDelegate/AppDelegate.swift
================================================
import UIKit
import ModernRIBs

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
  
  var window: UIWindow?
  
  private var launchRouter: LaunchRouting?
  private var urlHandler: URLHandler?
  
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    let window = UIWindow(frame: UIScreen.main.bounds)
    self.window = window
    
    let result = AppRootBuilder(dependency: AppComponent()).build()
    self.launchRouter = result.launchRouter
    self.urlHandler = result.urlHandler
    launchRouter?.launch(from: window)
    
    return true
  }
  
}

protocol URLHandler: AnyObject {
  func handle(_ url: URL)
}


================================================
FILE: MiniSuperApp/AppHome/AppHomeBuilder.swift
================================================
import ModernRIBs

public protocol AppHomeDependency: Dependency {
}

final class AppHomeComponent: Component<AppHomeDependency>, TransportHomeDependency {
}

// MARK: - Builder

public protocol AppHomeBuildable: Buildable {
  func build(withListener listener: AppHomeListener) -> ViewableRouting
}

public final class AppHomeBuilder: Builder<AppHomeDependency>, AppHomeBuildable {
  
  public override init(dependency: AppHomeDependency) {
    super.init(dependency: dependency)
  }
  
  public func build(withListener listener: AppHomeListener) -> ViewableRouting {
    let component = AppHomeComponent(dependency: dependency)
    let viewController = AppHomeViewController()
    let interactor = AppHomeInteractor(presenter: viewController)
    interactor.listener = listener
    
    let transportHomeBuilder = TransportHomeBuilder(dependency: component)
    
    return AppHomeRouter(
      interactor: interactor,
      viewController: viewController,
      transportHomeBuildable: transportHomeBuilder
    )
  }
}


================================================
FILE: MiniSuperApp/AppHome/AppHomeInteractor.swift
================================================
import ModernRIBs

protocol AppHomeRouting: ViewableRouting {
  func attachTransportHome()
  func detachTransportHome()
}

protocol AppHomePresentable: Presentable {
  var listener: AppHomePresentableListener? { get set }
  
  func updateWidget(_ viewModels: [HomeWidgetViewModel])
}

public protocol AppHomeListener: AnyObject {
  // TODO: Declare methods the interactor can invoke to communicate with other RIBs.
}

final class AppHomeInteractor: PresentableInteractor<AppHomePresentable>, AppHomeInteractable, AppHomePresentableListener {
  
  weak var router: AppHomeRouting?
  weak var listener: AppHomeListener?
  
  override init(presenter: AppHomePresentable) {
    super.init(presenter: presenter)
    presenter.listener = self
  }
  
  override func didBecomeActive() {
    super.didBecomeActive()
    
    let viewModels = [
      HomeWidgetModel(
        imageName: "car",
        title: "슈퍼택시",
        tapHandler: { [weak self] in
          self?.router?.attachTransportHome()
        }
      ),
      HomeWidgetModel(
        imageName: "cart",
        title: "슈퍼마트",
        tapHandler: { }
      )
    ]
    
    presenter.updateWidget(viewModels.map(HomeWidgetViewModel.init))
  }
  
  func transportHomeDidTapClose() {
    router?.detachTransportHome()
  }
  
}


================================================
FILE: MiniSuperApp/AppHome/AppHomeRouter.swift
================================================
import ModernRIBs


protocol AppHomeInteractable: Interactable, TransportHomeListener {
  var router: AppHomeRouting? { get set }
  var listener: AppHomeListener? { get set }
}

protocol AppHomeViewControllable: ViewControllable {

}

final class AppHomeRouter: ViewableRouter<AppHomeInteractable, AppHomeViewControllable>, AppHomeRouting {
  
  private let transportHomeBuildable: TransportHomeBuildable
  private var transportHomeRouting: Routing?
  private let transitioningDelegate: PushModalPresentationController
  
  init(
    interactor: AppHomeInteractable,
    viewController: AppHomeViewControllable,
    transportHomeBuildable: TransportHomeBuildable
  ) {
    self.transitioningDelegate = PushModalPresentationController()
    self.transportHomeBuildable = transportHomeBuildable
    super.init(interactor: interactor, viewController: viewController)
    interactor.router = self
  }
  
  func attachTransportHome() {
    if transportHomeRouting != nil {
      return
    }
    
    let router = transportHomeBuildable.build(withListener: interactor)
    presentWithPushTransition(router.viewControllable, animated: true)
    attachChild(router)
    self.transportHomeRouting = router
  }
  
  func detachTransportHome() {
    guard let router = transportHomeRouting else {
      return
    }
    
    viewController.dismiss(completion: nil)
    self.transportHomeRouting = nil
    detachChild(router)
  }
  
  private func presentWithPushTransition(_ viewControllable: ViewControllable, animated: Bool) {
    viewControllable.uiviewController.modalPresentationStyle = .custom
    viewControllable.uiviewController.transitioningDelegate = transitioningDelegate
    viewController.present(viewControllable, animated: true, completion: nil)
  }
  
}


================================================
FILE: MiniSuperApp/AppHome/AppHomeViewController.swift
================================================
import ModernRIBs
import UIKit

protocol AppHomePresentableListener: AnyObject {
}

final class AppHomeViewController: UIViewController, AppHomePresentable, AppHomeViewControllable {
  
  weak var listener: AppHomePresentableListener?
  
  private let widgetStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.axis = .horizontal
    stackView.distribution = .fillEqually
    stackView.alignment = .top
    stackView.spacing = 20
    return stackView
  }()
  
  init() {
    super.init(nibName: nil, bundle: nil)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  func updateWidget(_ viewModels: [HomeWidgetViewModel]) {
    let views = viewModels.map { HomeWidgetView(viewModel: $0) }
    
    views.forEach {
      $0.addShadowWithRoundedCorners(12)
      widgetStackView.addArrangedSubview($0)
    }
  }
  
  private func setupViews() {
    title = "홈"
    tabBarItem = UITabBarItem(title: "홈", image: UIImage(systemName: "house"), selectedImage: UIImage(systemName: "house.fill"))
    view.backgroundColor = .backgroundColor
    view.addSubview(widgetStackView)
    
    NSLayoutConstraint.activate([
      widgetStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
      widgetStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      widgetStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
    ])
  }
  
}


================================================
FILE: MiniSuperApp/AppHome/HomeWidgetModel.swift
================================================
import Foundation

struct HomeWidgetModel {
  let imageName: String
  let title: String
  let tapHandler: () -> Void
}


================================================
FILE: MiniSuperApp/AppHome/Views/HomeWidgetView.swift
================================================
import UIKit

struct HomeWidgetViewModel {
  let image: UIImage?
  let title: String
  let tapHandler: () -> Void
  
  init(_ model: HomeWidgetModel) {
    image = UIImage(systemName: model.imageName)
    title = model.title
    tapHandler = model.tapHandler
  }
}

final class HomeWidgetView: UIView {
  
  init(viewModel: HomeWidgetViewModel) {
    super.init(frame: .zero)
    
    setupViews()
    update(with: viewModel)
  }
  
  required init?(coder: NSCoder) {
    fatalError()
  }
  
  private var tapHandler: (() -> Void)?
  
  private func update(with viewModel: HomeWidgetViewModel) {
    imageView.image = viewModel.image
    titleLabel.text = viewModel.title
    tapHandler = viewModel.tapHandler
  }

  private let imageView: UIImageView = {
    let imageView = UIImageView()
    imageView.tintColor = .black
    imageView.translatesAutoresizingMaskIntoConstraints = false
    return imageView
  }()
  
  private let titleLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.textAlignment = .center
    label.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
    return label
  }()
  
  private func setupViews() {
    addSubview(imageView)
    addSubview(titleLabel)
    backgroundColor = .white
    
    let tap = UITapGestureRecognizer(target: self, action: #selector(didTap))
    addGestureRecognizer(tap)
    
    NSLayoutConstraint.activate([
      imageView.topAnchor.constraint(equalTo: self.topAnchor, constant: 15),
      imageView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
      imageView.widthAnchor.constraint(equalToConstant: 50),
      imageView.heightAnchor.constraint(equalToConstant: 50),
      
      titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 5),
      titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor),
      titleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor),
      titleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -14)
    ])
  }
  
  @objc
  private func didTap() {
    tapHandler?()
  }
  
}


================================================
FILE: MiniSuperApp/AppRoot/AppRootBuilder.swift
================================================
import ModernRIBs
import UIKit

protocol AppRootDependency: Dependency {
  // TODO: Declare the set of dependencies required by this RIB, but cannot be
  // created by this RIB.
}

final class AppRootComponent: Component<AppRootDependency>, AppHomeDependency, FinanceHomeDependency, ProfileHomeDependency  {
  
  // TODO: Declare 'fileprivate' dependencies that are only used by this RIB.
}

// MARK: - Builder

protocol AppRootBuildable: Buildable {
  func build() -> (launchRouter: LaunchRouting, urlHandler: URLHandler)
}

final class AppRootBuilder: Builder<AppRootDependency>, AppRootBuildable {
  
  override init(dependency: AppRootDependency) {
    super.init(dependency: dependency)
  }
  
  func build() -> (launchRouter: LaunchRouting, urlHandler: URLHandler) {
    let component = AppRootComponent(dependency: dependency)
    
    let tabBar = RootTabBarController()
    
    let interactor = AppRootInteractor(presenter: tabBar)
    
    let appHome = AppHomeBuilder(dependency: component)
    let financeHome = FinanceHomeBuilder(dependency: component)
    let profileHome = ProfileHomeBuilder(dependency: component)
    let router = AppRootRouter(
      interactor: interactor,
      viewController: tabBar,
      appHome: appHome,
      financeHome: financeHome,
      profileHome: profileHome
    )
    
    return (router, interactor)
  }
}


================================================
FILE: MiniSuperApp/AppRoot/AppRootInteractor.swift
================================================
import Foundation
import ModernRIBs

protocol AppRootRouting: ViewableRouting {
  func attachTabs()
}

protocol AppRootPresentable: Presentable {
  var listener: AppRootPresentableListener? { get set }
  // TODO: Declare methods the interactor can invoke the presenter to present data.
}

protocol AppRootListener: AnyObject {
  // TODO: Declare methods the interactor can invoke to communicate with other RIBs.
}

final class AppRootInteractor: PresentableInteractor<AppRootPresentable>, AppRootInteractable, AppRootPresentableListener, URLHandler {
  
  weak var router: AppRootRouting?
  weak var listener: AppRootListener?
  
  // TODO: Add additional dependencies to constructor. Do not perform any logic
  // in constructor.
  override init(presenter: AppRootPresentable) {
    super.init(presenter: presenter)
    presenter.listener = self
  }
  
  override func didBecomeActive() {
    super.didBecomeActive()
    
    router?.attachTabs()
  }
  
  override func willResignActive() {
    super.willResignActive()
    // TODO: Pause any business logic.
  }
  
  func handle(_ url: URL) {
    
  }
}


================================================
FILE: MiniSuperApp/AppRoot/AppRootRouter.swift
================================================
import ModernRIBs

protocol AppRootInteractable: Interactable,
                              AppHomeListener,
                              FinanceHomeListener,
                              ProfileHomeListener {
  var router: AppRootRouting? { get set }
  var listener: AppRootListener? { get set }
}

protocol AppRootViewControllable: ViewControllable {
  func setViewControllers(_ viewControllers: [ViewControllable])
}

final class AppRootRouter: LaunchRouter<AppRootInteractable, AppRootViewControllable>, AppRootRouting {
  
  private let appHome: AppHomeBuildable
  private let financeHome: FinanceHomeBuildable
  private let profileHome: ProfileHomeBuildable
  
  private var appHomeRouting: ViewableRouting?
  private var financeHomeRouting: ViewableRouting?
  private var profileHomeRouting: ViewableRouting?
  
  init(
    interactor: AppRootInteractable,
    viewController: AppRootViewControllable,
    appHome: AppHomeBuildable,
    financeHome: FinanceHomeBuildable,
    profileHome: ProfileHomeBuildable
  ) {
    self.appHome = appHome
    self.financeHome = financeHome
    self.profileHome = profileHome
    
    super.init(interactor: interactor, viewController: viewController)
    interactor.router = self
  }
  
  func attachTabs() {
    let appHomeRouting = appHome.build(withListener: interactor)
    let financeHomeRouting = financeHome.build(withListener: interactor)
    let profileHomeRouting = profileHome.build(withListener: interactor)
    
    attachChild(appHomeRouting)
    attachChild(financeHomeRouting)
    attachChild(profileHomeRouting)
    
    let viewControllers = [
      NavigationControllerable(root: appHomeRouting.viewControllable),
      NavigationControllerable(root: financeHomeRouting.viewControllable),
      profileHomeRouting.viewControllable
    ]
    
    viewController.setViewControllers(viewControllers)
  }
}


================================================
FILE: MiniSuperApp/AppRoot/RootTabBarController.swift
================================================
import UIKit
import ModernRIBs

protocol AppRootPresentableListener: AnyObject {
  
}

final class RootTabBarController: UITabBarController, AppRootViewControllable, AppRootPresentable {
  weak var listener: AppRootPresentableListener?
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    tabBar.isTranslucent = false
    tabBar.tintColor = .black
    tabBar.backgroundColor = .white
  }
  
  func setViewControllers(_ viewControllers: [ViewControllable]) {
    super.setViewControllers(viewControllers.map(\.uiviewController), animated: false)
  }
}


================================================
FILE: MiniSuperApp/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
  "colors" : [
    {
      "idiom" : "universal"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: MiniSuperApp/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "20x20"
    },
    {
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "20x20"
    },
    {
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "29x29"
    },
    {
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "29x29"
    },
    {
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "40x40"
    },
    {
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "40x40"
    },
    {
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "60x60"
    },
    {
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "60x60"
    },
    {
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "20x20"
    },
    {
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "20x20"
    },
    {
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "29x29"
    },
    {
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "29x29"
    },
    {
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "40x40"
    },
    {
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "40x40"
    },
    {
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "76x76"
    },
    {
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "76x76"
    },
    {
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "83.5x83.5"
    },
    {
      "idiom" : "ios-marketing",
      "scale" : "1x",
      "size" : "1024x1024"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: MiniSuperApp/Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: MiniSuperApp/Assets.xcassets/map_seoul.imageset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "filename" : "map_seoul.png",
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: MiniSuperApp/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
    <device id="retina6_1" orientation="portrait" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="System colors in document resources" minToolsVersion="11.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="EHf-IW-A2E">
            <objects>
                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Super" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CX5-z6-Rgm">
                                <rect key="frame" x="141" y="425.5" width="132.5" height="55"/>
                                <fontDescription key="fontDescription" type="system" weight="heavy" pointSize="46"/>
                                <color key="textColor" systemColor="systemRedColor"/>
                                <nil key="highlightedColor"/>
                            </label>
                        </subviews>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                        <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                        <constraints>
                            <constraint firstItem="CX5-z6-Rgm" firstAttribute="centerX" secondItem="6Tk-OE-BBY" secondAttribute="centerX" id="39B-mV-2d6"/>
                            <constraint firstItem="CX5-z6-Rgm" firstAttribute="centerY" secondItem="6Tk-OE-BBY" secondAttribute="centerY" id="MVn-2c-RfV"/>
                        </constraints>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="52.173913043478265" y="375"/>
        </scene>
    </scenes>
    <resources>
        <systemColor name="systemRedColor">
            <color red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
        </systemColor>
    </resources>
</document>


================================================
FILE: MiniSuperApp/FinanceHome/FinanceHomeBuilder.swift
================================================
import ModernRIBs

protocol FinanceHomeDependency: Dependency {
  // TODO: Declare the set of dependencies required by this RIB, but cannot be
  // created by this RIB.
}

final class FinanceHomeComponent: Component<FinanceHomeDependency> {
  
  // TODO: Declare 'fileprivate' dependencies that are only used by this RIB.
}

// MARK: - Builder

protocol FinanceHomeBuildable: Buildable {
  func build(withListener listener: FinanceHomeListener) -> FinanceHomeRouting
}

final class FinanceHomeBuilder: Builder<FinanceHomeDependency>, FinanceHomeBuildable {
  
  override init(dependency: FinanceHomeDependency) {
    super.init(dependency: dependency)
  }
  
  func build(withListener listener: FinanceHomeListener) -> FinanceHomeRouting {
    let _ = FinanceHomeComponent(dependency: dependency)
    let viewController = FinanceHomeViewController()
    let interactor = FinanceHomeInteractor(presenter: viewController)
    interactor.listener = listener
    return FinanceHomeRouter(interactor: interactor, viewController: viewController)
  }
}


================================================
FILE: MiniSuperApp/FinanceHome/FinanceHomeInteractor.swift
================================================
import ModernRIBs

protocol FinanceHomeRouting: ViewableRouting {
  // TODO: Declare methods the interactor can invoke to manage sub-tree via the router.
}

protocol FinanceHomePresentable: Presentable {
  var listener: FinanceHomePresentableListener? { get set }
  // TODO: Declare methods the interactor can invoke the presenter to present data.
}

protocol FinanceHomeListener: AnyObject {
  // TODO: Declare methods the interactor can invoke to communicate with other RIBs.
}

final class FinanceHomeInteractor: PresentableInteractor<FinanceHomePresentable>, FinanceHomeInteractable, FinanceHomePresentableListener {
  
  weak var router: FinanceHomeRouting?
  weak var listener: FinanceHomeListener?
  
  // TODO: Add additional dependencies to constructor. Do not perform any logic
  // in constructor.
  override init(presenter: FinanceHomePresentable) {
    super.init(presenter: presenter)
    presenter.listener = self
  }
  
  override func didBecomeActive() {
    super.didBecomeActive()
    // TODO: Implement business logic here.
  }
  
  override func willResignActive() {
    super.willResignActive()
    // TODO: Pause any business logic.
  }
}


================================================
FILE: MiniSuperApp/FinanceHome/FinanceHomeRouter.swift
================================================
import ModernRIBs

protocol FinanceHomeInteractable: Interactable {
  var router: FinanceHomeRouting? { get set }
  var listener: FinanceHomeListener? { get set }
}

protocol FinanceHomeViewControllable: ViewControllable {
  // TODO: Declare methods the router invokes to manipulate the view hierarchy.
}

final class FinanceHomeRouter: ViewableRouter<FinanceHomeInteractable, FinanceHomeViewControllable>, FinanceHomeRouting {
  
  // TODO: Constructor inject child builder protocols to allow building children.
  override init(interactor: FinanceHomeInteractable, viewController: FinanceHomeViewControllable) {
    super.init(interactor: interactor, viewController: viewController)
    interactor.router = self
  }
}


================================================
FILE: MiniSuperApp/FinanceHome/FinanceHomeViewController.swift
================================================
import ModernRIBs
import UIKit

protocol FinanceHomePresentableListener: AnyObject {
  // TODO: Declare properties and methods that the view controller can invoke to perform
  // business logic, such as signIn(). This protocol is implemented by the corresponding
  // interactor class.
}

final class FinanceHomeViewController: UIViewController, FinanceHomePresentable, FinanceHomeViewControllable {
  
  weak var listener: FinanceHomePresentableListener?
  
  init() {
    super.init(nibName: nil, bundle: nil)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  private let label: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    return label
  }()
  
  func setupViews() {
    title = "슈퍼페이"
    tabBarItem = UITabBarItem(title: "슈퍼페이", image: UIImage(systemName: "creditcard"), selectedImage: UIImage(systemName: "creditcard.fill"))
    label.text = "Finance Home"
    view.backgroundColor = .systemBlue
    view.addSubview(label)
    NSLayoutConstraint.activate([
      label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    ])
  }
}


================================================
FILE: MiniSuperApp/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>UIApplicationSupportsIndirectInputEvents</key>
	<true/>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
</dict>
</plist>


================================================
FILE: MiniSuperApp/ProfileHome/ProfileHomeBuilder.swift
================================================
import ModernRIBs

protocol ProfileHomeDependency: Dependency {
  // TODO: Declare the set of dependencies required by this RIB, but cannot be
  // created by this RIB.
}

final class ProfileHomeComponent: Component<ProfileHomeDependency> {
  
  // TODO: Declare 'fileprivate' dependencies that are only used by this RIB.
}

// MARK: - Builder

protocol ProfileHomeBuildable: Buildable {
  func build(withListener listener: ProfileHomeListener) -> ProfileHomeRouting
}

final class ProfileHomeBuilder: Builder<ProfileHomeDependency>, ProfileHomeBuildable {
  
  override init(dependency: ProfileHomeDependency) {
    super.init(dependency: dependency)
  }
  
  func build(withListener listener: ProfileHomeListener) -> ProfileHomeRouting {
    let _ = ProfileHomeComponent(dependency: dependency)
    let viewController = ProfileHomeViewController()
    let interactor = ProfileHomeInteractor(presenter: viewController)
    interactor.listener = listener
    return ProfileHomeRouter(interactor: interactor, viewController: viewController)
  }
}


================================================
FILE: MiniSuperApp/ProfileHome/ProfileHomeInteractor.swift
================================================
import ModernRIBs

protocol ProfileHomeRouting: ViewableRouting {
  // TODO: Declare methods the interactor can invoke to manage sub-tree via the router.
}

protocol ProfileHomePresentable: Presentable {
  var listener: ProfileHomePresentableListener? { get set }
  // TODO: Declare methods the interactor can invoke the presenter to present data.
}

protocol ProfileHomeListener: AnyObject {
  // TODO: Declare methods the interactor can invoke to communicate with other RIBs.
}

final class ProfileHomeInteractor: PresentableInteractor<ProfileHomePresentable>, ProfileHomeInteractable, ProfileHomePresentableListener {
  
  weak var router: ProfileHomeRouting?
  weak var listener: ProfileHomeListener?
  
  // TODO: Add additional dependencies to constructor. Do not perform any logic
  // in constructor.
  override init(presenter: ProfileHomePresentable) {
    super.init(presenter: presenter)
    presenter.listener = self
  }
  
  override func didBecomeActive() {
    super.didBecomeActive()
    // TODO: Implement business logic here.
  }
  
  override func willResignActive() {
    super.willResignActive()
    // TODO: Pause any business logic.
  }
}


================================================
FILE: MiniSuperApp/ProfileHome/ProfileHomeRouter.swift
================================================
import ModernRIBs

protocol ProfileHomeInteractable: Interactable {
  var router: ProfileHomeRouting? { get set }
  var listener: ProfileHomeListener? { get set }
}

protocol ProfileHomeViewControllable: ViewControllable {
  // TODO: Declare methods the router invokes to manipulate the view hierarchy.
}

final class ProfileHomeRouter: ViewableRouter<ProfileHomeInteractable, ProfileHomeViewControllable>, ProfileHomeRouting {
  
  // TODO: Constructor inject child builder protocols to allow building children.
  override init(interactor: ProfileHomeInteractable, viewController: ProfileHomeViewControllable) {
    super.init(interactor: interactor, viewController: viewController)
    interactor.router = self
  }
}


================================================
FILE: MiniSuperApp/ProfileHome/ProfileHomeViewController.swift
================================================
import ModernRIBs
import UIKit

protocol ProfileHomePresentableListener: AnyObject {
  // TODO: Declare properties and methods that the view controller can invoke to perform
  // business logic, such as signIn(). This protocol is implemented by the corresponding
  // interactor class.
}

final class ProfileHomeViewController: UIViewController, ProfileHomePresentable, ProfileHomeViewControllable {
  
  weak var listener: ProfileHomePresentableListener?
  
  init() {
    super.init(nibName: nil, bundle: nil)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  private let label: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    return label
  }()
  
  func setupViews() {
    tabBarItem = UITabBarItem(title: "프로필", image: UIImage(systemName: "person"), selectedImage: UIImage(systemName: "person.fill"))
    label.text = "Profile Home"
    view.backgroundColor = .systemTeal
    view.addSubview(label)
    NSLayoutConstraint.activate([
      label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    ])
  }
}


================================================
FILE: MiniSuperApp/TransportHome/TransportHomeBuilder.swift
================================================
import ModernRIBs

protocol TransportHomeDependency: Dependency {
}

final class TransportHomeComponent: Component<TransportHomeDependency> {

}

// MARK: - Builder

protocol TransportHomeBuildable: Buildable {
  func build(withListener listener: TransportHomeListener) -> TransportHomeRouting
}

final class TransportHomeBuilder: Builder<TransportHomeDependency>, TransportHomeBuildable {
  
  override init(dependency: TransportHomeDependency) {
    super.init(dependency: dependency)
  }
  
  func build(withListener listener: TransportHomeListener) -> TransportHomeRouting {
    _ = TransportHomeComponent(dependency: dependency)
    
    let viewController = TransportHomeViewController()
    
    let interactor = TransportHomeInteractor(presenter: viewController)
    interactor.listener = listener
    
    return TransportHomeRouter(
      interactor: interactor,
      viewController: viewController
    )
  }
}


================================================
FILE: MiniSuperApp/TransportHome/TransportHomeInteractor.swift
================================================
import ModernRIBs
import Combine
import Foundation

protocol TransportHomeRouting: ViewableRouting {
}

protocol TransportHomePresentable: Presentable {
  var listener: TransportHomePresentableListener? { get set }
  
}

protocol TransportHomeListener: AnyObject {
  func transportHomeDidTapClose()
}

protocol TransportHomeInteractorDependency {
}

final class TransportHomeInteractor: PresentableInteractor<TransportHomePresentable>, TransportHomeInteractable, TransportHomePresentableListener {
  
  weak var router: TransportHomeRouting?
  weak var listener: TransportHomeListener?
  
  
  override init(presenter: TransportHomePresentable) {
    super.init(presenter: presenter)
    presenter.listener = self
  }
  
  override func didBecomeActive() {
    super.didBecomeActive()
    
  }
  
  override func willResignActive() {
    super.willResignActive()
    // TODO: Pause any business logic.
  }
  
  func didTapBack() {
    listener?.transportHomeDidTapClose()
  }
}


================================================
FILE: MiniSuperApp/TransportHome/TransportHomeRouter.swift
================================================
import ModernRIBs

protocol TransportHomeInteractable: Interactable {
  var router: TransportHomeRouting? { get set }
  var listener: TransportHomeListener? { get set }
}

protocol TransportHomeViewControllable: ViewControllable {
  // TODO: Declare methods the router invokes to manipulate the view hierarchy.
}

final class TransportHomeRouter: ViewableRouter<TransportHomeInteractable, TransportHomeViewControllable>, TransportHomeRouting {
  
  override init(
    interactor: TransportHomeInteractable,
    viewController: TransportHomeViewControllable
  ) {
    super.init(interactor: interactor, viewController: viewController)
    interactor.router = self
  }
  
}


================================================
FILE: MiniSuperApp/TransportHome/TransportHomeViewController.swift
================================================
import ModernRIBs
import UIKit

protocol TransportHomePresentableListener: AnyObject {
  func didTapBack()
}

final class TransportHomeViewController: UIViewController, TransportHomePresentable, TransportHomeViewControllable {
  
  weak var listener: TransportHomePresentableListener?
  
  private let mapView: UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFill
    imageView.image = UIImage(named: "map_seoul")
    return imageView
  }()
  
  private let searchView: UIView = {
    let view = UIView()
    view.translatesAutoresizingMaskIntoConstraints = false
    view.addShadowWithRoundedCorners(8)
    view.backgroundColor = .white
    return view
  }()
  
  private let departureLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 18, weight: .medium)
    label.text = "우리집"
    return label
  }()
  
  private let destinationLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 18, weight: .medium)
    label.text = "회사"
    return label
  }()
  
  private let arrowImageView: UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.tintColor = .black
    imageView.image = UIImage(
      systemName: "arrow.right",
      withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .semibold)
    )
    return imageView
  }()
  
  private let rideTypeView: RideTypeView = {
    let view = RideTypeView()
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
  }()
  
  private let superPayView: SuperPayView = {
    let view = SuperPayView()
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
  }()
  
  private lazy var backButton: UIButton = {
    let button = UIButton()
    button.translatesAutoresizingMaskIntoConstraints = false
    button.backgroundColor = .white
    button.roundCorners(25)
    button.tintColor = .black
    button.setImage(
      UIImage(
        systemName: "chevron.backward",
        withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .semibold)
      ),
      for: .normal
    )
    button.addTarget(self, action: #selector(backButtonDidTap), for: .touchUpInside)
    return button
  }()
  
  private let rideTypeStackView: UIStackView = {
    let stack = UIStackView()
    
    return stack
  }()
  
  private let paymentStackView: UIStackView = {
    let stack = UIStackView()
    
    return stack
  }()
  
  private let rideInfoPane: UIView = {
    let view = UIView()
    view.translatesAutoresizingMaskIntoConstraints = false
    view.backgroundColor = .white
    view.addShadowWithRoundedCorners()
    return view
  }()
  
  private lazy var rideConfirmButton: UIButton = {
    let button = UIButton(type: .system)
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setTitle("슈퍼택시 호출하기", for: .normal)
    button.backgroundColor = .primaryRed
    button.tintColor = .white
    button.addTarget(self, action: #selector(didTapRideConfirmButton), for: .touchUpInside)
    button.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
    return button
  }()
  
  private let separatorView: UIView = {
    let view = UIView()
    view.translatesAutoresizingMaskIntoConstraints = false
    view.backgroundColor = .systemGray6
    return view
  }()
  
  func setSuperPayBalance(_ balanceText: String) {
    superPayView.setBalanceText("잔고: \(balanceText)원")
  }
  
  init() {
    super.init(nibName: nil, bundle: nil)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  private func setupViews() {
    view.addSubview(mapView)
    view.addSubview(searchView)
    searchView.addSubview(arrowImageView)
    searchView.addSubview(departureLabel)
    searchView.addSubview(destinationLabel)
    view.addSubview(backButton)
    view.addSubview(rideInfoPane)
    rideInfoPane.addSubview(rideTypeView)
    rideInfoPane.addSubview(superPayView)
    rideInfoPane.addSubview(separatorView)
    rideInfoPane.addSubview(rideConfirmButton)
    
    NSLayoutConstraint.activate([
      mapView.topAnchor.constraint(equalTo: view.topAnchor),
      mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
      mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
      mapView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.7),
      
      searchView.leadingAnchor.constraint(equalTo: backButton.trailingAnchor, constant: 10),
      searchView.centerYAnchor.constraint(equalTo: backButton.centerYAnchor),
      searchView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
      searchView.heightAnchor.constraint(equalToConstant: 50),
      
      departureLabel.leadingAnchor.constraint(equalTo: searchView.leadingAnchor, constant: 60),
      departureLabel.centerYAnchor.constraint(equalTo: searchView.centerYAnchor),
      
      destinationLabel.trailingAnchor.constraint(equalTo: searchView.trailingAnchor, constant: -60),
      destinationLabel.centerYAnchor.constraint(equalTo: searchView.centerYAnchor),
      
      rideInfoPane.bottomAnchor.constraint(equalTo: view.bottomAnchor),
      rideInfoPane.leadingAnchor.constraint(equalTo: view.leadingAnchor),
      rideInfoPane.trailingAnchor.constraint(equalTo: view.trailingAnchor),
      rideInfoPane.topAnchor.constraint(equalTo: mapView.bottomAnchor),
      
      backButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
      backButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
      backButton.widthAnchor.constraint(equalToConstant: 50),
      backButton.heightAnchor.constraint(equalToConstant: 50),
      
      arrowImageView.centerXAnchor.constraint(equalTo: searchView.centerXAnchor),
      arrowImageView.centerYAnchor.constraint(equalTo: searchView.centerYAnchor),
      
      rideTypeView.leadingAnchor.constraint(equalTo: rideInfoPane.leadingAnchor, constant: 30),
      rideTypeView.trailingAnchor.constraint(equalTo: rideInfoPane.trailingAnchor, constant: -30),
      rideTypeView.topAnchor.constraint(equalTo: rideInfoPane.topAnchor, constant: 10),
      rideTypeView.heightAnchor.constraint(equalToConstant: 70),
      
      separatorView.topAnchor.constraint(equalTo: rideTypeView.bottomAnchor),
      separatorView.leadingAnchor.constraint(equalTo: rideInfoPane.leadingAnchor),
      separatorView.trailingAnchor.constraint(equalTo: rideInfoPane.trailingAnchor),
      separatorView.heightAnchor.constraint(equalToConstant: 1),
      
      superPayView.leadingAnchor.constraint(equalTo: rideInfoPane.leadingAnchor, constant: 30),
      superPayView.trailingAnchor.constraint(equalTo: rideInfoPane.trailingAnchor, constant: -30),
      superPayView.topAnchor.constraint(equalTo: separatorView.bottomAnchor, constant: 0),
      superPayView.bottomAnchor.constraint(equalTo: rideConfirmButton.topAnchor),
      
      rideConfirmButton.leadingAnchor.constraint(equalTo: rideInfoPane.leadingAnchor, constant: 30),
      rideConfirmButton.trailingAnchor.constraint(equalTo: rideInfoPane.trailingAnchor, constant: -30),
      rideConfirmButton.bottomAnchor.constraint(equalTo: rideInfoPane.safeAreaLayoutGuide.bottomAnchor, constant: -20),
      rideConfirmButton.heightAnchor.constraint(equalToConstant: 60)
    ])
  }
  
  @objc
  private func backButtonDidTap() {
    listener?.didTapBack()
  }
  
  @objc
  private func didTapRideConfirmButton() {
  }
}


================================================
FILE: MiniSuperApp/TransportHome/Views/RideTypeView.swift
================================================
import UIKit

final class RideTypeView: UIView {
  
  private let thumbnailView: UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFit
    imageView.tintColor = .black
    imageView.image = UIImage(
      systemName: "bolt.car",
      withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .semibold)
    )
    return imageView
  }()
  
  private let rideTypeNameLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 18, weight: .medium)
    label.text = "슈퍼전기차 택시"
    return label
  }()
  
  private let priceLabel: UILabel = {
    let label = UILabel()
    label.font = UIFont.systemFont(ofSize: 18, weight: .medium)
    label.translatesAutoresizingMaskIntoConstraints = false
    label.text = "18,000원"
    return label
  }()
  
  init() {
    super.init(frame: .zero)
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    setupViews()
  }
  
  private func setupViews() {
    addSubview(thumbnailView)
    addSubview(priceLabel)
    addSubview(rideTypeNameLabel)
    
    NSLayoutConstraint.activate([
      thumbnailView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
      thumbnailView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
      thumbnailView.widthAnchor.constraint(equalToConstant: 40),
      thumbnailView.heightAnchor.constraint(equalToConstant: 40),
      
      rideTypeNameLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
      rideTypeNameLabel.leadingAnchor.constraint(equalTo: thumbnailView.trailingAnchor, constant: 10),
      
      priceLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor),
      priceLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor)
    ])
  }
}


================================================
FILE: MiniSuperApp/TransportHome/Views/SuperPayView.swift
================================================
import UIKit

final class SuperPayView: UIView {
  
  private let thumbnailView: UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.backgroundColor = .systemBlue
    imageView.roundCorners(4)
    return imageView
  }()
  
  private let nameLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 18, weight: .medium)
    label.text = "슈퍼페이"
    return label
  }()
  
  private let balanceLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 18, weight: .medium)
    label.text = "---원"
    return label
  }()
  
  func setBalanceText(_ text: String) {
    balanceLabel.text = text
  }
  
  init() {
    super.init(frame: .zero)
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    setupViews()
  }
  
  private func setupViews() {
    addSubview(thumbnailView)
    addSubview(nameLabel)
    addSubview(balanceLabel)
    
    NSLayoutConstraint.activate([
      thumbnailView.widthAnchor.constraint(equalToConstant: 46),
      thumbnailView.heightAnchor.constraint(equalToConstant: 34),
      thumbnailView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
      
      nameLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
      nameLabel.leadingAnchor.constraint(equalTo: thumbnailView.trailingAnchor, constant: 10),
      
      balanceLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor),
      balanceLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
    ])
  }
}


================================================
FILE: MiniSuperApp/Utils/Array+Utils.swift
================================================
import Foundation

extension Array {
  subscript(safe index: Int) -> Element? {
    return indices ~= index ? self[index] : nil
  }
}



================================================
FILE: MiniSuperApp/Utils/PushModalPresentationController.swift
================================================
import UIKit

public final class PushModalPresentationController: NSObject, UIViewControllerTransitioningDelegate {
  
  public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return PushModalPresentTransitioning()
  }
  
  public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return PushModalDismissTransitioning()
  }
  
}

private final class PushModalPresentTransitioning: NSObject, UIViewControllerAnimatedTransitioning {
  func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.25
  }
  
  func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    guard let toViewController = transitionContext.viewController(forKey: .to) else {
      return
    }
    
    let containerView = transitionContext.containerView
    let toView = transitionContext.view(forKey: .to)
    
    var toViewInitialFrame   = transitionContext.initialFrame(for: toViewController)
    let toViewFinalFrame     = transitionContext.finalFrame(for: toViewController)
    toView.map(containerView.addSubview)
    
    toViewInitialFrame.origin = CGPoint(x: containerView.bounds.maxX, y: containerView.bounds.minY)
    toViewInitialFrame.size = toViewFinalFrame.size
    toView?.frame = toViewInitialFrame
    
    UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [.allowUserInteraction, .curveEaseOut], animations: {
      toView?.frame = toViewFinalFrame
    }) { _ in
      let isCompleted = !transitionContext.transitionWasCancelled
      transitionContext.completeTransition(isCompleted)
    }
  }
  
}

private final class PushModalDismissTransitioning: NSObject, UIViewControllerAnimatedTransitioning {
  
  func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.25
  }
  
  func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    guard let fromViewController = transitionContext.viewController(forKey: .from) else {
      return
    }
    
    let containerView = transitionContext.containerView
    let toView = transitionContext.view(forKey: .to)
    let fromView = transitionContext.view(forKey: .from)
    var fromViewFinalFrame = transitionContext.finalFrame(for: fromViewController)
    
    toView.map(containerView.addSubview)
    
    if let fromView = fromView {
      fromViewFinalFrame = fromView.frame.offsetBy(dx: fromView.frame.width, dy: 0)
    }
    
    UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [.allowUserInteraction, .curveEaseOut], animations: {
      fromView?.frame = fromViewFinalFrame
    }) { _ in
      let isCompleted = !transitionContext.transitionWasCancelled
      transitionContext.completeTransition(isCompleted)
    }
  }
  
}


================================================
FILE: MiniSuperApp/Utils/RIBs+Utils.swift
================================================
import UIKit
import ModernRIBs

final class NavigationControllerable: ViewControllable {
  
  var uiviewController: UIViewController { self.navigationController }
  let navigationController: UINavigationController
  
  public init(root: ViewControllable) {
    let navigation = UINavigationController(rootViewController: root.uiviewController)
    navigation.navigationBar.isTranslucent = false
    navigation.navigationBar.backgroundColor = .white
    navigation.navigationBar.scrollEdgeAppearance = navigation.navigationBar.standardAppearance
    
    self.navigationController = navigation
  }
}

extension ViewControllable {
  
  func present(_ viewControllable: ViewControllable, animated: Bool, completion: (() -> Void)?) {
    self.uiviewController.present(viewControllable.uiviewController, animated: true, completion: completion)
  }
  
  func dismiss(completion: (() -> Void)?) {
    self.uiviewController.dismiss(animated: true, completion: completion)
  }
  
  func pushViewController(_ viewControllable: ViewControllable, animated: Bool) {
    if let nav = self.uiviewController as? UINavigationController {
      nav.pushViewController(viewControllable.uiviewController, animated: animated)
    } else {
      self.uiviewController.navigationController?.pushViewController(viewControllable.uiviewController, animated: animated)
    }
  }
  
  func popViewController(animated: Bool) {
    if let nav = self.uiviewController as? UINavigationController {
      nav.popViewController(animated: animated)
    } else {
      self.uiviewController.navigationController?.popViewController(animated: animated)
    }
  }
  
  func popToRoot(animated: Bool) {
    if let nav = self.uiviewController as? UINavigationController {
      nav.popToRootViewController(animated: animated)
    } else {
      self.uiviewController.navigationController?.popToRootViewController(animated: animated)
    }
  }
  
  func setViewControllers(_ viewControllerables: [ViewControllable]) {
    if let nav = self.uiviewController as? UINavigationController {
      nav.setViewControllers(viewControllerables.map(\.uiviewController), animated: true)
    } else {
      self.uiviewController.navigationController?.setViewControllers(viewControllerables.map(\.uiviewController), animated: true)
    }
  }
}


================================================
FILE: MiniSuperApp/Utils/UIColor+Super.swift
================================================
import UIKit

extension UIColor {
  static let backgroundColor = UIColor(hex: "#F1F5F9FF")!
  static let primaryRed = UIColor(hex: "#eb445aff")!
}



================================================
FILE: MiniSuperApp/Utils/UIColor+Utils.swift
================================================
import UIKit

extension UIColor {
  convenience init?(hex: String) {
    let r, g, b, a: CGFloat
    
    if hex.hasPrefix("#") {
      let start = hex.index(hex.startIndex, offsetBy: 1)
      let hexColor = String(hex[start...])
      
      if hexColor.count == 8 {
        let scanner = Scanner(string: hexColor)
        var hexNumber: UInt64 = 0
        
        if scanner.scanHexInt64(&hexNumber) {
          r = CGFloat((hexNumber & 0xff000000) >> 24) / 255
          g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255
          b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255
          a = CGFloat(hexNumber & 0x000000ff) / 255
          
          self.init(red: r, green: g, blue: b, alpha: a)
          return
        }
      }
    }
    
    return nil
  }
}


================================================
FILE: MiniSuperApp/Utils/UIImage+Utils.swift
================================================
import UIKit

public extension UIImage {
  convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
    let rect = CGRect(origin: .zero, size: size)
    UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
    color.setFill()
    UIRectFill(rect)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    guard let cgImage = image?.cgImage else { return nil }
    
    self.init(cgImage: cgImage)
  }
}


================================================
FILE: MiniSuperApp/Utils/UITableView+Utils.swift
================================================
import UIKit

public protocol Reusable: AnyObject {
  static var reuseIdentifier: String { get }
}

public extension Reusable {
  static var reuseIdentifier: String {
    return String(describing: self)
  }
}

extension UITableViewCell: Reusable {}

public extension UITableView {
  
  func register<T: UITableViewCell>(cellType: T.Type) {
    self.register(cellType, forCellReuseIdentifier: T.reuseIdentifier)
  }
  
  func dequeueReusableCell<T: UITableViewCell>(for indexPath: IndexPath, cellType: T.Type = T.self) -> T {
    guard let cell = self.dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
      fatalError("Failed to dequeue reusable cell")
    }
    return cell
  }
}


================================================
FILE: MiniSuperApp/Utils/UIView+Utils.swift
================================================
import UIKit

extension UIView {
  func addShadowWithRoundedCorners(
    _ radius: CGFloat = 16,
    shadowColor: CGColor = UIColor.black.cgColor,
    opacity: Float = 0.1
  ) {
    self.layer.cornerCurve = .continuous
    self.layer.masksToBounds = false
    self.layer.shadowColor = shadowColor
    self.layer.shadowOffset = CGSize(width: 0, height: 0)
    self.layer.shadowOpacity = opacity
    self.layer.shadowRadius = 2.5
    self.layer.cornerRadius = radius
  }
  
  func roundCorners(
    _ radius: CGFloat = 16
  ) {
    self.layer.cornerCurve = .continuous
    self.layer.cornerRadius = radius
    self.clipsToBounds = true
  }
}


================================================
FILE: MiniSuperApp.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 52;
	objects = {

/* Begin PBXBuildFile section */
		F57EBE542738468700FE9319 /* UIImage+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57EBE512738468700FE9319 /* UIImage+Utils.swift */; };
		F57EBE552738468700FE9319 /* Array+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57EBE522738468700FE9319 /* Array+Utils.swift */; };
		F57EBE562738468700FE9319 /* UITableView+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57EBE532738468700FE9319 /* UITableView+Utils.swift */; };
		F57F6AE826DB4A2700C0117D /* FinanceHomeRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57F6AE426DB4A2700C0117D /* FinanceHomeRouter.swift */; };
		F57F6AE926DB4A2700C0117D /* FinanceHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57F6AE526DB4A2700C0117D /* FinanceHomeViewController.swift */; };
		F57F6AEA26DB4A2700C0117D /* FinanceHomeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57F6AE626DB4A2700C0117D /* FinanceHomeBuilder.swift */; };
		F57F6AEB26DB4A2700C0117D /* FinanceHomeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57F6AE726DB4A2700C0117D /* FinanceHomeInteractor.swift */; };
		F57F6AF026DB4A2D00C0117D /* ProfileHomeRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57F6AEC26DB4A2D00C0117D /* ProfileHomeRouter.swift */; };
		F57F6AF126DB4A2D00C0117D /* ProfileHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57F6AED26DB4A2D00C0117D /* ProfileHomeViewController.swift */; };
		F57F6AF226DB4A2D00C0117D /* ProfileHomeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57F6AEE26DB4A2D00C0117D /* ProfileHomeBuilder.swift */; };
		F57F6AF326DB4A2D00C0117D /* ProfileHomeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57F6AEF26DB4A2D00C0117D /* ProfileHomeInteractor.swift */; };
		F57F6AFE26DB4CD700C0117D /* RootTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57F6AFD26DB4CD700C0117D /* RootTabBarController.swift */; };
		F5829F5926DB2BC400BFA8CD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5829F5826DB2BC400BFA8CD /* AppDelegate.swift */; };
		F5829F6226DB2BC500BFA8CD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F5829F6126DB2BC500BFA8CD /* Assets.xcassets */; };
		F5829F6526DB2BC500BFA8CD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F5829F6326DB2BC500BFA8CD /* LaunchScreen.storyboard */; };
		F5829F6E26DB2DE900BFA8CD /* ModernRIBs in Frameworks */ = {isa = PBXBuildFile; productRef = F5829F6D26DB2DE900BFA8CD /* ModernRIBs */; };
		F5829F7426DB34AA00BFA8CD /* AppRootRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5829F7026DB34AA00BFA8CD /* AppRootRouter.swift */; };
		F5829F7626DB34AA00BFA8CD /* AppRootBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5829F7226DB34AA00BFA8CD /* AppRootBuilder.swift */; };
		F5829F7726DB34AA00BFA8CD /* AppRootInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5829F7326DB34AA00BFA8CD /* AppRootInteractor.swift */; };
		F5829F7926DB397300BFA8CD /* AppComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5829F7826DB397300BFA8CD /* AppComponent.swift */; };
		F5EAA219270B395900EF2B70 /* HomeWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA213270B395800EF2B70 /* HomeWidgetView.swift */; };
		F5EAA21A270B395900EF2B70 /* AppHomeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA214270B395800EF2B70 /* AppHomeBuilder.swift */; };
		F5EAA21B270B395900EF2B70 /* AppHomeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA215270B395900EF2B70 /* AppHomeInteractor.swift */; };
		F5EAA21C270B395900EF2B70 /* AppHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA216270B395900EF2B70 /* AppHomeViewController.swift */; };
		F5EAA21D270B395900EF2B70 /* HomeWidgetModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA217270B395900EF2B70 /* HomeWidgetModel.swift */; };
		F5EAA21E270B395900EF2B70 /* AppHomeRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA218270B395900EF2B70 /* AppHomeRouter.swift */; };
		F5EAA220270B39DC00EF2B70 /* PushModalPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA21F270B39DC00EF2B70 /* PushModalPresentationController.swift */; };
		F5EAA229270B3A4500EF2B70 /* TransportHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA222270B3A4500EF2B70 /* TransportHomeViewController.swift */; };
		F5EAA22A270B3A4500EF2B70 /* TransportHomeRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA223270B3A4500EF2B70 /* TransportHomeRouter.swift */; };
		F5EAA22B270B3A4500EF2B70 /* TransportHomeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA224270B3A4500EF2B70 /* TransportHomeBuilder.swift */; };
		F5EAA22C270B3A4500EF2B70 /* TransportHomeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA225270B3A4500EF2B70 /* TransportHomeInteractor.swift */; };
		F5EAA22D270B3A4500EF2B70 /* RideTypeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA227270B3A4500EF2B70 /* RideTypeView.swift */; };
		F5EAA22E270B3A4500EF2B70 /* SuperPayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA228270B3A4500EF2B70 /* SuperPayView.swift */; };
		F5EAA230270B3A7100EF2B70 /* RIBs+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA22F270B3A7100EF2B70 /* RIBs+Utils.swift */; };
		F5EAA232270B3A8F00EF2B70 /* UIView+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA231270B3A8F00EF2B70 /* UIView+Utils.swift */; };
		F5EAA234270B3A9A00EF2B70 /* UIColor+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA233270B3A9A00EF2B70 /* UIColor+Utils.swift */; };
		F5EAA236270B3AA600EF2B70 /* UIColor+Super.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EAA235270B3AA600EF2B70 /* UIColor+Super.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
		F57EBE512738468700FE9319 /* UIImage+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Utils.swift"; sourceTree = "<group>"; };
		F57EBE522738468700FE9319 /* Array+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Utils.swift"; sourceTree = "<group>"; };
		F57EBE532738468700FE9319 /* UITableView+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+Utils.swift"; sourceTree = "<group>"; };
		F57F6AE426DB4A2700C0117D /* FinanceHomeRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinanceHomeRouter.swift; sourceTree = "<group>"; };
		F57F6AE526DB4A2700C0117D /* FinanceHomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinanceHomeViewController.swift; sourceTree = "<group>"; };
		F57F6AE626DB4A2700C0117D /* FinanceHomeBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinanceHomeBuilder.swift; sourceTree = "<group>"; };
		F57F6AE726DB4A2700C0117D /* FinanceHomeInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinanceHomeInteractor.swift; sourceTree = "<group>"; };
		F57F6AEC26DB4A2D00C0117D /* ProfileHomeRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHomeRouter.swift; sourceTree = "<group>"; };
		F57F6AED26DB4A2D00C0117D /* ProfileHomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHomeViewController.swift; sourceTree = "<group>"; };
		F57F6AEE26DB4A2D00C0117D /* ProfileHomeBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHomeBuilder.swift; sourceTree = "<group>"; };
		F57F6AEF26DB4A2D00C0117D /* ProfileHomeInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHomeInteractor.swift; sourceTree = "<group>"; };
		F57F6AFD26DB4CD700C0117D /* RootTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootTabBarController.swift; sourceTree = "<group>"; };
		F5829F5526DB2BC400BFA8CD /* MiniSuperApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MiniSuperApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
		F5829F5826DB2BC400BFA8CD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
		F5829F6126DB2BC500BFA8CD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		F5829F6426DB2BC500BFA8CD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
		F5829F6626DB2BC500BFA8CD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		F5829F7026DB34AA00BFA8CD /* AppRootRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRootRouter.swift; sourceTree = "<group>"; };
		F5829F7226DB34AA00BFA8CD /* AppRootBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRootBuilder.swift; sourceTree = "<group>"; };
		F5829F7326DB34AA00BFA8CD /* AppRootInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRootInteractor.swift; sourceTree = "<group>"; };
		F5829F7826DB397300BFA8CD /* AppComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppComponent.swift; sourceTree = "<group>"; };
		F5EAA213270B395800EF2B70 /* HomeWidgetView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeWidgetView.swift; sourceTree = "<group>"; };
		F5EAA214270B395800EF2B70 /* AppHomeBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppHomeBuilder.swift; sourceTree = "<group>"; };
		F5EAA215270B395900EF2B70 /* AppHomeInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppHomeInteractor.swift; sourceTree = "<group>"; };
		F5EAA216270B395900EF2B70 /* AppHomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppHomeViewController.swift; sourceTree = "<group>"; };
		F5EAA217270B395900EF2B70 /* HomeWidgetModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeWidgetModel.swift; sourceTree = "<group>"; };
		F5EAA218270B395900EF2B70 /* AppHomeRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppHomeRouter.swift; sourceTree = "<group>"; };
		F5EAA21F270B39DC00EF2B70 /* PushModalPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushModalPresentationController.swift; sourceTree = "<group>"; };
		F5EAA222270B3A4500EF2B70 /* TransportHomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransportHomeViewController.swift; sourceTree = "<group>"; };
		F5EAA223270B3A4500EF2B70 /* TransportHomeRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransportHomeRouter.swift; sourceTree = "<group>"; };
		F5EAA224270B3A4500EF2B70 /* TransportHomeBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransportHomeBuilder.swift; sourceTree = "<group>"; };
		F5EAA225270B3A4500EF2B70 /* TransportHomeInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransportHomeInteractor.swift; sourceTree = "<group>"; };
		F5EAA227270B3A4500EF2B70 /* RideTypeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RideTypeView.swift; sourceTree = "<group>"; };
		F5EAA228270B3A4500EF2B70 /* SuperPayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuperPayView.swift; sourceTree = "<group>"; };
		F5EAA22F270B3A7100EF2B70 /* RIBs+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RIBs+Utils.swift"; sourceTree = "<group>"; };
		F5EAA231270B3A8F00EF2B70 /* UIView+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Utils.swift"; sourceTree = "<group>"; };
		F5EAA233270B3A9A00EF2B70 /* UIColor+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Utils.swift"; sourceTree = "<group>"; };
		F5EAA235270B3AA600EF2B70 /* UIColor+Super.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Super.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		F5829F5226DB2BC400BFA8CD /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				F5829F6E26DB2DE900BFA8CD /* ModernRIBs in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		F57F6AD926DB49E500C0117D /* AppHome */ = {
			isa = PBXGroup;
			children = (
				F5EAA214270B395800EF2B70 /* AppHomeBuilder.swift */,
				F5EAA215270B395900EF2B70 /* AppHomeInteractor.swift */,
				F5EAA218270B395900EF2B70 /* AppHomeRouter.swift */,
				F5EAA216270B395900EF2B70 /* AppHomeViewController.swift */,
				F5EAA217270B395900EF2B70 /* HomeWidgetModel.swift */,
				F5EAA212270B395800EF2B70 /* Views */,
			);
			path = AppHome;
			sourceTree = "<group>";
		};
		F57F6ADA26DB49EA00C0117D /* FinanceHome */ = {
			isa = PBXGroup;
			children = (
				F57F6AE426DB4A2700C0117D /* FinanceHomeRouter.swift */,
				F57F6AE526DB4A2700C0117D /* FinanceHomeViewController.swift */,
				F57F6AE626DB4A2700C0117D /* FinanceHomeBuilder.swift */,
				F57F6AE726DB4A2700C0117D /* FinanceHomeInteractor.swift */,
			);
			path = FinanceHome;
			sourceTree = "<group>";
		};
		F57F6ADB26DB49FD00C0117D /* ProfileHome */ = {
			isa = PBXGroup;
			children = (
				F57F6AEC26DB4A2D00C0117D /* ProfileHomeRouter.swift */,
				F57F6AED26DB4A2D00C0117D /* ProfileHomeViewController.swift */,
				F57F6AEE26DB4A2D00C0117D /* ProfileHomeBuilder.swift */,
				F57F6AEF26DB4A2D00C0117D /* ProfileHomeInteractor.swift */,
			);
			path = ProfileHome;
			sourceTree = "<group>";
		};
		F57F6AFF26DB585100C0117D /* Utils */ = {
			isa = PBXGroup;
			children = (
				F57EBE522738468700FE9319 /* Array+Utils.swift */,
				F57EBE512738468700FE9319 /* UIImage+Utils.swift */,
				F57EBE532738468700FE9319 /* UITableView+Utils.swift */,
				F5EAA235270B3AA600EF2B70 /* UIColor+Super.swift */,
				F5EAA233270B3A9A00EF2B70 /* UIColor+Utils.swift */,
				F5EAA231270B3A8F00EF2B70 /* UIView+Utils.swift */,
				F5EAA21F270B39DC00EF2B70 /* PushModalPresentationController.swift */,
				F5EAA22F270B3A7100EF2B70 /* RIBs+Utils.swift */,
			);
			path = Utils;
			sourceTree = "<group>";
		};
		F5829F4C26DB2BC400BFA8CD = {
			isa = PBXGroup;
			children = (
				F5829F5726DB2BC400BFA8CD /* MiniSuperApp */,
				F5829F5626DB2BC400BFA8CD /* Products */,
			);
			sourceTree = "<group>";
		};
		F5829F5626DB2BC400BFA8CD /* Products */ = {
			isa = PBXGroup;
			children = (
				F5829F5526DB2BC400BFA8CD /* MiniSuperApp.app */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		F5829F5726DB2BC400BFA8CD /* MiniSuperApp */ = {
			isa = PBXGroup;
			children = (
				F5829F7A26DB3AF900BFA8CD /* AppDelegate */,
				F5829F6F26DB33CF00BFA8CD /* AppRoot */,
				F57F6AD926DB49E500C0117D /* AppHome */,
				F5EAA221270B3A4500EF2B70 /* TransportHome */,
				F57F6ADA26DB49EA00C0117D /* FinanceHome */,
				F57F6ADB26DB49FD00C0117D /* ProfileHome */,
				F57F6AFF26DB585100C0117D /* Utils */,
				F5829F6126DB2BC500BFA8CD /* Assets.xcassets */,
				F5829F6326DB2BC500BFA8CD /* LaunchScreen.storyboard */,
				F5829F6626DB2BC500BFA8CD /* Info.plist */,
			);
			path = MiniSuperApp;
			sourceTree = "<group>";
		};
		F5829F6F26DB33CF00BFA8CD /* AppRoot */ = {
			isa = PBXGroup;
			children = (
				F5829F7026DB34AA00BFA8CD /* AppRootRouter.swift */,
				F5829F7226DB34AA00BFA8CD /* AppRootBuilder.swift */,
				F5829F7326DB34AA00BFA8CD /* AppRootInteractor.swift */,
				F57F6AFD26DB4CD700C0117D /* RootTabBarController.swift */,
			);
			path = AppRoot;
			sourceTree = "<group>";
		};
		F5829F7A26DB3AF900BFA8CD /* AppDelegate */ = {
			isa = PBXGroup;
			children = (
				F5829F5826DB2BC400BFA8CD /* AppDelegate.swift */,
				F5829F7826DB397300BFA8CD /* AppComponent.swift */,
			);
			path = AppDelegate;
			sourceTree = "<group>";
		};
		F5EAA212270B395800EF2B70 /* Views */ = {
			isa = PBXGroup;
			children = (
				F5EAA213270B395800EF2B70 /* HomeWidgetView.swift */,
			);
			path = Views;
			sourceTree = "<group>";
		};
		F5EAA221270B3A4500EF2B70 /* TransportHome */ = {
			isa = PBXGroup;
			children = (
				F5EAA222270B3A4500EF2B70 /* TransportHomeViewController.swift */,
				F5EAA223270B3A4500EF2B70 /* TransportHomeRouter.swift */,
				F5EAA224270B3A4500EF2B70 /* TransportHomeBuilder.swift */,
				F5EAA225270B3A4500EF2B70 /* TransportHomeInteractor.swift */,
				F5EAA226270B3A4500EF2B70 /* Views */,
			);
			path = TransportHome;
			sourceTree = "<group>";
		};
		F5EAA226270B3A4500EF2B70 /* Views */ = {
			isa = PBXGroup;
			children = (
				F5EAA227270B3A4500EF2B70 /* RideTypeView.swift */,
				F5EAA228270B3A4500EF2B70 /* SuperPayView.swift */,
			);
			path = Views;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		F5829F5426DB2BC400BFA8CD /* MiniSuperApp */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = F5829F6926DB2BC500BFA8CD /* Build configuration list for PBXNativeTarget "MiniSuperApp" */;
			buildPhases = (
				F5829F5126DB2BC400BFA8CD /* Sources */,
				F5829F5226DB2BC400BFA8CD /* Frameworks */,
				F5829F5326DB2BC400BFA8CD /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = MiniSuperApp;
			packageProductDependencies = (
				F5829F6D26DB2DE900BFA8CD /* ModernRIBs */,
			);
			productName = SuperRedApp;
			productReference = F5829F5526DB2BC400BFA8CD /* MiniSuperApp.app */;
			productType = "com.apple.product-type.application";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		F5829F4D26DB2BC400BFA8CD /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastSwiftUpdateCheck = 1250;
				LastUpgradeCheck = 1250;
				TargetAttributes = {
					F5829F5426DB2BC400BFA8CD = {
						CreatedOnToolsVersion = 12.5.1;
					};
				};
			};
			buildConfigurationList = F5829F5026DB2BC400BFA8CD /* Build configuration list for PBXProject "MiniSuperApp" */;
			compatibilityVersion = "Xcode 9.3";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = F5829F4C26DB2BC400BFA8CD;
			packageReferences = (
				F5829F6C26DB2DE900BFA8CD /* XCRemoteSwiftPackageReference "ModernRIBs" */,
			);
			productRefGroup = F5829F5626DB2BC400BFA8CD /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				F5829F5426DB2BC400BFA8CD /* MiniSuperApp */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		F5829F5326DB2BC400BFA8CD /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				F5829F6526DB2BC500BFA8CD /* LaunchScreen.storyboard in Resources */,
				F5829F6226DB2BC500BFA8CD /* Assets.xcassets in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		F5829F5126DB2BC400BFA8CD /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				F5EAA229270B3A4500EF2B70 /* TransportHomeViewController.swift in Sources */,
				F57F6AF226DB4A2D00C0117D /* ProfileHomeBuilder.swift in Sources */,
				F5EAA22A270B3A4500EF2B70 /* TransportHomeRouter.swift in Sources */,
				F5EAA232270B3A8F00EF2B70 /* UIView+Utils.swift in Sources */,
				F57F6AEA26DB4A2700C0117D /* FinanceHomeBuilder.swift in Sources */,
				F5EAA22E270B3A4500EF2B70 /* SuperPayView.swift in Sources */,
				F5EAA220270B39DC00EF2B70 /* PushModalPresentationController.swift in Sources */,
				F57F6AF126DB4A2D00C0117D /* ProfileHomeViewController.swift in Sources */,
				F57F6AF326DB4A2D00C0117D /* ProfileHomeInteractor.swift in Sources */,
				F5829F7626DB34AA00BFA8CD /* AppRootBuilder.swift in Sources */,
				F5EAA230270B3A7100EF2B70 /* RIBs+Utils.swift in Sources */,
				F57EBE542738468700FE9319 /* UIImage+Utils.swift in Sources */,
				F5EAA22B270B3A4500EF2B70 /* TransportHomeBuilder.swift in Sources */,
				F5EAA21D270B395900EF2B70 /* HomeWidgetModel.swift in Sources */,
				F5EAA21E270B395900EF2B70 /* AppHomeRouter.swift in Sources */,
				F5EAA21A270B395900EF2B70 /* AppHomeBuilder.swift in Sources */,
				F5EAA234270B3A9A00EF2B70 /* UIColor+Utils.swift in Sources */,
				F5EAA22C270B3A4500EF2B70 /* TransportHomeInteractor.swift in Sources */,
				F5829F7726DB34AA00BFA8CD /* AppRootInteractor.swift in Sources */,
				F5EAA22D270B3A4500EF2B70 /* RideTypeView.swift in Sources */,
				F5EAA219270B395900EF2B70 /* HomeWidgetView.swift in Sources */,
				F57F6AFE26DB4CD700C0117D /* RootTabBarController.swift in Sources */,
				F57F6AEB26DB4A2700C0117D /* FinanceHomeInteractor.swift in Sources */,
				F5EAA21B270B395900EF2B70 /* AppHomeInteractor.swift in Sources */,
				F57F6AE826DB4A2700C0117D /* FinanceHomeRouter.swift in Sources */,
				F57F6AE926DB4A2700C0117D /* FinanceHomeViewController.swift in Sources */,
				F5829F5926DB2BC400BFA8CD /* AppDelegate.swift in Sources */,
				F5EAA21C270B395900EF2B70 /* AppHomeViewController.swift in Sources */,
				F5EAA236270B3AA600EF2B70 /* UIColor+Super.swift in Sources */,
				F5829F7926DB397300BFA8CD /* AppComponent.swift in Sources */,
				F57F6AF026DB4A2D00C0117D /* ProfileHomeRouter.swift in Sources */,
				F57EBE562738468700FE9319 /* UITableView+Utils.swift in Sources */,
				F57EBE552738468700FE9319 /* Array+Utils.swift in Sources */,
				F5829F7426DB34AA00BFA8CD /* AppRootRouter.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXVariantGroup section */
		F5829F6326DB2BC500BFA8CD /* LaunchScreen.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				F5829F6426DB2BC500BFA8CD /* Base */,
			);
			name = LaunchScreen.storyboard;
			sourceTree = "<group>";
		};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
		F5829F6726DB2BC500BFA8CD /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 14.5;
				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
				MTL_FAST_MATH = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = iphoneos;
				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
			};
			name = Debug;
		};
		F5829F6826DB2BC500BFA8CD /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 14.5;
				MTL_ENABLE_DEBUG_INFO = NO;
				MTL_FAST_MATH = YES;
				SDKROOT = iphoneos;
				SWIFT_COMPILATION_MODE = wholemodule;
				SWIFT_OPTIMIZATION_LEVEL = "-O";
				VALIDATE_PRODUCT = YES;
			};
			name = Release;
		};
		F5829F6A26DB2BC500BFA8CD /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
				CODE_SIGN_STYLE = Automatic;
				INFOPLIST_FILE = MiniSuperApp/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				PRODUCT_BUNDLE_IDENTIFIER = com.red.MiniSuperApp;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = 1;
			};
			name = Debug;
		};
		F5829F6B26DB2BC500BFA8CD /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
				CODE_SIGN_STYLE = Automatic;
				INFOPLIST_FILE = MiniSuperApp/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				PRODUCT_BUNDLE_IDENTIFIER = com.red.MiniSuperApp;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = 1;
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		F5829F5026DB2BC400BFA8CD /* Build configuration list for PBXProject "MiniSuperApp" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				F5829F6726DB2BC500BFA8CD /* Debug */,
				F5829F6826DB2BC500BFA8CD /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		F5829F6926DB2BC500BFA8CD /* Build configuration list for PBXNativeTarget "MiniSuperApp" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				F5829F6A26DB2BC500BFA8CD /* Debug */,
				F5829F6B26DB2BC500BFA8CD /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
		F5829F6C26DB2DE900BFA8CD /* XCRemoteSwiftPackageReference "ModernRIBs" */ = {
			isa = XCRemoteSwiftPackageReference;
			repositoryURL = "https://github.com/DevYeom/ModernRIBs";
			requirement = {
				kind = upToNextMajorVersion;
				minimumVersion = 1.0.1;
			};
		};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
		F5829F6D26DB2DE900BFA8CD /* ModernRIBs */ = {
			isa = XCSwiftPackageProductDependency;
			package = F5829F6C26DB2DE900BFA8CD /* XCRemoteSwiftPackageReference "ModernRIBs" */;
			productName = ModernRIBs;
		};
/* End XCSwiftPackageProductDependency section */
	};
	rootObject = F5829F4D26DB2BC400BFA8CD /* Project object */;
}


================================================
FILE: MiniSuperApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:">
   </FileRef>
</Workspace>


================================================
FILE: MiniSuperApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>IDEDidComputeMac32BitWarning</key>
	<true/>
</dict>
</plist>


================================================
FILE: MiniSuperApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
================================================
{
  "object": {
    "pins": [
      {
        "package": "ModernRIBs",
        "repositoryURL": "https://github.com/DevYeom/ModernRIBs",
        "state": {
          "branch": null,
          "revision": "5e0a67365a1fb18ca06b919dbf53608843ddc284",
          "version": "1.0.1"
        }
      }
    ]
  },
  "version": 1
}


================================================
FILE: MiniSuperApp.xcodeproj/xcshareddata/xcschemes/MiniSuperApp.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1250"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "F5829F5426DB2BC400BFA8CD"
               BuildableName = "MiniSuperApp.app"
               BlueprintName = "MiniSuperApp"
               ReferencedContainer = "container:MiniSuperApp.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES"
      codeCoverageEnabled = "YES">
      <Testables>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "F5829F5426DB2BC400BFA8CD"
            BuildableName = "MiniSuperApp.app"
            BlueprintName = "MiniSuperApp"
            ReferencedContainer = "container:MiniSuperApp.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "F5829F5426DB2BC400BFA8CD"
            BuildableName = "MiniSuperApp.app"
            BlueprintName = "MiniSuperApp"
            ReferencedContainer = "container:MiniSuperApp.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: README.md
================================================
<a href="https://fastcampus.co.kr/dev_red_rsj?utm_source=soojin-github&utm_medium=readme&utm_campaign=soojin">
  <img src="https://soojin.ro/assets/posts/fastcampus-0.png" />
</a>

<div align = "center">
  <a href="https://fastcampus.co.kr/dev_red_rsj?utm_source=soojin-github&utm_medium=readme&utm_campaign=soojin">
    <img src="https://img.shields.io/badge/강의-패스트캠퍼스-red?style=flat" />
  </a>
  <a href="https://soojin.ro">
    <img src="https://img.shields.io/badge/iOS개발자-노수진-orange?style=flat" />
  </a>
  <a href="https://github.com/nsoojin/MiniSuperApp-fastcampus">
    <img src="https://img.shields.io/badge/실습 프로젝트-미니슈퍼앱-378805?style=flat" />
  </a>
  <a href="https://github.com/nsoojin/MiniSuperApp-fastcampus/discussions/categories/아무-질문이나-환영합니다">
    <img src="https://img.shields.io/badge/질문-환영-ffda00?style=flat" />
  </a>
</div>

### "모바일 개발자에게 확장성(scalability)이란

모바일 팀과 앱의 규모가 계속 커져도 사용자 경험과 개발자 경험 모두를 안정적으로 유지하는 것이라고 생각합니다.

개발자의 기술력은 개발 과정에서 발생하는 병목 현상을 얼마나 잘 처리하는지에서 보여지죠. 서버의 경우에는 많은 사용자가 몰릴 때 병목 현상이 발생하지만, 모바일의 경우에는 하나의 프로그램에 다수의 개발자들의 코드가 몰릴 때 병목이 발생한다고 볼 수 있습니다."

관련글: [모바일 개발자에게 scalability란 뭘까](https://soojin.ro/blog/scalability)
<br>

# 강의 내용

### 1부. 코드 레벨 아키텍처: 재사용 가능한 코드를 만드는 스킬

객체를 작게 만들고, 작은 객체를 조합해서 복잡한 기능으로 합치는 것이 아키텍처의 시작입니다. Massive View Controller, Massive View Model, Massive Interactor는 아키텍처만의 문제가 아니라 개발자의 [composition](https://en.wikipedia.org/wiki/Object_composition) 활용 능력에 따라 달라질 수 있습니다. Composition이 강력한 아키텍처 프레임워크 RIBs를 기반으로 미니 슈퍼앱을 만들어봅니다.

관련1: [스위프트로 다시보는 객체지향 프로그래밍: 피해야할 코딩 습관](https://soojin.ro/blog/solid-principles-in-swift)
<br>
관련2: [개발자와 라면 조리법](https://soojin.ro/blog/programmer-and-ramyun)
<br>
관련3: [google/promises를 활용한 스위프트 비동기 프로그래밍과 에러 핸들링](https://soojin.ro/blog/using-google-promises-swift)

### 2부. 모듈 레벨 아키텍처: 유지 보수와 개발 속도를 고려하는 모듈화

'느슨하게 결합된 모듈 구조'는 '확장성 있는 아키텍처'와 같은 말이나 다름없습니다. 200명의 iOS 앱 개발자가 기여하는 슈퍼앱 그랩, 약 75명이 기여하는 [에어비엔비](https://medium.com/airbnb-engineering/designing-for-productivity-in-a-large-scale-ios-application-9376a430a0bf) 같은 회사의 개발자들이 생산성을 지킬 수 있는 방법입니다. 왜 모듈화를 하면 빌드 시간이 줄어들어서 생산성이 오르는지 원리를 알아보고, 실습을 통해 미니 슈퍼앱에 적용해봅니다.

관련1: [모바일 앱의 느슨한 결합](https://soojin.ro/blog/loose-coupling)
<br>
관련2: [Sourcery 개발자로부터 배우는 모바일 아키텍처와 개발자 경험](https://soojin.ro/blog/pragmatic-programmer)

### 3부. 자동화 테스팅

현업 개발자들이 테스트를 처음 시작하기 어려운 이유는 레거시 코드가 테스트 불가능한 구조로 짜여져 있기 때문입니다. 하지만 실습에서 짜는 코드는 99% 테스트 가능한 코드입니다. 테스트 가능한 코드의 특징을 한번이라도 익히고 직접 테스트를 작성해보면 레거시 코드에 조금씩 도입하기도 쉽습니다. 유닛테스트, 스냅샷테스트, UI테스트, 통합테스트를 작성해봅니다.

관련1: [테스트와 좋은 설계의 관계, 그리고 나쁜 설계의 영향](https://soojin.ro/blog/tests-and-design)
<br>
관련2: [테스트 코드 작성하면 좋은 점](https://soojin.ro/blog/writing-test-code)
<br>
관련3: [uber/RIBs 유닛 테스트 짜기](https://soojin.ro/blog/unit-testing-ribs)
<br>
관련4: [XCTest 소요시간 단축하기](https://soojin.ro/blog/application-library-test)

### 4부. 확장성 있는 인프라: 코드만으로 해결할 수 없는 문제들

확장성 있는 아키텍처를 만들고 유지하려면 코드 뿐 아니라 개발 프로세스도 뒷받침해줘야 합니다. 피쳐플래그와 품질 모니터링을 도입해서 얻을 수 있는 것들과 제가 경험해본 좋은 개발 문화 사례를 공유합니다.

관련1: [앱 안정성을 향한 끊임없는 여정](https://soojin.ro/blog/journey-to-app-stability)
<br>
관련2: [팀워크](https://soojin.ro/blog/teamwork)
<br>
관련3: [개인과 팀이 성장하는 모바일 개발 환경](https://soojin.ro/blog/mobile-platform)


================================================
FILE: Samples/DefaultsStore/DefaultsStore.swift
================================================
import Foundation

public protocol DefaultsStore {
  var isInitialLaunch: Bool { get set }
  var lastNoticeDate: Double { get set }
}

public struct DefaultsStoreImp: DefaultsStore {
  
  public var isInitialLaunch: Bool {
    get {
      userDefaults.bool(forKey: kIsInitialLaunch)
    }
    set {
      userDefaults.set(newValue, forKey: kIsInitialLaunch)
    }
  }
  
  public var lastNoticeDate: Double {
    get {
      userDefaults.double(forKey: kLastNoticeDate)
    }
    set {
      userDefaults.set(newValue, forKey: kLastNoticeDate)
    }
  }
  
  private let userDefaults: UserDefaults
  
  private let kIsInitialLaunch = "kIsInitialLaunch"
  private let kLastNoticeDate = "kLastNoticeDate"
  
  public init(defaults: UserDefaults) {
    self.userDefaults = defaults
  }
  
}


================================================
FILE: Samples/Network/HTTPMethod.swift
================================================
import Foundation

public enum HTTPMethod: String, Encodable {
  case get = "GET"
  case post = "POST"
  case put = "PUT"
}


================================================
FILE: Samples/Network/Network.swift
================================================
import Combine
import Foundation

public typealias QueryItems = [String: AnyHashable]
public typealias HTTPHeader = [String: String]

public protocol Request: Hashable {
  associatedtype Output: Decodable
  
  var endpoint: URL { get }
  var method: HTTPMethod { get }
  var query: QueryItems { get }
  var header: HTTPHeader { get }
}

public protocol Network {
  func send<T: Request>(_ request: T) -> AnyPublisher<Response<T.Output>, Error>
}

public struct Response<T: Decodable> {
  public let output: T
  public let statusCode: Int
  
  public init(output: T, statusCode: Int) {
    self.output = output
    self.statusCode = statusCode
  }
}


================================================
FILE: Samples/Network/NetworkError.swift
================================================
import Foundation

public enum NetworkError: Error {
  case invalidURL(url: String?)
}


================================================
FILE: Samples/NetworkImp/NetworkImp.swift
================================================
import Foundation
import Network
import Combine

public final class NetworkImp: Network {
  
  private let session: URLSession
  
  public init(
    session: URLSession
  ) {
    self.session = session
  }
  
  public func send<T>(_ request: T) -> AnyPublisher<Response<T.Output>, Error> where T: Request {
    do {
      let urlRequest = try RequestFactory(request: request).urlRequestRepresentation()
      return session.dataTaskPublisher(for: urlRequest)
        .tryMap { data, response in
          let output = try JSONDecoder().decode(T.Output.self, from: data)
          return Response(output: output, statusCode: (response as? HTTPURLResponse)?.statusCode ?? 0)
        }
        .eraseToAnyPublisher()
    } catch {
      return Fail(error: error).eraseToAnyPublisher()
    }
  }
  
}

private final class RequestFactory<T: Request> {
  
  let request: T
  private var urlComponents: URLComponents?
  
  init(request: T) {
    self.request = request
    self.urlComponents = URLComponents(url: request.endpoint, resolvingAgainstBaseURL: true)
  }
  
  func urlRequestRepresentation() throws -> URLRequest {
    switch request.method {
    case .get:
      return try makeGetRequest()
    case .post:
      return try makePostRequest()
    case .put:
      return try makePutRequest()
    }
  }
  
  private func makeGetRequest() throws -> URLRequest {
    if request.query.isEmpty == false {
      urlComponents?.queryItems = request.query.map { URLQueryItem(name: $0.key, value: "\($0.value)") }
    }
    return try makeURLRequest()
  }
  
  private func makePostRequest() throws -> URLRequest {
    let body = try JSONSerialization.data(withJSONObject: request.query, options: [])
    return try makeURLRequest(httpBody: body)
  }
  
  private func makePutRequest() throws -> URLRequest {
    if request.query.isEmpty == false {
      urlComponents?.queryItems = request.query.map { URLQueryItem(name: $0.key, value: "\($0.value)") }
    }
    return try makeURLRequest()
  }
  
  private func makeURLRequest(httpBody: Data? = nil) throws -> URLRequest {
    guard let url = urlComponents?.url else {
      throw NetworkError.invalidURL(url: request.endpoint.absoluteString)
    }
    
    var urlRequest = URLRequest(url: url)
    request.header.forEach {
      urlRequest.setValue($0.value, forHTTPHeaderField: $0.key)
    }
    urlRequest.httpMethod = request.method.rawValue
    urlRequest.httpBody = httpBody
    
    return urlRequest
  }
}


================================================
FILE: Samples/RIBsTestSupport/RoutingMock.swift
================================================
import Foundation
import ModernRIBs
import Combine

public final class RoutingMock: Routing {
  
  public var loadHandler: (() -> ())?
  public var loadCallCount: Int = 0
  public var attachChildHandler: ((_ child: Routing) -> ())?
  public var attachChildCallCount: Int = 0
  public var detachChildHandler: ((_ child: Routing) -> ())?
  public var detachChildCallCount: Int = 0
  
  public var interactable: Interactable
  
  public var children: [Routing] = [Routing]() { didSet { childrenSetCallCount += 1 } }
  public var childrenSetCallCount = 0
  
  public init(
    interactable: Interactable
  ) {
    self.interactable = interactable
  }
  
  public func load() {
    loadCallCount += 1
    if let loadHandler = loadHandler {
      return loadHandler()
    }
  }
  
  public func attachChild(_ child: Routing) {
    attachChildCallCount += 1
    if let attachChildHandler = attachChildHandler {
      return attachChildHandler(child)
    }
  }

  public func detachChild(_ child: Routing) {
    detachChildCallCount += 1
    if let detachChildHandler = detachChildHandler {
      return detachChildHandler(child)
    }
  }
  
  public var lifecycleSubject = PassthroughSubject<RouterLifecycle, Never>() {
    didSet {
      lifecycleSubjectSetCallCount += 1
    }
  }
  public var lifecycleSubjectSetCallCount = 0
  public var lifecycle: AnyPublisher<RouterLifecycle, Never> { return lifecycleSubject.eraseToAnyPublisher() }
}


================================================
FILE: Samples/RIBsTestSupport/ViewControllableMock.swift
================================================
import Foundation
import ModernRIBs
import UIKit

public final class ViewControllableMock: UIViewController, ViewControllable {
  
  public init() {
    super.init(nibName: nil, bundle: nil)
  }
  
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  public var presentCallCount = 0
  public override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
    presentCallCount += 1
  }
  
  public var dismissCallCount = 0
  public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
    dismissCallCount += 1
  }

}


================================================
FILE: Samples/RIBsTestSupport/ViewableRoutingMock.swift
================================================
import Foundation
import ModernRIBs
import Combine

open class ViewableRoutingMock: ViewableRouting {
  // Variables
  public var viewControllable: ViewControllable
  public var interactable: Interactable { didSet { interactableSetCallCount += 1 } }
  public var interactableSetCallCount = 0
  public var children: [Routing] = [Routing]() { didSet { childrenSetCallCount += 1 } }
  public var childrenSetCallCount = 0
  public var lifecycleSubject = PassthroughSubject<RouterLifecycle, Never>() {
    didSet {
      lifecycleSubjectSetCallCount += 1
    }
  }
  public var lifecycleSubjectSetCallCount = 0
  public var lifecycle: AnyPublisher<RouterLifecycle, Never> { return lifecycleSubject.eraseToAnyPublisher() }

  // Function Handlers
  public var loadHandler: (() -> ())?
  public var loadCallCount: Int = 0
  public var attachChildHandler: ((_ child: Routing) -> ())?
  public var attachChildCallCount: Int = 0
  public var detachChildHandler: ((_ child: Routing) -> ())?
  public var detachChildCallCount: Int = 0

  public init(
    interactable: Interactable,
    viewControllable: ViewControllable
  ) {
    self.interactable = interactable
    self.viewControllable = viewControllable
  }

  public func load() {
    loadCallCount += 1
    if let loadHandler = loadHandler {
      return loadHandler()
    }
  }

  public func attachChild(_ child: Routing) {
    attachChildCallCount += 1
    if let attachChildHandler = attachChildHandler {
      return attachChildHandler(child)
    }
  }

  public func detachChild(_ child: Routing) {
    detachChildCallCount += 1
    if let detachChildHandler = detachChildHandler {
      return detachChildHandler(child)
    }
  }
}


================================================
FILE: Samples/TestUtil.swift
================================================
import Foundation

enum TestUtilError: Error {
  case fileNotFound
}

final class TestUtil {
  static func path(for fileName: String, in bundleClass: AnyClass) throws -> String {
    if let path = Bundle(for: bundleClass).path(forResource: fileName, ofType: nil) {
      return path
    } else {
      throw TestUtilError.fileNotFound
    }
  }
}


================================================
FILE: Samples/Topup/CardOnFileCell.swift
================================================
import UIKit

final class CardOnFileCell: UITableViewCell {
  
  func setImage(_ image: UIImage?) {
    thumbnailView.image = image
  }
  
  func setTitle(_ title: String) {
    titleLabel.text = title
  }
  
  private let thumbnailView: UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFill
    imageView.clipsToBounds = true
    imageView.roundCorners(4)
    return imageView
  }()
  
  private let titleLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.numberOfLines = 1
    return label
  }()
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    
    setupViews()
  }
  
  private func setupViews() {
    contentView.addSubview(thumbnailView)
    contentView.addSubview(titleLabel)
    
    NSLayoutConstraint.activate([
      thumbnailView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
      thumbnailView.widthAnchor.constraint(equalToConstant: 46),
      thumbnailView.heightAnchor.constraint(equalToConstant: 34),
      thumbnailView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
      
      titleLabel.centerYAnchor.constraint(equalTo: thumbnailView.centerYAnchor),
      titleLabel.leadingAnchor.constraint(equalTo: thumbnailView.trailingAnchor, constant: 14)
    ])
  }
  
}


================================================
FILE: Samples/Topup/CardOnFileViewController.swift
================================================
import ModernRIBs
import UIKit

protocol CardOnFilePresentableListener: AnyObject {
  func didTapClose()
  func didSelectItem(at: Int)
}

final class CardOnFileViewController: UIViewController, CardOnFilePresentable, CardOnFileViewControllable, UITableViewDataSource, UITableViewDelegate {
  
  weak var listener: CardOnFilePresentableListener?
  
  func update(with viewModels: [PaymentMethodViewModel]) {
    self.viewModels = viewModels
    tableView.reloadData()
  }
  
  init() {
    super.init(nibName: nil, bundle: nil)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  private var viewModels: [PaymentMethodViewModel] = []
  
  private lazy var tableView: UITableView = {
    let tableView = UITableView()
    tableView.translatesAutoresizingMaskIntoConstraints = false
    tableView.dataSource = self
    tableView.delegate = self
    tableView.register(cellType: CardOnFileCell.self)
    tableView.tableFooterView = UIView()
    tableView.rowHeight = 60
    tableView.separatorInset = .zero
    return tableView
  }()
  
  private func setupViews() {
    title = "카드 선택"
    view.backgroundColor = .white
    view.addSubview(tableView)
    
    setupNavigationItem(with: .back, target: self, action: #selector(didTapClose))
    
    NSLayoutConstraint.activate([
      tableView.topAnchor.constraint(equalTo: view.topAnchor),
      tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
      tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
      tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
    ])
  }
  
  // MARK: - UITableView
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return viewModels.count + 1
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell: CardOnFileCell = tableView.dequeueReusableCell(for: indexPath)

    if let viewModel = viewModels[safe: indexPath.row] {
      cell.setImage(UIImage(color: viewModel.color))
      cell.setTitle("\(viewModel.name) \(viewModel.digits)")
    } else {
      cell.setImage(UIImage(systemName: "plus.rectangle"))
      cell.setTitle("카드 추가")
    }

    return cell
  }
  
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    
    listener?.didSelectItem(at: indexPath.row)
  }
  
  @objc
  private func didTapClose() {
    listener?.didTapClose()
  }
  
}


================================================
FILE: Samples/Topup/EnterAmountViewController.swift
================================================
import ModernRIBs
import UIKit

protocol EnterAmountPresentableListener: AnyObject {
  func didTapClose()
  func didTapPaymentMethod()
  func didTapTopup(with amount: Double)
}

final class EnterAmountViewController: UIViewController, EnterAmountPresentable, EnterAmountViewControllable {
  
  weak var listener: EnterAmountPresentableListener?
  
  func updateSelectedPaymentMethod(with viewModel: SelectedPaymentMethodViewModel) {
    selectedPaymentMethodView.update(with: viewModel)
  }
  
  func startLoading() {
    activityIndicator.startAnimating()
    ctaButton.isEnabled = false
  }
  
  func stopLoading() {
    activityIndicator.stopAnimating()
    ctaButton.isEnabled = true
  }
  
  private lazy var selectedPaymentMethodView: SelectedPaymentMethodView = {
    let view = SelectedPaymentMethodView()
    view.translatesAutoresizingMaskIntoConstraints = false
    view.addShadowWithRoundedCorners()
    let tap = UITapGestureRecognizer(target: self, action: #selector(didTapPaymentMethod))
    view.addGestureRecognizer(tap)
    return view
  }()
  
  private let enterAmountWidget: EnterAmountWidget = {
    let widget = EnterAmountWidget()
    widget.translatesAutoresizingMaskIntoConstraints = false
    widget.addShadowWithRoundedCorners()
    return widget
  }()
  
  private lazy var ctaButton: UIButton = {
    let cta = UIButton(type: .system)
    cta.translatesAutoresizingMaskIntoConstraints = false
    cta.roundCorners()
    cta.setTitle("충전", for: .normal)
    cta.titleLabel?.font = UIFont.systemFont(ofSize: 20, weight: .semibold)
    cta.setBackgroundImage(UIImage(color: .primaryRed), for: .normal)
    cta.tintColor = .white
    cta.addTarget(self, action: #selector(didTapCTAButton), for: .touchUpInside)
    return cta
  }()
  
  private let activityIndicator: UIActivityIndicatorView = {
    let activity = UIActivityIndicatorView(style: .medium)
    activity.translatesAutoresizingMaskIntoConstraints = false
    activity.hidesWhenStopped = true
    activity.stopAnimating()
    return activity
  }()
  
  init() {
    super.init(nibName: nil, bundle: nil)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  private func setupViews() {
    title = "충전하기"
    view.backgroundColor = .backgroundColor
    
    setupNavigationItem(with: .close, target: self, action: #selector(didTapClose))
    
    view.addSubview(selectedPaymentMethodView)
    view.addSubview(enterAmountWidget)
    view.addSubview(ctaButton)
    view.addSubview(activityIndicator)
    
    NSLayoutConstraint.activate([
      selectedPaymentMethodView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      selectedPaymentMethodView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
      selectedPaymentMethodView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
      selectedPaymentMethodView.heightAnchor.constraint(equalToConstant: 70),
      
      enterAmountWidget.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      enterAmountWidget.topAnchor.constraint(equalTo: selectedPaymentMethodView.bottomAnchor, constant: 20),
      enterAmountWidget.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
      
      ctaButton.heightAnchor.constraint(equalToConstant: 60),
      ctaButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      ctaButton.topAnchor.constraint(equalTo: enterAmountWidget.bottomAnchor, constant: 40),
      ctaButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
      
      activityIndicator.centerXAnchor.constraint(equalTo: ctaButton.centerXAnchor),
      activityIndicator.centerYAnchor.constraint(equalTo: ctaButton.centerYAnchor),
    ])
  }
  
  @objc
  private func didTapClose() {
    listener?.didTapClose()
  }
  
  @objc
  private func didTapCTAButton() {
    if let amount = enterAmountWidget.text.flatMap(Double.init) {
      listener?.didTapTopup(with: amount)
    }
  }
  
  @objc
  private func didTapPaymentMethod() {
    listener?.didTapPaymentMethod()
  }
}


================================================
FILE: Samples/Topup/Views/EnterAmountWidget.swift
================================================
import UIKit

final class EnterAmountWidget: UIView {
  
  var text: String? {
    amountTextField.text
  }

  init() {
    super.init(frame: .zero)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  private let titleLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
    label.text = "금액"
    label.numberOfLines = 1
    return label
  }()
  
  private lazy var amountStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    let button = UIButton()
    stackView.axis = .horizontal
    stackView.alignment = .fill
    stackView.distribution = .fill
    stackView.spacing = 5
    stackView.addArrangedSubview(self.amountTextField)
    stackView.addArrangedSubview(self.currencyLabel)
    return stackView
  }()
  
  private let amountTextField: UITextField = {
    let textField = UITextField()
    textField.translatesAutoresizingMaskIntoConstraints = false
    textField.borderStyle = .none
    textField.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
    textField.textAlignment = .right
    textField.keyboardType = .numberPad
    return textField
  }()
  
  private let currencyLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
    label.text = "원"
    return label
  }()
  
  private func setupViews() {
    self.backgroundColor = .white
    self.addSubview(titleLabel)
    self.addSubview(amountStackView)
    
    NSLayoutConstraint.activate([
      titleLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 16),
      titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16),
      titleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16),
      amountStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16),
      amountStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16),
      amountStackView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),
      amountStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -16)
    ])
  }
}



================================================
FILE: Samples/Topup/Views/SelectedPaymentMethodView.swift
================================================
import UIKit

struct SelectedPaymentMethodViewModel {
  let image: UIImage?
  let name: String
  
  init(_ paymentMethod: PaymentMethod) {
    image = UIColor(hex: paymentMethod.color).flatMap { UIImage(color: $0) }
    name = "\(paymentMethod.name) \(paymentMethod.digits)"
  }
}

final class SelectedPaymentMethodView: UIView {
  
  func update(with viewModel: SelectedPaymentMethodViewModel) {
    self.thumbnailView.image = viewModel.image
    self.nameLabel.text = viewModel.name
  }
  
  init() {
    super.init(frame: .zero)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  private let thumbnailView: UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleToFill
    imageView.roundCorners(4)
    imageView.backgroundColor = .systemGray3
    return imageView
  }()
  
  private let nameLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
    label.numberOfLines = 1
    return label
  }()
  
  private let rightChevronIcon: UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.image = UIImage(
      systemName: "chevron.right",
      withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .medium)
    )
    imageView.tintColor = .systemGray3
    return imageView
  }()
  
  private func setupViews() {
    self.backgroundColor = .white
    self.addSubview(thumbnailView)
    self.addSubview(nameLabel)
    self.addSubview(rightChevronIcon)
    
    NSLayoutConstraint.activate([
      thumbnailView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
      thumbnailView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20),
      thumbnailView.widthAnchor.constraint(equalToConstant: 46),
      thumbnailView.heightAnchor.constraint(equalToConstant: 34),
      nameLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
      nameLabel.leadingAnchor.constraint(equalTo: thumbnailView.trailingAnchor, constant: 22),
      rightChevronIcon.centerYAnchor.constraint(equalTo: self.centerYAnchor),
      rightChevronIcon.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -24)
    ])
  }
}


================================================
FILE: Samples/TopupDependencyMock.swift
================================================
@testable import TopupImp
import Foundation
import CombineUtil
import FinanceEntity
import FinanceRepositoryTestSupport
import FinanceRepository
import Combine
import ModernRIBs
import RIBsUtil
import Topup
import SuperUI

final class TopupDependencyMock: TopupInteractorDependency {
  var cardOnFileRepository: CardOnFileRepository = CardOnFileRepositoryMock()
  var paymentMethodStream: CurrentValuePublisher<PaymentMethod> = .init(
    PaymentMethod(id: "", name: "", digits: "", color: "", isPrimary: false)
  )
}

final class TopupRoutingMock: TopupRouting {
  
  var attachAddPaymentMethodCallCount = 0
  var attachAddPaymentMethodCloseButtonType: DismissButtonType?
  func attachAddPaymentMethod(closeButtonType: DismissButtonType) {
    attachAddPaymentMethodCallCount += 1
    attachAddPaymentMethodCloseButtonType = closeButtonType
  }
  
  var detachAddPaymentMethodCallCount = 0
  func detachAddPaymentMethod() {
    detachAddPaymentMethodCallCount += 1
  }
  
  var attachEnterAmountCallCount = 0
  func attachEnterAmount() {
    attachEnterAmountCallCount += 1
  }
  
  var detachEnterAmountCallCount = 0
  func detachEnterAmount() {
    detachEnterAmountCallCount += 1
  }
  
  var attachCardOnFileCallCount = 0
  var attachCardOnFileCallCountPaymentMethods: [PaymentMethod]?
  func attachCardOnFile(paymentMethods: [PaymentMethod]) {
    attachCardOnFileCallCount += 1
  }
  
  var detachCardOnFileCallCount = 0
  func detachCardOnFile() {
    detachCardOnFileCallCount += 1
  }
  
  var popToRootCallCount = 0
  func popToRoot() {
    popToRootCallCount += 1
  }
  
  // Variables
  var interactable: Interactable { didSet { interactableSetCallCount += 1 } }
  var interactableSetCallCount = 0
  var children: [Routing] = [Routing]() { didSet { childrenSetCallCount += 1 } }
  var childrenSetCallCount = 0
  var lifecycleSubject = PassthroughSubject<RouterLifecycle, Never>() {
    didSet {
      lifecycleSubjectSetCallCount += 1
    }
  }
  var lifecycleSubjectSetCallCount = 0
  var lifecycle: AnyPublisher<RouterLifecycle, Never> { return lifecycleSubject.eraseToAnyPublisher() }
  
  // Function Handlers
  var loadHandler: (() -> ())?
  var loadCallCount: Int = 0
  var attachChildHandler: ((_ child: Routing) -> ())?
  var attachChildCallCount: Int = 0
  var detachChildHandler: ((_ child: Routing) -> ())?
  var detachChildCallCount: Int = 0
  
  init(
    interactable: Interactable
  ) {
    self.interactable = interactable
  }
  
  var cleanupViewsCallCount = 0
  func cleanupViews() {
    cleanupViewsCallCount += 1
  }
  
  func load() {
    loadCallCount += 1
    if let loadHandler = loadHandler {
      return loadHandler()
    }
  }
  
  func attachChild(_ child: Routing) {
    attachChildCallCount += 1
    if let attachChildHandler = attachChildHandler {
      return attachChildHandler(child)
    }
  }
  
  func detachChild(_ child: Routing) {
    detachChildCallCount += 1
    if let detachChildHandler = detachChildHandler {
      return detachChildHandler(child)
    }
  }
}

final class TopupInteractableMock: TopupInteractable {
  var router: TopupRouting?
  var listener: TopupListener?
  var presentationDelegateProxy = AdaptivePresentationControllerDelegateProxy()
  
  var addPaymentMethodDidTapCloseCallCount = 0
  func addPaymentMethodDidTapClose() {
    addPaymentMethodDidTapCloseCallCount += 1
  }
  
  var addPaymentMethodDidAddCardCallCount = 0
  var addPaymentMethodDidAddCardPaymentMethod: PaymentMethod?
  func addPaymentMethodDidAddCard(paymentMethod: PaymentMethod) {
    addPaymentMethodDidAddCardCallCount += 1
    addPaymentMethodDidAddCardPaymentMethod = paymentMethod
  }
  
  var enterAmountDidTapCloseCallCount = 0
  func enterAmountDidTapClose() {
    enterAmountDidTapCloseCallCount += 1
  }
  
  var enterAmountDidTapPaymentMethodCallCount = 0
  func enterAmountDidTapPaymentMethod() {
    enterAmountDidTapPaymentMethodCallCount += 1
  }
  
  var enterAmountDidFinishTopupCallCount = 0
  func enterAmountDidFinishTopup() {
    enterAmountDidFinishTopupCallCount += 1
  }
  
  var cardOnFileDidTapCloseCallCount = 0
  func cardOnFileDidTapClose() {
    cardOnFileDidTapCloseCallCount += 1
  }
  
  var cardOnFileDidTapAddCardCallCount = 0
  func cardOnFileDidTapAddCard() {
    cardOnFileDidTapAddCardCallCount += 1
  }
  
  var cardOnFileDidSelectCardCallCount = 0
  var cardOnFileDidSelectCardIndex: Int?
  func cardOnFileDidSelectCard(at index: Int) {
    cardOnFileDidSelectCardCallCount += 1
    cardOnFileDidSelectCardIndex = index
  }
  
  func activate() {
    
  }
  
  func deactivate() {
    
  }
  
  var isActive: Bool { isActiveSubject.value }
  var isActiveStream: AnyPublisher<Bool, Never> { isActiveSubject.eraseToAnyPublisher() }
  private let isActiveSubject = CurrentValueSubject<Bool, Never>(false)
}


================================================
FILE: completed/MiniSuperApp/.gitignore
================================================
xcuserdata/


================================================
FILE: completed/MiniSuperApp/AddPaymentMethodIntegrationTests/AddPaymentMethodIntegrationTests.swift
================================================
import XCTest
import Hammer
import FinanceRepository
import FinanceRepositoryTestSupport
import AddPaymentMethodTestSupport
import ModernRIBs
import RIBsUtil
import FinanceEntity
@testable import AddPaymentMethodImp

class AddPaymentMethodIntegrationTests: XCTestCase {
  
  private var eventGenerator: EventGenerator!
  private var dependency: AddPaymentMethodDependencyMock!
  private var listener: AddPaymentMethodListenerMock!
  private var viewController: UIViewController!
  private var router: Routing!
  
  private var repository: CardOnFileRepositoryMock {
    dependency.cardOnFileRepository as! CardOnFileRepositoryMock
  }
  
  override func setUpWithError() throws {
    try super.setUpWithError()
    
    self.dependency = AddPaymentMethodDependencyMock()
    self.listener = AddPaymentMethodListenerMock()
    
    let builder = AddPaymentMethodBuilder(dependency: self.dependency)
    let router = builder.build(withListener: self.listener, closeButtonType: .close)
    
    let navigation = NavigationControllerable(root: router.viewControllable)
    self.viewController = navigation.uiviewController
    
    eventGenerator = try EventGenerator(viewController: navigation.navigationController)
    
    router.load()
    router.interactable.activate()
    
    self.router = router
  }
  
  func testAddPaymentMethod() throws {
    // given
    repository.addedPaymentMethod = PaymentMethod(
      id: "1234",
      name: "",
      digits: "",
      color: "",
      isPrimary: false
    )
    
    let cardNumberTF = try eventGenerator.viewWithIdentifier("addpaymentmethod_cardnumber_textfield")
    try eventGenerator.fingerTap(at: cardNumberTF)
    try eventGenerator.keyType("1234123412341234")
    
    let cvc = try eventGenerator.viewWithIdentifier("addpaymentmethod_security_textfield")
    try eventGenerator.fingerTap(at: cvc)
    try eventGenerator.keyType("123")
    
    let expiry = try eventGenerator.viewWithIdentifier("addpaymentmethod_expiry_textfield")
    try eventGenerator.fingerTap(at: expiry)
    try eventGenerator.keyType("1212")
    
    // when
    let confirm = try eventGenerator.viewWithIdentifier("addpaymentmethod_addcard_button")
    try eventGenerator.fingerTap(at: confirm)
    
    // then
    XCTAssertEqual(repository.addCardCallCount, 1)
    try eventGenerator.wait(0.2)
    XCTAssertEqual(listener.addPaymentMethodDidAddCardCallCount, 1)
    XCTAssertEqual(listener.addPaymentMethodDidAddCardPaymentMethod?.id, "1234")
  }
}

final class AddPaymentMethodDependencyMock: AddPaymentMethodDependency {
  var cardOnFileRepository: CardOnFileRepository = CardOnFileRepositoryMock()
}


================================================
FILE: completed/MiniSuperApp/CX/.gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata


================================================
FILE: completed/MiniSuperApp/CX/Package.swift
================================================
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
  name: "CX",
  platforms: [.iOS(.v14)],
  products: [
    // Products define the executables and libraries a package produces, and make them visible to other packages.
    .library(
      name: "AppHome",
      targets: ["AppHome"]
    ),
  ],
  dependencies: [
    .package(name: "ModernRIBs", url: "https://github.com/DevYeom/ModernRIBs", .exact("1.0.1")),
    .package(path: "../Finance"),
    .package(path: "../Transport"),
    .package(path: "../Platform")
  ],
  targets: [
    // Targets are the basic building blocks of a package. A target can define a module or a test suite.
    // Targets can depend on other targets in this package, and on products in packages this package depends on.
    .target(
      name: "AppHome",
      dependencies: [
        "ModernRIBs",
        .product(name: "FinanceRepository", package: "Finance"),
        .product(name: "TransportHome", package: "Transport"),
        .product(name: "SuperUI", package: "Platform"),
      ]
    ),
  ]
)


================================================
FILE: completed/MiniSuperApp/CX/README.md
================================================
# CX

A description of this package.


================================================
FILE: completed/MiniSuperApp/CX/Sources/AppHome/AppHomeBuilder.swift
================================================
import ModernRIBs
import FinanceRepository
import TransportHome

public protocol AppHomeDependency: Dependency {
  var cardOnFileRepository: CardOnFileRepository { get }
  var superPayRepository: SuperPayRepository { get }
  var transportHomeBuildable: TransportHomeBuildable { get }
}

final class AppHomeComponent: Component<AppHomeDependency> {
  var cardOnFileRepository: CardOnFileRepository { dependency.cardOnFileRepository }
  var superPayRepository: SuperPayRepository { dependency.superPayRepository }
  var transportHomeBuildable: TransportHomeBuildable { dependency.transportHomeBuildable }
}

// MARK: - Builder

public protocol AppHomeBuildable: Buildable {
  func build(withListener listener: AppHomeListener) -> ViewableRouting
}

public final class AppHomeBuilder: Builder<AppHomeDependency>, AppHomeBuildable {
  
  public override init(dependency: AppHomeDependency) {
    super.init(dependency: dependency)
  }
  
  public func build(withListener listener: AppHomeListener) -> ViewableRouting {
    let component = AppHomeComponent(dependency: dependency)
    let viewController = AppHomeViewController()
    let interactor = AppHomeInteractor(presenter: viewController)
    interactor.listener = listener
    
    return AppHomeRouter(
      interactor: interactor,
      viewController: viewController,
      transportHomeBuildable: component.transportHomeBuildable
    )
  }
}


================================================
FILE: completed/MiniSuperApp/CX/Sources/AppHome/AppHomeInteractor.swift
================================================
import ModernRIBs

protocol AppHomeRouting: ViewableRouting {
  func attachTransportHome()
  func detachTransportHome()
}

protocol AppHomePresentable: Presentable {
  var listener: AppHomePresentableListener? { get set }
  
  func updateWidget(_ viewModels: [HomeWidgetViewModel])
}

public protocol AppHomeListener: AnyObject {
}

final class AppHomeInteractor: PresentableInteractor<AppHomePresentable>, AppHomeInteractable, AppHomePresentableListener {
  
  weak var router: AppHomeRouting?
  weak var listener: AppHomeListener?
  
  override init(presenter: AppHomePresentable) {
    super.init(presenter: presenter)
    presenter.listener = self
  }
  
  override func didBecomeActive() {
    super.didBecomeActive()
    
    let viewModels = [
      HomeWidgetModel(
        imageName: "car",
        title: "슈퍼택시",
        tapHandler: { [weak self] in
          self?.router?.attachTransportHome()
        }
      ),
      HomeWidgetModel(
        imageName: "cart",
        title: "슈퍼마트",
        tapHandler: { }
      )
    ]
    
    presenter.updateWidget(viewModels.map(HomeWidgetViewModel.init))
  }
  
  func transportHomeDidTapClose() {
    router?.detachTransportHome()
  }
  
}


================================================
FILE: completed/MiniSuperApp/CX/Sources/AppHome/AppHomeRouter.swift
================================================
import ModernRIBs
import SuperUI
import TransportHome

protocol AppHomeInteractable: Interactable, TransportHomeListener {
  var router: AppHomeRouting? { get set }
  var listener: AppHomeListener? { get set }
}

protocol AppHomeViewControllable: ViewControllable {

}

final class AppHomeRouter: ViewableRouter<AppHomeInteractable, AppHomeViewControllable>, AppHomeRouting {
  
  private let transportHomeBuildable: TransportHomeBuildable
  private var transportHomeRouting: Routing?
  private let transitioningDelegate: PushModalPresentationController
  
  init(
    interactor: AppHomeInteractable,
    viewController: AppHomeViewControllable,
    transportHomeBuildable: TransportHomeBuildable
  ) {
    self.transitioningDelegate = PushModalPresentationController()
    self.transportHomeBuildable = transportHomeBuildable
    super.init(interactor: interactor, viewController: viewController)
    interactor.router = self
  }
  
  func attachTransportHome() {
    if transportHomeRouting != nil {
      return
    }
    
    let router = transportHomeBuildable.build(withListener: interactor)
    presentWithPushTransition(router.viewControllable, animated: true)
    attachChild(router)
    self.transportHomeRouting = router
  }
  
  func detachTransportHome() {
    guard let router = transportHomeRouting else {
      return
    }
    
    viewController.dismiss(completion: nil)
    self.transportHomeRouting = nil
    detachChild(router)
  }
  
  private func presentWithPushTransition(_ viewControllable: ViewControllable, animated: Bool) {
    viewControllable.uiviewController.modalPresentationStyle = .custom
    viewControllable.uiviewController.transitioningDelegate = transitioningDelegate
    viewController.present(viewControllable, animated: true, completion: nil)
  }
  
}


================================================
FILE: completed/MiniSuperApp/CX/Sources/AppHome/AppHomeViewController.swift
================================================
import ModernRIBs
import UIKit

protocol AppHomePresentableListener: AnyObject {
}

final class AppHomeViewController: UIViewController, AppHomePresentable, AppHomeViewControllable {
  
  weak var listener: AppHomePresentableListener?
  
  private let widgetStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.axis = .horizontal
    stackView.distribution = .fillEqually
    stackView.alignment = .top
    stackView.spacing = 20
    return stackView
  }()
  
  init() {
    super.init(nibName: nil, bundle: nil)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  func updateWidget(_ viewModels: [HomeWidgetViewModel]) {
    let views = viewModels.map { HomeWidgetView(viewModel: $0) }
    
    views.forEach {
      $0.addShadowWithRoundedCorners(12)
      widgetStackView.addArrangedSubview($0)
    }
  }
  
  private func setupViews() {
    title = "홈"
    tabBarItem = UITabBarItem(title: "홈", image: UIImage(systemName: "house"), selectedImage: UIImage(systemName: "house.fill"))
    view.backgroundColor = .backgroundColor
    view.addSubview(widgetStackView)
    
    NSLayoutConstraint.activate([
      widgetStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
      widgetStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      widgetStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
    ])
  }
  
}


================================================
FILE: completed/MiniSuperApp/CX/Sources/AppHome/HomeWidgetModel.swift
================================================
import Foundation

struct HomeWidgetModel {
  let imageName: String
  let title: String
  let tapHandler: () -> Void
}


================================================
FILE: completed/MiniSuperApp/CX/Sources/AppHome/Views/HomeWidgetView.swift
================================================
import UIKit

struct HomeWidgetViewModel {
  let image: UIImage?
  let title: String
  let tapHandler: () -> Void
  
  init(_ model: HomeWidgetModel) {
    image = UIImage(systemName: model.imageName)
    title = model.title
    tapHandler = model.tapHandler
  }
}

final class HomeWidgetView: UIView {
  
  init(viewModel: HomeWidgetViewModel) {
    super.init(frame: .zero)
    
    setupViews()
    update(with: viewModel)
  }
  
  required init?(coder: NSCoder) {
    fatalError()
  }
  
  private var tapHandler: (() -> Void)?
  
  private func update(with viewModel: HomeWidgetViewModel) {
    imageView.image = viewModel.image
    titleLabel.text = viewModel.title
    tapHandler = viewModel.tapHandler
  }

  private let imageView: UIImageView = {
    let imageView = UIImageView()
    imageView.tintColor = .black
    imageView.translatesAutoresizingMaskIntoConstraints = false
    return imageView
  }()
  
  private let titleLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.textAlignment = .center
    label.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
    return label
  }()
  
  private func setupViews() {
    addSubview(imageView)
    addSubview(titleLabel)
    backgroundColor = .white
    
    let tap = UITapGestureRecognizer(target: self, action: #selector(didTap))
    addGestureRecognizer(tap)
    
    NSLayoutConstraint.activate([
      imageView.topAnchor.constraint(equalTo: self.topAnchor, constant: 15),
      imageView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
      imageView.widthAnchor.constraint(equalToConstant: 50),
      imageView.heightAnchor.constraint(equalToConstant: 50),
      
      titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 5),
      titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor),
      titleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor),
      titleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -14)
    ])
  }
  
  @objc
  private func didTap() {
    tapHandler?()
  }
  
}


================================================
FILE: completed/MiniSuperApp/Finance/.gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata


================================================
FILE: completed/MiniSuperApp/Finance/.swiftpm/xcode/xcshareddata/xcschemes/TopupImp.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1300"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "TopupImp"
               BuildableName = "TopupImp"
               BlueprintName = "TopupImp"
               ReferencedContainer = "container:">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES"
      codeCoverageEnabled = "YES">
      <Testables>
         <TestableReference
            skipped = "NO">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "TopupImpTests"
               BuildableName = "TopupImpTests"
               BlueprintName = "TopupImpTests"
               ReferencedContainer = "container:">
            </BuildableReference>
         </TestableReference>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "TopupImp"
            BuildableName = "TopupImp"
            BlueprintName = "TopupImp"
            ReferencedContainer = "container:">
         </BuildableReference>
      </MacroExpansion>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: completed/MiniSuperApp/Finance/.swiftpm/xcode/xcshareddata/xcschemes/TopupImpTests.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1300"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
         <TestableReference
            skipped = "NO">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "TopupImpTests"
               BuildableName = "TopupImpTests"
               BlueprintName = "TopupImpTests"
               ReferencedContainer = "container:">
            </BuildableReference>
         </TestableReference>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: completed/MiniSuperApp/Finance/Package.swift
================================================
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
  name: "Finance",
  platforms: [.iOS(.v14)],
  products: [
    .library(
      name: "AddPaymentMethod",
      targets: ["AddPaymentMethod"]
    ),
    .library(
      name: "AddPaymentMethodImp",
      targets: ["AddPaymentMethodImp"]
    ),
    .library(
      name: "AddPaymentMethodTestSupport",
      targets: ["AddPaymentMethodTestSupport"]
    ),
    .library(
      name: "Topup",
      targets: ["Topup"]
    ),
    .library(
      name: "TopupImp",
      targets: ["TopupImp"]
    ),
    .library(
      name: "TopupTestSupport",
      targets: ["TopupTestSupport"]
    ),
    .library(
      name: "FinanceHome",
      targets: ["FinanceHome"]
    ),
    .library(
      name: "FinanceEntity",
      targets: ["FinanceEntity"]
    ),
    .library(
      name: "FinanceRepository",
      targets: ["FinanceRepository"]
    ),
    .library(
      name: "FinanceRepositoryTestSupport",
      targets: ["FinanceRepositoryTestSupport"]
    ),
  ],
  dependencies: [
    .package(name: "ModernRIBs", url: "https://github.com/DevYeom/ModernRIBs", .exact("1.0.1")),
    .package(path: "../Platform")
  ],
  targets: [
    .target(
      name: "AddPaymentMethod",
      dependencies: [
        "ModernRIBs",
        "FinanceEntity",
        .product(name: "RIBsUtil", package: "Platform"),
      ]
    ),
    .target(
      name: "AddPaymentMethodImp",
      dependencies: [
        "ModernRIBs",
        "AddPaymentMethod",
        "FinanceEntity",
        "FinanceRepository",
        .product(name: "RIBsUtil", package: "Platform"),
        .product(name: "SuperUI", package: "Platform")
      ]
    ),
    .target(
      name: "AddPaymentMethodTestSupport",
      dependencies: [
        "ModernRIBs",
        "FinanceEntity",
        "AddPaymentMethod",
        .product(name: "RIBsUtil", package: "Platform"),
        .product(name: "RIBsTestSupport", package: "Platform"),
      ]
    ),
    .target(
      name: "Topup",
      dependencies: [
        "ModernRIBs"
      ]
    ),
    .target(
      name: "TopupImp",
      dependencies: [
        "ModernRIBs",
        "Topup",
        "FinanceEntity",
        "FinanceRepository",
        "AddPaymentMethod",
        .product(name: "RIBsUtil", package: "Platform"),
        .product(name: "SuperUI", package: "Platform")
      ]
    ),
    .target(
      name: "TopupTestSupport",
      dependencies: [
        "Topup"
      ]
    ),
    .target(
      name: "FinanceHome",
      dependencies: [
        "ModernRIBs",
        "FinanceEntity",
        "FinanceRepository",
        "AddPaymentMethod",
        "Topup",
        .product(name: "RIBsUtil", package: "Platform"),
        .product(name: "SuperUI", package: "Platform")
      ]
    ),
    .target(
      name: "FinanceEntity",
      dependencies: [
      ]
    ),
    .target(
      name: "FinanceRepository",
      dependencies: [
        "FinanceEntity",
        .product(name: "CombineUtil", package: "Platform"),
        .product(name: "Network", package: "Platform")
      ]
    ),
    .target(
      name: "FinanceRepositoryTestSupport",
      dependencies: [
        "FinanceEntity",
        "FinanceRepository",
        .product(name: "CombineUtil", package: "Platform")
      ]
    ),
    .testTarget(
      name: "TopupImpTests",
      dependencies: [
        "TopupImp",
        "FinanceRepositoryTestSupport",
        "TopupTestSupport",
        "AddPaymentMethodTestSupport",
        .product(name: "RIBsTestSupport", package: "Platform"),
        .product(name: "PlatformTestSupport", package: "Platform")
      ],
      exclude: [
        "EnterAmount/__Snapshots__",
        "CardOnFile/__Snapshots__"
      ]
    )
  ]
)


================================================
FILE: completed/MiniSuperApp/Finance/README.md
================================================
# Finance

A description of this package.


================================================
FILE: completed/MiniSuperApp/Finance/Sources/AddPaymentMethod/AddPaymentMethodInterface.swift
================================================
import Foundation
import ModernRIBs
import FinanceEntity
import RIBsUtil

public protocol AddPaymentMethodBuildable: Buildable {
  func build(withListener listener: AddPaymentMethodListener, closeButtonType: DismissButtonType) -> ViewableRouting
}

public protocol AddPaymentMethodListener: AnyObject {
  func addPaymentMethodDidTapClose()
  func addPaymentMethodDidAddCard(paymentMethod: PaymentMethod)
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/AddPaymentMethodImp/AddPaymentMethodBuilder.swift
================================================
import ModernRIBs
import FinanceRepository
import RIBsUtil
import AddPaymentMethod

public protocol AddPaymentMethodDependency: Dependency {
  var cardOnFileRepository: CardOnFileRepository { get }
}

final class AddPaymentMethodComponent: Component<AddPaymentMethodDependency>, AddPaymentMethodInteractorDependency {
  var cardOnFileRepository: CardOnFileRepository { dependency.cardOnFileRepository }
}

// MARK: - Builder

public final class AddPaymentMethodBuilder: Builder<AddPaymentMethodDependency>, AddPaymentMethodBuildable {
  
  public override init(dependency: AddPaymentMethodDependency) {
    super.init(dependency: dependency)
  }
  
  public func build(withListener listener: AddPaymentMethodListener, closeButtonType: DismissButtonType) -> ViewableRouting {
    let component = AddPaymentMethodComponent(dependency: dependency)
    let viewController = AddPaymentMethodViewController(closeButtonType: closeButtonType)
    let interactor = AddPaymentMethodInteractor(
      presenter: viewController,
      dependency: component
    )
    interactor.listener = listener
    return AddPaymentMethodRouter(interactor: interactor, viewController: viewController)
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/AddPaymentMethodImp/AddPaymentMethodInteractor.swift
================================================
import ModernRIBs
import Combine
import FinanceEntity
import FinanceRepository
import AddPaymentMethod
import Foundation

protocol AddPaymentMethodRouting: ViewableRouting {
}

protocol AddPaymentMethodPresentable: Presentable {
  var listener: AddPaymentMethodPresentableListener? { get set }
}

protocol AddPaymentMethodInteractorDependency {
  var cardOnFileRepository: CardOnFileRepository { get }
}

final class AddPaymentMethodInteractor: PresentableInteractor<AddPaymentMethodPresentable>, AddPaymentMethodInteractable, AddPaymentMethodPresentableListener {
  
  weak var router: AddPaymentMethodRouting?
  weak var listener: AddPaymentMethodListener?
  
  private let dependency: AddPaymentMethodInteractorDependency
  
  private var cancellables: Set<AnyCancellable>
  
  init(
    presenter: AddPaymentMethodPresentable,
    dependency: AddPaymentMethodInteractorDependency
  ) {
    self.dependency = dependency
    self.cancellables = .init()
    super.init(presenter: presenter)
    presenter.listener = self
  }
  
  override func didBecomeActive() {
    super.didBecomeActive()
  }
  
  override func willResignActive() {
    super.willResignActive()
  }
  
  func didTapClose() {
    listener?.addPaymentMethodDidTapClose()
  }
  
  func didTapConfirm(with number: String, cvc: String, expiry: String) {
    let info = AddPaymentMethodInfo(number: number, cvc: cvc, expiration: expiry)
    dependency.cardOnFileRepository.addCard(info: info)
      .receive(on: DispatchQueue.main)
      .sink(
        receiveCompletion: { _ in },
        receiveValue: { [weak self] method in
          self?.listener?.addPaymentMethodDidAddCard(paymentMethod: method)
        }
      ).store(in: &cancellables)
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/AddPaymentMethodImp/AddPaymentMethodRouter.swift
================================================
import ModernRIBs
import AddPaymentMethod

protocol AddPaymentMethodInteractable: Interactable {
  var router: AddPaymentMethodRouting? { get set }
  var listener: AddPaymentMethodListener? { get set }
}

protocol AddPaymentMethodViewControllable: ViewControllable {
}

final class AddPaymentMethodRouter: ViewableRouter<AddPaymentMethodInteractable, AddPaymentMethodViewControllable>, AddPaymentMethodRouting {
  
  override init(interactor: AddPaymentMethodInteractable, viewController: AddPaymentMethodViewControllable) {
    super.init(interactor: interactor, viewController: viewController)
    interactor.router = self
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/AddPaymentMethodImp/AddPaymentMethodViewController.swift
================================================
import ModernRIBs
import UIKit
import RIBsUtil
import SuperUI

protocol AddPaymentMethodPresentableListener: AnyObject {
  func didTapClose()
  func didTapConfirm(with number: String, cvc: String, expiry: String)
}

final class AddPaymentMethodViewController: UIViewController, AddPaymentMethodPresentable, AddPaymentMethodViewControllable {
  
  weak var listener: AddPaymentMethodPresentableListener?
  
  private let cardNumberTextField: UITextField = {
    let textField = makeTextField()
    textField.placeholder = "카드 번호"
    textField.accessibilityIdentifier = "addpaymentmethod_cardnumber_textfield"
    return textField
  }()
  
  private let stackView: UIStackView = {
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.axis = .horizontal
    stackView.alignment = .center
    stackView.distribution = .fillEqually
    stackView.spacing = 14
    return stackView
  }()
  
  private let securityTextField: UITextField = {
    let textField = makeTextField()
    textField.placeholder = "CVC"
    textField.accessibilityIdentifier = "addpaymentmethod_security_textfield"
    return textField
  }()
  
  private let expirationTextField: UITextField = {
    let textField = makeTextField()
    textField.placeholder = "유효기한"
    textField.accessibilityIdentifier = "addpaymentmethod_expiry_textfield"
    return textField
  }()
  
  private lazy var addCardButton: UIButton = {
    let button = UIButton()
    button.translatesAutoresizingMaskIntoConstraints = false
    button.roundCorners()
    button.backgroundColor = .primaryRed
    button.setTitle("추가하기", for: .normal)
    button.accessibilityIdentifier = "addpaymentmethod_addcard_button"
    button.addTarget(self, action: #selector(didTapAddCard), for: .touchUpInside)
    return button
  }()
  
  private static func makeTextField() -> UITextField {
    let textField = UITextField()
    textField.translatesAutoresizingMaskIntoConstraints = false
    textField.backgroundColor = .white
    textField.borderStyle = .roundedRect
    textField.keyboardType = .numberPad
    return textField
  }
  
  init(closeButtonType: DismissButtonType) {
    super.init(nibName: nil, bundle: nil)
    
    setupViews()
    setupNavigationItem(with: closeButtonType, target: self, action: #selector(didTapClose))
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
    setupNavigationItem(with: .close, target: self, action: #selector(didTapClose))
  }
  
  private func setupViews() {
    title = "카드 추가"
    
    view.backgroundColor = .backgroundColor
    view.addSubview(cardNumberTextField)
    view.addSubview(stackView)
    view.addSubview(addCardButton)
    
    stackView.addArrangedSubview(securityTextField)
    stackView.addArrangedSubview(expirationTextField)
    
    NSLayoutConstraint.activate([
      cardNumberTextField.topAnchor.constraint(equalTo: view.topAnchor, constant: 40),
      cardNumberTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
      cardNumberTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40),
      
      cardNumberTextField.bottomAnchor.constraint(equalTo: stackView.topAnchor, constant: -20),
      stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
      stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40),
      
      stackView.bottomAnchor.constraint(equalTo: addCardButton.topAnchor, constant: -20),
      addCardButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
      addCardButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40),
      
      cardNumberTextField.heightAnchor.constraint(equalToConstant: 60),
      securityTextField.heightAnchor.constraint(equalToConstant: 60),
      expirationTextField.heightAnchor.constraint(equalToConstant: 60),
      addCardButton.heightAnchor.constraint(equalToConstant: 60)
    ])
  }
  
  @objc
  private func didTapAddCard() {
    if
      let number = cardNumberTextField.text,
      let cvc = securityTextField.text,
      let expiry = expirationTextField.text {
      listener?.didTapConfirm(with: number, cvc: cvc, expiry: expiry)
    }
  }
  
  @objc
  private func didTapClose() {
    listener?.didTapClose()
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/AddPaymentMethodTestSupport/AddPaymentMethodTestSupport.swift
================================================
import Foundation
import AddPaymentMethod
import ModernRIBs
import RIBsUtil
import RIBsTestSupport
import FinanceEntity

public final class AddPaymentMethodBuildableMock: AddPaymentMethodBuildable {
  
  public var buildCallCount = 0
  public var closeButtonType: DismissButtonType?
  public func build(withListener listener: AddPaymentMethodListener, closeButtonType: DismissButtonType) -> ViewableRouting {
    buildCallCount += 1
    self.closeButtonType = closeButtonType
    
    return ViewableRoutingMock(
      interactable: Interactor(),
      viewControllable: ViewControllableMock()
    )
  }
  
  public init() {
    
  }
}

public final class AddPaymentMethodListenerMock: AddPaymentMethodListener {
  public var addPaymentMethodDidTapCloseCallCount = 0
  public func addPaymentMethodDidTapClose() {
    addPaymentMethodDidTapCloseCallCount += 1
  }
  
  public var addPaymentMethodDidAddCardCallCount = 0
  public var addPaymentMethodDidAddCardPaymentMethod: PaymentMethod?
  public func addPaymentMethodDidAddCard(paymentMethod: PaymentMethod) {
    addPaymentMethodDidAddCardCallCount += 1
    addPaymentMethodDidAddCardPaymentMethod = paymentMethod
  }
  
  public init() {
    
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceEntity/AddPaymentMethodInfo.swift
================================================
import Foundation

public struct AddPaymentMethodInfo {
  public let number: String
  public let cvc: String
  public let expiration: String
  
  public init(
    number: String,
    cvc: String,
    expiration: String
  ) {
    self.number = number
    self.cvc = cvc
    self.expiration = expiration
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceEntity/PaymentMethod.swift
================================================
import Foundation

public struct PaymentMethod: Decodable {
  public let id: String
  public let name: String
  public let digits: String
  public let color: String
  public let isPrimary: Bool
  
  public init(
    id: String,
    name: String,
    digits: String,
    color: String,
    isPrimary: Bool
  ) {
    self.id = id
    self.name = name
    self.digits = digits
    self.color = color
    self.isPrimary = isPrimary
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/CardOnFileDashboardBuilder.swift
================================================
import ModernRIBs
import FinanceRepository

protocol CardOnFileDashboardDependency: Dependency {
  var cardOnFileRepository: CardOnFileRepository { get }
}

final class CardOnFileDashboardComponent: Component<CardOnFileDashboardDependency>, CardOnFileDashboardInteractorDependency {
  var cardOnFileRepository: CardOnFileRepository { dependency.cardOnFileRepository }
}

// MARK: - Builder

protocol CardOnFileDashboardBuildable: Buildable {
  func build(withListener listener: CardOnFileDashboardListener) -> CardOnFileDashboardRouting
}

final class CardOnFileDashboardBuilder: Builder<CardOnFileDashboardDependency>, CardOnFileDashboardBuildable {
  
  override init(dependency: CardOnFileDashboardDependency) {
    super.init(dependency: dependency)
  }
  
  func build(withListener listener: CardOnFileDashboardListener) -> CardOnFileDashboardRouting {
    let component = CardOnFileDashboardComponent(dependency: dependency)
    let viewController = CardOnFileDashboardViewController()
    let interactor = CardOnFileDashboardInteractor(
      presenter: viewController,
      dependency: component
    )
    interactor.listener = listener
    return CardOnFileDashboardRouter(interactor: interactor, viewController: viewController)
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/CardOnFileDashboardInteractor.swift
================================================
import ModernRIBs
import Combine
import FinanceRepository
import Foundation

protocol CardOnFileDashboardRouting: ViewableRouting {
}

protocol CardOnFileDashboardPresentable: Presentable {
  var listener: CardOnFileDashboardPresentableListener? { get set }
  
  func update(with viewModels: [PaymentMethodViewModel])
}

protocol CardOnFileDashboardListener: AnyObject {
  func cardOnFileDashboardDidTapAddPaymentMethod()
}

protocol CardOnFileDashboardInteractorDependency {
  var cardOnFileRepository: CardOnFileRepository { get }
}

final class CardOnFileDashboardInteractor: PresentableInteractor<CardOnFileDashboardPresentable>, CardOnFileDashboardInteractable, CardOnFileDashboardPresentableListener {
  
  weak var router: CardOnFileDashboardRouting?
  weak var listener: CardOnFileDashboardListener?
  
  private let dependency: CardOnFileDashboardInteractorDependency
  
  private var cancellables: Set<AnyCancellable>
  
  init(
    presenter: CardOnFileDashboardPresentable,
    dependency: CardOnFileDashboardInteractorDependency
  ) {
    self.dependency = dependency
    self.cancellables = .init()
    super.init(presenter: presenter)
    presenter.listener = self
  }
  
  override func didBecomeActive() {
    super.didBecomeActive()
    
    dependency.cardOnFileRepository.cardOnFile
      .receive(on: DispatchQueue.main)
      .sink { methods in
        let viewModels = methods.prefix(5).map(PaymentMethodViewModel.init)
        self.presenter.update(with: viewModels)
      }.store(in: &cancellables)
  }
  
  override func willResignActive() {
    super.willResignActive()
    
    cancellables.forEach { $0.cancel() }
    cancellables.removeAll()
  }
  
  func didTapAddPaymentMethod() {
    listener?.cardOnFileDashboardDidTapAddPaymentMethod()
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/CardOnFileDashboardRouter.swift
================================================
import ModernRIBs

protocol CardOnFileDashboardInteractable: Interactable {
  var router: CardOnFileDashboardRouting? { get set }
  var listener: CardOnFileDashboardListener? { get set }
}

protocol CardOnFileDashboardViewControllable: ViewControllable {
}

final class CardOnFileDashboardRouter: ViewableRouter<CardOnFileDashboardInteractable, CardOnFileDashboardViewControllable>, CardOnFileDashboardRouting {
  
  override init(interactor: CardOnFileDashboardInteractable, viewController: CardOnFileDashboardViewControllable) {
    super.init(interactor: interactor, viewController: viewController)
    interactor.router = self
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/CardOnFileDashboardViewController.swift
================================================
import ModernRIBs
import UIKit

protocol CardOnFileDashboardPresentableListener: AnyObject {
  func didTapAddPaymentMethod()
}

final class CardOnFileDashboardViewController: UIViewController, CardOnFileDashboardPresentable, CardOnFileDashboardViewControllable {
  
  weak var listener: CardOnFileDashboardPresentableListener?
  
  private let headerStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.alignment = .fill
    stackView.distribution = .equalSpacing
    stackView.axis = .horizontal
    return stackView
  }()
  
  private let titleLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = .systemFont(ofSize: 22, weight: .semibold)
    label.text = "카드 및 계좌"
    return label
  }()
  
  private lazy var seeAllButton: UIButton = {
    let button = UIButton()
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setTitle("전체보기", for: .normal)
    button.setTitleColor(.systemBlue, for: .normal)
    button.addTarget(self, action: #selector(seeAllButtonTapped), for: .touchUpInside)
    return button
  }()
  
  private let cardOnFileStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.alignment = .fill
    stackView.distribution = .equalSpacing
    stackView.axis = .vertical
    stackView.spacing = 12
    return stackView
  }()
  
  private lazy var addMethodButton: AddPaymentMethodButton = {
    let button = AddPaymentMethodButton()
    button.translatesAutoresizingMaskIntoConstraints = false
    button.roundCorners()
    button.backgroundColor = .systemGray4
    button.addTarget(self, action: #selector(addButtonDidTap), for: .touchUpInside)
    return button
  }()
  
  init() {
    super.init(nibName: nil, bundle: nil)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  func update(with viewModels: [PaymentMethodViewModel]) {
    cardOnFileStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
    
    let views = viewModels.map(PaymentMethodView.init)
    
    views.forEach {
      $0.roundCorners()
      cardOnFileStackView.addArrangedSubview($0)
    }
    
    cardOnFileStackView.addArrangedSubview(addMethodButton)
    
    let heightConstraints = views.map { $0.heightAnchor.constraint(equalToConstant: 60) }
    NSLayoutConstraint.activate(heightConstraints)
  }
  
  private func setupViews() {
    view.addSubview(headerStackView)
    view.addSubview(cardOnFileStackView)
    
    headerStackView.addArrangedSubview(titleLabel)
    headerStackView.addArrangedSubview(seeAllButton)
    
    NSLayoutConstraint.activate([
      headerStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
      headerStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      headerStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
      
      cardOnFileStackView.topAnchor.constraint(equalTo: headerStackView.bottomAnchor, constant: 10),
      cardOnFileStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      cardOnFileStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
      cardOnFileStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
      
      addMethodButton.heightAnchor.constraint(equalToConstant: 60),
    ])
  }
  
  @objc
  private func seeAllButtonTapped() {
    
  }
  
  @objc
  private func addButtonDidTap() {
    listener?.didTapAddPaymentMethod()
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/PaymentMethodViewModel.swift
================================================
import UIKit
import FinanceEntity

struct PaymentMethodViewModel {
  let name: String
  let digits: String
  let color: UIColor
  
  init(_ paymentMethod: PaymentMethod) {
    name = paymentMethod.name
    digits = "**** \(paymentMethod.digits)"
    color = UIColor(hex: paymentMethod.color) ?? .systemGray2
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/Views/AddPaymentMethodButton.swift
================================================
import UIKit

final class AddPaymentMethodButton: UIControl {
  
  private let plusIcon: UIImageView = {
    let imageView = UIImageView(
      image: UIImage(
        systemName: "plus",
        withConfiguration: UIImage.SymbolConfiguration(pointSize: 24, weight: .semibold)
      )
    )
    imageView.tintColor = .white
    imageView.translatesAutoresizingMaskIntoConstraints = false
    
    return imageView
  }()
  
  init() {
    super.init(frame: .zero)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  private func setupViews() {
    addSubview(plusIcon)
    
    NSLayoutConstraint.activate([
      plusIcon.centerXAnchor.constraint(equalTo: self.centerXAnchor),
      plusIcon.centerYAnchor.constraint(equalTo: self.centerYAnchor),
    ])
  }
  
}



================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/Views/PaymentMethodView.swift
================================================
import UIKit

final class PaymentMethodView: UIView {
  
  private let nameLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = .systemFont(ofSize: 18, weight: .semibold)
    label.textColor = .white
    label.text = "우리은행"
    return label
  }()
  
  private let subtitleLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = .systemFont(ofSize: 15, weight: .regular)
    label.textColor = .white
    label.text = "**** 9999"
    return label
  }()
  
  init(viewModel: PaymentMethodViewModel) {
    super.init(frame: .zero)
    
    setupViews()
    
    nameLabel.text = viewModel.name
    subtitleLabel.text = viewModel.digits
    backgroundColor = viewModel.color
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  private func setupViews() {
    addSubview(nameLabel)
    addSubview(subtitleLabel)
    backgroundColor = .systemIndigo
    
    NSLayoutConstraint.activate([
      nameLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 24),
      nameLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
      
      subtitleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -24),
      subtitleLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor)
    ])
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/FinanceHomeBuilder.swift
================================================
import ModernRIBs
import FinanceRepository
import AddPaymentMethod
import CombineUtil
import Topup

public protocol FinanceHomeDependency: Dependency {
  var cardOnFileRepository: CardOnFileRepository { get }
  var superPayRepository: SuperPayRepository { get }
  var topupBuildable: TopupBuildable { get }
  var addPaymentMethodBuildable: AddPaymentMethodBuildable { get }
}

final class FinanceHomeComponent: Component<FinanceHomeDependency>, SuperPayDashboardDependency, CardOnFileDashboardDependency {
  var cardOnFileRepository: CardOnFileRepository { dependency.cardOnFileRepository }
  var superPayRepository: SuperPayRepository { dependency.superPayRepository }
  var balance: ReadOnlyCurrentValuePublisher<Double> { superPayRepository.balance }
  var topupBuildable: TopupBuildable { dependency.topupBuildable }
  var addPaymentMethodBuildable: AddPaymentMethodBuildable { dependency.addPaymentMethodBuildable }
}

// MARK: - Builder

public protocol FinanceHomeBuildable: Buildable {
  func build(withListener listener: FinanceHomeListener) -> ViewableRouting
}

public final class FinanceHomeBuilder: Builder<FinanceHomeDependency>, FinanceHomeBuildable {
  
  public override init(dependency: FinanceHomeDependency) {
    super.init(dependency: dependency)
  }
  
  public func build(withListener listener: FinanceHomeListener) -> ViewableRouting {
    let viewController = FinanceHomeViewController()
    
    let component = FinanceHomeComponent(
      dependency: dependency
    )
    
    let interactor = FinanceHomeInteractor(presenter: viewController)
    interactor.listener = listener
    
    let superPayDashboardBuilder = SuperPayDashboardBuilder(dependency: component)
    let cardOnFileDashboardBuilder = CardOnFileDashboardBuilder(dependency: component)
    
    return FinanceHomeRouter(
      interactor: interactor,
      viewController: viewController,
      superPayDashboardBuildable: superPayDashboardBuilder,
      cardOnFileDashboardBuildable: cardOnFileDashboardBuilder,
      addPaymentMethodBuildable: component.addPaymentMethodBuildable,
      topupBuildable: component.topupBuildable
    )
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/FinanceHomeInteractor.swift
================================================
import ModernRIBs
import UIKit
import SuperUI
import FinanceEntity

protocol FinanceHomeRouting: ViewableRouting {
  func attachSuperPayDashboard()
  func attachCardOnFileDashboard()
  func attachAddPaymentMethod()
  func detachAddPaymentMethod()
  func attachTopup()
  func detachTopup()
}

protocol FinanceHomePresentable: Presentable {
  var listener: FinanceHomePresentableListener? { get set }
}

public protocol FinanceHomeListener: AnyObject {
}

final class FinanceHomeInteractor: PresentableInteractor<FinanceHomePresentable>, FinanceHomeInteractable, FinanceHomePresentableListener, AdaptivePresentationControllerDelegate {
  
  weak var router: FinanceHomeRouting?
  weak var listener: FinanceHomeListener?
  
  let presentationDelegateProxy: AdaptivePresentationControllerDelegateProxy
  
  override init(presenter: FinanceHomePresentable) {
    self.presentationDelegateProxy = AdaptivePresentationControllerDelegateProxy()
    super.init(presenter: presenter)
    presenter.listener = self
    self.presentationDelegateProxy.delegate = self
  }
  
  override func didBecomeActive() {
    super.didBecomeActive()
    
    router?.attachSuperPayDashboard()
    router?.attachCardOnFileDashboard()
  }
  
  override func willResignActive() {
    super.willResignActive()
  }
  
  func presentationControllerDidDismiss() {
    router?.detachAddPaymentMethod()
  }
  
  // MARK: - CardOnFileDashboardListener
  func cardOnFileDashboardDidTapAddPaymentMethod() {
    router?.attachAddPaymentMethod()
  }
  
  // MARK: - AddPaymentMethodListener
  func addPaymentMethodDidTapClose() {
    router?.detachAddPaymentMethod()
  }
  
  func addPaymentMethodDidAddCard(paymentMethod: PaymentMethod) {
    router?.detachAddPaymentMethod()
  }
  
  func superPayDashboardDidTapTopup() {
    router?.attachTopup()
  }
  
  func topupDidClose() {
    router?.detachTopup()
  }
  
  func topupDidFinish() {
    router?.detachTopup()
  }
  
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/FinanceHomeRouter.swift
================================================
import ModernRIBs
import SuperUI
import AddPaymentMethod
import Topup
import RIBsUtil

protocol FinanceHomeInteractable: Interactable, SuperPayDashboardListener, CardOnFileDashboardListener, AddPaymentMethodListener, TopupListener {
  var router: FinanceHomeRouting? { get set }
  var listener: FinanceHomeListener? { get set }
  var presentationDelegateProxy: AdaptivePresentationControllerDelegateProxy { get }
}

protocol FinanceHomeViewControllable: ViewControllable {
  func addDashboard(_ view: ViewControllable)
}

final class FinanceHomeRouter: ViewableRouter<FinanceHomeInteractable, FinanceHomeViewControllable>, FinanceHomeRouting {
  
  private let superPayDashboardBuildable: SuperPayDashboardBuildable
  private var superPayRouting: Routing?
  
  private let cardOnFileDashboardBuildable: CardOnFileDashboardBuildable
  private var cardOnFileRouting: Routing?
  
  private let addPaymentMethodBuildable: AddPaymentMethodBuildable
  private var addPaymentMethodRouting: Routing?
  
  private let topupBuildable: TopupBuildable
  private var topupRouting: Routing?
  
  init(
    interactor: FinanceHomeInteractable,
    viewController: FinanceHomeViewControllable,
    superPayDashboardBuildable: SuperPayDashboardBuildable,
    cardOnFileDashboardBuildable: CardOnFileDashboardBuildable,
    addPaymentMethodBuildable: AddPaymentMethodBuildable,
    topupBuildable: TopupBuildable
  ) {
    self.superPayDashboardBuildable = superPayDashboardBuildable
    self.cardOnFileDashboardBuildable = cardOnFileDashboardBuildable
    self.addPaymentMethodBuildable = addPaymentMethodBuildable
    self.topupBuildable = topupBuildable
    super.init(interactor: interactor, viewController: viewController)
    interactor.router = self
  }
  
  func attachSuperPayDashboard() {
    if superPayRouting != nil {
      return
    }
    
    let router = superPayDashboardBuildable.build(withListener: interactor)
    
    let dashboard = router.viewControllable
    viewController.addDashboard(dashboard)
    
    self.superPayRouting = router
    attachChild(router)
  }
  
  func attachCardOnFileDashboard() {
    if cardOnFileRouting != nil {
      return
    }
    
    let router = cardOnFileDashboardBuildable.build(withListener: interactor)
    let dashboard = router.viewControllable
    viewController.addDashboard(dashboard)
    
    self.cardOnFileRouting = router
    attachChild(router)
  }
  
  func attachAddPaymentMethod() {
    if addPaymentMethodRouting != nil {
      return
    }
    
    let router = addPaymentMethodBuildable.build(withListener: interactor, closeButtonType: .close)
    let navigation = NavigationControllerable(root: router.viewControllable)
    navigation.navigationController.presentationController?.delegate = interactor.presentationDelegateProxy
    viewControllable.present(navigation, animated: true, completion: nil)
    
    addPaymentMethodRouting = router
    attachChild(router)
  }
  
  func detachAddPaymentMethod() {
    guard let router = addPaymentMethodRouting else {
      return
    }
    
    viewControllable.dismiss(completion: nil)
    
    detachChild(router)
    addPaymentMethodRouting = nil
  }

  func attachTopup() {
    if topupRouting != nil {
      return
    }
    
    let router = topupBuildable.build(withListener: interactor)
    topupRouting = router
    attachChild(router)
  }
  
  func detachTopup() {
    guard let router = topupRouting else {
      return
    }
    
    detachChild(router)
    self.topupRouting = nil
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/FinanceHomeViewController.swift
================================================
import ModernRIBs
import UIKit

protocol FinanceHomePresentableListener: AnyObject {
}

final class FinanceHomeViewController: UIViewController, FinanceHomePresentable, FinanceHomeViewControllable {
  
  weak var listener: FinanceHomePresentableListener?
  
  private let stackView: UIStackView = {
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.axis = .vertical
    stackView.alignment = .fill
    stackView.distribution = .equalSpacing
    stackView.spacing = 4
    return stackView
  }()
  
  init() {
    super.init(nibName: nil, bundle: nil)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  func setupViews() {
    title = "슈퍼페이"
    tabBarItem = UITabBarItem(title: "슈퍼페이", image: UIImage(systemName: "creditcard"), selectedImage: UIImage(systemName: "creditcard.fill"))
    tabBarItem.accessibilityIdentifier = "superpay_home_tab_bar_item"
    view.backgroundColor = .white
    view.addSubview(stackView)
    
    NSLayoutConstraint.activate([
      stackView.topAnchor.constraint(equalTo: view.topAnchor),
      stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
      stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
    ])
  }
  
  func addDashboard(_ view: ViewControllable) {
    let vc = view.uiviewController
    
    addChild(vc)
    stackView.addArrangedSubview(vc.view)
    vc.didMove(toParent: self)
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/SuperPayDashboard/Formatter.swift
================================================
import Foundation

struct Formatter {
  static let balanceFormatter: NumberFormatter = {
    let formatter = NumberFormatter()
    formatter.numberStyle = .decimal
    return formatter
  }()
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/SuperPayDashboard/SuperPayDashboardBuilder.swift
================================================
import ModernRIBs
import Foundation
import CombineUtil

protocol SuperPayDashboardDependency: Dependency {
  var balance: ReadOnlyCurrentValuePublisher<Double> { get }
}

final class SuperPayDashboardComponent: Component<SuperPayDashboardDependency>, SuperPayDashboardInteractorDependency {
  var balanceFormatter: NumberFormatter { Formatter.balanceFormatter }
  var balance: ReadOnlyCurrentValuePublisher<Double> { dependency.balance }
}

// MARK: - Builder

protocol SuperPayDashboardBuildable: Buildable {
  func build(withListener listener: SuperPayDashboardListener) -> SuperPayDashboardRouting
}

final class SuperPayDashboardBuilder: Builder<SuperPayDashboardDependency>, SuperPayDashboardBuildable {
  
  override init(dependency: SuperPayDashboardDependency) {
    super.init(dependency: dependency)
  }
  
  func build(withListener listener: SuperPayDashboardListener) -> SuperPayDashboardRouting {
    let component = SuperPayDashboardComponent(dependency: dependency)
    let viewController = SuperPayDashboardViewController()
    let interactor = SuperPayDashboardInteractor(
      presenter: viewController,
      dependency: component
    )
    interactor.listener = listener
    return SuperPayDashboardRouter(interactor: interactor, viewController: viewController)
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/SuperPayDashboard/SuperPayDashboardInteractor.swift
================================================
import ModernRIBs
import Combine
import Foundation
import CombineUtil

protocol SuperPayDashboardRouting: ViewableRouting {
}

protocol SuperPayDashboardPresentable: Presentable {
  var listener: SuperPayDashboardPresentableListener? { get set }
  
  func updateBalance(_ balance: String)
}

protocol SuperPayDashboardListener: AnyObject {
  func superPayDashboardDidTapTopup()
}

protocol SuperPayDashboardInteractorDependency {
  var balance: ReadOnlyCurrentValuePublisher<Double> { get }
  var balanceFormatter: NumberFormatter { get }
}

final class SuperPayDashboardInteractor: PresentableInteractor<SuperPayDashboardPresentable>, SuperPayDashboardInteractable, SuperPayDashboardPresentableListener {
  
  weak var router: SuperPayDashboardRouting?
  weak var listener: SuperPayDashboardListener?
  
  private let dependency: SuperPayDashboardInteractorDependency
  
  private var cancellables: Set<AnyCancellable>
  
  init(
    presenter: SuperPayDashboardPresentable,
    dependency: SuperPayDashboardInteractorDependency
  ) {
    self.dependency = dependency
    self.cancellables = .init()
    super.init(presenter: presenter)
    presenter.listener = self
  }
  
  override func didBecomeActive() {
    super.didBecomeActive()
    
    dependency.balance
      .receive(on: DispatchQueue.main)
      .sink { [weak self] balance in
      self?.dependency.balanceFormatter.string(from: NSNumber(value: balance)).map({
        self?.presenter.updateBalance($0)
      })
    }.store(in: &cancellables)
  }
  
  override func willResignActive() {
    super.willResignActive()
  }
  
  func topupButtonDidTap() {
    listener?.superPayDashboardDidTapTopup()
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/SuperPayDashboard/SuperPayDashboardRouter.swift
================================================
import ModernRIBs

protocol SuperPayDashboardInteractable: Interactable {
  var router: SuperPayDashboardRouting? { get set }
  var listener: SuperPayDashboardListener? { get set }
}

protocol SuperPayDashboardViewControllable: ViewControllable {
}

final class SuperPayDashboardRouter: ViewableRouter<SuperPayDashboardInteractable, SuperPayDashboardViewControllable>, SuperPayDashboardRouting {
  
  override init(interactor: SuperPayDashboardInteractable, viewController: SuperPayDashboardViewControllable) {
    super.init(interactor: interactor, viewController: viewController)
    interactor.router = self
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceHome/SuperPayDashboard/SuperPayDashboardViewController.swift
================================================
import ModernRIBs
import UIKit

protocol SuperPayDashboardPresentableListener: AnyObject {
  func topupButtonDidTap()
}

final class SuperPayDashboardViewController: UIViewController, SuperPayDashboardPresentable, SuperPayDashboardViewControllable {
  
  weak var listener: SuperPayDashboardPresentableListener?
  
  private let headerStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.alignment = .fill
    stackView.distribution = .equalSpacing
    stackView.axis = .horizontal
    return stackView
  }()
  
  private let titleLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = .systemFont(ofSize: 22, weight: .semibold)
    label.text = "슈퍼페이 잔고"
    return label
  }()
  
  private lazy var topupButton: UIButton = {
    let button = UIButton()
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setTitle("충전하기", for: .normal)
    button.setTitleColor(.systemBlue, for: .normal)
    button.accessibilityIdentifier = "superpay_dashboard_topup_button"
    button.addTarget(self, action: #selector(topupButtonDidTap), for: .touchUpInside)
    return button
  }()
  
  private let cardView: UIView = {
    let view = UIView()
    view.translatesAutoresizingMaskIntoConstraints = false
    view.layer.cornerRadius = 16
    view.layer.cornerCurve = .continuous
    view.backgroundColor = .systemIndigo
    return view
  }()
  
  private let currencyLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = .systemFont(ofSize: 22, weight: .semibold)
    label.text = "원"
    label.textColor = .white
    return label
  }()
  
  private let balanceAmountLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = .systemFont(ofSize: 22, weight: .semibold)
    label.accessibilityIdentifier = "superpay_dashboard_balance_label"
    label.textColor = .white
    return label
  }()
  
  private let balanceStackView: UIStackView = {
    let stack = UIStackView()
    stack.translatesAutoresizingMaskIntoConstraints = false
    stack.alignment = .fill
    stack.distribution = .equalSpacing
    stack.axis = .horizontal
    stack.spacing = 4
    return stack
  }()
  
  init() {
    super.init(nibName: nil, bundle: nil)
    
    setupViews()
  }
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  private func setupViews() {
    view.addSubview(headerStackView)
    view.addSubview(cardView)
    
    headerStackView.addArrangedSubview(titleLabel)
    headerStackView.addArrangedSubview(topupButton)
    
    cardView.addSubview(balanceStackView)
    balanceStackView.addArrangedSubview(balanceAmountLabel)
    balanceStackView.addArrangedSubview(currencyLabel)
    
    NSLayoutConstraint.activate([
      headerStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
      headerStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      headerStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
      
      cardView.topAnchor.constraint(equalTo: headerStackView.bottomAnchor, constant: 10),
      cardView.heightAnchor.constraint(equalToConstant: 180),
      cardView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      cardView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
      cardView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20),
      
      balanceStackView.centerXAnchor.constraint(equalTo: cardView.centerXAnchor),
      balanceStackView.centerYAnchor.constraint(equalTo: cardView.centerYAnchor)
    ])
  }
  
  func updateBalance(_ balance: String) {
    balanceAmountLabel.text = balance
  }
  
  @objc
  private func topupButtonDidTap() {
    listener?.topupButtonDidTap()
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceRepository/AddCardRequest.swift
================================================
import Foundation
import Network
import FinanceEntity

struct AddCardRequest: Request {
  typealias Output = AddCardResponse
  
  let endpoint: URL
  let method: HTTPMethod
  let query: QueryItems
  let header: HTTPHeader
  
  init(baseURL: URL, info: AddPaymentMethodInfo) {
    self.endpoint = baseURL.appendingPathComponent("/addCard")
    self.method = .post
    self.query = [
      "number": info.number,
      "cvc": info.cvc,
      "expiry": info.expiration
    ]
    self.header = [:]
  }
}

struct AddCardResponse: Decodable {
  let card: PaymentMethod
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceRepository/CardOnFileRepository.swift
================================================
import Foundation
import Combine
import FinanceEntity
import CombineUtil
import Network

public protocol CardOnFileRepository {
  var cardOnFile: ReadOnlyCurrentValuePublisher<[PaymentMethod]> { get }
  func addCard(info: AddPaymentMethodInfo) -> AnyPublisher<PaymentMethod, Error>
  func fetch()
}

public final class CardOnFileRepositoryImp: CardOnFileRepository {
  public var cardOnFile: ReadOnlyCurrentValuePublisher<[PaymentMethod]> { paymentMethodsSubject }
  
  private let paymentMethodsSubject = CurrentValuePublisher<[PaymentMethod]>([
//    PaymentMethod(id: "0", name: "우리은행", digits: "0123", color: "#f19a38ff", isPrimary: false),
//    PaymentMethod(id: "1", name: "신한카드", digits: "0987", color: "#3478f6ff", isPrimary: false),
//    PaymentMethod(id: "2", name: "현대카드", digits: "8121", color: "#78c5f5ff", isPrimary: false),
//    PaymentMethod(id: "3", name: "국민은행", digits: "2812", color: "#65c466ff", isPrimary: false),
//    PaymentMethod(id: "4", name: "카카오뱅크", digits: "8751", color: "#ffcc00ff", isPrimary: false)
  ])
  
  public func addCard(info: AddPaymentMethodInfo) -> AnyPublisher<PaymentMethod, Error> {
    let request = AddCardRequest(baseURL: baseURL, info: info)
    return network.send(request)
      .map(\.output.card)
      .handleEvents(
        receiveSubscription: nil,
        receiveOutput: { [weak self] method in
          guard let this = self else {
            return
          }
          this.paymentMethodsSubject.send(this.paymentMethodsSubject.value + [method])
        },
        receiveCompletion: nil,
        receiveCancel: nil,
        receiveRequest: nil
      )
      .eraseToAnyPublisher()
  }
  
  public func fetch() {
    let request = CardOnFileRequest(baseURL: baseURL)
    network.send(request).map(\.output.cards)
      .sink(
        receiveCompletion: { _ in },
        receiveValue: { [weak self] cards in
          self?.paymentMethodsSubject.send(cards)
        }
      ).store(in: &cancellables)
  }
  
  private let network: Network
  private let baseURL: URL
  private var cancellables: Set<AnyCancellable>
  
  public init(network: Network, baseURL: URL) {
    self.network = network
    self.baseURL = baseURL
    self.cancellables = .init()
  }
  
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceRepository/CardOnFileRequest.swift
================================================
import Foundation
import Network
import FinanceEntity

struct CardOnFileRequest: Request {
  typealias Output = CardOnFileResponse
  
  let endpoint: URL
  let method: HTTPMethod
  let query: QueryItems
  let header: HTTPHeader
  
  init(baseURL: URL) {
    self.endpoint = baseURL.appendingPathComponent("/cards")
    self.method = .get
    self.query = [:]
    self.header = [:]
  }
}

struct CardOnFileResponse: Decodable {
  let cards: [PaymentMethod]
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceRepository/SuperPayRepository.swift
================================================
import Foundation
import Combine
import CombineUtil
import Network

public protocol SuperPayRepository {
  var balance: ReadOnlyCurrentValuePublisher<Double> { get }
  func topup(amount: Double, paymentMethodID: String) -> AnyPublisher<Void, Error>
}

public final class SuperPayRepositoryImp: SuperPayRepository {
  
  public var balance: ReadOnlyCurrentValuePublisher<Double> { balanceSubject }
  private let balanceSubject = CurrentValuePublisher<Double>(0)
  
  public func topup(amount: Double, paymentMethodID: String) -> AnyPublisher<Void, Error> {
    let request = TopupRequest(baseURL: baseURL, amount: amount, paymentMethodID: paymentMethodID)
    
    return network.send(request)
      .handleEvents(
        receiveSubscription: nil,
        receiveOutput: { [weak self] _ in
          let newBalance = (self?.balanceSubject.value).map { $0 + amount }
          newBalance.map { self?.balanceSubject.send($0) }
        },
        receiveCompletion: nil,
        receiveCancel: nil,
        receiveRequest: nil
      )
      .map({ _ in })
      .eraseToAnyPublisher()
  }
  
  private let bgQueue = DispatchQueue(label: "topup.repository.queue")
  
  private let network: Network
  private let baseURL: URL
  
  public init(network: Network, baseURL: URL) {
    self.network = network
    self.baseURL = baseURL
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceRepository/TopupRequest.swift
================================================
import Foundation
import Network

struct TopupRequest: Request {
  typealias Output = TopupResponse
  
  let endpoint: URL
  let method: HTTPMethod
  let query: QueryItems
  let header: HTTPHeader
    
  init(baseURL: URL, amount: Double, paymentMethodID: String) {
    self.endpoint = baseURL.appendingPathComponent("/topup")
    self.method = .post
    self.query = [
      "amount": amount,
      "paymentMethodID": paymentMethodID
    ]
    self.header = [:]
  }
  
}

struct TopupResponse: Decodable {
  let status: String
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceRepositoryTestSupport/CardOnFileRepositoryMock.swift
================================================
import Foundation
import FinanceRepository
import CombineUtil
import Combine
import FinanceEntity

public final class CardOnFileRepositoryMock: CardOnFileRepository {
  
  public var cardOnFileSubject: CurrentValuePublisher<[PaymentMethod]> = .init([])
  public var cardOnFile: ReadOnlyCurrentValuePublisher<[PaymentMethod]> { cardOnFileSubject }
  
  public var addCardCallCount = 0
  public var addCardInfo: AddPaymentMethodInfo?
  public var addedPaymentMethod: PaymentMethod?
  public func addCard(info: AddPaymentMethodInfo) -> AnyPublisher<PaymentMethod, Error> {
    addCardCallCount += 1
    addCardInfo = info
    
    if let addedPaymentMethod = addedPaymentMethod {
      return Just(addedPaymentMethod).setFailureType(to: Error.self).eraseToAnyPublisher()
    } else {
      return Fail(error: NSError(domain: "CardOnFileRepositoryMock", code: 0, userInfo: nil)).eraseToAnyPublisher()
    }
  }
  
  public var fetchCallCount = 0
  public func fetch() {
    fetchCallCount += 1
  }
  
  public init() {
    
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/FinanceRepositoryTestSupport/SuperPayRepositoryMock.swift
================================================
import Foundation
import FinanceRepository
import CombineUtil
import Combine

public final class SuperPayRepositoryMock: SuperPayRepository {
  
  public var balanceSubject = CurrentValuePublisher<Double>(0)
  public var balance: ReadOnlyCurrentValuePublisher<Double> { balanceSubject }
  
  public var topupCallCount = 0
  public var topupAmount: Double?
  public var paymentMethodID: String?
  public var shouldTopupSucceed: Bool = true
  public func topup(amount: Double, paymentMethodID: String) -> AnyPublisher<Void, Error> {
    topupCallCount += 1
    topupAmount = amount
    self.paymentMethodID = paymentMethodID
    
    if shouldTopupSucceed {
      return Just(()).setFailureType(to: Error.self).eraseToAnyPublisher()
    } else {
      return Fail(error: NSError(domain: "SuperPayRepositoryMock", code: 0, userInfo: nil)).eraseToAnyPublisher()
    }
  }
  
  public init() {
    
  }
  
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/Topup/TopupInterface.swift
================================================
import Foundation
import ModernRIBs

public protocol TopupBuildable: Buildable {
  func build(withListener listener: TopupListener) -> Routing
}

public protocol TopupListener: AnyObject {
  func topupDidClose()
  func topupDidFinish()
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/TopupImp/Array+Utils.swift
================================================
import Foundation

extension Array {
  subscript(safe index: Int) -> Element? {
    return indices ~= index ? self[index] : nil
  }
}



================================================
FILE: completed/MiniSuperApp/Finance/Sources/TopupImp/CardOnFile/CardOnFileBuilder.swift
================================================
import ModernRIBs
import FinanceEntity

protocol CardOnFileDependency: Dependency {
}

final class CardOnFileComponent: Component<CardOnFileDependency> {
}

// MARK: - Builder

protocol CardOnFileBuildable: Buildable {
  func build(withListener listener: CardOnFileListener, paymentMethods: [PaymentMethod]) -> CardOnFileRouting
}

final class CardOnFileBuilder: Builder<CardOnFileDependency>, CardOnFileBuildable {
  
  override init(dependency: CardOnFileDependency) {
    super.init(dependency: dependency)
  }
  
  func build(withListener listener: CardOnFileListener, paymentMethods: [PaymentMethod]) -> CardOnFileRouting {
    _ = CardOnFileComponent(dependency: dependency)
    let viewController = CardOnFileViewController()
    let interactor = CardOnFileInteractor(presenter: viewController, paymentMethods: paymentMethods)
    interactor.listener = listener
    return CardOnFileRouter(interactor: interactor, viewController: viewController)
  }
}


================================================
FILE: completed/MiniSuperApp/Finance/Sources/TopupImp/CardOnFile/CardOnFileCell.swift
================================================
import UIKit

final class CardOnFileCell: UITableViewCell {
  
  func setImage(_ image: UIImage?) {
    thumbnailView.image = image
  }
  
  func setTitle(_ title: String) {
    titleLabel.text = title
  }
  
  private let thumbnailView: UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFill
    imageView.clipsToBounds = true
    imageView.roundCorners(4)
    return imageView
  }()
  
  private let titleLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.numberOfLines = 1
    return label
  }()
  
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    
    setupViews()
  }
  
  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    
    setupViews()
  }
  
  private func setupViews() {
    contentView.addSubview(thumbnailView)
    contentView.addSubview(titleLabel)
    
    NSLayoutConstraint.activate([
      thumbnailView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
      thumbnailView.widthAnchor.constraint(equalToConstant: 46),
Download .txt
gitextract_mzfutfxp/

├── .gitignore
├── MiniSuperApp/
│   ├── AppDelegate/
│   │   ├── AppComponent.swift
│   │   └── AppDelegate.swift
│   ├── AppHome/
│   │   ├── AppHomeBuilder.swift
│   │   ├── AppHomeInteractor.swift
│   │   ├── AppHomeRouter.swift
│   │   ├── AppHomeViewController.swift
│   │   ├── HomeWidgetModel.swift
│   │   └── Views/
│   │       └── HomeWidgetView.swift
│   ├── AppRoot/
│   │   ├── AppRootBuilder.swift
│   │   ├── AppRootInteractor.swift
│   │   ├── AppRootRouter.swift
│   │   └── RootTabBarController.swift
│   ├── Assets.xcassets/
│   │   ├── AccentColor.colorset/
│   │   │   └── Contents.json
│   │   ├── AppIcon.appiconset/
│   │   │   └── Contents.json
│   │   ├── Contents.json
│   │   └── map_seoul.imageset/
│   │       └── Contents.json
│   ├── Base.lproj/
│   │   └── LaunchScreen.storyboard
│   ├── FinanceHome/
│   │   ├── FinanceHomeBuilder.swift
│   │   ├── FinanceHomeInteractor.swift
│   │   ├── FinanceHomeRouter.swift
│   │   └── FinanceHomeViewController.swift
│   ├── Info.plist
│   ├── ProfileHome/
│   │   ├── ProfileHomeBuilder.swift
│   │   ├── ProfileHomeInteractor.swift
│   │   ├── ProfileHomeRouter.swift
│   │   └── ProfileHomeViewController.swift
│   ├── TransportHome/
│   │   ├── TransportHomeBuilder.swift
│   │   ├── TransportHomeInteractor.swift
│   │   ├── TransportHomeRouter.swift
│   │   ├── TransportHomeViewController.swift
│   │   └── Views/
│   │       ├── RideTypeView.swift
│   │       └── SuperPayView.swift
│   └── Utils/
│       ├── Array+Utils.swift
│       ├── PushModalPresentationController.swift
│       ├── RIBs+Utils.swift
│       ├── UIColor+Super.swift
│       ├── UIColor+Utils.swift
│       ├── UIImage+Utils.swift
│       ├── UITableView+Utils.swift
│       └── UIView+Utils.swift
├── MiniSuperApp.xcodeproj/
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   ├── contents.xcworkspacedata
│   │   └── xcshareddata/
│   │       ├── IDEWorkspaceChecks.plist
│   │       └── swiftpm/
│   │           └── Package.resolved
│   └── xcshareddata/
│       └── xcschemes/
│           └── MiniSuperApp.xcscheme
├── README.md
├── Samples/
│   ├── DefaultsStore/
│   │   └── DefaultsStore.swift
│   ├── Network/
│   │   ├── HTTPMethod.swift
│   │   ├── Network.swift
│   │   └── NetworkError.swift
│   ├── NetworkImp/
│   │   └── NetworkImp.swift
│   ├── RIBsTestSupport/
│   │   ├── RoutingMock.swift
│   │   ├── ViewControllableMock.swift
│   │   └── ViewableRoutingMock.swift
│   ├── TestUtil.swift
│   ├── Topup/
│   │   ├── CardOnFileCell.swift
│   │   ├── CardOnFileViewController.swift
│   │   ├── EnterAmountViewController.swift
│   │   └── Views/
│   │       ├── EnterAmountWidget.swift
│   │       └── SelectedPaymentMethodView.swift
│   └── TopupDependencyMock.swift
└── completed/
    └── MiniSuperApp/
        ├── .gitignore
        ├── AddPaymentMethodIntegrationTests/
        │   └── AddPaymentMethodIntegrationTests.swift
        ├── CX/
        │   ├── .gitignore
        │   ├── Package.swift
        │   ├── README.md
        │   └── Sources/
        │       └── AppHome/
        │           ├── AppHomeBuilder.swift
        │           ├── AppHomeInteractor.swift
        │           ├── AppHomeRouter.swift
        │           ├── AppHomeViewController.swift
        │           ├── HomeWidgetModel.swift
        │           └── Views/
        │               └── HomeWidgetView.swift
        ├── Finance/
        │   ├── .gitignore
        │   ├── .swiftpm/
        │   │   └── xcode/
        │   │       └── xcshareddata/
        │   │           └── xcschemes/
        │   │               ├── TopupImp.xcscheme
        │   │               └── TopupImpTests.xcscheme
        │   ├── Package.swift
        │   ├── README.md
        │   ├── Sources/
        │   │   ├── AddPaymentMethod/
        │   │   │   └── AddPaymentMethodInterface.swift
        │   │   ├── AddPaymentMethodImp/
        │   │   │   ├── AddPaymentMethodBuilder.swift
        │   │   │   ├── AddPaymentMethodInteractor.swift
        │   │   │   ├── AddPaymentMethodRouter.swift
        │   │   │   └── AddPaymentMethodViewController.swift
        │   │   ├── AddPaymentMethodTestSupport/
        │   │   │   └── AddPaymentMethodTestSupport.swift
        │   │   ├── FinanceEntity/
        │   │   │   ├── AddPaymentMethodInfo.swift
        │   │   │   └── PaymentMethod.swift
        │   │   ├── FinanceHome/
        │   │   │   ├── CardOnFileDashboard/
        │   │   │   │   ├── CardOnFileDashboardBuilder.swift
        │   │   │   │   ├── CardOnFileDashboardInteractor.swift
        │   │   │   │   ├── CardOnFileDashboardRouter.swift
        │   │   │   │   ├── CardOnFileDashboardViewController.swift
        │   │   │   │   ├── PaymentMethodViewModel.swift
        │   │   │   │   └── Views/
        │   │   │   │       ├── AddPaymentMethodButton.swift
        │   │   │   │       └── PaymentMethodView.swift
        │   │   │   ├── FinanceHomeBuilder.swift
        │   │   │   ├── FinanceHomeInteractor.swift
        │   │   │   ├── FinanceHomeRouter.swift
        │   │   │   ├── FinanceHomeViewController.swift
        │   │   │   └── SuperPayDashboard/
        │   │   │       ├── Formatter.swift
        │   │   │       ├── SuperPayDashboardBuilder.swift
        │   │   │       ├── SuperPayDashboardInteractor.swift
        │   │   │       ├── SuperPayDashboardRouter.swift
        │   │   │       └── SuperPayDashboardViewController.swift
        │   │   ├── FinanceRepository/
        │   │   │   ├── AddCardRequest.swift
        │   │   │   ├── CardOnFileRepository.swift
        │   │   │   ├── CardOnFileRequest.swift
        │   │   │   ├── SuperPayRepository.swift
        │   │   │   └── TopupRequest.swift
        │   │   ├── FinanceRepositoryTestSupport/
        │   │   │   ├── CardOnFileRepositoryMock.swift
        │   │   │   └── SuperPayRepositoryMock.swift
        │   │   ├── Topup/
        │   │   │   └── TopupInterface.swift
        │   │   ├── TopupImp/
        │   │   │   ├── Array+Utils.swift
        │   │   │   ├── CardOnFile/
        │   │   │   │   ├── CardOnFileBuilder.swift
        │   │   │   │   ├── CardOnFileCell.swift
        │   │   │   │   ├── CardOnFileInteractor.swift
        │   │   │   │   ├── CardOnFileRouter.swift
        │   │   │   │   └── CardOnFileViewController.swift
        │   │   │   ├── EnterAmount/
        │   │   │   │   ├── EnterAmountBuilder.swift
        │   │   │   │   ├── EnterAmountInteractor.swift
        │   │   │   │   ├── EnterAmountRouter.swift
        │   │   │   │   ├── EnterAmountViewController.swift
        │   │   │   │   ├── EnterAmountWidget.swift
        │   │   │   │   └── SelectedPaymentMethodView.swift
        │   │   │   ├── Models/
        │   │   │   │   └── PaymentMethodViewModel.swift
        │   │   │   ├── TopupBuilder.swift
        │   │   │   ├── TopupInteractor.swift
        │   │   │   └── TopupRouter.swift
        │   │   └── TopupTestSupport/
        │   │       └── TopupMock.swift
        │   └── Tests/
        │       └── TopupImpTests/
        │           ├── CardOnFile/
        │           │   ├── CardOnFileMock.swift
        │           │   └── CardOnFileViewTests.swift
        │           ├── EnterAmount/
        │           │   ├── EnterAmountInteractorTests.swift
        │           │   ├── EnterAmountMock.swift
        │           │   ├── EnterAmountRouterTests.swift
        │           │   └── EnterAmountViewTests.swift
        │           └── Topup/
        │               ├── TopupInteractorTests.swift
        │               ├── TopupMock.swift
        │               └── TopupRouterTests.swift
        ├── MiniSuperApp/
        │   ├── AppDelegate/
        │   │   ├── AppComponent.swift
        │   │   └── AppDelegate.swift
        │   ├── AppRoot/
        │   │   ├── AppRootBuilder.swift
        │   │   ├── AppRootComponent.swift
        │   │   ├── AppRootInteractor.swift
        │   │   ├── AppRootRouter.swift
        │   │   ├── BaseURL.swift
        │   │   ├── RootTabBarController.swift
        │   │   ├── SetupURLProtocol.swift
        │   │   └── SuperAppURLProtocol.swift
        │   ├── Assets.xcassets/
        │   │   ├── AccentColor.colorset/
        │   │   │   └── Contents.json
        │   │   ├── AppIcon.appiconset/
        │   │   │   └── Contents.json
        │   │   └── Contents.json
        │   ├── Base.lproj/
        │   │   └── LaunchScreen.storyboard
        │   ├── Info.plist
        │   └── Utils/
        │       └── Array+Utils.swift
        ├── MiniSuperApp.xcodeproj/
        │   ├── project.pbxproj
        │   ├── project.xcworkspace/
        │   │   ├── contents.xcworkspacedata
        │   │   └── xcshareddata/
        │   │       ├── IDEWorkspaceChecks.plist
        │   │       └── swiftpm/
        │   │           └── Package.resolved
        │   └── xcshareddata/
        │       └── xcschemes/
        │           ├── AddPaymentMethodIntegrationTests.xcscheme
        │           ├── MiniSuperApp.xcscheme
        │           ├── MiniSuperAppUITests.xcscheme
        │           └── TestHost.xcscheme
        ├── MiniSuperAppUITests/
        │   ├── Response/
        │   │   ├── cardOnFile.json
        │   │   └── topupSuccessResponse.json
        │   ├── TestUtil.swift
        │   └── TopupImpUITests.swift
        ├── Platform/
        │   ├── .gitignore
        │   ├── Package.swift
        │   ├── README.md
        │   └── Sources/
        │       ├── CombineUtil/
        │       │   └── Combine+Utils.swift
        │       ├── DefaultsStore/
        │       │   └── DefaultsStore.swift
        │       ├── Network/
        │       │   ├── HTTPMethod.swift
        │       │   ├── Network.swift
        │       │   └── NetworkError.swift
        │       ├── NetworkImp/
        │       │   └── NetworkImp.swift
        │       ├── PlatformTestSupport/
        │       │   └── PlatformTestSupport.swift
        │       ├── RIBsTestSupport/
        │       │   ├── RoutingMock.swift
        │       │   ├── ViewControllableMock.swift
        │       │   └── ViewableRoutingMock.swift
        │       ├── RIBsUtil/
        │       │   └── RIBs+Util.swift
        │       └── SuperUI/
        │           ├── AdaptivePresentationControllerDelegate.swift
        │           ├── PushModalPresentationController.swift
        │           ├── UIColor+Super.swift
        │           ├── UIColor+Utils.swift
        │           ├── UIImage+Utils.swift
        │           ├── UITableView+Utils.swift
        │           ├── UIView+Utils.swift
        │           └── UIViewController+Utils.swift
        ├── Profile/
        │   ├── .gitignore
        │   ├── Package.swift
        │   ├── README.md
        │   └── Sources/
        │       └── ProfileHome/
        │           ├── ProfileHomeBuilder.swift
        │           ├── ProfileHomeInteractor.swift
        │           ├── ProfileHomeRouter.swift
        │           └── ProfileHomeViewController.swift
        ├── Samples/
        │   ├── TestUtil.swift
        │   └── TopupDependencyMock.swift
        ├── TestHost/
        │   ├── AppDelegate.swift
        │   ├── Assets.xcassets/
        │   │   ├── AccentColor.colorset/
        │   │   │   └── Contents.json
        │   │   ├── AppIcon.appiconset/
        │   │   │   └── Contents.json
        │   │   └── Contents.json
        │   ├── Base.lproj/
        │   │   └── LaunchScreen.storyboard
        │   └── Info.plist
        └── Transport/
            ├── .gitignore
            ├── Package.swift
            ├── README.md
            └── Sources/
                ├── TransportHome/
                │   └── TransportHomeInterface.swift
                └── TransportHomeImp/
                    ├── Formatter.swift
                    ├── TransportHomeBuilder.swift
                    ├── TransportHomeInteractor.swift
                    ├── TransportHomeRouter.swift
                    ├── TransportHomeViewController.swift
                    └── Views/
                        ├── RideTypeView.swift
                        └── SuperPayView.swift
Condensed preview — 212 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (411K chars).
[
  {
    "path": ".gitignore",
    "chars": 12,
    "preview": "xcuserdata/\n"
  },
  {
    "path": "MiniSuperApp/AppDelegate/AppComponent.swift",
    "chars": 179,
    "preview": "import Foundation\nimport ModernRIBs\n\nfinal class AppComponent: Component<EmptyDependency>, AppRootDependency {\n  \n  init"
  },
  {
    "path": "MiniSuperApp/AppDelegate/AppDelegate.swift",
    "chars": 725,
    "preview": "import UIKit\nimport ModernRIBs\n\n@main\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n  \n  var window: UIWindow?"
  },
  {
    "path": "MiniSuperApp/AppHome/AppHomeBuilder.swift",
    "chars": 1021,
    "preview": "import ModernRIBs\n\npublic protocol AppHomeDependency: Dependency {\n}\n\nfinal class AppHomeComponent: Component<AppHomeDep"
  },
  {
    "path": "MiniSuperApp/AppHome/AppHomeInteractor.swift",
    "chars": 1281,
    "preview": "import ModernRIBs\n\nprotocol AppHomeRouting: ViewableRouting {\n  func attachTransportHome()\n  func detachTransportHome()\n"
  },
  {
    "path": "MiniSuperApp/AppHome/AppHomeRouter.swift",
    "chars": 1761,
    "preview": "import ModernRIBs\n\n\nprotocol AppHomeInteractable: Interactable, TransportHomeListener {\n  var router: AppHomeRouting? { "
  },
  {
    "path": "MiniSuperApp/AppHome/AppHomeViewController.swift",
    "chars": 1547,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol AppHomePresentableListener: AnyObject {\n}\n\nfinal class AppHomeViewController: U"
  },
  {
    "path": "MiniSuperApp/AppHome/HomeWidgetModel.swift",
    "chars": 119,
    "preview": "import Foundation\n\nstruct HomeWidgetModel {\n  let imageName: String\n  let title: String\n  let tapHandler: () -> Void\n}\n"
  },
  {
    "path": "MiniSuperApp/AppHome/Views/HomeWidgetView.swift",
    "chars": 2099,
    "preview": "import UIKit\n\nstruct HomeWidgetViewModel {\n  let image: UIImage?\n  let title: String\n  let tapHandler: () -> Void\n  \n  i"
  },
  {
    "path": "MiniSuperApp/AppRoot/AppRootBuilder.swift",
    "chars": 1359,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol AppRootDependency: Dependency {\n  // TODO: Declare the set of dependencies requ"
  },
  {
    "path": "MiniSuperApp/AppRoot/AppRootInteractor.swift",
    "chars": 1106,
    "preview": "import Foundation\nimport ModernRIBs\n\nprotocol AppRootRouting: ViewableRouting {\n  func attachTabs()\n}\n\nprotocol AppRootP"
  },
  {
    "path": "MiniSuperApp/AppRoot/AppRootRouter.swift",
    "chars": 1870,
    "preview": "import ModernRIBs\n\nprotocol AppRootInteractable: Interactable,\n                              AppHomeListener,\n          "
  },
  {
    "path": "MiniSuperApp/AppRoot/RootTabBarController.swift",
    "chars": 566,
    "preview": "import UIKit\nimport ModernRIBs\n\nprotocol AppRootPresentableListener: AnyObject {\n  \n}\n\nfinal class RootTabBarController:"
  },
  {
    "path": "MiniSuperApp/Assets.xcassets/AccentColor.colorset/Contents.json",
    "chars": 123,
    "preview": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }"
  },
  {
    "path": "MiniSuperApp/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1591,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "MiniSuperApp/Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "MiniSuperApp/Assets.xcassets/map_seoul.imageset/Contents.json",
    "chars": 307,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n     "
  },
  {
    "path": "MiniSuperApp/Base.lproj/LaunchScreen.storyboard",
    "chars": 3304,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "MiniSuperApp/FinanceHome/FinanceHomeBuilder.swift",
    "chars": 1046,
    "preview": "import ModernRIBs\n\nprotocol FinanceHomeDependency: Dependency {\n  // TODO: Declare the set of dependencies required by t"
  },
  {
    "path": "MiniSuperApp/FinanceHome/FinanceHomeInteractor.swift",
    "chars": 1162,
    "preview": "import ModernRIBs\n\nprotocol FinanceHomeRouting: ViewableRouting {\n  // TODO: Declare methods the interactor can invoke t"
  },
  {
    "path": "MiniSuperApp/FinanceHome/FinanceHomeRouter.swift",
    "chars": 719,
    "preview": "import ModernRIBs\n\nprotocol FinanceHomeInteractable: Interactable {\n  var router: FinanceHomeRouting? { get set }\n  var "
  },
  {
    "path": "MiniSuperApp/FinanceHome/FinanceHomeViewController.swift",
    "chars": 1240,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol FinanceHomePresentableListener: AnyObject {\n  // TODO: Declare properties and m"
  },
  {
    "path": "MiniSuperApp/Info.plist",
    "chars": 1384,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "MiniSuperApp/ProfileHome/ProfileHomeBuilder.swift",
    "chars": 1046,
    "preview": "import ModernRIBs\n\nprotocol ProfileHomeDependency: Dependency {\n  // TODO: Declare the set of dependencies required by t"
  },
  {
    "path": "MiniSuperApp/ProfileHome/ProfileHomeInteractor.swift",
    "chars": 1162,
    "preview": "import ModernRIBs\n\nprotocol ProfileHomeRouting: ViewableRouting {\n  // TODO: Declare methods the interactor can invoke t"
  },
  {
    "path": "MiniSuperApp/ProfileHome/ProfileHomeRouter.swift",
    "chars": 719,
    "preview": "import ModernRIBs\n\nprotocol ProfileHomeInteractable: Interactable {\n  var router: ProfileHomeRouting? { get set }\n  var "
  },
  {
    "path": "MiniSuperApp/ProfileHome/ProfileHomeViewController.swift",
    "chars": 1212,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol ProfileHomePresentableListener: AnyObject {\n  // TODO: Declare properties and m"
  },
  {
    "path": "MiniSuperApp/TransportHome/TransportHomeBuilder.swift",
    "chars": 922,
    "preview": "import ModernRIBs\n\nprotocol TransportHomeDependency: Dependency {\n}\n\nfinal class TransportHomeComponent: Component<Trans"
  },
  {
    "path": "MiniSuperApp/TransportHome/TransportHomeInteractor.swift",
    "chars": 978,
    "preview": "import ModernRIBs\nimport Combine\nimport Foundation\n\nprotocol TransportHomeRouting: ViewableRouting {\n}\n\nprotocol Transpo"
  },
  {
    "path": "MiniSuperApp/TransportHome/TransportHomeRouter.swift",
    "chars": 672,
    "preview": "import ModernRIBs\n\nprotocol TransportHomeInteractable: Interactable {\n  var router: TransportHomeRouting? { get set }\n  "
  },
  {
    "path": "MiniSuperApp/TransportHome/TransportHomeViewController.swift",
    "chars": 7752,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol TransportHomePresentableListener: AnyObject {\n  func didTapBack()\n}\n\nfinal clas"
  },
  {
    "path": "MiniSuperApp/TransportHome/Views/RideTypeView.swift",
    "chars": 1893,
    "preview": "import UIKit\n\nfinal class RideTypeView: UIView {\n  \n  private let thumbnailView: UIImageView = {\n    let imageView = UII"
  },
  {
    "path": "MiniSuperApp/TransportHome/Views/SuperPayView.swift",
    "chars": 1707,
    "preview": "import UIKit\n\nfinal class SuperPayView: UIView {\n  \n  private let thumbnailView: UIImageView = {\n    let imageView = UII"
  },
  {
    "path": "MiniSuperApp/Utils/Array+Utils.swift",
    "chars": 135,
    "preview": "import Foundation\n\nextension Array {\n  subscript(safe index: Int) -> Element? {\n    return indices ~= index ? self[index"
  },
  {
    "path": "MiniSuperApp/Utils/PushModalPresentationController.swift",
    "chars": 3030,
    "preview": "import UIKit\n\npublic final class PushModalPresentationController: NSObject, UIViewControllerTransitioningDelegate {\n  \n "
  },
  {
    "path": "MiniSuperApp/Utils/RIBs+Utils.swift",
    "chars": 2289,
    "preview": "import UIKit\nimport ModernRIBs\n\nfinal class NavigationControllerable: ViewControllable {\n  \n  var uiviewController: UIVi"
  },
  {
    "path": "MiniSuperApp/Utils/UIColor+Super.swift",
    "chars": 148,
    "preview": "import UIKit\n\nextension UIColor {\n  static let backgroundColor = UIColor(hex: \"#F1F5F9FF\")!\n  static let primaryRed = UI"
  },
  {
    "path": "MiniSuperApp/Utils/UIColor+Utils.swift",
    "chars": 771,
    "preview": "import UIKit\n\nextension UIColor {\n  convenience init?(hex: String) {\n    let r, g, b, a: CGFloat\n    \n    if hex.hasPref"
  },
  {
    "path": "MiniSuperApp/Utils/UIImage+Utils.swift",
    "chars": 478,
    "preview": "import UIKit\n\npublic extension UIImage {\n  convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1))"
  },
  {
    "path": "MiniSuperApp/Utils/UITableView+Utils.swift",
    "chars": 715,
    "preview": "import UIKit\n\npublic protocol Reusable: AnyObject {\n  static var reuseIdentifier: String { get }\n}\n\npublic extension Reu"
  },
  {
    "path": "MiniSuperApp/Utils/UIView+Utils.swift",
    "chars": 640,
    "preview": "import UIKit\n\nextension UIView {\n  func addShadowWithRoundedCorners(\n    _ radius: CGFloat = 16,\n    shadowColor: CGColo"
  },
  {
    "path": "MiniSuperApp.xcodeproj/project.pbxproj",
    "chars": 30177,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 52;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "MiniSuperApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": "MiniSuperApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "MiniSuperApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
    "chars": 323,
    "preview": "{\n  \"object\": {\n    \"pins\": [\n      {\n        \"package\": \"ModernRIBs\",\n        \"repositoryURL\": \"https://github.com/DevY"
  },
  {
    "path": "MiniSuperApp.xcodeproj/xcshareddata/xcschemes/MiniSuperApp.xcscheme",
    "chars": 2919,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1250\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "README.md",
    "chars": 3149,
    "preview": "<a href=\"https://fastcampus.co.kr/dev_red_rsj?utm_source=soojin-github&utm_medium=readme&utm_campaign=soojin\">\n  <img sr"
  },
  {
    "path": "Samples/DefaultsStore/DefaultsStore.swift",
    "chars": 788,
    "preview": "import Foundation\n\npublic protocol DefaultsStore {\n  var isInitialLaunch: Bool { get set }\n  var lastNoticeDate: Double "
  },
  {
    "path": "Samples/Network/HTTPMethod.swift",
    "chars": 124,
    "preview": "import Foundation\n\npublic enum HTTPMethod: String, Encodable {\n  case get = \"GET\"\n  case post = \"POST\"\n  case put = \"PUT"
  },
  {
    "path": "Samples/Network/Network.swift",
    "chars": 649,
    "preview": "import Combine\nimport Foundation\n\npublic typealias QueryItems = [String: AnyHashable]\npublic typealias HTTPHeader = [Str"
  },
  {
    "path": "Samples/Network/NetworkError.swift",
    "chars": 87,
    "preview": "import Foundation\n\npublic enum NetworkError: Error {\n  case invalidURL(url: String?)\n}\n"
  },
  {
    "path": "Samples/NetworkImp/NetworkImp.swift",
    "chars": 2462,
    "preview": "import Foundation\nimport Network\nimport Combine\n\npublic final class NetworkImp: Network {\n  \n  private let session: URLS"
  },
  {
    "path": "Samples/RIBsTestSupport/RoutingMock.swift",
    "chars": 1436,
    "preview": "import Foundation\nimport ModernRIBs\nimport Combine\n\npublic final class RoutingMock: Routing {\n  \n  public var loadHandle"
  },
  {
    "path": "Samples/RIBsTestSupport/ViewControllableMock.swift",
    "chars": 649,
    "preview": "import Foundation\nimport ModernRIBs\nimport UIKit\n\npublic final class ViewControllableMock: UIViewController, ViewControl"
  },
  {
    "path": "Samples/RIBsTestSupport/ViewableRoutingMock.swift",
    "chars": 1685,
    "preview": "import Foundation\nimport ModernRIBs\nimport Combine\n\nopen class ViewableRoutingMock: ViewableRouting {\n  // Variables\n  p"
  },
  {
    "path": "Samples/TestUtil.swift",
    "chars": 347,
    "preview": "import Foundation\n\nenum TestUtilError: Error {\n  case fileNotFound\n}\n\nfinal class TestUtil {\n  static func path(for file"
  },
  {
    "path": "Samples/Topup/CardOnFileCell.swift",
    "chars": 1591,
    "preview": "import UIKit\n\nfinal class CardOnFileCell: UITableViewCell {\n  \n  func setImage(_ image: UIImage?) {\n    thumbnailView.im"
  },
  {
    "path": "Samples/Topup/CardOnFileViewController.swift",
    "chars": 2542,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol CardOnFilePresentableListener: AnyObject {\n  func didTapClose()\n  func didSelec"
  },
  {
    "path": "Samples/Topup/EnterAmountViewController.swift",
    "chars": 4128,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol EnterAmountPresentableListener: AnyObject {\n  func didTapClose()\n  func didTapP"
  },
  {
    "path": "Samples/Topup/Views/EnterAmountWidget.swift",
    "chars": 2380,
    "preview": "import UIKit\n\nfinal class EnterAmountWidget: UIView {\n  \n  var text: String? {\n    amountTextField.text\n  }\n\n  init() {\n"
  },
  {
    "path": "Samples/Topup/Views/SelectedPaymentMethodView.swift",
    "chars": 2411,
    "preview": "import UIKit\n\nstruct SelectedPaymentMethodViewModel {\n  let image: UIImage?\n  let name: String\n  \n  init(_ paymentMethod"
  },
  {
    "path": "Samples/TopupDependencyMock.swift",
    "chars": 4793,
    "preview": "@testable import TopupImp\nimport Foundation\nimport CombineUtil\nimport FinanceEntity\nimport FinanceRepositoryTestSupport\n"
  },
  {
    "path": "completed/MiniSuperApp/.gitignore",
    "chars": 12,
    "preview": "xcuserdata/\n"
  },
  {
    "path": "completed/MiniSuperApp/AddPaymentMethodIntegrationTests/AddPaymentMethodIntegrationTests.swift",
    "chars": 2639,
    "preview": "import XCTest\nimport Hammer\nimport FinanceRepository\nimport FinanceRepositoryTestSupport\nimport AddPaymentMethodTestSupp"
  },
  {
    "path": "completed/MiniSuperApp/CX/.gitignore",
    "chars": 126,
    "preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\nDerivedData/\n.swiftpm/xcode/package.xcworkspace/contents.xcworkspac"
  },
  {
    "path": "completed/MiniSuperApp/CX/Package.swift",
    "chars": 1160,
    "preview": "// swift-tools-version:5.5\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
  },
  {
    "path": "completed/MiniSuperApp/CX/README.md",
    "chars": 37,
    "preview": "# CX\n\nA description of this package.\n"
  },
  {
    "path": "completed/MiniSuperApp/CX/Sources/AppHome/AppHomeBuilder.swift",
    "chars": 1400,
    "preview": "import ModernRIBs\nimport FinanceRepository\nimport TransportHome\n\npublic protocol AppHomeDependency: Dependency {\n  var c"
  },
  {
    "path": "completed/MiniSuperApp/CX/Sources/AppHome/AppHomeInteractor.swift",
    "chars": 1196,
    "preview": "import ModernRIBs\n\nprotocol AppHomeRouting: ViewableRouting {\n  func attachTransportHome()\n  func detachTransportHome()\n"
  },
  {
    "path": "completed/MiniSuperApp/CX/Sources/AppHome/AppHomeRouter.swift",
    "chars": 1796,
    "preview": "import ModernRIBs\nimport SuperUI\nimport TransportHome\n\nprotocol AppHomeInteractable: Interactable, TransportHomeListener"
  },
  {
    "path": "completed/MiniSuperApp/CX/Sources/AppHome/AppHomeViewController.swift",
    "chars": 1547,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol AppHomePresentableListener: AnyObject {\n}\n\nfinal class AppHomeViewController: U"
  },
  {
    "path": "completed/MiniSuperApp/CX/Sources/AppHome/HomeWidgetModel.swift",
    "chars": 119,
    "preview": "import Foundation\n\nstruct HomeWidgetModel {\n  let imageName: String\n  let title: String\n  let tapHandler: () -> Void\n}\n"
  },
  {
    "path": "completed/MiniSuperApp/CX/Sources/AppHome/Views/HomeWidgetView.swift",
    "chars": 2099,
    "preview": "import UIKit\n\nstruct HomeWidgetViewModel {\n  let image: UIImage?\n  let title: String\n  let tapHandler: () -> Void\n  \n  i"
  },
  {
    "path": "completed/MiniSuperApp/Finance/.gitignore",
    "chars": 126,
    "preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\nDerivedData/\n.swiftpm/xcode/package.xcworkspace/contents.xcworkspac"
  },
  {
    "path": "completed/MiniSuperApp/Finance/.swiftpm/xcode/xcshareddata/xcschemes/TopupImp.xcscheme",
    "chars": 2730,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1300\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "completed/MiniSuperApp/Finance/.swiftpm/xcode/xcshareddata/xcschemes/TopupImpTests.xcscheme",
    "chars": 1780,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1300\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "completed/MiniSuperApp/Finance/Package.swift",
    "chars": 3833,
    "preview": "// swift-tools-version:5.5\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
  },
  {
    "path": "completed/MiniSuperApp/Finance/README.md",
    "chars": 42,
    "preview": "# Finance\n\nA description of this package.\n"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/AddPaymentMethod/AddPaymentMethodInterface.swift",
    "chars": 406,
    "preview": "import Foundation\nimport ModernRIBs\nimport FinanceEntity\nimport RIBsUtil\n\npublic protocol AddPaymentMethodBuildable: Bui"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/AddPaymentMethodImp/AddPaymentMethodBuilder.swift",
    "chars": 1182,
    "preview": "import ModernRIBs\nimport FinanceRepository\nimport RIBsUtil\nimport AddPaymentMethod\n\npublic protocol AddPaymentMethodDepe"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/AddPaymentMethodImp/AddPaymentMethodInteractor.swift",
    "chars": 1718,
    "preview": "import ModernRIBs\nimport Combine\nimport FinanceEntity\nimport FinanceRepository\nimport AddPaymentMethod\nimport Foundation"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/AddPaymentMethodImp/AddPaymentMethodRouter.swift",
    "chars": 631,
    "preview": "import ModernRIBs\nimport AddPaymentMethod\n\nprotocol AddPaymentMethodInteractable: Interactable {\n  var router: AddPaymen"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/AddPaymentMethodImp/AddPaymentMethodViewController.swift",
    "chars": 4336,
    "preview": "import ModernRIBs\nimport UIKit\nimport RIBsUtil\nimport SuperUI\n\nprotocol AddPaymentMethodPresentableListener: AnyObject {"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/AddPaymentMethodTestSupport/AddPaymentMethodTestSupport.swift",
    "chars": 1202,
    "preview": "import Foundation\nimport AddPaymentMethod\nimport ModernRIBs\nimport RIBsUtil\nimport RIBsTestSupport\nimport FinanceEntity\n"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceEntity/AddPaymentMethodInfo.swift",
    "chars": 308,
    "preview": "import Foundation\n\npublic struct AddPaymentMethodInfo {\n  public let number: String\n  public let cvc: String\n  public le"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceEntity/PaymentMethod.swift",
    "chars": 434,
    "preview": "import Foundation\n\npublic struct PaymentMethod: Decodable {\n  public let id: String\n  public let name: String\n  public l"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/CardOnFileDashboardBuilder.swift",
    "chars": 1245,
    "preview": "import ModernRIBs\nimport FinanceRepository\n\nprotocol CardOnFileDashboardDependency: Dependency {\n  var cardOnFileReposit"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/CardOnFileDashboardInteractor.swift",
    "chars": 1777,
    "preview": "import ModernRIBs\nimport Combine\nimport FinanceRepository\nimport Foundation\n\nprotocol CardOnFileDashboardRouting: Viewab"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/CardOnFileDashboardRouter.swift",
    "chars": 637,
    "preview": "import ModernRIBs\n\nprotocol CardOnFileDashboardInteractable: Interactable {\n  var router: CardOnFileDashboardRouting? { "
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/CardOnFileDashboardViewController.swift",
    "chars": 3683,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol CardOnFileDashboardPresentableListener: AnyObject {\n  func didTapAddPaymentMeth"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/PaymentMethodViewModel.swift",
    "chars": 314,
    "preview": "import UIKit\nimport FinanceEntity\n\nstruct PaymentMethodViewModel {\n  let name: String\n  let digits: String\n  let color: "
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/Views/AddPaymentMethodButton.swift",
    "chars": 836,
    "preview": "import UIKit\n\nfinal class AddPaymentMethodButton: UIControl {\n  \n  private let plusIcon: UIImageView = {\n    let imageVi"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/CardOnFileDashboard/Views/PaymentMethodView.swift",
    "chars": 1408,
    "preview": "import UIKit\n\nfinal class PaymentMethodView: UIView {\n  \n  private let nameLabel: UILabel = {\n    let label = UILabel()\n"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/FinanceHomeBuilder.swift",
    "chars": 2137,
    "preview": "import ModernRIBs\nimport FinanceRepository\nimport AddPaymentMethod\nimport CombineUtil\nimport Topup\n\npublic protocol Fina"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/FinanceHomeInteractor.swift",
    "chars": 1938,
    "preview": "import ModernRIBs\nimport UIKit\nimport SuperUI\nimport FinanceEntity\n\nprotocol FinanceHomeRouting: ViewableRouting {\n  fun"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/FinanceHomeRouter.swift",
    "chars": 3508,
    "preview": "import ModernRIBs\nimport SuperUI\nimport AddPaymentMethod\nimport Topup\nimport RIBsUtil\n\nprotocol FinanceHomeInteractable:"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/FinanceHomeViewController.swift",
    "chars": 1495,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol FinanceHomePresentableListener: AnyObject {\n}\n\nfinal class FinanceHomeViewContr"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/SuperPayDashboard/Formatter.swift",
    "chars": 193,
    "preview": "import Foundation\n\nstruct Formatter {\n  static let balanceFormatter: NumberFormatter = {\n    let formatter = NumberForma"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/SuperPayDashboard/SuperPayDashboardBuilder.swift",
    "chars": 1289,
    "preview": "import ModernRIBs\nimport Foundation\nimport CombineUtil\n\nprotocol SuperPayDashboardDependency: Dependency {\n  var balance"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/SuperPayDashboard/SuperPayDashboardInteractor.swift",
    "chars": 1670,
    "preview": "import ModernRIBs\nimport Combine\nimport Foundation\nimport CombineUtil\n\nprotocol SuperPayDashboardRouting: ViewableRoutin"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/SuperPayDashboard/SuperPayDashboardRouter.swift",
    "chars": 617,
    "preview": "import ModernRIBs\n\nprotocol SuperPayDashboardInteractable: Interactable {\n  var router: SuperPayDashboardRouting? { get "
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceHome/SuperPayDashboard/SuperPayDashboardViewController.swift",
    "chars": 3986,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol SuperPayDashboardPresentableListener: AnyObject {\n  func topupButtonDidTap()\n}\n"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceRepository/AddCardRequest.swift",
    "chars": 565,
    "preview": "import Foundation\nimport Network\nimport FinanceEntity\n\nstruct AddCardRequest: Request {\n  typealias Output = AddCardResp"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceRepository/CardOnFileRepository.swift",
    "chars": 2230,
    "preview": "import Foundation\nimport Combine\nimport FinanceEntity\nimport CombineUtil\nimport Network\n\npublic protocol CardOnFileRepos"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceRepository/CardOnFileRequest.swift",
    "chars": 458,
    "preview": "import Foundation\nimport Network\nimport FinanceEntity\n\nstruct CardOnFileRequest: Request {\n  typealias Output = CardOnFi"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceRepository/SuperPayRepository.swift",
    "chars": 1332,
    "preview": "import Foundation\nimport Combine\nimport CombineUtil\nimport Network\n\npublic protocol SuperPayRepository {\n  var balance: "
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceRepository/TopupRequest.swift",
    "chars": 530,
    "preview": "import Foundation\nimport Network\n\nstruct TopupRequest: Request {\n  typealias Output = TopupResponse\n  \n  let endpoint: U"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceRepositoryTestSupport/CardOnFileRepositoryMock.swift",
    "chars": 1026,
    "preview": "import Foundation\nimport FinanceRepository\nimport CombineUtil\nimport Combine\nimport FinanceEntity\n\npublic final class Ca"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/FinanceRepositoryTestSupport/SuperPayRepositoryMock.swift",
    "chars": 903,
    "preview": "import Foundation\nimport FinanceRepository\nimport CombineUtil\nimport Combine\n\npublic final class SuperPayRepositoryMock:"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/Topup/TopupInterface.swift",
    "chars": 238,
    "preview": "import Foundation\nimport ModernRIBs\n\npublic protocol TopupBuildable: Buildable {\n  func build(withListener listener: Top"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/Array+Utils.swift",
    "chars": 135,
    "preview": "import Foundation\n\nextension Array {\n  subscript(safe index: Int) -> Element? {\n    return indices ~= index ? self[index"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/CardOnFile/CardOnFileBuilder.swift",
    "chars": 959,
    "preview": "import ModernRIBs\nimport FinanceEntity\n\nprotocol CardOnFileDependency: Dependency {\n}\n\nfinal class CardOnFileComponent: "
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/CardOnFile/CardOnFileCell.swift",
    "chars": 1591,
    "preview": "import UIKit\n\nfinal class CardOnFileCell: UITableViewCell {\n  \n  func setImage(_ image: UIImage?) {\n    thumbnailView.im"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/CardOnFile/CardOnFileInteractor.swift",
    "chars": 1364,
    "preview": "import ModernRIBs\nimport FinanceEntity\n\nprotocol CardOnFileRouting: ViewableRouting {\n}\n\nprotocol CardOnFilePresentable:"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/CardOnFile/CardOnFileRouter.swift",
    "chars": 547,
    "preview": "import ModernRIBs\n\nprotocol CardOnFileInteractable: Interactable {\n  var router: CardOnFileRouting? { get set }\n  var li"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/CardOnFile/CardOnFileViewController.swift",
    "chars": 2550,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol CardOnFilePresentableListener: AnyObject {\n  func didTapClose()\n  func didSelec"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/EnterAmount/EnterAmountBuilder.swift",
    "chars": 1466,
    "preview": "import ModernRIBs\nimport CombineUtil\nimport FinanceEntity\nimport FinanceRepository\nimport CombineSchedulers\n\nprotocol En"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/EnterAmount/EnterAmountInteractor.swift",
    "chars": 2450,
    "preview": "import ModernRIBs\nimport Combine\nimport Foundation\nimport CombineUtil\nimport FinanceEntity\nimport FinanceRepository\nimpo"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/EnterAmount/EnterAmountRouter.swift",
    "chars": 557,
    "preview": "import ModernRIBs\n\nprotocol EnterAmountInteractable: Interactable {\n  var router: EnterAmountRouting? { get set }\n  var "
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/EnterAmount/EnterAmountViewController.swift",
    "chars": 4197,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol EnterAmountPresentableListener: AnyObject {\n  func didTapClose()\n  func didTapP"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/EnterAmount/EnterAmountWidget.swift",
    "chars": 2450,
    "preview": "import UIKit\n\nfinal class EnterAmountWidget: UIView {\n  \n  var text: String? {\n    amountTextField.text\n  }\n\n  init() {\n"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/EnterAmount/SelectedPaymentMethodView.swift",
    "chars": 2432,
    "preview": "import UIKit\nimport FinanceEntity\n\nstruct SelectedPaymentMethodViewModel {\n  let image: UIImage?\n  let name: String\n  \n "
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/Models/PaymentMethodViewModel.swift",
    "chars": 314,
    "preview": "import UIKit\nimport FinanceEntity\n\nstruct PaymentMethodViewModel {\n  let name: String\n  let digits: String\n  let color: "
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/TopupBuilder.swift",
    "chars": 2457,
    "preview": "import ModernRIBs\nimport FinanceRepository\nimport CombineUtil\nimport AddPaymentMethod\nimport FinanceEntity\nimport Topup\n"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/TopupInteractor.swift",
    "chars": 3018,
    "preview": "import ModernRIBs\nimport RIBsUtil\nimport FinanceEntity\nimport FinanceRepository\nimport CombineUtil\nimport AddPaymentMeth"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupImp/TopupRouter.swift",
    "chars": 4760,
    "preview": "import ModernRIBs\nimport AddPaymentMethod\nimport SuperUI\nimport RIBsUtil\nimport FinanceEntity\nimport Topup\n\nprotocol Top"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Sources/TopupTestSupport/TopupMock.swift",
    "chars": 346,
    "preview": "import Foundation\nimport Topup\n\npublic final class TopupListenerMock: TopupListener {\n  \n  public var topupDidCloseCallC"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Tests/TopupImpTests/CardOnFile/CardOnFileMock.swift",
    "chars": 678,
    "preview": "@testable import TopupImp\nimport Foundation\nimport RIBsTestSupport\nimport FinanceEntity\n\nfinal class CardOnFileBuildable"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Tests/TopupImpTests/CardOnFile/CardOnFileViewTests.swift",
    "chars": 767,
    "preview": "@testable import TopupImp\nimport XCTest\nimport Foundation\nimport SnapshotTesting\nimport FinanceEntity\n\nfinal class CardO"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Tests/TopupImpTests/EnterAmount/EnterAmountInteractorTests.swift",
    "chars": 2991,
    "preview": "@testable import TopupImp\nimport XCTest\nimport FinanceEntity\nimport FinanceRepositoryTestSupport\n\nfinal class EnterAmoun"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Tests/TopupImpTests/EnterAmount/EnterAmountMock.swift",
    "chars": 2294,
    "preview": "@testable import TopupImp\nimport Foundation\nimport CombineUtil\nimport FinanceEntity\nimport FinanceRepository\nimport Fina"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Tests/TopupImpTests/EnterAmount/EnterAmountRouterTests.swift",
    "chars": 195,
    "preview": "@testable import TopupImp\nimport XCTest\n\nfinal class EnterAmountRouterTests: XCTestCase {\n  \n  private var router: Enter"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Tests/TopupImpTests/EnterAmount/EnterAmountViewTests.swift",
    "chars": 1624,
    "preview": "import XCTest\nimport Foundation\n@testable import TopupImp\nimport SnapshotTesting\nimport FinanceEntity\n\nfinal class Enter"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Tests/TopupImpTests/Topup/TopupInteractorTests.swift",
    "chars": 4740,
    "preview": "@testable import TopupImp\nimport XCTest\nimport TopupTestSupport\nimport FinanceEntity\nimport FinanceRepositoryTestSupport"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Tests/TopupImpTests/Topup/TopupMock.swift",
    "chars": 4774,
    "preview": "import Foundation\nimport FinanceRepository\nimport FinanceRepositoryTestSupport\nimport CombineUtil\nimport FinanceEntity\ni"
  },
  {
    "path": "completed/MiniSuperApp/Finance/Tests/TopupImpTests/Topup/TopupRouterTests.swift",
    "chars": 2627,
    "preview": "@testable import TopupImp\nimport XCTest\nimport RIBsTestSupport\nimport AddPaymentMethodTestSupport\nimport ModernRIBs\n\nfin"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/AppDelegate/AppComponent.swift",
    "chars": 179,
    "preview": "import Foundation\nimport ModernRIBs\n\nfinal class AppComponent: Component<EmptyDependency>, AppRootDependency {\n  \n  init"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/AppDelegate/AppDelegate.swift",
    "chars": 725,
    "preview": "import UIKit\nimport ModernRIBs\n\n@main\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n  \n  var window: UIWindow?"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/AppRoot/AppRootBuilder.swift",
    "chars": 1167,
    "preview": "import ModernRIBs\nimport UIKit\nimport FinanceRepository\nimport FinanceHome\nimport AppHome\nimport ProfileHome\n\nprotocol A"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/AppRoot/AppRootComponent.swift",
    "chars": 1889,
    "preview": "import Foundation\nimport AppHome\nimport FinanceHome\nimport ProfileHome\nimport FinanceRepository\nimport ModernRIBs\nimport"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/AppRoot/AppRootInteractor.swift",
    "chars": 797,
    "preview": "import Foundation\nimport ModernRIBs\n\nprotocol AppRootRouting: ViewableRouting {\n  func attachTabs()\n}\n\nprotocol AppRootP"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/AppRoot/AppRootRouter.swift",
    "chars": 1939,
    "preview": "import ModernRIBs\nimport RIBsUtil\nimport FinanceHome\nimport AppHome\nimport ProfileHome\n\nprotocol AppRootInteractable: In"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/AppRoot/BaseURL.swift",
    "chars": 221,
    "preview": "import Foundation\n\nstruct BaseURL {\n  var financeBaseURL: URL {\n    #if UITESTING\n    return URL(string: \"http://localho"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/AppRoot/RootTabBarController.swift",
    "chars": 566,
    "preview": "import UIKit\nimport ModernRIBs\n\nprotocol AppRootPresentableListener: AnyObject {\n  \n}\n\nfinal class RootTabBarController:"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/AppRoot/SetupURLProtocol.swift",
    "chars": 1361,
    "preview": "import Foundation\n\nfunc setupURLProtocol() {\n  // Topup\n  let topupResponse: [String: Any] = [\n    \"status\": \"success\"\n "
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/AppRoot/SuperAppURLProtocol.swift",
    "chars": 1286,
    "preview": "import Foundation\n\ntypealias Path = String\ntypealias MockResponse = (statusCode: Int, data: Data?)\n\nfinal class SuperApp"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/Assets.xcassets/AccentColor.colorset/Contents.json",
    "chars": 123,
    "preview": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1591,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/Base.lproj/LaunchScreen.storyboard",
    "chars": 3304,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/Info.plist",
    "chars": 1483,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp/Utils/Array+Utils.swift",
    "chars": 134,
    "preview": "import Foundation\n\nextension Array {\n  subscript(safe index: Int) -> Element? {\n    return indices ~= index ? self[index"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp.xcodeproj/project.pbxproj",
    "chars": 52093,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 52;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
    "chars": 1983,
    "preview": "{\n  \"object\": {\n    \"pins\": [\n      {\n        \"package\": \"combine-schedulers\",\n        \"repositoryURL\": \"https://github."
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp.xcodeproj/xcshareddata/xcschemes/AddPaymentMethodIntegrationTests.xcscheme",
    "chars": 1892,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1300\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp.xcodeproj/xcshareddata/xcschemes/MiniSuperApp.xcscheme",
    "chars": 3772,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1250\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp.xcodeproj/xcshareddata/xcschemes/MiniSuperAppUITests.xcscheme",
    "chars": 1836,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1300\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperApp.xcodeproj/xcshareddata/xcschemes/TestHost.xcscheme",
    "chars": 3795,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1300\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperAppUITests/Response/cardOnFile.json",
    "chars": 407,
    "preview": "{\n  \"cards\": [\n    {\n      \"id\": \"0\",\n      \"name\": \"우리은행\",\n      \"digits\": \"0123\",\n      \"color\": \"#f19a38ff\",\n      \"i"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperAppUITests/Response/topupSuccessResponse.json",
    "chars": 26,
    "preview": "{\n  \"status\": \"success\"\n}\n"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperAppUITests/TestUtil.swift",
    "chars": 347,
    "preview": "import Foundation\n\nenum TestUtilError: Error {\n  case fileNotFound\n}\n\nfinal class TestUtil {\n  static func path(for file"
  },
  {
    "path": "completed/MiniSuperApp/MiniSuperAppUITests/TopupImpUITests.swift",
    "chars": 1150,
    "preview": "import XCTest\nimport Swifter\n\nfinal class TopupImpUITests: XCTestCase {\n  \n  private var app: XCUIApplication!\n  private"
  },
  {
    "path": "completed/MiniSuperApp/Platform/.gitignore",
    "chars": 126,
    "preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\nDerivedData/\n.swiftpm/xcode/package.xcworkspace/contents.xcworkspac"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Package.swift",
    "chars": 2338,
    "preview": "// swift-tools-version:5.5\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
  },
  {
    "path": "completed/MiniSuperApp/Platform/README.md",
    "chars": 43,
    "preview": "# Platform\n\nA description of this package.\n"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/CombineUtil/Combine+Utils.swift",
    "chars": 924,
    "preview": "import Combine\nimport CombineExt\nimport Foundation\n\npublic class ReadOnlyCurrentValuePublisher<Element>: Publisher {\n  \n"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/DefaultsStore/DefaultsStore.swift",
    "chars": 788,
    "preview": "import Foundation\n\npublic protocol DefaultsStore {\n  var isInitialLaunch: Bool { get set }\n  var lastNoticeDate: Double "
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/Network/HTTPMethod.swift",
    "chars": 124,
    "preview": "import Foundation\n\npublic enum HTTPMethod: String, Encodable {\n  case get = \"GET\"\n  case post = \"POST\"\n  case put = \"PUT"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/Network/Network.swift",
    "chars": 649,
    "preview": "import Combine\nimport Foundation\n\npublic typealias QueryItems = [String: AnyHashable]\npublic typealias HTTPHeader = [Str"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/Network/NetworkError.swift",
    "chars": 87,
    "preview": "import Foundation\n\npublic enum NetworkError: Error {\n  case invalidURL(url: String?)\n}\n"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/NetworkImp/NetworkImp.swift",
    "chars": 2462,
    "preview": "import Foundation\nimport Network\nimport Combine\n\npublic final class NetworkImp: Network {\n  \n  private let session: URLS"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/PlatformTestSupport/PlatformTestSupport.swift",
    "chars": 18,
    "preview": "import Foundation\n"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/RIBsTestSupport/RoutingMock.swift",
    "chars": 1436,
    "preview": "import Foundation\nimport ModernRIBs\nimport Combine\n\npublic final class RoutingMock: Routing {\n  \n  public var loadHandle"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/RIBsTestSupport/ViewControllableMock.swift",
    "chars": 649,
    "preview": "import Foundation\nimport ModernRIBs\nimport UIKit\n\npublic final class ViewControllableMock: UIViewController, ViewControl"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/RIBsTestSupport/ViewableRoutingMock.swift",
    "chars": 1685,
    "preview": "import Foundation\nimport ModernRIBs\nimport Combine\n\nopen class ViewableRoutingMock: ViewableRouting {\n  // Variables\n  p"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/RIBsUtil/RIBs+Util.swift",
    "chars": 2785,
    "preview": "import Foundation\nimport ModernRIBs\nimport UIKit\n\npublic enum DismissButtonType {\n  case back, close\n  \n  public var ico"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/SuperUI/AdaptivePresentationControllerDelegate.swift",
    "chars": 474,
    "preview": "import UIKit\n\npublic protocol AdaptivePresentationControllerDelegate: AnyObject {\n  func presentationControllerDidDismis"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/SuperUI/PushModalPresentationController.swift",
    "chars": 3030,
    "preview": "import UIKit\n\npublic final class PushModalPresentationController: NSObject, UIViewControllerTransitioningDelegate {\n  \n "
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/SuperUI/UIColor+Super.swift",
    "chars": 155,
    "preview": "import UIKit\n\npublic extension UIColor {\n  static let backgroundColor = UIColor(hex: \"#F1F5F9FF\")!\n  static let primaryR"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/SuperUI/UIColor+Utils.swift",
    "chars": 778,
    "preview": "import UIKit\n\npublic extension UIColor {\n  convenience init?(hex: String) {\n    let r, g, b, a: CGFloat\n    \n    if hex."
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/SuperUI/UIImage+Utils.swift",
    "chars": 478,
    "preview": "import UIKit\n\npublic extension UIImage {\n  convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1))"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/SuperUI/UITableView+Utils.swift",
    "chars": 715,
    "preview": "import UIKit\n\npublic protocol Reusable: AnyObject {\n  static var reuseIdentifier: String { get }\n}\n\npublic extension Reu"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/SuperUI/UIView+Utils.swift",
    "chars": 647,
    "preview": "import UIKit\n\npublic extension UIView {\n  func addShadowWithRoundedCorners(\n    _ radius: CGFloat = 16,\n    shadowColor:"
  },
  {
    "path": "completed/MiniSuperApp/Platform/Sources/SuperUI/UIViewController+Utils.swift",
    "chars": 463,
    "preview": "import UIKit\nimport RIBsUtil\n\npublic extension UIViewController {\n  func setupNavigationItem(with buttonType: DismissBut"
  },
  {
    "path": "completed/MiniSuperApp/Profile/.gitignore",
    "chars": 126,
    "preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\nDerivedData/\n.swiftpm/xcode/package.xcworkspace/contents.xcworkspac"
  },
  {
    "path": "completed/MiniSuperApp/Profile/Package.swift",
    "chars": 888,
    "preview": "// swift-tools-version:5.5\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
  },
  {
    "path": "completed/MiniSuperApp/Profile/README.md",
    "chars": 42,
    "preview": "# Profile\n\nA description of this package.\n"
  },
  {
    "path": "completed/MiniSuperApp/Profile/Sources/ProfileHome/ProfileHomeBuilder.swift",
    "chars": 889,
    "preview": "import ModernRIBs\n\npublic protocol ProfileHomeDependency: Dependency {\n}\n\nfinal class ProfileHomeComponent: Component<Pr"
  },
  {
    "path": "completed/MiniSuperApp/Profile/Sources/ProfileHome/ProfileHomeInteractor.swift",
    "chars": 728,
    "preview": "import ModernRIBs\n\nprotocol ProfileHomeRouting: ViewableRouting {\n}\n\nprotocol ProfileHomePresentable: Presentable {\n  va"
  },
  {
    "path": "completed/MiniSuperApp/Profile/Sources/ProfileHome/ProfileHomeRouter.swift",
    "chars": 557,
    "preview": "import ModernRIBs\n\nprotocol ProfileHomeInteractable: Interactable {\n  var router: ProfileHomeRouting? { get set }\n  var "
  },
  {
    "path": "completed/MiniSuperApp/Profile/Sources/ProfileHome/ProfileHomeViewController.swift",
    "chars": 1011,
    "preview": "import ModernRIBs\nimport UIKit\n\nprotocol ProfileHomePresentableListener: AnyObject {\n}\n\nfinal class ProfileHomeViewContr"
  },
  {
    "path": "completed/MiniSuperApp/Samples/TestUtil.swift",
    "chars": 341,
    "preview": "import Foundation\n\nenum TestUtilError: Error {\n  case fileNotFound\n}\n\nclass TestUtil {\n  static func path(for fileName: "
  },
  {
    "path": "completed/MiniSuperApp/Samples/TopupDependencyMock.swift",
    "chars": 4863,
    "preview": "//\n//  File.swift\n//  \n//\n//  Created by Soojin Ro on 2021/10/04.\n//\n\n@testable import TopupImp\nimport Foundation\nimport"
  },
  {
    "path": "completed/MiniSuperApp/TestHost/AppDelegate.swift",
    "chars": 367,
    "preview": "import UIKit\n\n@main\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n  public var window: UIWindow?\n\n  func appl"
  },
  {
    "path": "completed/MiniSuperApp/TestHost/Assets.xcassets/AccentColor.colorset/Contents.json",
    "chars": 123,
    "preview": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }"
  },
  {
    "path": "completed/MiniSuperApp/TestHost/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1591,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "completed/MiniSuperApp/TestHost/Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "completed/MiniSuperApp/TestHost/Base.lproj/LaunchScreen.storyboard",
    "chars": 1665,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  }
]

// ... and 12 more files (download for full content)

About this extraction

This page contains the full source code of the nsoojin/MiniSuperApp-fastcampus GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 212 files (359.2 KB), approximately 104.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!