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),
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.