extracting inline fragment view logic to a controller

This commit is contained in:
Adam Brown 2022-08-25 14:27:06 +01:00
parent 92c615af31
commit d403d16731

View File

@ -20,8 +20,11 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.associateContentStateWith import im.vector.app.core.extensions.associateContentStateWith
import im.vector.app.core.extensions.clearErrorOnChange import im.vector.app.core.extensions.clearErrorOnChange
import im.vector.app.core.extensions.content import im.vector.app.core.extensions.content
@ -30,6 +33,7 @@ import im.vector.app.core.extensions.realignPercentagesToParent
import im.vector.app.core.extensions.setOnImeDoneListener import im.vector.app.core.extensions.setOnImeDoneListener
import im.vector.app.core.extensions.showKeyboard import im.vector.app.core.extensions.showKeyboard
import im.vector.app.core.extensions.toReducedUrl import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.ensureProtocol
import im.vector.app.core.utils.ensureTrailingSlash import im.vector.app.core.utils.ensureTrailingSlash
import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.core.utils.openUrlInExternalBrowser
@ -37,42 +41,41 @@ import im.vector.app.databinding.FragmentFtueServerSelectionCombinedBinding
import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.onboarding.OnboardingFlow
import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewEvents
import im.vector.app.features.onboarding.OnboardingViewModel
import im.vector.app.features.onboarding.OnboardingViewState import im.vector.app.features.onboarding.OnboardingViewState
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
import reactivecircus.flowbinding.android.view.clicks
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class FtueAuthCombinedServerSelectionFragment : class FtueAuthCombinedServerSelectionFragment : AbstractFtueAuthFragment<FragmentFtueServerSelectionCombinedBinding>() {
AbstractFtueAuthFragment<FragmentFtueServerSelectionCombinedBinding>() {
@Inject lateinit var stringProvider: StringProvider
private lateinit var controller: Controller
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueServerSelectionCombinedBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueServerSelectionCombinedBinding {
return FragmentFtueServerSelectionCombinedBinding.inflate(inflater, container, false) return FragmentFtueServerSelectionCombinedBinding.inflate(inflater, container, false).also {
controller = Controller(viewLifecycleOwner, it, stringProvider, errorFormatter)
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupViews() controller.listener = object : Controller.Listener {
} override fun onNavigationClicked() {
viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnBack))
}
private fun setupViews() { override fun updateServerUrl() {
views.chooseServerRoot.realignPercentagesToParent() viewModel.handle(OnboardingAction.HomeServerChange.EditHomeServer(views.chooseServerInput.content().ensureProtocol().ensureTrailingSlash()))
views.chooseServerToolbar.setNavigationOnClickListener { }
viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnBack))
} override fun getInTouchClicked() {
views.chooseServerInput.associateContentStateWith(button = views.chooseServerSubmit, enabledPredicate = { canSubmit(it) }) openUrlInExternalBrowser(requireContext(), getString(R.string.ftue_ems_url))
views.chooseServerInput.setOnImeDoneListener {
if (canSubmit(views.chooseServerInput.content())) {
updateServerUrl()
} }
} }
views.chooseServerGetInTouch.debouncedClicks { openUrlInExternalBrowser(requireContext(), getString(R.string.ftue_ems_url)) }
views.chooseServerSubmit.debouncedClicks { updateServerUrl() }
views.chooseServerInput.clearErrorOnChange(viewLifecycleOwner)
}
private fun canSubmit(url: String) = url.isNotEmpty()
private fun updateServerUrl() {
viewModel.handle(OnboardingAction.HomeServerChange.EditHomeServer(views.chooseServerInput.content().ensureProtocol().ensureTrailingSlash()))
} }
override fun resetViewModel() { override fun resetViewModel() {
@ -80,13 +83,47 @@ class FtueAuthCombinedServerSelectionFragment :
} }
override fun updateWithState(state: OnboardingViewState) { override fun updateWithState(state: OnboardingViewState) {
views.chooseServerHeaderSubtitle.setText( controller.setData(state)
when (state.onboardingFlow) { }
OnboardingFlow.SignIn -> R.string.ftue_auth_choose_server_sign_in_subtitle
OnboardingFlow.SignUp -> R.string.ftue_auth_choose_server_subtitle override fun onError(throwable: Throwable) {
else -> throw IllegalStateException("Invalid flow state") controller.setError(throwable)
}
class Controller(
lifecycleOwner: LifecycleOwner,
private val views: FragmentFtueServerSelectionCombinedBinding,
private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter,
private val viewModel: OnboardingViewModel,
) {
var listener: Listener? = null
init {
views.chooseServerRoot.realignPercentagesToParent()
views.chooseServerToolbar.setNavigationOnClickListener { listener?.onNavigationClicked() }
views.chooseServerInput.associateContentStateWith(button = views.chooseServerSubmit, enabledPredicate = { canSubmit(it) })
views.chooseServerInput.setOnImeDoneListener {
if (canSubmit(views.chooseServerInput.content())) {
listener?.updateServerUrl()
} }
) }
views.chooseServerGetInTouch.debouncedClicks(lifecycleOwner) { listener?.getInTouchClicked() }
views.chooseServerSubmit.debouncedClicks(lifecycleOwner) { listener?.updateServerUrl() }
views.chooseServerInput.clearErrorOnChange(lifecycleOwner)
}
private fun canSubmit(url: String) = url.isNotEmpty()
fun setData(state: OnboardingViewState) {
views.chooseServerHeaderSubtitle.setText(
when (state.onboardingFlow) {
OnboardingFlow.SignIn -> R.string.ftue_auth_choose_server_sign_in_subtitle
OnboardingFlow.SignUp -> R.string.ftue_auth_choose_server_subtitle
else -> throw IllegalStateException("Invalid flow state")
}
)
if (views.chooseServerInput.content().isEmpty()) { if (views.chooseServerInput.content().isEmpty()) {
val userUrlInput = state.selectedHomeserver.userFacingUrl?.toReducedUrlKeepingSchemaIfInsecure() ?: viewModel.getDefaultHomeserverUrl() val userUrlInput = state.selectedHomeserver.userFacingUrl?.toReducedUrlKeepingSchemaIfInsecure() ?: viewModel.getDefaultHomeserverUrl()
@ -97,12 +134,26 @@ class FtueAuthCombinedServerSelectionFragment :
views.chooseServerInput.editText().showKeyboard(true) views.chooseServerInput.editText().showKeyboard(true)
} }
override fun onError(throwable: Throwable) { fun setError(throwable: Throwable) {
views.chooseServerInput.error = when { views.chooseServerInput.error = when {
throwable.isHomeserverUnavailable() -> getString(R.string.login_error_homeserver_not_found) throwable.isHomeserverUnavailable() -> stringProvider.getString(R.string.login_error_homeserver_not_found)
else -> errorFormatter.toHumanReadable(throwable) else -> errorFormatter.toHumanReadable(throwable)
}
println(views.chooseServerInput.error)
}
private fun String.toReducedUrlKeepingSchemaIfInsecure() = toReducedUrl(keepSchema = this.startsWith("http://"))
interface Listener {
fun onNavigationClicked()
fun updateServerUrl()
fun getInTouchClicked()
} }
} }
}
private fun String.toReducedUrlKeepingSchemaIfInsecure() = toReducedUrl(keepSchema = this.startsWith("http://"))
private fun View.debouncedClicks(lifecycleOwner: LifecycleOwner, onClicked: () -> Unit) {
clicks()
.onEach { onClicked() }
.launchIn(lifecycleOwner.lifecycleScope)
} }