From db820efc3a374d22101e00c99dde33a53ab7a0a1 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 28 Oct 2021 15:43:51 +0300 Subject: [PATCH] Fix UI issues. --- .../app/core/ui/list/GenericButtonItem.kt | 12 +++++ .../createpoll/CreatePollController.kt | 3 ++ .../features/createpoll/CreatePollFragment.kt | 44 ++++++++++++++++++- .../createpoll/CreatePollViewEvents.kt | 6 ++- .../createpoll/CreatePollViewModel.kt | 40 +++++++++++++---- .../createpoll/CreatePollViewState.kt | 3 +- .../src/main/res/drawable/ic_delete_10dp.xml | 18 ++++++++ .../main/res/layout/fragment_create_poll.xml | 14 ++++++ .../item_form_text_input_with_delete.xml | 6 +-- vector/src/main/res/values/strings.xml | 5 +++ 10 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_delete_10dp.xml diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericButtonItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericButtonItem.kt index eb683631fd..fe59c82ce9 100644 --- a/vector/src/main/java/im/vector/app/core/ui/list/GenericButtonItem.kt +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericButtonItem.kt @@ -15,6 +15,8 @@ */ package im.vector.app.core.ui.list +import android.graphics.Typeface +import android.view.Gravity import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import com.airbnb.epoxy.EpoxyAttribute @@ -47,6 +49,12 @@ abstract class GenericButtonItem : VectorEpoxyModel() @DrawableRes var iconRes: Int? = null + @EpoxyAttribute + var gravity: Int = Gravity.CENTER + + @EpoxyAttribute + var bold: Boolean = false + override fun bind(holder: Holder) { super.bind(holder) holder.button.text = text @@ -58,6 +66,10 @@ abstract class GenericButtonItem : VectorEpoxyModel() holder.button.icon = null } + holder.button.gravity = gravity or Gravity.CENTER_VERTICAL + val textStyle = if (bold) Typeface.BOLD else Typeface.NORMAL + holder.button.setTypeface(null, textStyle) + holder.button.onClick(buttonClickAction) } diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollController.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollController.kt index cf4ec19581..7fa9831c94 100644 --- a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollController.kt +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollController.kt @@ -16,6 +16,7 @@ package im.vector.app.features.createpoll +import android.view.Gravity import com.airbnb.epoxy.EpoxyController import im.vector.app.R import im.vector.app.core.resources.ColorProvider @@ -85,6 +86,8 @@ class CreatePollController @Inject constructor( id("add_option") text(host.stringProvider.getString(R.string.create_poll_add_option)) textColor(host.colorProvider.getColor(R.color.palette_element_green)) + gravity(Gravity.START) + bold(true) buttonClickAction { host.callback?.onAddOption() } diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollFragment.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollFragment.kt index 8e065de5e6..c456669215 100644 --- a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollFragment.kt +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollFragment.kt @@ -21,9 +21,11 @@ import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.args import com.airbnb.mvrx.withState +import im.vector.app.R import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentCreatePollBinding @@ -41,7 +43,6 @@ class CreatePollFragment @Inject constructor( ) : VectorBaseFragment(), CreatePollController.Callback { private val viewModel: CreatePollViewModel by activityViewModel() - private val createPollArgs: CreatePollArgs by args() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCreatePollBinding { return FragmentCreatePollBinding.inflate(inflater, container, false) @@ -61,6 +62,18 @@ class CreatePollFragment @Inject constructor( views.createPollButton.debouncedClicks { viewModel.handle(CreatePollAction.OnCreatePoll) } + + viewModel.subscribe(this) { + views.createPollButton.isEnabled = it.canCreatePoll + } + + viewModel.observeViewEvents { + when (it) { + CreatePollViewEvents.Success -> handleSuccess() + CreatePollViewEvents.EmptyQuestionError -> handleEmptyQuestionError() + is CreatePollViewEvents.NotEnoughOptionsError -> handleNotEnoughOptionsError(it.requiredOptionsCount) + } + } } override fun invalidate() = withState(viewModel) { @@ -82,4 +95,33 @@ class CreatePollFragment @Inject constructor( override fun onAddOption() { viewModel.handle(CreatePollAction.OnAddOption) } + + private fun handleSuccess() { + requireActivity().finish() + } + + private fun handleEmptyQuestionError() { + renderToast(getString(R.string.create_poll_empty_question_error)) + } + + private fun handleNotEnoughOptionsError(requiredOptionsCount: Int) { + renderToast( + resources.getQuantityString( + R.plurals.create_poll_not_enough_options_error, + requiredOptionsCount, + requiredOptionsCount + ) + ) + } + + private fun renderToast(message: String) { + views.createPollToast.removeCallbacks(hideToastRunnable) + views.createPollToast.text = message + views.createPollToast.isVisible = true + views.createPollToast.postDelayed(hideToastRunnable, 2_000) + } + + private val hideToastRunnable = Runnable { + views.createPollToast.isVisible = false + } } diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewEvents.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewEvents.kt index 8541a1d482..288e5dad01 100644 --- a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewEvents.kt @@ -18,4 +18,8 @@ package im.vector.app.features.createpoll import im.vector.app.core.platform.VectorViewEvents -sealed class CreatePollViewEvents : VectorViewEvents +sealed class CreatePollViewEvents : VectorViewEvents { + object Success : CreatePollViewEvents() + object EmptyQuestionError : CreatePollViewEvents() + data class NotEnoughOptionsError(val requiredOptionsCount: Int) : CreatePollViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewModel.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewModel.kt index 3ccb1b17c3..51143dc36b 100644 --- a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewModel.kt @@ -25,11 +25,10 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel import org.matrix.android.sdk.api.session.Session -import timber.log.Timber class CreatePollViewModel @AssistedInject constructor( @Assisted private val initialState: CreatePollViewState, - private val session: Session + session: Session ) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId)!! @@ -41,6 +40,8 @@ class CreatePollViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory { + private const val REQUIRED_MIN_OPTION_COUNT = 2 + @JvmStatic override fun create(viewModelContext: ViewModelContext, state: CreatePollViewState): CreatePollViewModel { val factory = when (viewModelContext) { @@ -52,11 +53,11 @@ class CreatePollViewModel @AssistedInject constructor( } init { - // Initialize with 2 default empty options + // Initialize with REQUIRED_MIN_OPTION_COUNT default empty options setState { copy( question = "", - options = listOf("", "") + options = List(REQUIRED_MIN_OPTION_COUNT) { "" } ) } } @@ -72,14 +73,27 @@ class CreatePollViewModel @AssistedInject constructor( } private fun handleOnCreatePoll() = withState { state -> - + val nonEmptyOptions = state.options.filter { it.isNotEmpty() } + when { + state.question.isEmpty() -> { + _viewEvents.post(CreatePollViewEvents.EmptyQuestionError) + } + nonEmptyOptions.size < REQUIRED_MIN_OPTION_COUNT -> { + _viewEvents.post(CreatePollViewEvents.NotEnoughOptionsError(requiredOptionsCount = REQUIRED_MIN_OPTION_COUNT)) + } + else -> { + room.sendPoll(state.question, state.options) + _viewEvents.post(CreatePollViewEvents.Success) + } + } } private fun handleOnAddOption() { setState { val extendedOptions = options + "" copy( - options = extendedOptions + options = extendedOptions, + canCreatePoll = canCreatePoll(this.copy(options = extendedOptions)) ) } } @@ -88,7 +102,8 @@ class CreatePollViewModel @AssistedInject constructor( setState { val filteredOptions = options.filterIndexed { ind, _ -> ind != index } copy( - options = filteredOptions + options = filteredOptions, + canCreatePoll = canCreatePoll(this.copy(options = filteredOptions)) ) } } @@ -97,7 +112,8 @@ class CreatePollViewModel @AssistedInject constructor( setState { val changedOptions = options.mapIndexed { ind, s -> if (ind == index) option else s } copy( - options = changedOptions + options = changedOptions, + canCreatePoll = canCreatePoll(this.copy(options = changedOptions)) ) } } @@ -105,8 +121,14 @@ class CreatePollViewModel @AssistedInject constructor( private fun handleOnQuestionChanged(question: String) { setState { copy( - question = question + question = question, + canCreatePoll = canCreatePoll(this.copy(question = question)) ) } } + + private fun canCreatePoll(state: CreatePollViewState): Boolean { + return state.question.isNotEmpty() && + state.options.filter { it.isNotEmpty() }.size >= REQUIRED_MIN_OPTION_COUNT + } } diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewState.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewState.kt index 41629a390c..e1639af01c 100644 --- a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewState.kt +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewState.kt @@ -21,7 +21,8 @@ import com.airbnb.mvrx.MavericksState data class CreatePollViewState( val roomId: String, val question: String = "", - val options: List = emptyList() + val options: List = emptyList(), + val canCreatePoll: Boolean = false ) : MavericksState { constructor(args: CreatePollArgs) : this( diff --git a/vector/src/main/res/drawable/ic_delete_10dp.xml b/vector/src/main/res/drawable/ic_delete_10dp.xml new file mode 100644 index 0000000000..f8229a71d0 --- /dev/null +++ b/vector/src/main/res/drawable/ic_delete_10dp.xml @@ -0,0 +1,18 @@ + + + + diff --git a/vector/src/main/res/layout/fragment_create_poll.xml b/vector/src/main/res/layout/fragment_create_poll.xml index 0616b76150..841f52f2d8 100644 --- a/vector/src/main/res/layout/fragment_create_poll.xml +++ b/vector/src/main/res/layout/fragment_create_poll.xml @@ -78,4 +78,18 @@ android:text="@string/create_poll_button" app:layout_constraintBottom_toBottomOf="parent" /> + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_form_text_input_with_delete.xml b/vector/src/main/res/layout/item_form_text_input_with_delete.xml index e92732208b..adf526eab5 100644 --- a/vector/src/main/res/layout/item_form_text_input_with_delete.xml +++ b/vector/src/main/res/layout/item_form_text_input_with_delete.xml @@ -28,13 +28,13 @@ Option %1$d ADD OPTION CREATE POLL + Question cannot be empty + + At least %1$s option is required + At least %1$s options are required +