Skip to content

Commit

Permalink
✨ :: [#130] InputDialogFeature / InputDialog Binding
Browse files Browse the repository at this point in the history
  • Loading branch information
baekteun committed Jul 30, 2023
1 parent 1b1b67b commit ff314a5
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
import BaseFeatureInterface
import UIKit

public protocol InputDialogFactory {
func makeViewController(
title: String,
placeholder: String,
inputType: InputType,
confirmAction: @escaping (String) async -> Void
) -> any RoutedViewControllable
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import BaseFeatureInterface
import InputDialogFeatureInterface

final class InputDialogFactoryImpl: InputDialogFactory {
func makeViewController(
title: String,
placeholder: String,
inputType: InputType,
confirmAction: @escaping (String) async -> Void
) -> any RoutedViewControllable {
let store = InputDialogStore(confirmAction: confirmAction)
return InputDialogViewController(title: title, placeholder: placeholder, store: store)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import BaseFeature
import Combine
import InputDialogFeatureInterface
import Moordinator
import Store

Expand All @@ -8,26 +9,72 @@ final class InputDialogStore: BaseStore {
var subscription: Set<AnyCancellable> = .init()
var initialState: State
var stateSubject: CurrentValueSubject<State, Never>
private let confirmAction: (String) async -> Void

init() {
self.initialState = .init()
init(
inputType: InputType,
confirmAction: @escaping (String) async -> Void
) {
self.initialState = .init(inputType: inputType)
self.stateSubject = .init(initialState)
self.confirmAction = confirmAction
}

struct State {}
enum Action {}
enum Mutation {}
struct State {
var inputType: InputType
var inputText = ""
var isLoading = false
}
enum Action {
case updateInputText(String)
case dimmedBackgroundViewDidTap
case cancelButtonDidTap
case confirmButtonDidTap
}
enum Mutation {
case updateInputText(String)
case updateIsLoading(Bool)
}
}

extension InputDialogStore {
func mutate(state: State, action: Action) -> SideEffect<Mutation, Never> {
.none
switch action {
case let .updateInputText(inputText):
return .just(.updateInputText(inputText))

case .dimmedBackgroundViewDidTap, .cancelButtonDidTap:
route.send(DotoriRoutePath.dismiss)

case .confirmButtonDidTap:
let confirmEffect = SideEffect<Void, Never>
.async { [confirmAction, inputText = currentState.inputText] in
await confirmAction(inputText)
}
.handleEvents(receiveOutput: { [route] in
route.send(DotoriRoutePath.dismiss)
})
.flatMap { SideEffect<Mutation, Never>.none }
.eraseToSideEffect()
return .merge(
confirmEffect,
.just(.updateIsLoading(true))
)
}
return .none
}
}

extension InputDialogStore {
func reduce(state: State, mutate: Mutation) -> State {
var newState = state
switch mutate {
case let .updateInputText(inputText):
newState.inputText = inputText

case let .updateIsLoading(isLoading):
newState.isLoading = isLoading
}
return newState
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import BaseFeature
import CombineUtility
import DesignSystem
import InputDialogFeatureInterface
import Localization
import MSGLayout
import UIKit
Expand All @@ -9,6 +11,7 @@ final class InputDialogViewController: BaseStoredModalViewController<InputDialog
private let inputTextField = DotoriSimpleTextField()
private let cancelButton = DotoriOutlineButton(text: L10n.Global.cancelButtonTitle)
private let confirmButton = DotoriButton(text: L10n.Global.confirmButtonTitle)
private let loadingIndicatorView = UIActivityIndicatorView(style: .medium)

init(
title: String,
Expand All @@ -24,11 +27,21 @@ final class InputDialogViewController: BaseStoredModalViewController<InputDialog
fatalError("init(coder:) has not been implemented")
}

override func addView() {
super.addView()
confirmButton.addSubviews {
loadingIndicatorView
}
}

override func setLayout() {
MSGLayout.buildLayout {
contentView.layout
.center(.toSuperview())
.horizontal(.toSuperview(), .equal(40))

loadingIndicatorView.layout
.center(.toSuperview())
}

MSGLayout.stackedLayout(self.contentView) {
Expand All @@ -49,4 +62,67 @@ final class InputDialogViewController: BaseStoredModalViewController<InputDialog
.margin(.all(24))
}
}

override func bindAction() {
inputTextField.textPublisher
.map(Store.Action.updateInputText)
.sink(receiveValue: store.send(_:))
.store(in: &subscription)

view.tapGesturePublisher()
.map { _ in Store.Action.dimmedBackgroundViewDidTap }
.sink(receiveValue: store.send(_:))
.store(in: &subscription)

cancelButton.tapPublisher
.map { Store.Action.cancelButtonDidTap }
.sink(receiveValue: store.send(_:))
.store(in: &subscription)

confirmButton.tapPublisher
.map { Store.Action.confirmButtonDidTap }
.sink(receiveValue: store.send(_:))
.store(in: &subscription)
}

override func bindState() {
let sharedState = store.state.share()
.receive(on: DispatchQueue.main)

sharedState
.map(\.inputType)
.removeDuplicates()
.map(\.keyboardType)
.assign(to: \.keyboardType, on: inputTextField)
.store(in: &subscription)

sharedState
.map(\.inputText)
.removeDuplicates()
.map(Optional.init)
.assign(to: \.text, on: inputTextField)
.store(in: &subscription)

sharedState
.map(\.isLoading)
.removeDuplicates()
.sink(with: self, receiveValue: { owner, isLoading in
isLoading
? owner.loadingIndicatorView.startAnimating()
: owner.loadingIndicatorView.stopAnimating()

let title = isLoading ? "" : L10n.Global.confirmButtonTitle
owner.confirmButton.setTitle(title, for: .normal)
})
.store(in: &subscription)
}
}

private extension InputType {
var keyboardType: UIKeyboardType {
switch self {
case .number: return .numberPad
case .text: return .`default`
}
}
}

0 comments on commit ff314a5

Please sign in to comment.