Merge branch 'develop' into feature/home_rework

This commit is contained in:
Benoit Marty
2019-06-04 12:54:16 +02:00
94 changed files with 2304 additions and 778 deletions

View File

@ -88,7 +88,6 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
}
protected fun Disposable.disposeOnDestroy(): Disposable {
// TODO Ganfra: never disposed...
uiDisposables.add(this)
return this
}
@ -128,6 +127,8 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
unBinder?.unbind()
unBinder = null
uiDisposables.dispose()
}
override fun onResume() {

View File

@ -20,11 +20,13 @@ import android.text.Spannable
import com.otaliastudios.autocomplete.AutocompletePolicy
class CommandAutocompletePolicy : AutocompletePolicy {
var enabled: Boolean = true
override fun getQuery(text: Spannable): CharSequence {
if (text.length > 0) {
return text.substring(1, text.length)
}
// Should not happen
return ""
}
@ -34,7 +36,7 @@ class CommandAutocompletePolicy : AutocompletePolicy {
// Only if text which starts with '/' and without space
override fun shouldShowPopup(text: Spannable?, cursorPos: Int): Boolean {
return text?.startsWith("/") == true
return enabled && text?.startsWith("/") == true
&& !text.contains(" ")
}

View File

@ -72,9 +72,10 @@ class HomeModule {
val timelineMediaSizeProvider = TimelineMediaSizeProvider()
val colorProvider = ColorProvider(fragment.requireContext())
val timelineDateFormatter = get<TimelineDateFormatter>()
val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer, get())
val timelineItemFactory = TimelineItemFactory(
messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer),
messageItemFactory = messageItemFactory,
noticeItemFactory = NoticeItemFactory(noticeEventFormatter),
defaultItemFactory = DefaultItemFactory()
)

View File

@ -23,17 +23,21 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
sealed class RoomDetailActions {
data class SendMessage(val text: String) : RoomDetailActions()
data class SendMessage(val text: String, val autoMarkdown: Boolean) : RoomDetailActions()
data class SendMedia(val mediaFiles: List<MediaFile>) : RoomDetailActions()
data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions()
data class LoadMore(val direction: Timeline.Direction) : 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()
data class EnterReplyMode(val eventId: String) : RoomDetailActions()
}

View File

@ -40,6 +40,7 @@ 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.args
import com.airbnb.mvrx.fragmentViewModel
@ -53,6 +54,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.*
@ -74,6 +76,7 @@ import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.home.HomePermalinkHandler
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerActions
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerView
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerViewModel
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerViewState
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
@ -89,12 +92,17 @@ import im.vector.riotredesign.features.media.ImageMediaViewerActivity
import im.vector.riotredesign.features.media.VideoContentRenderer
import im.vector.riotredesign.features.media.VideoMediaViewerActivity
import im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity
import im.vector.riotredesign.features.settings.PreferencesManager
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_detail.*
import kotlinx.android.synthetic.main.merge_composer_layout.view.*
import org.commonmark.parser.Parser
import org.koin.android.ext.android.inject
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope
import org.koin.core.parameter.parametersOf
import ru.noties.markwon.Markwon
import ru.noties.markwon.html.HtmlPlugin
import timber.log.Timber
import java.io.File
@ -132,13 +140,12 @@ class RoomDetailFragment :
* @return the sanitized display name
*/
fun sanitizeDisplayname(displayName: String): String? {
var displayName = displayName
// sanity checks
if (!TextUtils.isEmpty(displayName)) {
val ircPattern = " (IRC)"
if (displayName.endsWith(ircPattern)) {
displayName = displayName.substring(0, displayName.length - ircPattern.length)
return displayName.substring(0, displayName.length - ircPattern.length)
}
}
@ -155,6 +162,7 @@ class RoomDetailFragment :
private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
private val textComposerViewModel: TextComposerViewModel by fragmentViewModel()
private val timelineEventController: TimelineEventController by inject { parametersOf(this) }
private val commandAutocompletePolicy = CommandAutocompletePolicy()
private val autocompleteCommandPresenter: AutocompleteCommandPresenter by inject { parametersOf(this) }
private val autocompleteUserPresenter: AutocompleteUserPresenter by inject { parametersOf(this) }
private val homePermalinkHandler: HomePermalinkHandler by inject()
@ -165,6 +173,9 @@ class RoomDetailFragment :
private lateinit var actionViewModel: ActionsHandler
@BindView(R.id.composerLayout)
lateinit var composerLayout: TextComposerView
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java)
@ -187,6 +198,77 @@ class RoomDetailFragment :
actionViewModel.actionCommandEvent.observe(this, Observer {
handleActions(it)
})
roomDetailViewModel.selectSubscribe(
RoomDetailViewState::sendMode,
RoomDetailViewState::selectedEvent,
RoomDetailViewState::roomId) { mode, event, roomId ->
when (mode) {
SendMode.REGULAR -> {
commandAutocompletePolicy.enabled = true
val uid = session.sessionParams.credentials.userId
val meMember = session.getRoom(roomId)?.getRoomMember(uid)
AvatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composerLayout.composerAvatarImageView)
composerLayout.collapse()
}
SendMode.EDIT,
SendMode.QUOTE,
SendMode.REPLY -> {
commandAutocompletePolicy.enabled = false
if (event == null) {
//we should ignore? can this happen?
Timber.e("Enter edit mode with no event selected")
return@selectSubscribe
}
//switch to expanded bar
composerLayout.composerRelatedMessageTitle.apply {
text = event.senderName
setTextColor(ContextCompat.getColor(requireContext(), AvatarRenderer.getColorFromUserId(event.root.sender
?: "")))
}
//TODO this is used at several places, find way to refactor?
val messageContent: MessageContent? =
event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.content.toModel()
val nonFormattedBody = messageContent?.body ?: ""
var formattedBody: CharSequence? = null
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
val parser = Parser.builder().build()
val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
formattedBody = Markwon.builder(requireContext())
.usePlugin(HtmlPlugin.create()).build().render(document)
}
composerLayout.composerRelatedMessageContent.text = formattedBody ?: nonFormattedBody
if (mode == SendMode.EDIT) {
//TODO if it's a reply we should trim the top part of message
composerLayout.composerEditText.setText(nonFormattedBody)
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_edit))
} else if (mode == SendMode.QUOTE) {
composerLayout.composerEditText.setText("")
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_quote))
} else if (mode == SendMode.REPLY) {
composerLayout.composerEditText.setText("")
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_reply))
}
AvatarRenderer.render(event.senderAvatar, event.root.sender
?: "", event.senderName, composerLayout.composerRelatedMessageAvatar)
composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length)
composerLayout.expand {
focusComposerAndShowKeyboard()
}
composerLayout.composerRelatedMessageCloseButton.setOnClickListener {
composerLayout.composerEditText.setText("")
roomDetailViewModel.resetSendMode()
}
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@ -233,8 +315,8 @@ class RoomDetailFragment :
private fun setupComposer() {
val elevation = 6f
val backgroundDrawable = ColorDrawable(Color.WHITE)
Autocomplete.on<Command>(composerEditText)
.with(CommandAutocompletePolicy())
Autocomplete.on<Command>(composerLayout.composerEditText)
.with(commandAutocompletePolicy)
.with(autocompleteCommandPresenter)
.with(elevation)
.with(backgroundDrawable)
@ -253,7 +335,7 @@ class RoomDetailFragment :
.build()
autocompleteUserPresenter.callback = this
Autocomplete.on<User>(composerEditText)
Autocomplete.on<User>(composerLayout.composerEditText)
.with(CharPolicy('@', true))
.with(autocompleteUserPresenter)
.with(elevation)
@ -281,7 +363,7 @@ class RoomDetailFragment :
// Add the span
val user = session.getUser(item.userId)
val span = PillImageSpan(glideRequests, context!!, item.userId, user)
span.bind(composerEditText)
span.bind(composerLayout.composerEditText)
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
@ -293,16 +375,16 @@ class RoomDetailFragment :
})
.build()
sendButton.setOnClickListener {
val textMessage = composerEditText.text.toString()
composerLayout.sendButton.setOnClickListener {
val textMessage = composerLayout.composerEditText.text.toString()
if (textMessage.isNotBlank()) {
roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage))
roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage, PreferencesManager.isMarkdownEnabled(requireContext())))
}
}
}
private fun setupAttachmentButton() {
attachmentButton.setOnClickListener {
composerLayout.attachmentButton.setOnClickListener {
val intent = Intent(requireContext(), FilePickerActivity::class.java)
intent.putExtra(FilePickerActivity.CONFIGS, Configurations.Builder()
.setCheckPermission(true)
@ -386,6 +468,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, composerLayout.composerAvatarImageView)
} else if (summary?.membership == Membership.INVITE && inviter != null) {
inviteView.visibility = View.VISIBLE
inviteView.render(inviter, VectorInviteView.Mode.LARGE)
@ -416,7 +503,7 @@ class RoomDetailFragment :
is SendMessageResult.MessageSent,
is SendMessageResult.SlashCommandHandled -> {
// Clear composer
composerEditText.text = null
composerLayout.composerEditText.text = null
}
is SendMessageResult.SlashCommandError -> {
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
@ -586,6 +673,18 @@ class RoomDetailFragment :
roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(eventId, clickedOn, opposite))
}
}
MessageMenuViewModel.ACTION_EDIT -> {
val eventId = actionData.data.toString()
roomDetailViewModel.process(RoomDetailActions.EnterEditMode(eventId))
}
MessageMenuViewModel.ACTION_QUOTE -> {
val eventId = actionData.data.toString()
roomDetailViewModel.process(RoomDetailActions.EnterQuoteMode(eventId))
}
MessageMenuViewModel.ACTION_REPLY -> {
val eventId = actionData.data.toString()
roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId))
}
else -> {
Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show()
}
@ -599,6 +698,7 @@ class RoomDetailFragment :
*
* @param text the text to insert.
*/
//TODO legacy, refactor
private fun insertUserDisplayNameInTextEditor(text: String?) {
//TODO move logic outside of fragment
if (null != text) {
@ -607,21 +707,21 @@ class RoomDetailFragment :
val myDisplayName = session.getUser(session.sessionParams.credentials.userId)?.displayName
if (TextUtils.equals(myDisplayName, text)) {
// current user
if (TextUtils.isEmpty(composerEditText.text)) {
composerEditText.append(Command.EMOTE.command + " ")
composerEditText.setSelection(composerEditText.text.length)
if (TextUtils.isEmpty(composerLayout.composerEditText.text)) {
composerLayout.composerEditText.append(Command.EMOTE.command + " ")
composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length)
// vibrate = true
}
} else {
// another user
if (TextUtils.isEmpty(composerEditText.text)) {
if (TextUtils.isEmpty(composerLayout.composerEditText.text)) {
// Ensure displayName will not be interpreted as a Slash command
if (text.startsWith("/")) {
composerEditText.append("\\")
composerLayout.composerEditText.append("\\")
}
composerEditText.append(sanitizeDisplayname(text)!! + ": ")
composerLayout.composerEditText.append(sanitizeDisplayname(text)!! + ": ")
} else {
composerEditText.text.insert(composerEditText.selectionStart, sanitizeDisplayname(text)!! + " ")
composerLayout.composerEditText.text.insert(composerLayout.composerEditText.selectionStart, sanitizeDisplayname(text)!! + " ")
}
// vibrate = true
@ -633,12 +733,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() {
composerLayout.composerEditText.requestFocus()
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.showSoftInput(composerLayout.composerEditText, InputMethodManager.SHOW_IMPLICIT)
}
fun showSnackWithMessage(message: String, duration: Int = Snackbar.LENGTH_SHORT) {
val snack = Snackbar.make(view!!, message, duration)
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
@ -35,11 +39,14 @@ import im.vector.riotredesign.features.command.CommandParser
import im.vector.riotredesign.features.command.ParsedCommand
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
) : VectorViewModel<RoomDetailViewState>(initialState) {
@ -83,9 +90,29 @@ 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)
is RoomDetailActions.EnterReplyMode -> handleReplyAction(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>>>>
@ -99,71 +126,145 @@ 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, autoMarkdown = action.autoMarkdown)
_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, action.autoMarkdown)
setState {
copy(
sendMode = SendMode.REGULAR,
selectedEvent = null
)
}
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
}
SendMode.QUOTE -> {
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))
}
SendMode.REPLY -> {
state.selectedEvent?.let {
room.replyToMessage(it.root, action.text)
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) {
@ -263,6 +364,34 @@ 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 handleReplyAction(action: RoomDetailActions.EnterReplyMode) {
room.getTimeLineEvent(action.eventId)?.let {
setState {
copy(
sendMode = SendMode.REPLY,
selectedEvent = it
)
}
}
}
private fun observeEventDisplayedActions() {
// We are buffering scroll events for one second

View File

@ -22,15 +22,33 @@ 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,
REPLY
}
data class RoomDetailViewState(
val roomId: String,
val eventId: String?,
val timeline: Timeline? = null,
val asyncInviter: 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

@ -0,0 +1,116 @@
package im.vector.riotredesign.features.home.room.detail.composer
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.isVisible
import androidx.transition.AutoTransition
import androidx.transition.Transition
import androidx.transition.TransitionManager
import butterknife.BindView
import butterknife.ButterKnife
import im.vector.riotredesign.R
/**
* Encapsulate the timeline composer UX.
*
*/
class TextComposerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) {
@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.composer_related_message_avatar_view)
lateinit var composerRelatedMessageAvatar: ImageView
@BindView(R.id.composer_related_message_action_image)
lateinit var composerRelatedMessageActionIcon: ImageView
@BindView(R.id.composer_related_message_close)
lateinit var composerRelatedMessageCloseButton: ImageButton
@BindView(R.id.composerEditText)
lateinit var composerEditText: EditText
@BindView(R.id.composer_avatar_view)
lateinit var composerAvatarImageView: ImageView
var currentConstraintSetId: Int = -1
init {
inflate(context, R.layout.merge_composer_layout, this)
ButterKnife.bind(this)
collapse(false)
}
fun collapse(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) {
if (currentConstraintSetId == R.layout.constraint_set_composer_layout_compact) {
//ignore we good
return
}
currentConstraintSetId = R.layout.constraint_set_composer_layout_compact
if (animate) {
val transition = AutoTransition()
// transition.duration = 5000
transition.addListener(object : Transition.TransitionListener {
override fun onTransitionEnd(transition: Transition) {
transitionComplete?.invoke()
}
override fun onTransitionResume(transition: Transition) {}
override fun onTransitionPause(transition: Transition) {}
override fun onTransitionCancel(transition: Transition) {}
override fun onTransitionStart(transition: Transition) {}
}
)
TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition)
}
ConstraintSet().also {
it.clone(context, currentConstraintSetId)
it.applyTo(this)
}
}
fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) {
if (currentConstraintSetId == R.layout.constraint_set_composer_layout_expanded) {
//ignore we good
return
}
currentConstraintSetId = R.layout.constraint_set_composer_layout_expanded
if (animate) {
val transition = AutoTransition()
// transition.duration = 5000
transition.addListener(object : Transition.TransitionListener {
override fun onTransitionEnd(transition: Transition) {
transitionComplete?.invoke()
}
override fun onTransitionResume(transition: Transition) {}
override fun onTransitionPause(transition: Transition) {}
override fun onTransitionCancel(transition: Transition) {}
override fun onTransitionStart(transition: Transition) {}
}
)
TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition)
}
ConstraintSet().also {
it.clone(context, currentConstraintSetId)
it.applyTo(this)
}
}
}

