Fix UI issues.
This commit is contained in:
parent
ac299d8c06
commit
db820efc3a
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue