WIP / edit message

This commit is contained in:
Valere 2019-05-23 16:44:51 +02:00
parent b0e80e49b3
commit 45ea5c356e
39 changed files with 978 additions and 146 deletions

View File

@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session.room.model.annotation

import im.vector.matrix.android.api.util.Cancelable

//TODO rename in relationService?
interface ReactionService {


@ -49,4 +50,13 @@ interface ReactionService {
*/
fun updateQuickReaction(reaction: String, oppositeReaction: String, targetEventId: String, myUserId: String)


/**
* Edit a text message body. Limited to "m.text" contentType
* @param targetEventId The event to edit
* @param newBodyText The edited body
* @param compatibilityBodyText The text that will appear on clients that don't support yet edition
*/
fun editTextMessage(targetEventId: String, newBodyText: String, compatibilityBodyText: String = "* $newBodyText"): Cancelable

}

View File

@ -34,6 +34,7 @@ interface SendService {
* @return a [Cancelable]
*/
fun sendTextMessage(text: String, msgType: String = MessageType.MSGTYPE_TEXT): Cancelable
fun sendFormattedTextMessage(text: String,formattedText: String): Cancelable

/**
* Method to send a media asynchronously.

View File

@ -19,6 +19,7 @@ import androidx.work.*
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.annotation.ReactionService
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.session.room.send.RedactEventWorker
@ -150,4 +151,23 @@ internal class DefaultReactionService(private val roomId: String,
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
.build()
}

override fun editTextMessage(targetEventId: String, newBodyText: String, compatibilityBodyText: String): Cancelable {
val event = eventFactory.createReplaceTextEvent(roomId, targetEventId, newBodyText, MessageType.MSGTYPE_TEXT, compatibilityBodyText)
val sendContentWorkerParams = SendEventWorker.Params(roomId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)

//TODO use relation API?
val workRequest = OneTimeWorkRequestBuilder<SendEventWorker>()
.setConstraints(WORK_CONSTRAINTS)
.setInputData(sendWorkData)
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
.build()

WorkManager.getInstance()
.beginUniqueWork(buildWorkIdentifier(REACTION_WORK), ExistingWorkPolicy.APPEND, workRequest)
.enqueue()
return CancelableWork(workRequest.id)

}
}

View File

@ -60,6 +60,17 @@ internal class DefaultSendService(private val roomId: String,
return CancelableWork(sendWork.id)
}

override fun sendFormattedTextMessage(text: String, formattedText: String): Cancelable {
val event = eventFactory.createFormattedTextEvent(roomId, text, formattedText).also {
saveLocalEcho(it)
}
val sendWork = createSendEventWork(event)
WorkManager.getInstance()
.beginUniqueWork(buildWorkIdentifier(SEND_WORK), ExistingWorkPolicy.APPEND, sendWork)
.enqueue()
return CancelableWork(sendWork.id)
}

override fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable {
val cancelableBag = CancelableBag()
attachments.forEach {

View File

@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.annotation.ReactionContent
import im.vector.matrix.android.api.session.room.model.annotation.ReactionInfo
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent
import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor

@ -35,6 +36,30 @@ internal class LocalEchoEventFactory(private val credentials: Credentials) {
return createEvent(roomId, content)
}

fun createFormattedTextEvent(roomId: String, text: String, formattedText: String): Event {
val content = MessageTextContent(
type = MessageType.MSGTYPE_TEXT,
format = MessageType.FORMAT_MATRIX_HTML,
body = text,
formattedBody = formattedText
)
return createEvent(roomId, content)
}


fun createReplaceTextEvent(roomId: String, targetEventId: String, newBodyText: String, msgType: String, compatibilityText: String): Event {
val content = MessageTextContent(
type = msgType,
body = compatibilityText,
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
newContent = MessageTextContent(
type = MessageType.MSGTYPE_TEXT,
body = newBodyText
).toContent()
)
return createEvent(roomId, content)
}

fun createMediaEvent(roomId: String, attachment: ContentAttachmentData): Event {
return when (attachment.type) {
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment)

View File

@ -0,0 +1,39 @@
package im.vector.riotredesign.core.utils

import android.view.animation.OvershootInterpolator
import androidx.annotation.LayoutRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.transition.ChangeBounds
import androidx.transition.Transition
import androidx.transition.TransitionManager


inline fun ConstraintLayout.updateConstraintSet(@LayoutRes layoutId: Int, rootLayoutForAnimation: ConstraintLayout? = null, noinline onAnimationEnd: (() -> Unit)? = null) {
if (rootLayoutForAnimation != null) {
val transition = ChangeBounds()
transition.interpolator = OvershootInterpolator()
transition.addListener(object : Transition.TransitionListener {
override fun onTransitionResume(transition: Transition) {
}

override fun onTransitionPause(transition: Transition) {
}

override fun onTransitionCancel(transition: Transition) {
}

override fun onTransitionStart(transition: Transition) {
}

override fun onTransitionEnd(transition: Transition) {
onAnimationEnd?.invoke()
}
})
TransitionManager.beginDelayedTransition(rootLayoutForAnimation, transition)
}
ConstraintSet().also {
it.clone(this@updateConstraintSet.context, layoutId)
it.applyTo(this@updateConstraintSet)
}
}

View File

@ -0,0 +1,12 @@
package im.vector.riotredesign.features

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class DebugActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_debug)
}
}

View File

@ -31,10 +31,13 @@ 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 ShowEditHistoryAction(val event: String,val editAggregatedSummary: EditAggregatedSummary) : RoomDetailActions()
data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val opposite: String) : RoomDetailActions()
data class ShowEditHistoryAction(val event: String, val editAggregatedSummary: EditAggregatedSummary) : RoomDetailActions()
object AcceptInvite : RoomDetailActions()
object RejectInvite : RoomDetailActions()

data class EnterEditMode(val eventId: String) : RoomDetailActions()
data class EnterQuoteMode(val eventId: String) : RoomDetailActions()


}

View File

@ -32,14 +32,17 @@ import android.view.HapticFeedbackConstants
import android.view.LayoutInflater
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.ImageButton
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.fragmentViewModel
@ -53,6 +56,7 @@ import com.otaliastudios.autocomplete.Autocomplete
import com.otaliastudios.autocomplete.AutocompleteCallback
import com.otaliastudios.autocomplete.CharPolicy
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.message.*
@ -92,6 +96,7 @@ import im.vector.riotredesign.features.media.VideoMediaViewerActivity
import im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_detail.*
import kotlinx.android.synthetic.main.include_composer_layout.*
import org.koin.android.ext.android.inject
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope
@ -165,6 +170,15 @@ class RoomDetailFragment :

private lateinit var actionViewModel: ActionsHandler

@BindView(R.id.composer_related_message_sender)
lateinit var composerRelatedMessageTitle: TextView
@BindView(R.id.composer_related_message_preview)
lateinit var composerRelatedMessageContent: TextView
@BindView(R.id.composerLayout)
lateinit var composerLayout: ConstraintLayout
@BindView(R.id.rootConstraintLayout)
lateinit var rootConstraintLayout: ConstraintLayout

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java)
@ -187,6 +201,65 @@ class RoomDetailFragment :
actionViewModel.actionCommandEvent.observe(this, Observer {
handleActions(it)
})

roomDetailViewModel.selectSubscribe(RoomDetailViewState::sendMode, RoomDetailViewState::selectedEvent, RoomDetailViewState::roomId) { mode, event, roomId ->
when (mode) {
SendMode.REGULAR -> {
val uid = session.sessionParams.credentials.userId
val meMember = session.getRoom(roomId)?.getRoomMember(uid)
AvatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composer_avatar_view)
composerLayout.updateConstraintSet(R.layout.constraint_set_composer_layout_compact, rootConstraintLayout) {
focusComposerAndShowKeyboard()
}
}
SendMode.EDIT,
SendMode.QUOTE -> {
if (event == null) {
//we should ignore? can this happen?
Timber.e("Enter edit mode with no event selected")
return@selectSubscribe
}
//switch to expanded bar
composerRelatedMessageTitle.text = event.senderName
composerRelatedMessageTitle.setTextColor(
ContextCompat.getColor(requireContext(), AvatarRenderer.getColorFromUserId(event.root.sender
?: ""))
)

val messageContent: MessageContent? =
event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.content.toModel()
val eventTextBody = messageContent?.body
composerRelatedMessageContent.text = eventTextBody


if (mode == SendMode.EDIT) {
composerEditText.setText(eventTextBody)
composer_related_message_action_image.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_edit))
} else {
composerEditText.setText("")
composer_related_message_action_image.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_quote))
}

AvatarRenderer.render(event.senderAvatar, event.root.sender
?: "", event.senderName, composer_avatar_view)

composerEditText.setSelection(composerEditText.text.length)
composerLayout.updateConstraintSet(R.layout.constraint_set_composer_layout_expanded, rootConstraintLayout) {
focusComposerAndShowKeyboard()
}

view?.findViewById<ImageButton>(R.id.composer_related_message_close)?.setOnClickListener {

composerRelatedMessageTitle.text = ""
composerRelatedMessageContent.text = ""
composerEditText.setText("")
roomDetailViewModel.resetSendMode()
}

}
}
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@ -398,6 +471,11 @@ class RoomDetailFragment :
if (summary?.membership == Membership.JOIN) {
timelineEventController.setTimeline(state.timeline)
inviteView.visibility = View.GONE

val uid = session.sessionParams.credentials.userId
val meMember = session.getRoom(state.roomId)?.getRoomMember(uid)
AvatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composer_avatar_view)

} else if (summary?.membership == Membership.INVITE && inviter != null) {
inviteView.visibility = View.VISIBLE
inviteView.render(inviter, VectorInviteView.Mode.LARGE)
@ -601,6 +679,14 @@ class RoomDetailFragment :
roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(eventId, clickedOn, opposite))
}
}
MessageMenuViewModel.ACTION_EDIT -> {
val eventId = actionData.data.toString() ?: return@let
roomDetailViewModel.process(RoomDetailActions.EnterEditMode(eventId))
}
MessageMenuViewModel.ACTION_QUOTE -> {
val eventId = actionData.data.toString() ?: return@let
roomDetailViewModel.process(RoomDetailActions.EnterQuoteMode(eventId))
}
else -> {
Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show()
}
@ -648,12 +734,16 @@ class RoomDetailFragment :
// v.vibrate(100)
// }
// }
composerEditText.requestFocus()
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.showSoftInput(composerEditText, InputMethodManager.SHOW_FORCED)
focusComposerAndShowKeyboard()
}
}

private fun focusComposerAndShowKeyboard() {
composerEditText.requestFocus()
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.showSoftInput(composerEditText, InputMethodManager.SHOW_IMPLICIT)
}

fun showSnackWithMessage(message: String, duration: Int = Snackbar.LENGTH_SHORT) {
val snack = Snackbar.make(view!!, message, Snackbar.LENGTH_SHORT)
snack.view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.notification_accent_color))

View File

@ -16,6 +16,7 @@

package im.vector.riotredesign.features.home.room.detail

import android.text.TextUtils
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.airbnb.mvrx.MvRxViewModelFactory
@ -25,8 +26,11 @@ import com.jakewharton.rxrelay2.BehaviorRelay
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.rx.rx
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorViewModel
@ -36,11 +40,14 @@ import im.vector.riotredesign.features.command.ParsedCommand
import im.vector.riotredesign.features.home.room.VisibleRoomStore
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
import io.reactivex.rxkotlin.subscribeBy
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
import org.koin.android.ext.android.get
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit


class RoomDetailViewModel(initialState: RoomDetailViewState,
private val session: Session,
private val visibleRoomHolder: VisibleRoomStore
@ -87,9 +94,28 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
is RoomDetailActions.UndoReaction -> handleUndoReact(action)
is RoomDetailActions.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
is RoomDetailActions.ShowEditHistoryAction -> handleShowEditHistoryReaction(action)
is RoomDetailActions.EnterEditMode -> handleEditAction(action)
is RoomDetailActions.EnterQuoteMode -> handleQuoteAction(action)
}
}

fun enterEditMode(event: TimelineEvent) {
setState {
copy(
sendMode = SendMode.EDIT,
selectedEvent = event
)
}
}

fun resetSendMode() {
setState {
copy(
sendMode = SendMode.REGULAR,
selectedEvent = null
)
}
}

private val _nonBlockingPopAlert = MutableLiveData<LiveEvent<Pair<Int, List<Any>>>>()
val nonBlockingPopAlert: LiveData<LiveEvent<Pair<Int, List<Any>>>>
@ -103,71 +129,135 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
// PRIVATE METHODS *****************************************************************************

private fun handleSendMessage(action: RoomDetailActions.SendMessage) {
// Handle slash command
val slashCommandResult = CommandParser.parseSplashCommand(action.text)
withState { state ->
when (state.sendMode) {
SendMode.REGULAR -> {
val slashCommandResult = CommandParser.parseSplashCommand(action.text)

when (slashCommandResult) {
is ParsedCommand.ErrorNotACommand -> {
// Send the text message to the room
room.sendTextMessage(action.text)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
}
is ParsedCommand.ErrorSyntax -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command)))
}
is ParsedCommand.ErrorEmptySlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/")))
}
is ParsedCommand.ErrorUnknownSlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand)))
}
is ParsedCommand.Invite -> {
handleInviteSlashCommand(slashCommandResult)
}
is ParsedCommand.SetUserPowerLevel -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.ClearScalarToken -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.SetMarkdown -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.UnbanUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.BanUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.KickUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.JoinRoom -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.PartRoom -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.SendEmote -> {
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
}
is ParsedCommand.ChangeTopic -> {
handleChangeTopicSlashCommand(slashCommandResult)
}
is ParsedCommand.ChangeDisplayName -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
when (slashCommandResult) {
is ParsedCommand.ErrorNotACommand -> {
// Send the text message to the room
room.sendTextMessage(action.text)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
}
is ParsedCommand.ErrorSyntax -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command)))
}
is ParsedCommand.ErrorEmptySlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/")))
}
is ParsedCommand.ErrorUnknownSlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand)))
}
is ParsedCommand.Invite -> {
handleInviteSlashCommand(slashCommandResult)
}
is ParsedCommand.SetUserPowerLevel -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.ClearScalarToken -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.SetMarkdown -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.UnbanUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.BanUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.KickUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.JoinRoom -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.PartRoom -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
is ParsedCommand.SendEmote -> {
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
}
is ParsedCommand.ChangeTopic -> {
handleChangeTopicSlashCommand(slashCommandResult)
}
is ParsedCommand.ChangeDisplayName -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
}
}
}
SendMode.EDIT -> {
room.editTextMessage(state?.selectedEvent?.root?.eventId ?: "", action.text)
setState {
copy(
sendMode = SendMode.REGULAR,
selectedEvent = null
)
}
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
}
SendMode.QUOTE -> {
withState { state ->
val messageContent: MessageContent? =
state.selectedEvent?.annotations?.editSummary?.aggregatedContent?.toModel()
?: state.selectedEvent?.root?.content.toModel()
val textMsg = messageContent?.body

val finalText = legacyRiotQuoteText(textMsg, action.text)

//TODO Refactor this, just temporary for quotes
val parser = Parser.builder().build()
val document = parser.parse(finalText)
val renderer = HtmlRenderer.builder().build()
val htmlText = renderer.render(document)
if (TextUtils.equals(finalText, htmlText)) {
room.sendTextMessage(finalText)
} else {
room.sendFormattedTextMessage(finalText, htmlText)
}
setState {
copy(
sendMode = SendMode.REGULAR,
selectedEvent = null
)
}
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
}

}
}
}
// Handle slash command

}

private fun legacyRiotQuoteText(quotedText: String?, myText: String): String {
val messageParagraphs = quotedText?.split("\n\n".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()
var quotedTextMsg = StringBuilder()
if (messageParagraphs != null) {
for (i in messageParagraphs.indices) {
if (messageParagraphs[i].trim({ it <= ' ' }) != "") {
quotedTextMsg.append("> ").append(messageParagraphs[i])
}

if (i + 1 != messageParagraphs.size) {
quotedTextMsg.append("\n\n")
}
}
}
val finalText = "$quotedTextMsg\n\n$myText"
return finalText
}

private fun handleShowEditHistoryReaction(action: RoomDetailActions.ShowEditHistoryAction) {
@ -271,6 +361,23 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
room.join(object : MatrixCallback<Unit> {})
}

private fun handleEditAction(action: RoomDetailActions.EnterEditMode) {
room.getTimeLineEvent(action.eventId)?.let {
enterEditMode(it)
}
}

private fun handleQuoteAction(action: RoomDetailActions.EnterQuoteMode) {
room.getTimeLineEvent(action.eventId)?.let {
setState {
copy(
sendMode = SendMode.QUOTE,
selectedEvent = it
)
}
}
}


private fun observeEventDisplayedActions() {
// We are buffering scroll events for one second

View File

@ -22,15 +22,32 @@ import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineData
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.user.model.User

/**
* Describes the current send mode:
* REGULAR: sends the text as a regular message
* QUOTE: User is currently quoting a message
* EDIT: User is currently editing an existing message
*
* Depending on the state the bottom toolbar will change (icons/preview/actions...)
*/
enum class SendMode {
REGULAR,
QUOTE,
EDIT
}

data class RoomDetailViewState(
val roomId: String,
val eventId: String?,
val timeline: Timeline? = null,
val inviter: Async<User> = Uninitialized,
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
val asyncTimelineData: Async<TimelineData> = Uninitialized
val asyncTimelineData: Async<TimelineData> = Uninitialized,
val sendMode: SendMode = SendMode.REGULAR,
val selectedEvent: TimelineEvent? = null
) : MvRxState {

constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId)

View File

@ -59,7 +59,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
listOf(
SimpleAction(ACTION_RESEND, R.string.resend, R.drawable.ic_corner_down_right, event.root.eventId),
//TODO delete icon
SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_material_delete, event.root.eventId)
SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId)
)
)
}
@ -67,14 +67,18 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes

