diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt index f0c43228c1..12538d314a 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt @@ -17,6 +17,8 @@ package im.vector.app.features.form import android.text.Editable +import android.view.View +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import com.google.android.material.textfield.TextInputEditText @@ -35,9 +37,18 @@ abstract class FormEditTextItem : VectorEpoxyModel() { @EpoxyAttribute var value: String? = null + @EpoxyAttribute + var showBottomSeparator: Boolean = true + + @EpoxyAttribute + var errorMessage: String? = null + @EpoxyAttribute var enabled: Boolean = true + @EpoxyAttribute + var inputType: Int? = null + @EpoxyAttribute var onTextChange: ((String) -> Unit)? = null @@ -51,14 +62,17 @@ abstract class FormEditTextItem : VectorEpoxyModel() { super.bind(holder) holder.textInputLayout.isEnabled = enabled holder.textInputLayout.hint = hint + holder.textInputLayout.error = errorMessage - // Update only if text is different - if (holder.textInputEditText.text.toString() != value) { + // Update only if text is different and value is not null + if (value != null && holder.textInputEditText.text.toString() != value) { holder.textInputEditText.setText(value) } holder.textInputEditText.isEnabled = enabled + inputType?.let { holder.textInputEditText.inputType = it } holder.textInputEditText.addTextChangedListener(onTextChangeListener) + holder.bottomSeparator.isVisible = showBottomSeparator } override fun shouldSaveViewState(): Boolean { @@ -73,5 +87,6 @@ abstract class FormEditTextItem : VectorEpoxyModel() { class Holder : VectorEpoxyHolder() { val textInputLayout by bind(R.id.formTextInputTextInputLayout) val textInputEditText by bind(R.id.formTextInputTextInputEditText) + val bottomSeparator by bind(R.id.formTextInputDivider) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsAction.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsAction.kt index c352bcfbda..1dac082b63 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsAction.kt @@ -20,6 +20,7 @@ import im.vector.app.core.platform.VectorViewModelAction import org.matrix.android.sdk.api.session.identity.ThreePid sealed class ThreePidsSettingsAction : VectorViewModelAction { + data class ChangeState(val newState: ThreePidsSettingsState) : ThreePidsSettingsAction() data class AddThreePid(val threePid: ThreePid) : ThreePidsSettingsAction() data class ContinueThreePid(val threePid: ThreePid) : ThreePidsSettingsAction() data class CancelThreePid(val threePid: ThreePid) : ThreePidsSettingsAction() diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt index 9e77adafc9..cf3e66bc2a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt @@ -16,15 +16,15 @@ package im.vector.app.features.settings.threepids +import android.text.InputType import android.view.View import com.airbnb.epoxy.TypedEpoxyController -import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import im.vector.app.R import im.vector.app.core.epoxy.loadingItem -import im.vector.app.core.epoxy.noResultItem +import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.getFormattedValue import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider @@ -33,6 +33,7 @@ import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.features.discovery.settingsContinueCancelItem import im.vector.app.features.discovery.settingsInformationItem import im.vector.app.features.discovery.settingsSectionTitleItem +import im.vector.app.features.form.formEditTextItem import org.matrix.android.sdk.api.session.identity.ThreePid import javax.inject.Inject @@ -44,6 +45,9 @@ class ThreePidsSettingsController @Inject constructor( interface InteractionListener { fun addEmail() fun addMsisdn() + fun cancelAdding() + fun doAddEmail(email: String) + fun doAddMsisdn(msisdn: String) fun continueThreePid(threePid: ThreePid) fun cancelThreePid(threePid: ThreePid) fun deleteThreePid(threePid: ThreePid) @@ -51,8 +55,15 @@ class ThreePidsSettingsController @Inject constructor( var interactionListener: InteractionListener? = null + private var currentInputValue = "" + override fun buildModels(data: ThreePidsSettingsViewState?) { if (data == null) return + + if (data.state is ThreePidsSettingsState.Idle) { + currentInputValue = "" + } + when (data.threePids) { is Loading -> { loadingItem { @@ -68,12 +79,12 @@ class ThreePidsSettingsController @Inject constructor( } is Success -> { val dataList = data.threePids.invoke() - buildThreePids(dataList, data.pendingThreePids) + buildThreePids(dataList, data) } } } - private fun buildThreePids(list: List, pendingThreePids: Async>) { + private fun buildThreePids(list: List, data: ThreePidsSettingsViewState) { val splited = list.groupBy { it is ThreePid.Email } val emails = splited[true].orEmpty() val msisdn = splited[false].orEmpty() @@ -86,16 +97,35 @@ class ThreePidsSettingsController @Inject constructor( emails.forEach { buildThreePid("email ", it) } // Pending threePids - pendingThreePids.invoke() + data.pendingThreePids.invoke() ?.filterIsInstance(ThreePid.Email::class.java) ?.forEach { buildPendingThreePid("p_email ", it) } - genericButtonItem { - id("addEmail") - text(stringProvider.getString(R.string.settings_add_email_address)) - textColor(colorProvider.getColor(R.color.riotx_accent)) - buttonClickAction(View.OnClickListener { interactionListener?.addEmail() }) - } + when (data.state) { + ThreePidsSettingsState.Idle -> + genericButtonItem { + id("addEmail") + text(stringProvider.getString(R.string.settings_add_email_address)) + textColor(colorProvider.getColor(R.color.riotx_accent)) + buttonClickAction(View.OnClickListener { interactionListener?.addEmail() }) + } + is ThreePidsSettingsState.AddingEmail -> { + formEditTextItem { + id("addingEmail") + inputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) + hint(stringProvider.getString(R.string.medium_email)) + errorMessage(data.state.error) + onTextChange { currentInputValue = it } + showBottomSeparator(false) + } + settingsContinueCancelItem { + id("contAddingEmail") + continueOnClick { interactionListener?.doAddEmail(currentInputValue) } + cancelOnClick { interactionListener?.cancelAdding() } + } + } + is ThreePidsSettingsState.AddingPhoneNumber -> Unit + }.exhaustive settingsSectionTitleItem { id("msisdn") @@ -105,26 +135,35 @@ class ThreePidsSettingsController @Inject constructor( msisdn.forEach { buildThreePid("msisdn ", it) } // Pending threePids - pendingThreePids.invoke() + data.pendingThreePids.invoke() ?.filterIsInstance(ThreePid.Msisdn::class.java) ?.forEach { buildPendingThreePid("p_msisdn ", it) } - /* - // TODO Support adding MSISDN - genericButtonItem { - id("addMsisdn") - text(stringProvider.getString(R.string.settings_add_phone_number)) - textColor(colorProvider.getColor(R.color.riotx_accent)) - buttonClickAction(View.OnClickListener { interactionListener?.addMsisdn() }) - } - */ - // Avoid empty area - if (msisdn.isEmpty()) { - noResultItem { - id("no_msisdn") - text(stringProvider.getString(R.string.settings_phone_numbers_empty)) + when (data.state) { + ThreePidsSettingsState.Idle -> + genericButtonItem { + id("addMsisdn") + text(stringProvider.getString(R.string.settings_add_phone_number)) + textColor(colorProvider.getColor(R.color.riotx_accent)) + buttonClickAction(View.OnClickListener { interactionListener?.addMsisdn() }) + } + is ThreePidsSettingsState.AddingEmail -> Unit + is ThreePidsSettingsState.AddingPhoneNumber -> { + formEditTextItem { + id("addingMsisdn") + inputType(InputType.TYPE_CLASS_PHONE) + hint(stringProvider.getString(R.string.medium_phone_number)) + errorMessage(data.state.error) + onTextChange { currentInputValue = it } + showBottomSeparator(false) + } + settingsContinueCancelItem { + id("contAddingMsisdn") + continueOnClick { interactionListener?.doAddMsisdn(currentInputValue) } + cancelOnClick { interactionListener?.cancelAdding() } + } } - } + }.exhaustive } private fun buildThreePid(idPrefix: String, threePid: ThreePid) { diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt index 1f95d78026..06ebf13b3b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt @@ -18,9 +18,7 @@ package im.vector.app.features.settings.threepids import android.content.DialogInterface import android.os.Bundle -import android.text.InputType import android.view.View -import android.widget.EditText import androidx.appcompat.app.AlertDialog import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -30,10 +28,11 @@ import im.vector.app.core.dialogs.withColoredButton import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.isEmail +import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.utils.toast import kotlinx.android.synthetic.main.fragment_generic_recycler.* import org.matrix.android.sdk.api.session.identity.ThreePid import javax.inject.Inject @@ -43,6 +42,7 @@ class ThreePidsSettingsFragment @Inject constructor( private val epoxyController: ThreePidsSettingsController ) : VectorBaseFragment(), + OnBackPressed, ThreePidsSettingsViewModel.Factory by viewModelFactory, ThreePidsSettingsController.InteractionListener { @@ -90,27 +90,15 @@ class ThreePidsSettingsFragment @Inject constructor( } override fun addEmail() { - val inflater = requireActivity().layoutInflater - val layout = inflater.inflate(R.layout.dialog_base_edit_text, null) - - val input = layout.findViewById(R.id.editText) - input.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS - - AlertDialog.Builder(requireActivity()) - .setTitle(R.string.settings_add_email_address) - .setView(layout) - .setPositiveButton(R.string.ok) { _, _ -> - val email = input.text.toString() - doAddEmail(email) - } - .setNegativeButton(R.string.cancel, null) - .show() + viewModel.handle(ThreePidsSettingsAction.ChangeState(ThreePidsSettingsState.AddingEmail(null))) } - private fun doAddEmail(email: String) { + override fun doAddEmail(email: String) { + viewModel.handle(ThreePidsSettingsAction.ChangeState(ThreePidsSettingsState.AddingEmail(null))) + // Check that email is valid if (!email.isEmail()) { - requireActivity().toast(R.string.auth_invalid_email) + viewModel.handle(ThreePidsSettingsAction.ChangeState(ThreePidsSettingsState.AddingEmail(getString(R.string.auth_invalid_email)))) return } @@ -118,9 +106,21 @@ class ThreePidsSettingsFragment @Inject constructor( } override fun addMsisdn() { + viewModel.handle(ThreePidsSettingsAction.ChangeState(ThreePidsSettingsState.AddingPhoneNumber(null))) + } + + override fun doAddMsisdn(msisdn: String) { + viewModel.handle(ThreePidsSettingsAction.ChangeState(ThreePidsSettingsState.AddingPhoneNumber(null))) + TODO("Not yet implemented") } + override fun cancelAdding() { + viewModel.handle(ThreePidsSettingsAction.ChangeState(ThreePidsSettingsState.Idle)) + // Hide the keyboard + view?.hideKeyboard() + } + override fun continueThreePid(threePid: ThreePid) { viewModel.handle(ThreePidsSettingsAction.ContinueThreePid(threePid)) } @@ -139,4 +139,15 @@ class ThreePidsSettingsFragment @Inject constructor( .show() .withColoredButton(DialogInterface.BUTTON_POSITIVE) } + + override fun onBackPressed(toolbarButton: Boolean): Boolean { + return withState(viewModel) { + if (it.state is ThreePidsSettingsState.Idle) { + false + } else { + cancelAdding() + true + } + } + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsState.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsState.kt new file mode 100644 index 0000000000..32a685b697 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsState.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.threepids + +sealed class ThreePidsSettingsState { + object Idle : ThreePidsSettingsState() + data class AddingEmail(val error: String?) : ThreePidsSettingsState() + data class AddingPhoneNumber(val error: String?) : ThreePidsSettingsState() +} diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt index 00a58f0610..ece17547e6 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt @@ -129,14 +129,23 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( override fun handle(action: ThreePidsSettingsAction) { when (action) { - is ThreePidsSettingsAction.AddThreePid -> handleAddThreePid(action) + is ThreePidsSettingsAction.AddThreePid -> handleAddThreePid(action) is ThreePidsSettingsAction.ContinueThreePid -> handleContinueThreePid(action) - is ThreePidsSettingsAction.CancelThreePid -> handleCancelThreePid(action) - is ThreePidsSettingsAction.AccountPassword -> handleAccountPassword(action) - is ThreePidsSettingsAction.DeleteThreePid -> handleDeleteThreePid(action) + is ThreePidsSettingsAction.CancelThreePid -> handleCancelThreePid(action) + is ThreePidsSettingsAction.AccountPassword -> handleAccountPassword(action) + is ThreePidsSettingsAction.DeleteThreePid -> handleDeleteThreePid(action) + is ThreePidsSettingsAction.ChangeState -> handleChangeState(action) }.exhaustive } + private fun handleChangeState(action: ThreePidsSettingsAction.ChangeState) { + setState { + copy( + state = action.newState + ) + } + } + private fun handleAddThreePid(action: ThreePidsSettingsAction.AddThreePid) { isLoading(true) diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewState.kt index 10ac51f229..47590286e0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewState.kt @@ -22,6 +22,7 @@ import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.identity.ThreePid data class ThreePidsSettingsViewState( + val state: ThreePidsSettingsState = ThreePidsSettingsState.Idle, val isLoading: Boolean = false, val threePids: Async> = Uninitialized, val pendingThreePids: Async> = Uninitialized diff --git a/vector/src/main/res/layout/item_form_text_input.xml b/vector/src/main/res/layout/item_form_text_input.xml index 775489c5d9..594bfc1788 100644 --- a/vector/src/main/res/layout/item_form_text_input.xml +++ b/vector/src/main/res/layout/item_form_text_input.xml @@ -14,6 +14,7 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/layout_horizontal_margin" android:layout_marginEnd="@dimen/layout_horizontal_margin" + app:errorEnabled="true" app:layout_constraintBottom_toTopOf="@+id/formTextInputDivider" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 553dc1674e..43a2f73947 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -684,7 +684,6 @@ Email addresses Phone numbers - No phone number has been added to your account Remove %s? Ensure that you have clicked on the link in the email we have sent to you.