Last active
May 7, 2020 19:12
-
-
Save brendanw/5772c37126c3b20c07a5a6c6e27ad6fd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open class BaseViewModel<T : Any, R : Any>( | |
initialState: T | |
) : CoroutineScope { | |
private val job = Job() | |
override val coroutineContext: CoroutineContext = Dispatchers.Main + job | |
protected val _state = ConflatedBroadcastChannel<T>(initialState) | |
val state = _state.asFlow() | |
val cState = state.wrap() | |
protected val _effect = Channel<R>() | |
val effect = _effect.consumeAsFlow().wrap() | |
val cEffect = effect.wrap() | |
open fun onClear() { | |
job.cancel() | |
} | |
fun updateState(action: T.() -> T) { | |
launch { | |
_state.update { action() } | |
} | |
} | |
} | |
suspend fun <T> ConflatedBroadcastChannel<T>.update(block: T.() -> T) { | |
val newValue = value.block() | |
send(newValue) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class EnterCredentialsContract { | |
enum class StateType { | |
DEFAULT, | |
SHOW_LOADER | |
} | |
data class State( | |
val type: StateType = DEFAULT, | |
val showLoader: Boolean = false | |
) | |
enum class Effect { | |
LaunchHome, | |
LaunchConfirmToken, | |
LaunchSubscribeInterstitial, | |
LaunchForgotPassword, | |
ShowInvalidUsernameOrPasswordError, | |
ShowInvalidEmailError | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class EnterCredentialsFragment : Fragment(), LoginFragment { | |
private lateinit var viewModel: EnterCredentialsViewModel | |
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { | |
return inflater.inflate(R.layout.auth_enter_creds, container, false) | |
} | |
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | |
super.onViewCreated(view, savedInstanceState) | |
viewModel = EnterCredentialsViewModel() | |
setup() | |
lifecycleScope.launch { viewModel.state.collect { render(it) } } | |
lifecycleScope.launch { | |
viewModel.effect.collect { effect -> | |
when (effect) { | |
LaunchHome -> { | |
val intent = Intent(view.context, MapActivity::class.java) | |
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK | |
view.context.startActivity(intent) | |
} | |
LaunchConfirmToken -> { | |
val intent = Intent(view.context, CreateAccountActivity::class.java) | |
intent.putExtra(CreateAccountActivity.KEY_OPEN_VERIFY, true) | |
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK | |
view.context.startActivity(intent) | |
} | |
LaunchSubscribeInterstitial -> { | |
val intent = Intent(view.context, CreateAccountActivity::class.java) | |
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK | |
intent.putExtra(CreateAccountActivity.KEY_OPEN_SUBSCRIBE, true) | |
view.context.startActivity(intent) | |
} | |
LaunchForgotPassword -> { | |
loginActivity().openFragment(ForgotPasswordFragment.newInstance()) | |
} | |
ShowInvalidUsernameOrPasswordError -> { | |
view.showErrorDialog("Sign In Error", "Invalid username or password") | |
} | |
ShowInvalidEmailError -> { | |
view.showErrorDialog("Sign In Error", getString(R.string.sign_up_error_username)) | |
} | |
}.safe | |
} | |
} | |
} | |
private fun render(state: EnterCredentialsContract.State) { | |
enter_creds_loader.visibleIf(state.showLoader) | |
} | |
override fun onDestroy() { | |
super.onDestroy() | |
viewModel.onClear() | |
} | |
private fun setup() { | |
sign_in_button.setOnClickListener { | |
viewModel.tapSignIn(username.text.toString(), password.text.toString()) | |
} | |
forgot_password.setOnClickListener { | |
viewModel.tapForgotPassword() | |
} | |
} | |
companion object { | |
fun newInstance() = EnterCredentialsFragment() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class EnterCredentialsViewController: AuthView { | |
var viewModel: EnterCredentialsViewModel? = nil | |
var email = String() | |
var password = String() | |
var signInResponse: SignInResponse? | |
var emailTextField: UITextField! | |
var passwordTextField: UITextField! | |
var signInButton: UIButton! | |
deinit { | |
viewModel?.onClear() | |
viewModel = nil | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
viewModel = EnterCredentialsViewModelKt.createEnterCredentialsVM() | |
setupUI() | |
viewModel?.cState.watch { [unowned self] state in | |
self.render(state: state!) | |
} | |
viewModel?.cEffect.watch { [unowned self] effect in | |
guard let effect = effect else { return } | |
switch(effect) { | |
case .launchhome: | |
AppDelegate.shared.rootViewController.switchToHome() | |
case .launchconfirmtoken: | |
let new = ConfirmTokenViewController() | |
self.navigationController?.pushViewController(new, animated: true) | |
case .launchforgotpassword: | |
let new = ForgotPasswordController() | |
self.navigationController?.pushViewController(new, animated: true) | |
case .launchsubscribeinterstitial: | |
self.present(UINavigationController(rootViewController: InterstitialViewController()), animated: true, completion: nil) | |
case .showinvalidusernameorpassworderror: | |
self.showAlert("Please use a valid email or password.") | |
case .showinvalidemailerror: | |
self.showAlert("Please use a valid email address") | |
default: | |
print("should not get here") | |
} | |
} | |
} | |
private func render(state: EnterCredentialsContract.State) { | |
if (state.showLoader) { | |
showSpinner(onView: view) | |
signInButton.isEnabled = false | |
} else { | |
self.removeSpinner() | |
self.signInButton.isEnabled = true | |
} | |
} | |
private func setupUI() { | |
// initialize views and position | |
} | |
override open var prefersStatusBarHidden: Bool { | |
return true | |
} | |
@objc func forgotBtnTapped(_ sender: UIButton) { | |
self.viewModel?.tapForgotPassword() | |
} | |
@objc func createAccountTapped(_ sender: UIButton) { | |
navigationController?.pushViewController(NameViewController(), animated: true) | |
} | |
func loginSuccess() { | |
self.removeSpinner() | |
self.signInButton.isEnabled = true | |
} | |
@objc func loginTapped(_ sender: UIButton) { | |
email = String(emailTextField.text!) | |
password = String(passwordTextField.text!) | |
showSpinner(onView: view) | |
signInButton.isEnabled = false | |
viewModel?.tapSignIn(username: email, password: password) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class EnterCredentialsViewModel( | |
private val userRepository: UserRepository = AppDependencies.userRepository, | |
private val analytics: Analytics = AppDependencies.analytics | |
) : BaseBetaViewModel<State, Effect>(State()) { | |
init { | |
analytics.trackEvent(AE.VIEW_SIGNIN) | |
} | |
fun tapForgotPassword() { | |
launch { _effect.send(LaunchForgotPassword) } | |
} | |
fun tapSignIn(username: String, password: String) { | |
launch { | |
if (!username.matches(emailRegex)) { | |
_effect.send(ShowInvalidEmailError) | |
return@launch | |
} | |
_state.update { copy(type = SHOW_LOADER, showLoader = true) } | |
userRepository.signIn(username, password) | |
.flowOn(ioDispatcher()) | |
.collect { response -> | |
if (response.success) { | |
//Amplitude.getInstance().userId = signInResponse._id | |
// val map = mapOf("email" to signInResponse.email) | |
//Amplitude.getInstance().setUserProperties(JSONObject(map)) | |
routeToNextTask(response) | |
} else { | |
_state.update { copy(showLoader = false) } | |
_effect.send(ShowInvalidUsernameOrPasswordError) | |
} | |
} | |
} | |
} | |
private suspend fun routeToNextTask(signInResponse: SignInResponse) { | |
if (signInResponse.isVerified) { | |
if (signInResponse.subscription != null && signInResponse.subscription.isSubscribed) { | |
_effect.send(LaunchHome) | |
} else { | |
_effect.send(LaunchSubscribeInterstitial) | |
} | |
} else { | |
_effect.send(LaunchConfirmToken) | |
} | |
} | |
} | |
fun createEnterCredentialsVM(): EnterCredentialsViewModel { | |
return EnterCredentialsViewModel() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment