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.View
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
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.clearErrorOnChange
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.showKeyboard
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.ensureTrailingSlash
import im.vector.app.core.utils.openUrlInExternalBrowser
@ -37,49 +41,82 @@ import im.vector.app.databinding.FragmentFtueServerSelectionCombinedBinding
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingFlow
import im.vector.app.features.onboarding.OnboardingViewEvents
import im.vector.app.features.onboarding.OnboardingViewModel
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 reactivecircus.flowbinding.android.view.clicks
import javax.inject.Inject
@AndroidEntryPoint
class FtueAuthCombinedServerSelectionFragment :
AbstractFtueAuthFragment<FragmentFtueServerSelectionCombinedBinding>() {
class FtueAuthCombinedServerSelectionFragment : AbstractFtueAuthFragment<FragmentFtueServerSelectionCombinedBinding>() {
@Inject lateinit var stringProvider: StringProvider
private lateinit var controller: Controller
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?) {
super.onViewCreated(view, savedInstanceState)
setupViews()
}
private fun setupViews() {
views.chooseServerRoot.realignPercentagesToParent()
views.chooseServerToolbar.setNavigationOnClickListener {
controller.listener = object : Controller.Listener {
override fun onNavigationClicked() {
viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnBack))
}
views.chooseServerInput.associateContentStateWith(button = views.chooseServerSubmit, enabledPredicate = { canSubmit(it) })
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() {
override fun updateServerUrl() {
viewModel.handle(OnboardingAction.HomeServerChange.EditHomeServer(views.chooseServerInput.content().ensureProtocol().ensureTrailingSlash()))
}
override fun getInTouchClicked() {
openUrlInExternalBrowser(requireContext(), getString(R.string.ftue_ems_url))
}
}
}
override fun resetViewModel() {
// do nothing
}
override fun updateWithState(state: OnboardingViewState) {
controller.setData(state)
}
override fun onError(throwable: Throwable) {
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
@ -97,12 +134,26 @@ class FtueAuthCombinedServerSelectionFragment :
views.chooseServerInput.editText().showKeyboard(true)
}
override fun onError(throwable: Throwable) {
fun setError(throwable: Throwable) {
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)
}
println(views.chooseServerInput.error)
}
private fun String.toReducedUrlKeepingSchemaIfInsecure() = toReducedUrl(keepSchema = this.startsWith("http://"))
interface Listener {
fun onNavigationClicked()
fun updateServerUrl()
fun getInTouchClicked()
}
}
}
private fun View.debouncedClicks(lifecycleOwner: LifecycleOwner, onClicked: () -> Unit) {
clicks()
.onEach { onClicked() }
.launchIn(lifecycleOwner.lifecycleScope)
}