View File

@ -21,8 +21,14 @@ import com.airbnb.mvrx.ViewModelContext
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.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.riotredesign.core.platform.VectorViewModel
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
import org.koin.android.ext.android.get
import ru.noties.markwon.Markwon
import ru.noties.markwon.html.HtmlPlugin
import timber.log.Timber
import java.text.SimpleDateFormat
import java.util.*
@ -31,7 +37,7 @@ import java.util.*
data class MessageActionState(
val userId: String,
val senderName: String,
val messageBody: String,
val messageBody: CharSequence,
val ts: String?,
val senderAvatarPath: String? = null)
: MvRxState
@ -54,10 +60,19 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.content.toModel()
val originTs = event.root.originServerTs
var body: CharSequence = messageContent?.body ?: ""
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
val parser = Parser.builder().build()
val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
// val renderer = HtmlRenderer.builder().build()
body = Markwon.builder(viewModelContext.activity)
.usePlugin(HtmlPlugin.create()).build().render(document)
// body = renderer.render(document)
}
MessageActionState(
event.root.sender ?: "",
parcel.informationData.memberName.toString(),
messageContent?.body ?: "",
body,
dateFormat.format(Date(originTs ?: 0)),
currentSession.contentUrlResolver().resolveFullSize(parcel.informationData.avatarUrl)
)