//TODO determine if can copy, forward, reply, quote, report?
val actions = ArrayList<SimpleAction>().apply {
this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_smile, event.root.eventId))
this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, event.root.eventId))
if (canCopy(type)) {
//TODO copy images? html? see ClipBoard
this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent.body))
}

if (canEdit(event, currentSession.sessionParams.credentials.userId)) {
this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, event.root.eventId))
}

if (canRedact(event, currentSession.sessionParams.credentials.userId)) {
this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_material_delete, event.root.eventId))
this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId))
}

if (canQuote(event, messageContent)) {
@ -159,6 +163,17 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
return event.root.sender == myUserId
}

private fun canEdit(event: TimelineEvent, myUserId: String): Boolean {
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
if (event.root.type != EventType.MESSAGE) return false
//TODO if user is admin or moderator
val messageContent = event.root.content.toModel<MessageContent>()
return event.root.sender == myUserId && (
messageContent?.type == MessageType.MSGTYPE_TEXT
|| messageContent?.type == MessageType.MSGTYPE_EMOTE
)
}


private fun canCopy(type: String): Boolean {
return when (type) {
@ -187,6 +202,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes

const val ACTION_ADD_REACTION = "add_reaction"
const val ACTION_COPY = "copy"
const val ACTION_EDIT = "edit"
const val ACTION_QUOTE = "quote"
const val ACTION_REPLY = "reply"
const val ACTION_SHARE = "share"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 728 B

View File

@ -0,0 +1,54 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="22dp"
android:viewportWidth="24"
android:viewportHeight="22">
<path
android:pathData="m13.5909,20.8117c-0.8681,0.2472 -1.7837,0.3795 -2.7298,0.3795 -5.5574,0 -10.0625,-4.5627 -10.0625,-10.1912 0,-5.6284 4.5051,-10.1912 10.0625,-10.1912 5.4556,0 9.8971,4.3972 10.058,9.8831h-0.9588c-0.1605,-4.9498 -4.1729,-8.9125 -9.0992,-8.9125 -5.0281,0 -9.1042,4.1282 -9.1042,9.2206 0,5.0924 4.0761,9.2206 9.1042,9.2206 0.951,0 1.868,-0.1477 2.7298,-0.4217z"
android:strokeLineJoin="round"
android:strokeWidth="1.047619"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9e9e9e"
android:strokeLineCap="round"/>
<path
android:pathData="m14.6944,16.8235h7.6667"
android:strokeLineJoin="round"
android:strokeWidth="2.095238"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9e9e9e"
android:strokeLineCap="round"/>
<path
android:pathData="m18.5278,12.9412l-0,7.7647"
android:strokeLineJoin="round"
android:strokeWidth="2.095238"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9e9e9e"
android:strokeLineCap="round"/>
<path
android:pathData="m7.0278,12.9412s1.4375,1.9412 3.8333,1.9412c2.3958,0 3.8333,-1.9412 3.8333,-1.9412"
android:strokeLineJoin="round"
android:strokeWidth="2.095238"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9e9e9e"
android:strokeLineCap="round"/>
<path
android:pathData="m7.9861,8.0882h0.0096"
android:strokeLineJoin="round"
android:strokeWidth="2.095238"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9e9e9e"
android:strokeLineCap="round"/>
<path
android:pathData="m13.7361,8.0882h0.0096"
android:strokeLineJoin="round"
android:strokeWidth="2.095238"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9e9e9e"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="21dp"
android:height="22dp"
android:viewportWidth="21"
android:viewportHeight="22">
<path
android:pathData="M19.468,10.571l-8.73,8.753a5.693,5.693 0,0 1,-8.066 0,5.728 5.728,0 0,1 0,-8.086l8.73,-8.752a3.795,3.795 0,0 1,5.378 0,3.818 3.818,0 0,1 0,5.39L8.04,16.63a1.898,1.898 0,0 1,-2.689 0,1.91 1.91,0 0,1 0,-2.696l8.065,-8.076"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9E9E9E"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M11,11m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#979797"
android:fillType="evenOdd"/>
<path
android:pathData="M7.667,7.667L14.333,14.333M14.333,7.667L7.667,14.333"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>

View File

@ -4,31 +4,19 @@
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M11,11m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:pathData="M3.222,1L18.778,1A2.222,2.222 0,0 1,21 3.222L21,18.778A2.222,2.222 0,0 1,18.778 21L3.222,21A2.222,2.222 0,0 1,1 18.778L1,3.222A2.222,2.222 0,0 1,3.222 1z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeColor="#9E9E9E"
android:strokeLineCap="round"/>
<path
android:pathData="M7,13C7,13 8.5,15 11,15C13.5,15 15,13 15,13"
android:pathData="M7.667,7.667l6.666,6.666M14.333,7.667l-6.666,6.666"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M7.5,7.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"
android:strokeWidth="1"
android:fillColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
<path
android:pathData="M14.5,7.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"
android:strokeWidth="1"
android:fillColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@ -4,19 +4,19 @@
android:viewportWidth="21"
android:viewportHeight="22">
<path
android:pathData="M9.497,3.06H2.888C1.845,3.06 1,3.93 1,5v13.576c0,1.07 0.845,1.94 1.888,1.94h13.218c1.042,0 1.888,-0.87 1.888,-1.94v-6.788"
android:pathData="M9.4969,3.0606L2.8882,3.0606C1.8454,3.0606 1,3.9289 1,5L1,18.5758C1,19.6469 1.8454,20.5152 2.8882,20.5152L16.1056,20.5152C17.1484,20.5152 17.9938,19.6469 17.9938,18.5758L17.9938,11.7879"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M16.578,1.606a1.966,1.966 0,0 1,2.832 0,2.097 2.097,0 0,1 0,2.91l-8.969,9.211 -3.776,0.97 0.944,-3.879 8.969,-9.212z"
android:pathData="M16.5776,1.6061C17.3598,0.8027 18.6278,0.8027 19.4099,1.6061C20.1921,2.4094 20.1921,3.7118 19.4099,4.5152L10.441,13.7273L6.6646,14.697L7.6087,10.8182L16.5776,1.6061Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M20.142,11H4.586M20.142,11L1.05,20.192 4.586,11 1.05,1.808z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#03B381"
android:strokeLineCap="round"/>
</vector>

View File

@ -3,7 +3,6 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout_height="50dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
@ -12,7 +11,8 @@
android:paddingLeft="@dimen/layout_horizontal_margin"
android:paddingTop="8dp"
android:paddingRight="@dimen/layout_horizontal_margin"
android:paddingBottom="8dp">
android:paddingBottom="8dp"
tools:layout_height="50dp">

<ImageView
android:id="@+id/action_icon"
@ -21,8 +21,8 @@
android:layout_gravity="center_vertical"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:src="@drawable/ic_material_delete"
android:tint="?android:attr/textColorSecondary" />
tools:src="@drawable/ic_delete"
android:tint="?android:attr/textColorTertiary" />

<TextView
android:id="@+id/action_title"

View File

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/composerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">

<View
android:id="@+id/related_message_backround"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?vctr_bottom_nav_background_color"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:layout_height="40dp" />

<View
android:id="@+id/related_message_background_top_separator"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?vctr_bottom_nav_background_border_color"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

<View
android:id="@+id/related_message_background_bottom_separator"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?vctr_bottom_nav_background_border_color"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

<TextView
android:id="@+id/composer_related_message_sender"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textStyle="bold"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@id/composer_related_message_preview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="@tools:sample/first_names" />

<TextView
android:id="@+id/composer_related_message_preview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="@tools:sample/lorem/random" />

<ImageView
android:id="@+id/composer_related_message_action_image"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="38dp"
android:alpha="0"
android:tint="?android:attr/textColorTertiary"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent"
tools:ignore="MissingConstraints"
tools:src="@drawable/ic_edit" />


<ImageButton
android:id="@+id/composer_related_message_close"
android:layout_width="22dp"
android:layout_height="22dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_close_round"
android:tint="@color/rosy_pink"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintStart_toEndOf="parent" />

<ImageView
android:id="@+id/composer_avatar_view"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/composerEditText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1"
tools:src="@tools:sample/avatars" />

<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_attachment"
android:tint="?attr/colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/sendButton"
app:layout_constraintStart_toEndOf="@id/composerEditText" />

<androidx.constraintlayout.widget.Barrier
android:id="@+id/composer_preview_barrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="bottom"
app:barrierMargin="8dp"
app:constraint_referenced_ids="composer_related_message_preview,composer_related_message_action_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

<ImageButton
android:id="@+id/sendButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_send"
android:tint="?attr/colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/attachmentButton" />

<EditText
android:id="@+id/composerEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:hint="@string/room_message_placeholder_not_encrypted"
android:maxHeight="200dp"
android:minHeight="48dp"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
android:padding="16dp"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/attachmentButton"
app:layout_constraintStart_toEndOf="@+id/composer_avatar_view"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem/random" />
<!--tools:text="@tools:sample/lorem/random"-->

</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/composerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">

<View
android:id="@+id/related_message_backround"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?vctr_bottom_nav_background_color"
app:layout_constraintBottom_toBottomOf="@id/composer_preview_barrier"
app:layout_constraintTop_toTopOf="parent" />

<View
android:id="@+id/related_message_background_top_separator"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?vctr_bottom_nav_background_border_color"
app:layout_constraintTop_toTopOf="@id/related_message_backround"
app:layout_constraintEnd_toEndOf="@id/related_message_backround"
app:layout_constraintStart_toStartOf="@+id/related_message_backround" />

<View
android:id="@+id/related_message_background_bottom_separator"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?vctr_bottom_nav_background_border_color"
app:layout_constraintBottom_toBottomOf="@id/related_message_backround"
app:layout_constraintEnd_toEndOf="@id/related_message_backround"
app:layout_constraintStart_toStartOf="@+id/related_message_backround" />

<TextView
android:id="@+id/composer_related_message_sender"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/composer_related_message_close"
app:layout_constraintStart_toEndOf="@id/composer_avatar_view"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/first_names" />

<TextView
android:id="@+id/composer_related_message_preview"
android:layout_width="0dp"
android:layout_height="match_parent"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?vctr_message_text_color"
app:layout_constrainedHeight="true"
app:layout_constraintEnd_toEndOf="@id/composer_related_message_sender"
app:layout_constraintStart_toStartOf="@id/composer_related_message_sender"
app:layout_constraintTop_toBottomOf="@id/composer_related_message_sender"
tools:text="@tools:sample/lorem/random" />

<ImageView
android:id="@+id/composer_related_message_action_image"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="38dp"
android:alpha="1"
android:tint="?android:attr/textColorTertiary"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="@id/composer_avatar_view"
app:layout_constraintStart_toStartOf="@id/composer_avatar_view"
app:layout_constraintTop_toBottomOf="@id/composer_avatar_view"
tools:src="@drawable/ic_edit" />


<ImageButton
android:id="@+id/composer_related_message_close"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_close_round"
android:tint="@color/rosy_pink"
app:layout_constraintBottom_toBottomOf="@id/composer_related_message_preview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/composer_related_message_preview" />


<ImageView
android:id="@+id/composer_avatar_view"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@id/composer_related_message_action_image"
app:layout_constraintEnd_toStartOf="@+id/composer_related_message_sender"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/composer_related_message_sender"
tools:src="@tools:sample/avatars" />


<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_attachment"
android:tint="?attr/colorAccent"
app:layout_constraintEnd_toStartOf="@+id/sendButton"
app:layout_constraintTop_toBottomOf="parent" />

<androidx.constraintlayout.widget.Barrier
android:id="@+id/composer_preview_barrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="bottom"
app:barrierMargin="8dp"
app:constraint_referenced_ids="composer_related_message_preview,composer_related_message_action_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

<ImageButton
android:id="@+id/sendButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_send"
android:tint="?attr/colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/composerEditText"
app:layout_constraintTop_toBottomOf="@id/composer_preview_barrier"
app:layout_constraintVertical_bias="1" />

<EditText
android:id="@+id/composerEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:hint="@string/room_message_placeholder_not_encrypted"
android:minHeight="48dp"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
android:padding="16dp"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/sendButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/composer_preview_barrier"
tools:text="@tools:sample/lorem" />

</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,6 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">

@ -89,66 +90,18 @@
android:background="?vctr_list_divider_color"
app:layout_constraintBottom_toTopOf="@+id/composerLayout" />

<RelativeLayout
android:id="@+id/composerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">

<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/sendButton"
android:layout_toLeftOf="@id/sendButton"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_attach_file_white"
android:tint="?attr/colorAccent" />

<ImageButton
android:id="@+id/sendButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_send_white"
android:tint="?attr/colorAccent" />

<EditText
android:id="@+id/composerEditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/attachmentButton"
android:layout_toLeftOf="@id/attachmentButton"
android:background="@android:color/transparent"
android:gravity="center_vertical"
android:hint="@string/room_message_placeholder_not_encrypted"
android:minHeight="48dp"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
android:padding="16dp"
android:textSize="14sp" />

</RelativeLayout>
<include layout="@layout/include_composer_layout" />

<im.vector.riotredesign.features.invite.VectorInviteView
android:id="@+id/inviteView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintVertical_bias="1.0" />
app:layout_constraintVertical_bias="1.0"
tools:visibility="gone" />

</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/composerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:constraintSet="@layout/constraint_set_composer_layout_compact"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">

<!-- ========================
/!\ Constraints for this layout are defined in external layout files that are used as constraint set for animation.
/!\ These 3 files must be modified to stay coherent!
======================== -->
<View
android:id="@+id/related_message_backround"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?vctr_bottom_nav_background_color"
tools:ignore="MissingConstraints" />

<View
android:id="@+id/related_message_background_top_separator"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?vctr_bottom_nav_background_border_color"
tools:ignore="MissingConstraints" />

<View
android:id="@+id/related_message_background_bottom_separator"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?vctr_bottom_nav_background_border_color"
tools:ignore="MissingConstraints" />

<TextView
android:id="@+id/composer_related_message_sender"
android:layout_width="0dp"
android:layout_height="0dp"
android:textStyle="bold"
tools:ignore="MissingConstraints"
tools:text="@tools:sample/first_names"
tools:visibility="gone" />

<TextView
android:id="@+id/composer_related_message_preview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="3"
android:textColor="?vctr_message_text_color"
tools:ignore="MissingConstraints"
tools:text="@tools:sample/lorem"
tools:visibility="gone" />

<ImageView
android:id="@+id/composer_related_message_action_image"
android:layout_width="0dp"
android:layout_height="0dp"
android:tint="?android:attr/textColorTertiary"
tools:ignore="MissingConstraints" />

<ImageButton
android:id="@+id/composer_related_message_close"
android:layout_width="22dp"
android:layout_height="22dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_close_round"
android:tint="@color/rosy_pink"
tools:ignore="MissingConstraints" />


<ImageView
android:id="@+id/composer_avatar_view"
android:layout_width="0dp"
android:layout_height="0dp"
tools:ignore="MissingConstraints"
tools:src="@tools:sample/avatars" />

<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_attachment"
android:tint="?attr/colorAccent"
tools:ignore="MissingConstraints" />

<androidx.constraintlayout.widget.Barrier
android:id="@+id/composer_preview_barrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="bottom"
app:barrierMargin="8dp"
app:constraint_referenced_ids="composer_related_message_preview,composer_related_message_action_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

<ImageButton
android:id="@+id/sendButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_send"
android:tint="?attr/colorAccent"
tools:ignore="MissingConstraints" />

<EditText
android:id="@+id/composerEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:hint="@string/room_message_placeholder_not_encrypted"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
android:padding="8dp"
android:textColor="?vctr_message_text_color"
android:textSize="14sp"
tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -4,6 +4,7 @@
<declare-styleable name="VectorStyles">

<attr name="vctr_bottom_nav_background_color" format="color" />
<attr name="vctr_bottom_nav_background_border_color" format="color" />

<!-- waiting view background -->
<attr name="vctr_waiting_background_color" format="color" />

View File

@ -19,6 +19,7 @@
<color name="tab_rooms">@color/accent_color_light</color>
<color name="tab_rooms_secondary">#5EA584</color>
<color name="tab_groups">#a6d0e5</color>

<color name="tab_groups_secondary">#81bddb</color>

<!-- color of the direct chat avatar ring (it's 50% of color accent) -->

View File

@ -27,6 +27,7 @@
<!-- activities background -->
<item name="android:windowBackground">@color/riot_primary_background_color_black</item>
<item name="vctr_bottom_nav_background_color">@color/primary_color_black</item>
<item name="vctr_bottom_nav_background_border_color">#FFE9EDF1</item>

<item name="vctr_direct_chat_circle">@drawable/direct_chat_circle_black</item>
</style>

View File

@ -21,6 +21,7 @@
<!-- default background color -->
<item name="android:colorBackground">@color/riot_primary_background_color_dark</item>
<item name="vctr_bottom_nav_background_color">@color/primary_color_dark</item>
<item name="vctr_bottom_nav_background_border_color">#FFE9EDF1</item>

<!-- waiting view background -->
<item name="vctr_waiting_background_color">#55555555</item>

View File

@ -23,6 +23,7 @@
<!-- default background color -->
<item name="android:colorBackground">@color/riot_primary_background_color_light</item>
<item name="vctr_bottom_nav_background_color">#FFF3F8FD</item>
<item name="vctr_bottom_nav_background_border_color">#FFE9EDF1</item>

<!-- default button -->
<item name="android:buttonStyle">@style/Widget.Vector.Button</item>

View File

@ -23,6 +23,7 @@
<item name="android:colorBackground">@color/riot_primary_background_color_status</item>
<item name="vctr_bottom_nav_background_color">@color/riot_primary_background_color_status
</item>
<item name="vctr_bottom_nav_background_border_color">#FFE9EDF1</item>

<item name="buttonStyle">@style/Widget.Vector.Button</item>