Fix UI issues.

This commit is contained in:
Onuray Sahin 2021-10-28 15:43:51 +03:00
parent ac299d8c06
commit db820efc3a
10 changed files with 136 additions and 15 deletions

View File

@ -15,6 +15,8 @@
*/ */
package im.vector.app.core.ui.list package im.vector.app.core.ui.list
import android.graphics.Typeface
import android.view.Gravity
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
@ -47,6 +49,12 @@ abstract class GenericButtonItem : VectorEpoxyModel<GenericButtonItem.Holder>()
@DrawableRes @DrawableRes
var iconRes: Int? = null var iconRes: Int? = null
@EpoxyAttribute
var gravity: Int = Gravity.CENTER
@EpoxyAttribute
var bold: Boolean = false
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
holder.button.text = text holder.button.text = text
@ -58,6 +66,10 @@ abstract class GenericButtonItem : VectorEpoxyModel<GenericButtonItem.Holder>()
holder.button.icon = null 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) holder.button.onClick(buttonClickAction)
} }

View File

@ -16,6 +16,7 @@
package im.vector.app.features.createpoll package im.vector.app.features.createpoll
import android.view.Gravity
import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
@ -85,6 +86,8 @@ class CreatePollController @Inject constructor(
id("add_option") id("add_option")
text(host.stringProvider.getString(R.string.create_poll_add_option)) text(host.stringProvider.getString(R.string.create_poll_add_option))
textColor(host.colorProvider.getColor(R.color.palette_element_green)) textColor(host.colorProvider.getColor(R.color.palette_element_green))
gravity(Gravity.START)
bold(true)
buttonClickAction { buttonClickAction {
host.callback?.onAddOption() host.callback?.onAddOption()
} }

View File

@ -21,9 +21,11 @@ import android.os.Parcelable
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.core.view.isVisible
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentCreatePollBinding import im.vector.app.databinding.FragmentCreatePollBinding
@ -41,7 +43,6 @@ class CreatePollFragment @Inject constructor(
) : VectorBaseFragment<FragmentCreatePollBinding>(), CreatePollController.Callback { ) : VectorBaseFragment<FragmentCreatePollBinding>(), CreatePollController.Callback {
private val viewModel: CreatePollViewModel by activityViewModel() private val viewModel: CreatePollViewModel by activityViewModel()
private val createPollArgs: CreatePollArgs by args()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCreatePollBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCreatePollBinding {
return FragmentCreatePollBinding.inflate(inflater, container, false) return FragmentCreatePollBinding.inflate(inflater, container, false)
@ -61,6 +62,18 @@ class CreatePollFragment @Inject constructor(
views.createPollButton.debouncedClicks { views.createPollButton.debouncedClicks {
viewModel.handle(CreatePollAction.OnCreatePoll) 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) { override fun invalidate() = withState(viewModel) {
@ -82,4 +95,33 @@ class CreatePollFragment @Inject constructor(
override fun onAddOption() { override fun onAddOption() {
viewModel.handle(CreatePollAction.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
}
} }

View File

@ -18,4 +18,8 @@ package im.vector.app.features.createpoll
import im.vector.app.core.platform.VectorViewEvents 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()
}

View File

@ -25,11 +25,10 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import timber.log.Timber
class CreatePollViewModel @AssistedInject constructor( class CreatePollViewModel @AssistedInject constructor(
@Assisted private val initialState: CreatePollViewState, @Assisted private val initialState: CreatePollViewState,
private val session: Session session: Session
) : VectorViewModel<CreatePollViewState, CreatePollAction, CreatePollViewEvents>(initialState) { ) : VectorViewModel<CreatePollViewState, CreatePollAction, CreatePollViewEvents>(initialState) {
private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
@ -41,6 +40,8 @@ class CreatePollViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory<CreatePollViewModel, CreatePollViewState> { companion object : MavericksViewModelFactory<CreatePollViewModel, CreatePollViewState> {
private const val REQUIRED_MIN_OPTION_COUNT = 2
@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: CreatePollViewState): CreatePollViewModel { override fun create(viewModelContext: ViewModelContext, state: CreatePollViewState): CreatePollViewModel {
val factory = when (viewModelContext) { val factory = when (viewModelContext) {
@ -52,11 +53,11 @@ class CreatePollViewModel @AssistedInject constructor(
} }
init { init {
// Initialize with 2 default empty options // Initialize with REQUIRED_MIN_OPTION_COUNT default empty options
setState { setState {
copy( copy(
question = "", question = "",
options = listOf("", "") options = List(REQUIRED_MIN_OPTION_COUNT) { "" }
) )
} }
} }
@ -72,14 +73,27 @@ class CreatePollViewModel @AssistedInject constructor(
} }
private fun handleOnCreatePoll() = withState { state -> 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() { private fun handleOnAddOption() {
setState { setState {
val extendedOptions = options + "" val extendedOptions = options + ""
copy( copy(
options = extendedOptions options = extendedOptions,
canCreatePoll = canCreatePoll(this.copy(options = extendedOptions))
) )
} }
} }
@ -88,7 +102,8 @@ class CreatePollViewModel @AssistedInject constructor(
setState { setState {
val filteredOptions = options.filterIndexed { ind, _ -> ind != index } val filteredOptions = options.filterIndexed { ind, _ -> ind != index }
copy( copy(
options = filteredOptions options = filteredOptions,
canCreatePoll = canCreatePoll(this.copy(options = filteredOptions))
) )
} }
} }
@ -97,7 +112,8 @@ class CreatePollViewModel @AssistedInject constructor(
setState { setState {
val changedOptions = options.mapIndexed { ind, s -> if (ind == index) option else s } val changedOptions = options.mapIndexed { ind, s -> if (ind == index) option else s }
copy( copy(
options = changedOptions options = changedOptions,
canCreatePoll = canCreatePoll(this.copy(options = changedOptions))
) )
} }
} }
@ -105,8 +121,14 @@ class CreatePollViewModel @AssistedInject constructor(
private fun handleOnQuestionChanged(question: String) { private fun handleOnQuestionChanged(question: String) {
setState { setState {
copy( 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
}
} }

View File

@ -21,7 +21,8 @@ import com.airbnb.mvrx.MavericksState
data class CreatePollViewState( data class CreatePollViewState(
val roomId: String, val roomId: String,
val question: String = "", val question: String = "",
val options: List<String> = emptyList() val options: List<String> = emptyList(),
val canCreatePoll: Boolean = false
) : MavericksState { ) : MavericksState {
constructor(args: CreatePollArgs) : this( constructor(args: CreatePollArgs) : this(

View File

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="10dp"
android:height="10dp"
android:viewportWidth="10"
android:viewportHeight="10">
<path
android:pathData="M0.9998,0.9997L8.9998,8.9997"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#737D8C"
android:strokeLineCap="round"/>
<path
android:pathData="M9.0005,0.9997L1.0005,8.9997"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#737D8C"
android:strokeLineCap="round"/>
</vector>

View File

@ -78,4 +78,18 @@
android:text="@string/create_poll_button" android:text="@string/create_poll_button"
app:layout_constraintBottom_toBottomOf="parent" /> app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/createPollToast"
style="@style/Widget.Vector.TextView.Caption.Toast"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginBottom="84dp"
android:visibility="gone"
android:accessibilityLiveRegion="polite"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="@string/voice_message_release_to_send_toast"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -28,13 +28,13 @@
<ImageButton <ImageButton
android:id="@+id/formTextInputDeleteButton" android:id="@+id/formTextInputDeleteButton"
android:layout_width="32dp" android:layout_width="24dp"
android:layout_height="32dp" android:layout_height="24dp"
android:layout_marginEnd="@dimen/layout_horizontal_margin" android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:background="@drawable/circle" android:background="@drawable/circle"
android:contentDescription="@string/delete" android:contentDescription="@string/delete"
android:scaleType="center" android:scaleType="center"
android:src="@drawable/ic_delete" android:src="@drawable/ic_delete_10dp"
app:layout_constraintBottom_toBottomOf="@id/formTextInputTextInputLayout" app:layout_constraintBottom_toBottomOf="@id/formTextInputTextInputLayout"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/formTextInputTextInputLayout" app:layout_constraintTop_toTopOf="@id/formTextInputTextInputLayout"

View File

@ -3634,4 +3634,9 @@
<string name="create_poll_options_hint">Option %1$d</string> <string name="create_poll_options_hint">Option %1$d</string>
<string name="create_poll_add_option">ADD OPTION</string> <string name="create_poll_add_option">ADD OPTION</string>
<string name="create_poll_button">CREATE POLL</string> <string name="create_poll_button">CREATE POLL</string>
<string name="create_poll_empty_question_error">Question cannot be empty</string>
<plurals name="create_poll_not_enough_options_error">
<item quantity="one">At least %1$s option is required</item>
<item quantity="other">At least %1$s options are required</item>
</plurals>
</resources> </resources>