View File

@ -50,16 +50,17 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
?: return null
val messageContent: MessageContent = event.root.content.toModel() ?: return null
val messageContent: MessageContent = event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.content.toModel() ?: return null
val type = messageContent.type
if (event.sendState == SendState.UNSENT) {
//Resend and Delete
return MessageMenuState(
listOf(
SimpleAction(ACTION_RESEND, R.string.resend, R.drawable.ic_corner_down_right, event.root.eventId),
SimpleAction(ACTION_RESEND, R.string.resend, R.drawable.ic_send, 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 +68,29 @@ 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))
if (event.sendState == SendState.SENDING) {
//TODO add cancel?
return@apply
}
//TODO is downloading attachement?
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 (canReply(event, messageContent)) {
this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, event.root.eventId))
}
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)) {
@ -82,9 +98,6 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, parcel.eventId))
}
if (canReply(event, messageContent)) {
this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_corner_down_right))
}
if (canShare(type)) {
if (messageContent is MessageImageContent) {
this.add(
@ -96,8 +109,6 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
//TODO
}
//TODO is uploading
//TODO is downloading
if (event.sendState == SendState.SENT) {
@ -159,6 +170,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 +209,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"

View File

@ -38,6 +38,7 @@ import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.core.linkify.VectorLinkify
import im.vector.riotredesign.core.resources.ColorProvider
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.core.utils.DebouncedClickListener
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
@ -52,7 +53,8 @@ import me.gujun.android.span.span
class MessageItemFactory(private val colorProvider: ColorProvider,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
private val timelineDateFormatter: TimelineDateFormatter,
private val htmlRenderer: EventHtmlRenderer) {
private val htmlRenderer: EventHtmlRenderer,
private val stringProvider: StringProvider) {
fun create(event: TimelineEvent,
nextEvent: TimelineEvent?,
@ -88,7 +90,9 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
avatarUrl = avatarUrl,
memberName = formattedMemberName,
showInformation = showInformation,
orderedReactionList = event.annotations?.reactionsSummary?.map { Triple(it.key, it.count, it.addedByMe) },
orderedReactionList = event.annotations?.reactionsSummary?.map {
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
},
hasBeenEdited = hasBeenEdited
)
@ -100,11 +104,11 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
val messageContent: MessageContent =
event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.content.toModel()
?: return null
?: //Malformed content, we should echo something on screen
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
if (messageContent.relatesTo?.type == RelationType.REPLACE) {
//TODO blank item or ignore??
// ignore this event
// ignore replace event, the targeted id is already edited
return BlankItem_()
}
// val all = event.root.toContent()

View File

@ -17,8 +17,10 @@
package im.vector.riotredesign.features.home.room.detail.timeline.helper
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.core.extensions.localDateTime
@ -42,33 +44,44 @@ object TimelineDisplayableEvents {
}
fun TimelineEvent.isDisplayable(): Boolean {
return TimelineDisplayableEvents.DISPLAYABLE_TYPES.contains(root.type) && !root.content.isNullOrEmpty()
}
fun List<TimelineEvent>.filterDisplayableEvents(): List<TimelineEvent> {
return this.filter {
it.isDisplayable()
if (!TimelineDisplayableEvents.DISPLAYABLE_TYPES.contains(root.type)) {
return false
}
if (root.content.isNullOrEmpty()) {
return false
}
//Edits should be filtered out!
if (EventType.MESSAGE == root.type
&& root.content.toModel<MessageContent>()?.relatesTo?.type == RelationType.REPLACE) {
return false
}
return true
}
//
//fun List<TimelineEvent>.filterDisplayableEvents(): List<TimelineEvent> {
// return this.filter {
// it.isDisplayable()
// }
//}
fun TimelineEvent.senderAvatar(): String? {
// We might have no avatar when user leave, so we try to get it from prevContent
return senderAvatar
?: if (root.type == EventType.STATE_ROOM_MEMBER) {
root.prevContent.toModel<RoomMember>()?.avatarUrl
} else {
null
}
?: if (root.type == EventType.STATE_ROOM_MEMBER) {
root.prevContent.toModel<RoomMember>()?.avatarUrl
} else {
null
}
}
fun TimelineEvent.senderName(): String? {
// We might have no senderName when user leave, so we try to get it from prevContent
return senderName
?: if (root.type == EventType.STATE_ROOM_MEMBER) {
root.prevContent.toModel<RoomMember>()?.displayName
} else {
null
}
?: if (root.type == EventType.STATE_ROOM_MEMBER) {
root.prevContent.toModel<RoomMember>()?.displayName
} else {
null
}
}
fun TimelineEvent.canBeMerged(): Boolean {

View File

@ -112,11 +112,12 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
(holder.reactionWrapper?.children?.elementAtOrNull(index) as? ReactionButton)?.let { reactionButton ->
reactionButton.isVisible = true
reactionButton.reactedListener = reactionClickListener
reactionButton.setTag(R.id.messageBottomInfo, reaction.first)
reactionButton.setTag(R.id.messageBottomInfo, reaction.key)
idToRefInFlow.add(reactionButton.id)
reactionButton.reactionString = reaction.first
reactionButton.reactionCount = reaction.second
reactionButton.setChecked(reaction.third)
reactionButton.reactionString = reaction.key
reactionButton.reactionCount = reaction.count
reactionButton.setChecked(reaction.addedByMe)
reactionButton.isEnabled = reaction.synced
}
}
// Just setting the view as gone will break the FlowHelper (and invisible will take too much space),

View File

@ -16,9 +16,8 @@
package im.vector.riotredesign.features.home.room.detail.timeline.item
import im.vector.matrix.android.api.session.room.send.SendState
import android.os.Parcelable
import im.vector.matrix.android.api.session.room.send.SendState
import kotlinx.android.parcel.Parcelize
@Parcelize
@ -31,6 +30,15 @@ data class MessageInformationData(
val memberName: CharSequence? = null,
val showInformation: Boolean = true,
/*List of reactions (emoji,count,isSelected)*/
var orderedReactionList: List<Triple<String,Int,Boolean>>? = null,
var orderedReactionList: List<ReactionInfoData>? = null,
var hasBeenEdited: Boolean = false
) : Parcelable
) : Parcelable
@Parcelize
data class ReactionInfoData(
val key: String,
val count: Int,
val addedByMe: Boolean,
val synced: Boolean
) : Parcelable

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

@ -1,22 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:height="13dp"
android:viewportWidth="22"
android:viewportHeight="22">
android:viewportHeight="13">
<path
android:pathData="M14.75,8.5L21,14.75 14.75,21"
android:pathData="M5.4444,1l-4.4444,4.3636l4.4444,4.3636"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M1,1v8.75a5,5 0,0 0,5 5h15"
android:pathData="M21,11.9091L21,9.7273C21,7.3173 19.0102,5.3636 16.5556,5.3636L1,5.3636"
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,164 @@
<?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="1dp"
android:background="?vctr_bottom_nav_background_border_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/composer_related_message_avatar_view"
android:layout_width="40dp"
android:layout_height="40dp"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toStartOf="parent"
tools:src="@tools:sample/avatars" />
<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"
android:visibility="invisible"
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,173 @@
<?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_constraintEnd_toEndOf="@id/related_message_backround"
app:layout_constraintStart_toStartOf="@+id/related_message_backround"
app:layout_constraintTop_toTopOf="@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" />
<ImageView
android:id="@+id/composer_related_message_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" />
<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_related_message_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_related_message_avatar_view"
app:layout_constraintStart_toStartOf="@id/composer_related_message_avatar_view"
app:layout_constraintTop_toBottomOf="@id/composer_related_message_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="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_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_toEndOf="@id/composer_avatar_view"
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">
@ -76,69 +77,20 @@
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/composerDivider"
app:layout_constraintBottom_toTopOf="@+id/composerLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomToolbar"
tools:listitem="@layout/item_timeline_event_base" />
<View
android:id="@+id/composerDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?vctr_list_divider_color"
app:layout_constraintBottom_toTopOf="@+id/composerLayout" />
<RelativeLayout
<im.vector.riotredesign.features.home.room.detail.composer.TextComposerView
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:inputType="textCapSentences|textMultiLine"
android:minHeight="48dp"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
android:padding="16dp"
android:textSize="14sp" />
</RelativeLayout>
app:layout_constraintStart_toStartOf="parent" />
<im.vector.riotredesign.features.invite.VectorInviteView
android:id="@+id/inviteView"

View File

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<merge 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:layout_width="match_parent"
android:layout_height="wrap_content"
tools:constraintSet="@layout/constraint_set_composer_layout_compact"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<!-- ========================
/!\ 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" />
<ImageView
android:id="@+id/composer_related_message_avatar_view"
android:layout_width="0dp"
android:layout_height="0dp"
tools:ignore="MissingConstraints"
tools:src="@tools:sample/avatars" />
<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" />
</merge>

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

@ -26,10 +26,9 @@
<string name="last_edited_info_message">Last edited by %s on %s</string>
<string name="malformed_message">Malformed event, cannot display</string>
<string name="create_new_room">Create New Room</string>
<string name="error_no_network">No network. Please check your Internet connection.</string>
<string name="action_change">"Change"</string>
<string name="change_room_directory_network">"Change network"</string>
<string name="please_wait">"Please wait…"</string>

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>