Merge pull request #214 from vector-im/feature/update_quick_reactions

Feature/ Update quick reactions
This commit is contained in:
Valere
2019-06-25 15:47:17 +02:00
committed by GitHub
13 changed files with 144 additions and 411 deletions

View File

@ -30,7 +30,7 @@ sealed class RoomDetailActions {
data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions()
data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailActions()
data class UndoReaction(val targetEventId: String, val key: String, val reason: String? = "") : RoomDetailActions()
data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val opposite: String) : RoomDetailActions()
data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val add: Boolean) : RoomDetailActions()
data class ShowEditHistoryAction(val event: String, val editAggregatedSummary: EditAggregatedSummary) : RoomDetailActions()
data class NavigateToEvent(val eventId: String, val position: Int?) : RoomDetailActions()
object AcceptInvite : RoomDetailActions()

View File

@ -708,9 +708,9 @@ class RoomDetailFragment :
.show()
}
MessageMenuViewModel.ACTION_QUICK_REACT -> {
//eventId,ClickedOn,Opposite
(actionData.data as? Triple<String, String, String>)?.let { (eventId, clickedOn, opposite) ->
roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(eventId, clickedOn, opposite))
//eventId,ClickedOn,Add
(actionData.data as? Triple<String, String, Boolean>)?.let { (eventId, clickedOn, add) ->
roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(eventId, clickedOn, add))
}
}
MessageMenuViewModel.ACTION_EDIT -> {

View File

@ -341,7 +341,11 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
private fun handleUpdateQuickReaction(action: RoomDetailActions.UpdateQuickReactAction) {
room.updateQuickReaction(action.selectedReaction, action.opposite, action.targetEventId, session.sessionParams.credentials.userId)
if (action.add) {
room.sendReaction(action.selectedReaction, action.targetEventId)
} else {
room.undoReaction(action.selectedReaction, action.targetEventId, session.sessionParams.credentials.userId)
}
}
private fun handleSendMedia(action: RoomDetailActions.SendMedia) {

View File

@ -94,8 +94,9 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() {
.commit()
}
quickReactionFragment.interactionListener = object : QuickReactionFragment.InteractionListener {
override fun didQuickReactWith(clikedOn: String, opposite: String, reactions: List<String>, eventId: String) {
actionHandlerModel.fireAction(MessageMenuViewModel.ACTION_QUICK_REACT, Triple(eventId, clikedOn, opposite))
override fun didQuickReactWith(clikedOn: String, add: Boolean, eventId: String) {
actionHandlerModel.fireAction(MessageMenuViewModel.ACTION_QUICK_REACT, Triple(eventId, clikedOn, add))
dismiss()
}
}

View File

@ -21,9 +21,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.transition.TransitionManager
import butterknife.BindView
import butterknife.ButterKnife
import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRx
@ -31,6 +28,7 @@ import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.riotredesign.EmojiCompatFontProvider
import im.vector.riotredesign.R
import kotlinx.android.synthetic.main.adapter_item_action_quick_reaction.*
import org.koin.android.ext.android.inject
/**
@ -40,21 +38,6 @@ class QuickReactionFragment : BaseMvRxFragment() {
private val viewModel: QuickReactionViewModel by fragmentViewModel(QuickReactionViewModel::class)
@BindView(R.id.root_layout)
lateinit var rootLayout: ConstraintLayout
@BindView(R.id.quick_react_1_text)
lateinit var quickReact1Text: TextView
@BindView(R.id.quick_react_2_text)
lateinit var quickReact2Text: TextView
@BindView(R.id.quick_react_3_text)
lateinit var quickReact3Text: TextView
@BindView(R.id.quick_react_4_text)
lateinit var quickReact4Text: TextView
var interactionListener: InteractionListener? = null
val fontProvider by inject<EmojiCompatFontProvider>()
@ -65,77 +48,38 @@ class QuickReactionFragment : BaseMvRxFragment() {
return view
}
lateinit var textViews: List<TextView>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
quickReact1Text.text = QuickReactionViewModel.agreePositive
quickReact2Text.text = QuickReactionViewModel.agreeNegative
quickReact3Text.text = QuickReactionViewModel.likePositive
quickReact4Text.text = QuickReactionViewModel.likeNegative
textViews = listOf(quickReaction0, quickReaction1, quickReaction2, quickReaction3,
quickReaction4, quickReaction5, quickReaction6, quickReaction7)
listOf(quickReact1Text, quickReact2Text, quickReact3Text, quickReact4Text).forEach {
it.typeface = fontProvider.typeface ?: Typeface.DEFAULT
}
//configure click listeners
quickReact1Text.setOnClickListener {
viewModel.toggleAgree(true)
}
quickReact2Text.setOnClickListener {
viewModel.toggleAgree(false)
}
quickReact3Text.setOnClickListener {
viewModel.toggleLike(true)
}
quickReact4Text.setOnClickListener {
viewModel.toggleLike(false)
textViews.forEachIndexed { index, textView ->
textView.typeface = fontProvider.typeface ?: Typeface.DEFAULT
textView.setOnClickListener {
viewModel.didSelect(index)
}
}
}
override fun invalidate() = withState(viewModel) {
TransitionManager.beginDelayedTransition(rootLayout)
when (it.agreeTrigleState) {
TriggleState.NONE -> {
quickReact1Text.alpha = 1f
quickReact2Text.alpha = 1f
}
TriggleState.FIRST -> {
quickReact1Text.alpha = 1f
quickReact2Text.alpha = 0.2f
}
TriggleState.SECOND -> {
quickReact1Text.alpha = 0.2f
quickReact2Text.alpha = 1f
}
}
when (it.likeTriggleState) {
TriggleState.NONE -> {
quickReact3Text.alpha = 1f
quickReact4Text.alpha = 1f
}
TriggleState.FIRST -> {
quickReact3Text.alpha = 1f
quickReact4Text.alpha = 0.2f
}
TriggleState.SECOND -> {
quickReact3Text.alpha = 0.2f
quickReact4Text.alpha = 1f
}
it.quickStates.forEachIndexed { index, qs ->
textViews[index].text = qs.reaction
textViews[index].alpha = if (qs.isSelected) 0.2f else 1f
}
if (it.selectionResult != null) {
val clikedOn = it.selectionResult.first
interactionListener?.didQuickReactWith(clikedOn, QuickReactionViewModel.getOpposite(clikedOn)
?: "", it.selectionResult.second, it.eventId)
if (it.result != null) {
interactionListener?.didQuickReactWith(it.result.reaction, it.result.isSelected, it.eventId)
}
}
interface InteractionListener {
fun didQuickReactWith(clikedOn: String, opposite: String, reactions: List<String>, eventId: String)
fun didQuickReactWith(clikedOn: String, add: Boolean, eventId: String)
}
companion object {

View File

@ -25,18 +25,16 @@ import org.koin.android.ext.android.get
/**
* Quick reactions state, it's a toggle with 3rd state
*/
enum class TriggleState {
NONE,
FIRST,
SECOND
}
data class ToggleState(
val reaction: String,
val isSelected: Boolean
)
data class QuickReactionState(
val agreeTrigleState: TriggleState = TriggleState.NONE,
val likeTriggleState: TriggleState = TriggleState.NONE,
/** Pair of 'clickedOn' and current toggles state*/
val selectionResult: Pair<String, List<String>>? = null,
val eventId: String = "") : MvRxState
val quickStates: List<ToggleState>,
val eventId: String = "",
val result: ToggleState? = null
) : MvRxState
/**
* Quick reaction view model
@ -44,107 +42,28 @@ data class QuickReactionState(
class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel<QuickReactionState>(initialState) {
fun toggleAgree(isFirst: Boolean) = withState {
if (isFirst) {
setState {
val newTriggle = if (it.agreeTrigleState == TriggleState.FIRST) TriggleState.NONE else TriggleState.FIRST
copy(
agreeTrigleState = newTriggle,
selectionResult = Pair(agreePositive, getReactions(this, newTriggle, null))
)
}
} else {
setState {
val newTriggle = if (it.agreeTrigleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND
copy(
agreeTrigleState = agreeTrigleState,
selectionResult = Pair(agreeNegative, getReactions(this, newTriggle, null))
)
}
fun didSelect(index: Int) = withState {
val isSelected = it.quickStates[index].isSelected
setState {
copy(result = ToggleState(it.quickStates[index].reaction, !isSelected))
}
}
fun toggleLike(isFirst: Boolean) = withState {
if (isFirst) {
setState {
val newTriggle = if (it.likeTriggleState == TriggleState.FIRST) TriggleState.NONE else TriggleState.FIRST
copy(
likeTriggleState = newTriggle,
selectionResult = Pair(likePositive, getReactions(this, null, newTriggle))
)
}
} else {
setState {
val newTriggle = if (it.likeTriggleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND
copy(
likeTriggleState = newTriggle,
selectionResult = Pair(likeNegative, getReactions(this, null, newTriggle))
)
}
}
}
private fun getReactions(state: QuickReactionState, newState1: TriggleState?, newState2: TriggleState?): List<String> {
return ArrayList<String>(4).apply {
when (newState2 ?: state.likeTriggleState) {
TriggleState.FIRST -> add(likePositive)
TriggleState.SECOND -> add(likeNegative)
else -> {
}
}
when (newState1 ?: state.agreeTrigleState) {
TriggleState.FIRST -> add(agreePositive)
TriggleState.SECOND -> add(agreeNegative)
else -> {
}
}
}
}
companion object : MvRxViewModelFactory<QuickReactionViewModel, QuickReactionState> {
val agreePositive = "👍"
val agreeNegative = "👎"
val likePositive = "🙂"
val likeNegative = "😔"
fun getOpposite(reaction: String): String? {
return when (reaction) {
agreePositive -> agreeNegative
agreeNegative -> agreePositive
likePositive -> likeNegative
likeNegative -> likePositive
else -> null
}
}
val quickEmojis = listOf("👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀")
override fun initialState(viewModelContext: ViewModelContext): QuickReactionState? {
// Args are accessible from the context.
// val foo = vieWModelContext.args<MyArgs>.foo
val currentSession = viewModelContext.activity.get<Session>()
val parcel = viewModelContext.args as TimelineEventFragmentArgs
val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
?: return null
var agreeTriggle: TriggleState = TriggleState.NONE
var likeTriggle: TriggleState = TriggleState.NONE
event.annotations?.reactionsSummary?.forEach {
//it.addedByMe
if (it.addedByMe) {
if (agreePositive == it.key) {
agreeTriggle = TriggleState.FIRST
} else if (agreeNegative == it.key) {
agreeTriggle = TriggleState.SECOND
}
if (likePositive == it.key) {
likeTriggle = TriggleState.FIRST
} else if (likeNegative == it.key) {
likeTriggle = TriggleState.SECOND
}
}
val summary = event.annotations?.reactionsSummary
val quickReactions = quickEmojis.map { emoji ->
ToggleState(emoji, summary?.firstOrNull { it.key == emoji }?.addedByMe ?: false)
}
return QuickReactionState(agreeTriggle, likeTriggle, null, event.root.eventId ?: "")
return QuickReactionState(quickReactions, event.root.eventId ?: "")
}
}
}

View File

@ -4,116 +4,116 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="96dp">
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
android:id="@+id/quick_react_1_text"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:textColor="@color/black"
android:textSize="30sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toTopOf="@id/quick_react_agree_text"
app:layout_constraintEnd_toStartOf="@id/quick_react_2_text"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
android:id="@+id/quickReactionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:text="@string/quick_reactions"
android:textColor="?riotx_text_secondary"
android:textSize="16sp"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/quickReaction0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="👍" />
<TextView
android:id="@+id/quick_react_2_text"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:textColor="@color/black"
android:textSize="30sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/quick_react_1_text"
app:layout_constraintEnd_toStartOf="@id/center_guideline"
app:layout_constraintStart_toEndOf="@id/quick_react_1_text"
app:layout_constraintTop_toTopOf="@id/quick_react_1_text"
tools:text="👎" />
<TextView
android:id="@+id/quick_react_agree_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/reactions_agree"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/center_guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/quick_react_1_text" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/center_guideline"
android:id="@+id/quickReaction1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="👎" />
<TextView
android:id="@+id/quick_react_3_text"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:textColor="@color/black"
android:textSize="30sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@+id/quick_react_1_text"
app:layout_constraintEnd_toStartOf="@id/quick_react_4_text"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/center_guideline"
app:layout_constraintTop_toTopOf="@id/quick_react_1_text"
android:id="@+id/quickReaction2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="😀" />
<TextView
android:id="@+id/quick_react_4_text"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:textColor="@color/black"
android:textSize="30sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/quick_react_3_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/quick_react_3_text"
app:layout_constraintTop_toTopOf="@id/quick_react_3_text"
tools:text="😞" />
<TextView
android:id="@+id/quick_react_like_text"
android:id="@+id/quickReaction3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="🎉" />
<TextView
android:id="@+id/quickReaction4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="😕" />
<TextView
android:id="@+id/quickReaction5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="♥" />
<TextView
android:id="@+id/quickReaction6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="🍆" />
<TextView
android:id="@+id/quickReaction7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="26sp"
tools:ignore="MissingConstraints"
tools:text="👀" />
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/reactionsFlowHelper"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/reactions_like"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/quick_react_agree_text"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
app:constraint_referenced_ids="quickReaction0,quickReaction1,quickReaction2,quickReaction3,quickReaction4,quickReaction5,quickReaction6,quickReaction7"
app:flow_horizontalGap="8dp"
app:flow_horizontalStyle="spread"
app:flow_verticalBias="0"
app:flow_verticalGap="4dp"
app:flow_wrapMode="chain"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/center_guideline"
app:layout_constraintTop_toTopOf="@id/quick_react_agree_text" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/quickReactionTitle" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -10,4 +10,5 @@
<string name="settings_other_third_party_notices">Other third party notices</string>
<string name="navigate_to_room_when_already_in_the_room">You are already viewing this room!</string>
<string name="quick_reactions">Quick Reactions</string>
</resources>