New RTE full screen implementation with BottomSheet (#7578)

* RTE full screen editor using custom BottomSheet

* Fix formatting menu item dimensions

* Fix bug with insets when opening attachment menu

* Clear the EditText for plain text mode when a message is sent

* Set `MessageComposerMode.Special` as a sealed class

* Fix insets issue on landscape

* Fix small UI issues with rounded corners

* Use simplified icons for full screen and minimise
This commit is contained in:
Jorge Martin Espinosa 2022-11-18 08:57:37 +01:00 committed by GitHub
parent 65d898e3de
commit 7417241cd5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1904 additions and 2238 deletions

1
changelog.d/7577.feature Normal file
View file

@ -0,0 +1 @@
New implementation of the full screen mode for the Rich Text Editor.

View file

@ -1642,7 +1642,10 @@
<string name="error_user_already_logged_in">It looks like youre trying to connect to another homeserver. Do you want to sign out?</string>
<string name="edit">Edit</string>
<string name="editing">Editing</string>
<string name="reply">Reply</string>
<string name="replying_to">Replying to %s</string>
<string name="quoting">Quoting</string>
<string name="reply_in_thread">Reply in thread</string>
<string name="view_in_room">View In Room</string>

View file

@ -49,6 +49,7 @@
<dimen name="composer_attachment_margin">1dp</dimen>
<dimen name="rich_text_composer_corner_radius_single_line">28dp</dimen>
<dimen name="rich_text_composer_corner_radius_expanded">14dp</dimen>
<dimen name="rich_text_composer_menu_item_size">44dp</dimen>
<dimen name="chat_bubble_margin_start">28dp</dimen>
<dimen name="chat_bubble_margin_end">6dp</dimen>

View file

@ -4,7 +4,7 @@
<style name="Widget.Vector.EditText.Composer" parent="Widget.AppCompat.EditText">
<item name="android:background">@android:color/transparent</item>
<item name="android:inputType">textCapSentences|textMultiLine</item>
<item name="android:maxLines">12</item>
<item name="android:maxLines">10</item>
<item name="android:minHeight">48dp</item>
<item name="android:padding">8dp</item>
<item name="android:textSize">15sp</item>
@ -14,9 +14,12 @@
<style name="Widget.Vector.EditText.RichTextComposer" parent="Widget.AppCompat.EditText">
<item name="android:background">@android:color/transparent</item>
<item name="android:inputType">textCapSentences|textMultiLine</item>
<item name="android:maxLines">12</item>
<item name="android:minHeight">20dp</item>
<item name="android:padding">0dp</item>
<item name="android:maxLines">10</item>
<item name="android:minHeight">40dp</item>
<item name="android:paddingTop">10dp</item>
<item name="android:paddingBottom">10dp</item>
<item name="paddingStart">12dp</item>
<item name="android:clipToPadding">false</item>
<item name="android:textSize">15sp</item>
<item name="android:textColor">?vctr_message_text_color</item>
</style>

View file

@ -0,0 +1,791 @@
package im.vector.app.core.utils
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.View.MeasureSpec
import android.view.ViewGroup
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.customview.widget.ViewDragHelper
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import timber.log.Timber
import java.lang.ref.WeakReference
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
/**
* BottomSheetBehavior that dynamically resizes its contents as it grows or shrinks.
* Most of the nested scrolling and touch events code is the same as in [BottomSheetBehavior], but we couldn't just extend it.
*/
class ExpandingBottomSheetBehavior<V : View> : CoordinatorLayout.Behavior<V> {
companion object {
/** Gets a [ExpandingBottomSheetBehavior] from the passed [view] if it exists. */
@Suppress("UNCHECKED_CAST")
fun <V : View> from(view: V): ExpandingBottomSheetBehavior<V>? {
val params = view.layoutParams as? CoordinatorLayout.LayoutParams ?: return null
return params.behavior as? ExpandingBottomSheetBehavior<V>
}
}
/** [Callback] to notify changes in dragging state and position. */
interface Callback {
/** Called when the dragging state of the BottomSheet changes. */
fun onStateChanged(state: State) {}
/** Called when the position of the BottomSheet changes while dragging. */
fun onSlidePositionChanged(view: View, yPosition: Float) {}
}
/** Represents the 4 possible states of the BottomSheet. */
enum class State(val value: Int) {
/** BottomSheet is at min height, collapsed at the bottom. */
Collapsed(0),
/** BottomSheet is being dragged by the user. */
Dragging(1),
/** BottomSheet has been released after being dragged by the user and is animating to its destination. */
Settling(2),
/** BottomSheet is at its max height. */
Expanded(3);
/** Returns whether the BottomSheet is being dragged or is settling after being dragged. */
fun isDraggingOrSettling(): Boolean = this == Dragging || this == Settling
}
/** Set to true to enable debug logging of sizes and offsets. Defaults to `false`. */
var enableDebugLogs = false
/** Current BottomSheet state. Default to [State.Collapsed]. */
var state: State = State.Collapsed
private set
/** Whether the BottomSheet can be dragged by the user or not. Defaults to `true`. */
var isDraggable = true
/** [Callback] to notify changes in dragging state and position. */
var callback: Callback? = null
set(value) {
field = value
// Send initial state
value?.onStateChanged(state)
}
/** Additional top offset in `dps` to add to the BottomSheet so it doesn't fill the whole screen. Defaults to `0`. */
var topOffset = 0
set(value) {
field = value
expandedOffset = -1
}
/** Whether the BottomSheet should be expanded up to the bottom of any [AppBarLayout] found in the parent [CoordinatorLayout]. Defaults to `false`. */
var avoidAppBarLayout = false
set(value) {
field = value
expandedOffset = -1
}
/**
* Whether to add the [scrimView], a 'shadow layer' that will be displayed while dragging/expanded so it obscures the content below the BottomSheet.
* Defaults to `false`.
*/
var useScrimView = false
/** Color to use for the [scrimView] shadow layer. */
var scrimViewColor = 0x60000000
/** [View.TRANSLATION_Z] in `dps` to apply to the [scrimView]. Defaults to `0dp`. */
var scrimViewTranslationZ = 0
/** Whether the content view should be layout to the top of the BottomSheet when it's collapsed. Defaults to true. */
var applyInsetsToContentViewWhenCollapsed = true
/** Lambda used to calculate a min collapsed when the view using the behavior should have a special 'collapsed' layout. It's null by default. */
var minCollapsedHeight: (() -> Int)? = null
// Internal BottomSheet implementation properties
private var ignoreEvents = false
private var touchingScrollingChild = false
private var lastY: Int = -1
private var collapsedOffset = -1
private var expandedOffset = -1
private var parentWidth = -1
private var parentHeight = -1
private var activePointerId = -1
private var lastNestedScrollDy = -1
private var isNestedScrolled = false
private var viewRef: WeakReference<V>? = null
private var nestedScrollingChildRef: WeakReference<View>? = null
private var velocityTracker: VelocityTracker? = null
private var dragHelper: ViewDragHelper? = null
private var scrimView: View? = null
private val stateSettlingTracker = StateSettlingTracker()
private var prevState: State? = null
private var insetBottom = 0
private var insetTop = 0
private var insetLeft = 0
private var insetRight = 0
private var initialPaddingTop = 0
private var initialPaddingBottom = 0
private var initialPaddingLeft = 0
private var initialPaddingRight = 0
private val minCollapsedOffset: Int?
get() {
val minHeight = minCollapsedHeight?.invoke() ?: return null
if (minHeight == -1) return null
return parentHeight - minHeight - insetBottom
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor() : super()
override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean {
parentWidth = parent.width
parentHeight = parent.height
if (viewRef == null) {
viewRef = WeakReference(child)
setWindowInsetsListener(child)
// Prevents clicking on overlapped items below the BottomSheet
child.isClickable = true
}
parent.updatePadding(left = insetLeft, right = insetRight)
ensureViewDragHelper(parent)
// Top coordinate before this layout pass
val savedTop = child.top
// Calculate default position of the BottomSheet's children
parent.onLayoutChild(child, layoutDirection)
// This should optimise calculations when they're not needed
if (state == State.Collapsed) {
calculateCollapsedOffset(child)
}
calculateExpandedOffset(parent)
// Apply top and bottom insets to contentView if needed
val appBar = findAppBarLayout(parent)
val contentView = parent.children.find { it !== appBar && it !== child && it !== scrimView }
if (applyInsetsToContentViewWhenCollapsed && state == State.Collapsed && contentView != null) {
val topOffset = appBar?.measuredHeight ?: 0
val bottomOffset = parentHeight - collapsedOffset + insetTop
val params = contentView.layoutParams as CoordinatorLayout.LayoutParams
if (params.bottomMargin != bottomOffset || params.topMargin != topOffset) {
params.topMargin = topOffset
params.bottomMargin = bottomOffset
contentView.layoutParams = params
}
}
// Add scrimView if needed
if (useScrimView && scrimView == null) {
val scrimView = View(parent.context)
scrimView.setBackgroundColor(scrimViewColor)
scrimView.translationZ = scrimViewTranslationZ * child.resources.displayMetrics.scaledDensity
scrimView.isVisible = false
val params = CoordinatorLayout.LayoutParams(
CoordinatorLayout.LayoutParams.MATCH_PARENT,
CoordinatorLayout.LayoutParams.MATCH_PARENT
)
scrimView.layoutParams = params
val currentIndex = parent.children.indexOf(child)
parent.addView(scrimView, currentIndex)
this.scrimView = scrimView
} else if (!useScrimView && scrimView != null) {
parent.removeView(scrimView)
scrimView = null
}
// Apply insets and resize child based on the current State
when (state) {
State.Collapsed -> {
scrimView?.alpha = 0f
val newHeight = parentHeight - collapsedOffset + insetTop
val params = child.layoutParams
if (params.height != newHeight) {
params.height = newHeight
child.layoutParams = params
}
// If the offset is < insetTop it will cover the status bar too
val newOffset = max(insetTop, collapsedOffset - insetTop)
ViewCompat.offsetTopAndBottom(child, newOffset)
log("State: Collapsed | Offset: $newOffset | Height: $newHeight")
}
State.Dragging, State.Settling -> {
val newOffset = savedTop - child.top
val percentage = max(0f, 1f - (newOffset.toFloat() / collapsedOffset.toFloat()))
scrimView?.let {
if (percentage == 0f) {
it.isVisible = false
} else {
it.alpha = percentage
it.isVisible = true
}
}
val params = child.layoutParams
params.height = parentHeight - savedTop
child.layoutParams = params
ViewCompat.offsetTopAndBottom(child, newOffset)
val stateStr = if (state == State.Dragging) "Dragging" else "Settling"
log("State: $stateStr | Offset: $newOffset | Percentage: $percentage")
}
State.Expanded -> {
val params = child.layoutParams
val newHeight = parentHeight - expandedOffset
if (params.height != newHeight) {
params.height = newHeight
child.layoutParams = params
}
ViewCompat.offsetTopAndBottom(child, expandedOffset)
log("State: Expanded | Offset: $expandedOffset | Height: $newHeight")
}
}
// Find a nested scrolling child to take into account for touch events
if (nestedScrollingChildRef == null) {
nestedScrollingChildRef = findScrollingChild(child)?.let { WeakReference(it) }
}
return true
}
// region: Touch events
override fun onInterceptTouchEvent(
parent: CoordinatorLayout,
child: V,
ev: MotionEvent
): Boolean {
// Almost everything inside here is verbatim to BottomSheetBehavior's onTouchEvent
if (viewRef != null && viewRef?.get() !== child) {
return true
}
val action = ev.actionMasked
if (action == MotionEvent.ACTION_DOWN) {
resetTouchEventTracking()
}
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain()
}
velocityTracker?.addMovement(ev)
when (action) {
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
touchingScrollingChild = false
activePointerId = MotionEvent.INVALID_POINTER_ID
if (ignoreEvents) {
ignoreEvents = false
return false
}
}
MotionEvent.ACTION_DOWN -> {
val x = ev.x.toInt()
lastY = ev.y.toInt()
// Only intercept nested scrolling events here if the view not being moved by the
// ViewDragHelper.
val scroll = nestedScrollingChildRef?.get()
if (state != State.Settling) {
if (scroll != null && parent.isPointInChildBounds(scroll, x, lastY)) {
activePointerId = ev.getPointerId(ev.actionIndex)
touchingScrollingChild = true
}
}
ignoreEvents = (activePointerId == MotionEvent.INVALID_POINTER_ID &&
!parent.isPointInChildBounds(child, x, lastY))
}
else -> Unit
}
if (!ignoreEvents && isDraggable && dragHelper?.shouldInterceptTouchEvent(ev) == true) {
return true
}
// If using scrim view, a click on it should collapse the bottom sheet
if (useScrimView && state == State.Expanded && action == MotionEvent.ACTION_DOWN) {
val y = ev.y.toInt()
if (y <= expandedOffset) {
setState(State.Collapsed)
return true
}
}
// We have to handle cases that the ViewDragHelper does not capture the bottom sheet because
// it is not the top most view of its parent. This is not necessary when the touch event is
// happening over the scrolling content as nested scrolling logic handles that case.
val scroll = nestedScrollingChildRef?.get()
return (action == MotionEvent.ACTION_MOVE &&
scroll != null &&
!ignoreEvents &&
state != State.Dragging &&
!parent.isPointInChildBounds(scroll, ev.x.toInt(), ev.y.toInt()) &&
dragHelper != null &&
abs(lastY - ev.y.toInt()) > (dragHelper?.touchSlop ?: 0))
}
override fun onTouchEvent(parent: CoordinatorLayout, child: V, ev: MotionEvent): Boolean {
// Almost everything inside here is verbatim to BottomSheetBehavior's onTouchEvent
val action = ev.actionMasked
if (state == State.Dragging && action == MotionEvent.ACTION_DOWN) {
return true
}
if (shouldHandleDraggingWithHelper()) {
dragHelper?.processTouchEvent(ev)
}
// Record the velocity
if (action == MotionEvent.ACTION_DOWN) {
resetTouchEventTracking()
}
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain()
}
velocityTracker?.addMovement(ev)
if (shouldHandleDraggingWithHelper() && action == MotionEvent.ACTION_MOVE && !ignoreEvents) {
if (abs(lastY - ev.y.toInt()) > (dragHelper?.touchSlop ?: 0)) {
dragHelper?.captureChildView(child, ev.getPointerId(ev.actionIndex))
}
}
return !ignoreEvents
}
private fun resetTouchEventTracking() {
activePointerId = ViewDragHelper.INVALID_POINTER
velocityTracker?.recycle()
velocityTracker = null
}
// endregion
override fun onAttachedToLayoutParams(params: CoordinatorLayout.LayoutParams) {
super.onAttachedToLayoutParams(params)
viewRef = null
dragHelper = null
}
override fun onDetachedFromLayoutParams() {
super.onDetachedFromLayoutParams()
viewRef = null
dragHelper = null
}
// region: Size measuring and utils
private fun calculateCollapsedOffset(child: View) {
val availableSpace = parentHeight - insetTop
child.measure(
MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(availableSpace, MeasureSpec.AT_MOST),
)
collapsedOffset = parentHeight - child.measuredHeight + insetTop
}
private fun calculateExpandedOffset(parent: CoordinatorLayout): Int {
expandedOffset = if (avoidAppBarLayout) {
findAppBarLayout(parent)?.measuredHeight ?: 0
} else {
0
} + topOffset + insetTop
return expandedOffset
}
private fun ensureViewDragHelper(parent: CoordinatorLayout) {
if (dragHelper == null) {
dragHelper = ViewDragHelper.create(parent, dragHelperCallback)
}
}
private fun findAppBarLayout(view: View): AppBarLayout? {
return when (view) {
is AppBarLayout -> view
is ViewGroup -> view.children.firstNotNullOfOrNull { findAppBarLayout(it) }
else -> null
}
}
private fun shouldHandleDraggingWithHelper(): Boolean {
return dragHelper != null && (isDraggable || state == State.Dragging)
}
private fun log(contents: String, vararg args: Any) {
if (!enableDebugLogs) return
Timber.d(contents, args)
}
// endregion
// region: State and delayed state settling
fun setState(state: State) {
if (state == this.state) {
return
} else if (viewRef?.get() == null) {
setInternalState(state)
} else {
viewRef?.get()?.let { child ->
runAfterLayout(child) { startSettling(child, state, false) }
}
}
}
private fun setInternalState(state: State) {
if (!this.state.isDraggingOrSettling()) {
prevState = this.state
}
this.state = state
viewRef?.get()?.requestLayout()
callback?.onStateChanged(state)
}
private fun startSettling(child: View, state: State, isReleasingView: Boolean) {
val top = getTopOffsetForState(state)
log("Settling to: $top")
val isSettling = dragHelper?.let {
if (isReleasingView) {
it.settleCapturedViewAt(child.left, top)
} else {
it.smoothSlideViewTo(child, child.left, top)
}
} ?: false
setInternalState(if (isSettling) State.Settling else state)
if (isSettling) {
stateSettlingTracker.continueSettlingToState(state)
}
}
private fun runAfterLayout(child: V, runnable: Runnable) {
if (isLayouting(child)) {
child.post(runnable)
} else {
runnable.run()
}
}
private fun isLayouting(child: V): Boolean {
return child.parent != null && child.parent.isLayoutRequested && ViewCompat.isAttachedToWindow(child)
}
private fun getTopOffsetForState(state: State): Int {
return when (state) {
State.Collapsed -> minCollapsedOffset ?: collapsedOffset
State.Expanded -> expandedOffset
else -> error("Cannot get offset for state $state")
}
}
// endregion
// region: Nested scroll
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: V,
directTargetChild: View,
target: View,
axes: Int,
type: Int
): Boolean {
lastNestedScrollDy = 0
isNestedScrolled = false
return (axes and ViewCompat.SCROLL_AXIS_VERTICAL) != 0
}
override fun onNestedPreScroll(
coordinatorLayout: CoordinatorLayout,
child: V,
target: View,
dx: Int,
dy: Int,
consumed: IntArray,
type: Int
) {
if (type == ViewCompat.TYPE_NON_TOUCH) return
val scrollingChild = nestedScrollingChildRef?.get()
if (target != scrollingChild) return
val currentTop = child.top
val newTop = currentTop - dy
if (dy > 0) {
// Upward scroll
if (newTop < expandedOffset) {
consumed[1] = currentTop - expandedOffset
ViewCompat.offsetTopAndBottom(child, -consumed[1])
setInternalState(State.Expanded)
} else {
if (!isDraggable) return
consumed[1] = dy
ViewCompat.offsetTopAndBottom(child, -dy)
setInternalState(State.Dragging)
}
} else if (dy < 0) {
// Scroll downward
if (!target.canScrollVertically(-1)) {
if (newTop <= collapsedOffset) {
if (!isDraggable) return
consumed[1] = dy
ViewCompat.offsetTopAndBottom(child, -dy)
setInternalState(State.Dragging)
} else {
consumed[1] = currentTop - collapsedOffset
ViewCompat.offsetTopAndBottom(child, -consumed[1])
setInternalState(State.Collapsed)
}
}
}
lastNestedScrollDy = dy
isNestedScrolled = true
}
override fun onNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: V,
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int,
consumed: IntArray
) {
// Empty to avoid default behaviour
}
override fun onNestedPreFling(
coordinatorLayout: CoordinatorLayout,
child: V,
target: View,
velocityX: Float,
velocityY: Float
): Boolean {
return target == nestedScrollingChildRef?.get() &&
(state != State.Expanded || super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY))
}
private fun findScrollingChild(view: View): View? {
return when {
!view.isVisible -> null
ViewCompat.isNestedScrollingEnabled(view) -> view
view is ViewGroup -> {
view.children.firstNotNullOfOrNull { findScrollingChild(it) }
}
else -> null
}
}
// endregion
// region: Insets
private fun setWindowInsetsListener(view: View) {
// Create a snapshot of the view's padding state.
initialPaddingLeft = view.paddingLeft
initialPaddingTop = view.paddingTop
initialPaddingRight = view.paddingRight
initialPaddingBottom = view.paddingBottom
// This should only be used to set initial insets and other edge cases where the insets can't be applied using an animation.
var applyInsetsFromAnimation = false
// This will animated inset changes, making them look a lot better. However, it won't update initial insets.
ViewCompat.setWindowInsetsAnimationCallback(view, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
override fun onProgress(insets: WindowInsetsCompat, runningAnimations: MutableList<WindowInsetsAnimationCompat>): WindowInsetsCompat {
return applyInsets(view, insets)
}
override fun onEnd(animation: WindowInsetsAnimationCompat) {
applyInsetsFromAnimation = false
view.requestApplyInsets()
}
})
ViewCompat.setOnApplyWindowInsetsListener(view) { _: View, insets: WindowInsetsCompat ->
if (!applyInsetsFromAnimation) {
applyInsetsFromAnimation = true
applyInsets(view, insets)
} else {
insets
}
}
// Request to apply insets as soon as the view is attached to a window.
if (ViewCompat.isAttachedToWindow(view)) {
ViewCompat.requestApplyInsets(view)
} else {
view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
v.removeOnAttachStateChangeListener(this)
ViewCompat.requestApplyInsets(v)
}
override fun onViewDetachedFromWindow(v: View) = Unit
})
}
}
private fun applyInsets(view: View, insets: WindowInsetsCompat): WindowInsetsCompat {
val insetsType = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime()
val imeInsets = insets.getInsets(insetsType)
insetTop = imeInsets.top
insetBottom = imeInsets.bottom
insetLeft = imeInsets.left
insetRight = imeInsets.right
val bottomPadding = initialPaddingBottom + insetBottom
view.setPadding(initialPaddingLeft, initialPaddingTop, initialPaddingRight, bottomPadding)
if (state == State.Collapsed) {
val params = view.layoutParams
params.height = CoordinatorLayout.LayoutParams.WRAP_CONTENT
view.layoutParams = params
calculateCollapsedOffset(view)
}
return WindowInsetsCompat.CONSUMED
}
// endregion
// Used to add dragging animations along with StateSettlingTracker, and set max and min dragging coordinates.
private val dragHelperCallback = object : ViewDragHelper.Callback() {
override fun tryCaptureView(child: View, pointerId: Int): Boolean {
if (state == State.Dragging) {
return false
}
if (touchingScrollingChild) {
return false
}
if (state == State.Expanded && activePointerId == pointerId) {
val scroll = nestedScrollingChildRef?.get()
if (scroll?.canScrollVertically(-1) == true) {
return false
}
}
return viewRef?.get() == child
}
override fun onViewDragStateChanged(state: Int) {
if (state == ViewDragHelper.STATE_DRAGGING && isDraggable) {
setInternalState(State.Dragging)
}
}
override fun onViewPositionChanged(
changedView: View,
left: Int,
top: Int,
dx: Int,
dy: Int
) {
super.onViewPositionChanged(changedView, left, top, dx, dy)
val params = changedView.layoutParams
params.height = parentHeight - top + insetBottom + insetTop
changedView.layoutParams = params
val collapsedOffset = minCollapsedOffset ?: collapsedOffset
val percentage = 1f - (top - insetTop).toFloat() / collapsedOffset.toFloat()
callback?.onSlidePositionChanged(changedView, percentage)
}
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
val actualCollapsedOffset = minCollapsedOffset ?: collapsedOffset
val targetState = if (yvel < 0) {
// Moving up
val currentTop = releasedChild.top
val yPositionPercentage = currentTop * 100f / actualCollapsedOffset
if (yPositionPercentage >= 0.5f) {
State.Expanded
} else {
State.Collapsed
}
} else if (yvel == 0f || abs(xvel) > abs(yvel)) {
// If the Y velocity is 0 or the swipe was mostly horizontal indicated by the X velocity
// being greater than the Y velocity, settle to the nearest correct height.
val currentTop = releasedChild.top
if (currentTop < actualCollapsedOffset / 2) {
State.Expanded
} else {
State.Collapsed
}
} else {
// Moving down
val currentTop = releasedChild.top
val yPositionPercentage = currentTop * 100f / actualCollapsedOffset
if (yPositionPercentage >= 0.5f) {
State.Collapsed
} else {
State.Expanded
}
}
startSettling(releasedChild, targetState, true)
}
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
return child.left
}
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
val collapsed = minCollapsedOffset ?: collapsedOffset
val maxTop = max(top, insetTop)
return min(max(maxTop, expandedOffset), collapsed)
}
override fun getViewVerticalDragRange(child: View): Int {
return minCollapsedOffset ?: collapsedOffset
}
}
// Used to set the current State in a delayed way.
private inner class StateSettlingTracker {
private lateinit var targetState: State
private var isContinueSettlingRunnablePosted = false
private val continueSettlingRunnable: Runnable = Runnable {
isContinueSettlingRunnablePosted = false
if (dragHelper?.continueSettling(true) == true) {
continueSettlingToState(targetState)
} else {
setInternalState(targetState)
}
}
fun continueSettlingToState(state: State) {
val view = viewRef?.get() ?: return
this.targetState = state
if (!isContinueSettlingRunnablePosted) {
ViewCompat.postOnAnimation(view, continueSettlingRunnable)
isContinueSettlingRunnablePosted = true
}
}
}
}

View file

@ -34,8 +34,6 @@ class JumpToBottomViewVisibilityManager(
private val layoutManager: LinearLayoutManager
) {
private var canShowButtonOnScroll = true
init {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
@ -45,7 +43,7 @@ class JumpToBottomViewVisibilityManager(
if (scrollingToPast) {
jumpToBottomView.hide()
} else if (canShowButtonOnScroll) {
} else {
maybeShowJumpToBottomViewVisibility()
}
}
@ -68,13 +66,7 @@ class JumpToBottomViewVisibilityManager(
}
}
fun hideAndPreventVisibilityChangesWithScrolling() {
jumpToBottomView.hide()
canShowButtonOnScroll = false
}
private fun maybeShowJumpToBottomViewVisibility() {
canShowButtonOnScroll = true
if (layoutManager.findFirstVisibleItemPosition() > 1) {
jumpToBottomView.show()
} else {

View file

@ -18,10 +18,12 @@ package im.vector.app.features.home.room.detail
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.core.view.GravityCompat
import androidx.core.view.WindowCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
@ -98,6 +100,11 @@ class RoomDetailActivity :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// For dealing with insets and status bar background color
WindowCompat.setDecorFitsSystemWindows(window, false)
window.statusBarColor = Color.TRANSPARENT
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
waitingView = views.waitingView.waitingView
val timelineArgs: TimelineArgs = intent?.extras?.getParcelableCompat(EXTRA_ROOM_DETAIL_ARGS) ?: return

View file

@ -35,16 +35,17 @@ import android.widget.TextView
import androidx.activity.addCallback
import androidx.annotation.StringRes
import androidx.appcompat.view.menu.MenuBuilder
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.net.toUri
import androidx.core.text.toSpannable
import androidx.core.util.Pair
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.forEach
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
@ -67,7 +68,6 @@ import im.vector.app.core.dialogs.ConfirmationDialogBuilder
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
import im.vector.app.core.extensions.animateLayoutChange
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.containsRtLOverride
@ -187,9 +187,7 @@ import im.vector.app.features.widgets.WidgetArgs
import im.vector.app.features.widgets.WidgetKind
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -418,20 +416,12 @@ class TimelineFragment :
}
}
if (savedInstanceState == null) {
handleSpaceShare()
ViewCompat.setOnApplyWindowInsetsListener(views.coordinatorLayout) { _, insets ->
val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars())
views.appBarLayout.updatePadding(top = imeInsets.top)
views.voiceMessageRecorderContainer.updatePadding(bottom = imeInsets.bottom)
insets
}
views.scrim.setOnClickListener {
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false))
}
messageComposerViewModel.stateFlow.map { it.isFullScreen }
.distinctUntilChanged()
.onEach { isFullScreen ->
toggleFullScreenEditor(isFullScreen)
}
.launchIn(viewLifecycleOwner.lifecycleScope)
}
private fun setupBackPressHandling() {
@ -1048,13 +1038,7 @@ class TimelineFragment :
override fun onLayoutCompleted(state: RecyclerView.State) {
super.onLayoutCompleted(state)
updateJumpToReadMarkerViewVisibility()
withState(messageComposerViewModel) { composerState ->
if (!composerState.isFullScreen) {
jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay()
} else {
jumpToBottomViewVisibilityManager.hideAndPreventVisibilityChangesWithScrolling()
}
}
jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay()
}
}.apply {
// For local rooms, pin the view's content to the top edge (the layout is reversed)
@ -1170,7 +1154,6 @@ class TimelineFragment :
if (mainState.tombstoneEvent == null) {
views.composerContainer.isInvisible = !messageComposerState.isComposerVisible
views.voiceMessageRecorderContainer.isVisible = messageComposerState.isVoiceMessageRecorderVisible
when (messageComposerState.canSendMessage) {
CanSendStatus.Allowed -> {
NotificationAreaView.State.Hidden
@ -2036,19 +2019,6 @@ class TimelineFragment :
}
}
private fun toggleFullScreenEditor(isFullScreen: Boolean) {
views.composerContainer.animateLayoutChange(200)
val constraintSet = ConstraintSet()
val constraintSetId = if (isFullScreen) {
R.layout.fragment_timeline_fullscreen
} else {
R.layout.fragment_timeline
}
constraintSet.clone(requireContext(), constraintSetId)
constraintSet.applyTo(views.rootConstraintLayout)
}
/**
* Returns true if the current room is a Thread room, false otherwise.
*/

View file

@ -33,7 +33,6 @@ sealed class MessageComposerAction : VectorViewModelAction {
data class OnEntersBackground(val composerText: String) : MessageComposerAction()
data class SlashCommandConfirmed(val parsedCommand: ParsedCommand) : MessageComposerAction()
data class InsertUserDisplayName(val userId: String) : MessageComposerAction()
data class SetFullScreen(val isFullScreen: Boolean) : MessageComposerAction()
// Voice Message

View file

@ -24,7 +24,6 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.Spannable
import android.text.format.DateUtils
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
@ -32,10 +31,7 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.Toast
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.text.buildSpannedString
import androidx.core.view.isGone
import androidx.core.view.isInvisible
@ -51,7 +47,6 @@ import com.vanniktech.emoji.EmojiPopup
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.error.fatalError
import im.vector.app.core.extensions.getVectorLastMessageContent
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.showKeyboard
import im.vector.app.core.glide.GlideApp
@ -59,7 +54,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.platform.lifecycleAwareLazy
import im.vector.app.core.platform.showOptimizedSnackbar
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.core.utils.ExpandingBottomSheetBehavior
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.registerForPermissionsResult
@ -86,14 +81,9 @@ import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAc
import im.vector.app.features.home.room.detail.TimelineViewModel
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillImageSpan
import im.vector.app.features.html.PillsPostProcessor
import im.vector.app.features.location.LocationSharingMode
import im.vector.app.features.media.ImageContentRenderer
import im.vector.app.features.poll.PollMode
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.share.SharedData
@ -104,18 +94,9 @@ import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import org.commonmark.parser.Parser
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import reactivecircus.flowbinding.android.view.focusChanges
import reactivecircus.flowbinding.android.widget.textChanges
import timber.log.Timber
@ -130,12 +111,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
@Inject lateinit var autoCompleterFactory: AutoCompleter.Factory
@Inject lateinit var avatarRenderer: AvatarRenderer
@Inject lateinit var matrixItemColorProvider: MatrixItemColorProvider
@Inject lateinit var eventHtmlRenderer: EventHtmlRenderer
@Inject lateinit var dimensionConverter: DimensionConverter
@Inject lateinit var imageContentRenderer: ImageContentRenderer
@Inject lateinit var shareIntentHandler: ShareIntentHandler
@Inject lateinit var pillsPostProcessorFactory: PillsPostProcessor.Factory
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var vectorFeatures: VectorFeatures
@Inject lateinit var buildMeta: BuildMeta
@ -147,10 +123,6 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
autoCompleterFactory.create(roomId, isThreadTimeLine())
}
private val pillsPostProcessor by lazy {
pillsPostProcessorFactory.create(roomId)
}
private val emojiPopup: EmojiPopup by lifecycleAwareLazy {
createEmojiPopup()
}
@ -166,6 +138,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
private lateinit var attachmentsHelper: AttachmentsHelper
private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView
private var bottomSheetBehavior: ExpandingBottomSheetBehavior<View>? = null
private val timelineViewModel: TimelineViewModel by parentFragmentViewModel()
private val messageComposerViewModel: MessageComposerViewModel by parentFragmentViewModel()
@ -192,6 +165,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
attachmentsHelper = AttachmentsHelper(requireContext(), this, buildMeta).register()
setupBottomSheet()
setupComposer()
setupEmojiButton()
@ -217,22 +191,15 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
}
}
messageComposerViewModel.stateFlow.map { it.isFullScreen }
.distinctUntilChanged()
.onEach { isFullScreen ->
composer.toggleFullScreen(isFullScreen)
}
.launchIn(viewLifecycleOwner.lifecycleScope)
messageComposerViewModel.onEach(MessageComposerViewState::sendMode, MessageComposerViewState::canSendMessage) { mode, canSend ->
if (!canSend.boolean()) {
return@onEach
}
when (mode) {
is SendMode.Regular -> renderRegularMode(mode.text.toString())
is SendMode.Edit -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text.toString())
is SendMode.Quote -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.action_quote, mode.text.toString())
is SendMode.Reply -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text.toString())
is SendMode.Edit -> renderSpecialMode(MessageComposerMode.Edit(mode.timelineEvent, mode.text.toString()))
is SendMode.Quote -> renderSpecialMode(MessageComposerMode.Quote(mode.timelineEvent, mode.text.toString()))
is SendMode.Reply -> renderSpecialMode(MessageComposerMode.Reply(mode.timelineEvent, mode.text.toString()))
is SendMode.Voice -> renderVoiceMessageMode(mode.text)
}
}
@ -242,6 +209,14 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
.onEach { onTypeSelected(it.attachmentType) }
.launchIn(lifecycleScope)
messageComposerViewModel.stateFlow.map { it.isFullScreen }
.distinctUntilChanged()
.onEach { isFullScreen ->
val state = if (isFullScreen) ExpandingBottomSheetBehavior.State.Expanded else ExpandingBottomSheetBehavior.State.Collapsed
bottomSheetBehavior?.setState(state)
}
.launchIn(viewLifecycleOwner.lifecycleScope)
if (savedInstanceState == null) {
handleShareData()
}
@ -280,11 +255,45 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
) { mainState, messageComposerState, attachmentState ->
if (mainState.tombstoneEvent != null) return@withState
composer.setInvisible(!messageComposerState.isComposerVisible)
(composer as? View)?.isInvisible = !messageComposerState.isComposerVisible
composer.sendButton.isInvisible = !messageComposerState.isSendButtonVisible
(composer as? RichTextComposerLayout)?.isTextFormattingEnabled = attachmentState.isTextFormattingEnabled
}
private fun setupBottomSheet() {
val parentView = view?.parent as? View ?: return
bottomSheetBehavior = ExpandingBottomSheetBehavior.from(parentView)?.apply {
applyInsetsToContentViewWhenCollapsed = true
topOffset = 22
useScrimView = true
scrimViewTranslationZ = 8
minCollapsedHeight = {
(composer as? RichTextComposerLayout)?.estimateCollapsedHeight() ?: -1
}
isDraggable = false
callback = object : ExpandingBottomSheetBehavior.Callback {
override fun onStateChanged(state: ExpandingBottomSheetBehavior.State) {
// Dragging is disabled while the composer is collapsed
bottomSheetBehavior?.isDraggable = state != ExpandingBottomSheetBehavior.State.Collapsed
val setFullScreen = when (state) {
ExpandingBottomSheetBehavior.State.Collapsed -> false
ExpandingBottomSheetBehavior.State.Expanded -> true
else -> return
}
(composer as? RichTextComposerLayout)?.setFullScreen(setFullScreen)
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(setFullScreen))
}
override fun onSlidePositionChanged(view: View, yPosition: Float) {
(composer as? RichTextComposerLayout)?.notifyIsBeingDragged(yPosition)
}
}
}
}
private fun setupComposer() {
val composerEditText = composer.editText
composerEditText.setHint(R.string.room_message_placeholder)
@ -382,8 +391,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
return
}
if (text.isNotBlank()) {
// We collapse ASAP, if not there will be a slight annoying delay
composer.collapse(true)
composer.renderComposerMode(MessageComposerMode.Normal(""))
lockSendButton = true
if (formattedText != null) {
messageComposerViewModel.handle(MessageComposerAction.SendMessage(text, formattedText, false))
@ -407,66 +415,12 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
private fun renderRegularMode(content: CharSequence) {
autoCompleter.exitSpecialMode()
composer.collapse()
composer.setTextIfDifferent(content)
composer.sendButton.contentDescription = getString(R.string.action_send)
composer.renderComposerMode(MessageComposerMode.Normal(content))
}
private fun renderSpecialMode(
event: TimelineEvent,
@DrawableRes iconRes: Int,
@StringRes descriptionRes: Int,
defaultContent: CharSequence,
) {
private fun renderSpecialMode(mode: MessageComposerMode.Special) {
autoCompleter.enterSpecialMode()
// switch to expanded bar
composer.composerRelatedMessageTitle.apply {
text = event.senderInfo.disambiguatedDisplayName
setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@")))
}
val messageContent: MessageContent? = event.getVectorLastMessageContent()
val nonFormattedBody = when (messageContent) {
is MessageAudioContent -> getAudioContentBodyText(messageContent)
is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion()
is MessageBeaconInfoContent -> getString(R.string.live_location_description)
else -> messageContent?.body.orEmpty()
}
var formattedBody: CharSequence? = null
if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
val parser = Parser.builder().build()
val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor)
}
composer.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody)
// Image Event
val data = event.buildImageContentRendererData(dimensionConverter.dpToPx(66))
val isImageVisible = if (data != null) {
imageContentRenderer.render(data, ImageContentRenderer.Mode.THUMBNAIL, composer.composerRelatedMessageImage)
true
} else {
imageContentRenderer.clear(composer.composerRelatedMessageImage)
false
}
composer.composerRelatedMessageImage.isVisible = isImageVisible
composer.replaceFormattedContent(defaultContent)
composer.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes))
composer.sendButton.contentDescription = getString(descriptionRes)
avatarRenderer.render(event.senderInfo.toMatrixItem(), composer.composerRelatedMessageAvatar)
composer.expand {
if (isAdded) {
// need to do it here also when not using quick reply
focusComposerAndShowKeyboard()
composer.composerRelatedMessageImage.isVisible = isImageVisible
}
}
focusComposerAndShowKeyboard()
composer.renderComposerMode(mode)
}
private fun observerUserTyping() {
@ -489,7 +443,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
}
private fun focusComposerAndShowKeyboard() {
if (composer.isVisible) {
if ((composer as? View)?.isVisible == true) {
composer.editText.showKeyboard(andRequestFocus = true)
}
}
@ -499,7 +453,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
composer.sendButton.alpha = 0f
composer.sendButton.isVisible = true
composer.sendButton.animate().alpha(1f).setDuration(150).start()
} else if (!event.isVisible) {
} else {
composer.sendButton.isInvisible = true
}
}
@ -510,15 +464,6 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
}
}
private fun getAudioContentBodyText(messageContent: MessageAudioContent): String {
val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong())
return if (messageContent.voiceMessageIndicator != null) {
getString(R.string.voice_message_reply_content, formattedDuration)
} else {
getString(R.string.audio_message_reply_content, messageContent.body, formattedDuration)
}
}
private fun createEmojiPopup(): EmojiPopup {
return EmojiPopup(
rootView = views.root,
@ -840,11 +785,6 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
return displayName
}
/**
* Returns the root thread event if we are in a thread room, otherwise returns null.
*/
fun getRootThreadEventId(): String? = withState(timelineViewModel) { it.rootThreadEventId }
/**
* Returns true if the current room is a Thread room, false otherwise.
*/

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.detail.composer
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
sealed interface MessageComposerMode {
data class Normal(val content: CharSequence?) : MessageComposerMode
sealed class Special(open val event: TimelineEvent, open val defaultContent: CharSequence) : MessageComposerMode
data class Edit(override val event: TimelineEvent, override val defaultContent: CharSequence) : Special(event, defaultContent)
class Quote(override val event: TimelineEvent, override val defaultContent: CharSequence) : Special(event, defaultContent)
class Reply(override val event: TimelineEvent, override val defaultContent: CharSequence) : Special(event, defaultContent)
}

View file

@ -19,35 +19,24 @@ package im.vector.app.features.home.room.detail.composer
import android.text.Editable
import android.widget.EditText
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
interface MessageComposerView {
companion object {
const val MAX_LINES_WHEN_COLLAPSED = 10
}
val text: Editable?
val formattedText: String?
val editText: EditText
val emojiButton: ImageButton?
val sendButton: ImageButton
val attachmentButton: ImageButton
val fullScreenButton: ImageButton?
val composerRelatedMessageTitle: TextView
val composerRelatedMessageContent: TextView
val composerRelatedMessageImage: ImageView
val composerRelatedMessageActionIcon: ImageView
val composerRelatedMessageAvatar: ImageView
var callback: Callback?
var isVisible: Boolean
fun collapse(animate: Boolean = true, transitionComplete: (() -> Unit)? = null)
fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null)
fun setTextIfDifferent(text: CharSequence?): Boolean
fun replaceFormattedContent(text: CharSequence)
fun toggleFullScreen(newValue: Boolean)
fun setInvisible(isInvisible: Boolean)
fun renderComposerMode(mode: MessageComposerMode)
}
interface Callback : ComposerEditText.Callback {

View file

@ -19,44 +19,59 @@ package im.vector.app.features.home.room.detail.composer
import android.content.Context
import android.net.Uri
import android.text.Editable
import android.text.format.DateUtils
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 android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.core.text.toSpannable
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.transition.ChangeBounds
import androidx.transition.Fade
import androidx.transition.Transition
import androidx.transition.TransitionManager
import androidx.transition.TransitionSet
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.animations.SimpleTransitionListener
import im.vector.app.core.extensions.getVectorLastMessageContent
import im.vector.app.core.extensions.setTextIfDifferent
import im.vector.app.core.extensions.showKeyboard
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.databinding.ComposerLayoutBinding
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData
import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillsPostProcessor
import im.vector.app.features.media.ImageContentRenderer
import org.commonmark.parser.Parser
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
/**
* Encapsulate the timeline composer UX.
*/
@AndroidEntryPoint
class PlainTextComposerLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), MessageComposerView {
) : LinearLayout(context, attrs, defStyleAttr), MessageComposerView {
@Inject lateinit var avatarRenderer: AvatarRenderer
@Inject lateinit var matrixItemColorProvider: MatrixItemColorProvider
@Inject lateinit var eventHtmlRenderer: EventHtmlRenderer
@Inject lateinit var dimensionConverter: DimensionConverter
@Inject lateinit var imageContentRenderer: ImageContentRenderer
@Inject lateinit var pillsPostProcessorFactory: PillsPostProcessor.Factory
private val views: ComposerLayoutBinding
override var callback: Callback? = null
private var currentConstraintSetId: Int = -1
private val animationDuration = 100L
override val text: Editable?
get() = views.composerEditText.text
@ -65,37 +80,23 @@ class PlainTextComposerLayout @JvmOverloads constructor(
override val editText: EditText
get() = views.composerEditText
@Suppress("RedundantNullableReturnType")
override val emojiButton: ImageButton?
get() = views.composerEmojiButton
override val sendButton: ImageButton
get() = views.sendButton
override fun setInvisible(isInvisible: Boolean) {
this.isInvisible = isInvisible
}
override val attachmentButton: ImageButton
get() = views.attachmentButton
override val fullScreenButton: ImageButton? = null
override val composerRelatedMessageActionIcon: ImageView
get() = views.composerRelatedMessageActionIcon
override val composerRelatedMessageAvatar: ImageView
get() = views.composerRelatedMessageAvatar
override val composerRelatedMessageContent: TextView
get() = views.composerRelatedMessageContent
override val composerRelatedMessageImage: ImageView
get() = views.composerRelatedMessageImage
override val composerRelatedMessageTitle: TextView
get() = views.composerRelatedMessageTitle
override var isVisible: Boolean
get() = views.root.isVisible
set(value) { views.root.isVisible = value }
init {
inflate(context, R.layout.composer_layout, this)
views = ComposerLayoutBinding.bind(this)
collapse(false)
views.composerEditText.maxLines = MessageComposerView.MAX_LINES_WHEN_COLLAPSED
collapse()
views.composerEditText.callback = object : ComposerEditText.Callback {
override fun onRichContentSelected(contentUri: Uri): Boolean {
@ -121,27 +122,15 @@ class PlainTextComposerLayout @JvmOverloads constructor(
}
}
override fun replaceFormattedContent(text: CharSequence) {
setTextIfDifferent(text)
}
override fun collapse(animate: Boolean, transitionComplete: (() -> Unit)?) {
if (currentConstraintSetId == R.layout.composer_layout_constraint_set_compact) {
// ignore we good
return
}
currentConstraintSetId = R.layout.composer_layout_constraint_set_compact
applyNewConstraintSet(animate, transitionComplete)
private fun collapse(transitionComplete: (() -> Unit)? = null) {
views.relatedMessageGroup.isVisible = false
transitionComplete?.invoke()
callback?.onExpandOrCompactChange()
}
override fun expand(animate: Boolean, transitionComplete: (() -> Unit)?) {
if (currentConstraintSetId == R.layout.composer_layout_constraint_set_expanded) {
// ignore we good
return
}
currentConstraintSetId = R.layout.composer_layout_constraint_set_expanded
applyNewConstraintSet(animate, transitionComplete)
private fun expand(transitionComplete: (() -> Unit)? = null) {
views.relatedMessageGroup.isVisible = true
transitionComplete?.invoke()
callback?.onExpandOrCompactChange()
}
@ -149,35 +138,92 @@ class PlainTextComposerLayout @JvmOverloads constructor(
return views.composerEditText.setTextIfDifferent(text)
}
override fun toggleFullScreen(newValue: Boolean) {
// Plain text composer has no full screen
override fun renderComposerMode(mode: MessageComposerMode) {
val specialMode = mode as? MessageComposerMode.Special
if (specialMode != null) {
renderSpecialMode(specialMode)
} else if (mode is MessageComposerMode.Normal) {
collapse()
editText.setTextIfDifferent(mode.content)
}
views.sendButton.apply {
if (mode is MessageComposerMode.Edit) {
contentDescription = resources.getString(R.string.action_save)
setImageResource(R.drawable.ic_composer_rich_text_save)
} else {
contentDescription = resources.getString(R.string.action_send)
setImageResource(R.drawable.ic_rich_composer_send)
}
}
}
private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) {
// val wasSendButtonInvisible = views.sendButton.isInvisible
if (animate) {
configureAndBeginTransition(transitionComplete)
private fun renderSpecialMode(specialMode: MessageComposerMode.Special) {
val event = specialMode.event
val defaultContent = specialMode.defaultContent
val iconRes: Int = when (specialMode) {
is MessageComposerMode.Reply -> R.drawable.ic_reply
is MessageComposerMode.Edit -> R.drawable.ic_edit
is MessageComposerMode.Quote -> R.drawable.ic_quote
}
ConstraintSet().also {
it.clone(context, currentConstraintSetId)
it.applyTo(this)
val pillsPostProcessor = pillsPostProcessorFactory.create(event.roomId)
// switch to expanded bar
views.composerRelatedMessageTitle.apply {
text = event.senderInfo.disambiguatedDisplayName
setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@")))
}
val messageContent: MessageContent? = event.getVectorLastMessageContent()
val nonFormattedBody = when (messageContent) {
is MessageAudioContent -> getAudioContentBodyText(messageContent)
is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion()
is MessageBeaconInfoContent -> resources.getString(R.string.live_location_description)
else -> messageContent?.body.orEmpty()
}
var formattedBody: CharSequence? = null
if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
val parser = Parser.builder().build()
val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor)
}
views.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody)
// Image Event
val data = event.buildImageContentRendererData(dimensionConverter.dpToPx(66))
val isImageVisible = if (data != null) {
imageContentRenderer.render(data, ImageContentRenderer.Mode.THUMBNAIL, views.composerRelatedMessageImage)
true
} else {
imageContentRenderer.clear(views.composerRelatedMessageImage)
false
}
views.composerRelatedMessageImage.isVisible = isImageVisible
views.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(context, iconRes))
avatarRenderer.render(event.senderInfo.toMatrixItem(), views.composerRelatedMessageAvatar)
views.composerEditText.setText(defaultContent)
expand {
// need to do it here also when not using quick reply
if (isVisible) {
showKeyboard(andRequestFocus = true)
}
views.composerRelatedMessageImage.isVisible = isImageVisible
}
// Might be updated by view state just after, but avoid blinks
// views.sendButton.isInvisible = wasSendButtonInvisible
}
private fun configureAndBeginTransition(transitionComplete: (() -> Unit)? = null) {
val transition = TransitionSet().apply {
ordering = TransitionSet.ORDERING_SEQUENTIAL
addTransition(ChangeBounds())
addTransition(Fade(Fade.IN))
duration = animationDuration
addListener(object : SimpleTransitionListener() {
override fun onTransitionEnd(transition: Transition) {
transitionComplete?.invoke()
}
})
private fun getAudioContentBodyText(messageContent: MessageAudioContent): String {
val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong())
return if (messageContent.voiceMessageIndicator != null) {
resources.getString(R.string.voice_message_reply_content, formattedDuration)
} else {
resources.getString(R.string.audio_message_reply_content, messageContent.body, formattedDuration)
}
TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition)
}
}

View file

@ -16,25 +16,34 @@
package im.vector.app.features.home.room.detail.composer
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.graphics.Color
import android.text.Editable
import android.text.TextWatcher
import android.util.AttributeSet
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import android.widget.LinearLayout
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.text.toSpannable
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import com.google.android.material.shape.MaterialShapeDrawable
import im.vector.app.R
import im.vector.app.core.extensions.animateLayoutChange
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.setTextIfDifferent
import im.vector.app.core.extensions.showKeyboard
import im.vector.app.databinding.ComposerRichTextLayoutBinding
import im.vector.app.databinding.ViewRichTextMenuButtonBinding
import io.element.android.wysiwyg.EditorEditText
@ -46,23 +55,22 @@ class RichTextComposerLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), MessageComposerView {
) : LinearLayout(context, attrs, defStyleAttr), MessageComposerView {
private val views: ComposerRichTextLayoutBinding
override var callback: Callback? = null
private var currentConstraintSetId: Int = -1
private val animationDuration = 100L
private val maxEditTextLinesWhenCollapsed = 12
private val isFullScreen: Boolean get() = currentConstraintSetId == R.layout.composer_rich_text_layout_constraint_set_fullscreen
// There is no need to persist these values since they're always updated by the parent fragment
private var isFullScreen = false
private var hasRelatedMessage = false
var isTextFormattingEnabled = true
set(value) {
if (field == value) return
syncEditTexts()
field = value
updateTextFieldBorder(isFullScreen)
updateEditTextVisibility()
}
@ -82,37 +90,94 @@ class RichTextComposerLayout @JvmOverloads constructor(
get() = views.sendButton
override val attachmentButton: ImageButton
get() = views.attachmentButton
override val fullScreenButton: ImageButton?
get() = views.composerFullScreenButton
override val composerRelatedMessageActionIcon: ImageView
get() = views.composerRelatedMessageActionIcon
override val composerRelatedMessageAvatar: ImageView
get() = views.composerRelatedMessageAvatar
override val composerRelatedMessageContent: TextView
get() = views.composerRelatedMessageContent
override val composerRelatedMessageImage: ImageView
get() = views.composerRelatedMessageImage
override val composerRelatedMessageTitle: TextView
get() = views.composerRelatedMessageTitle
override var isVisible: Boolean
get() = views.root.isVisible
set(value) { views.root.isVisible = value }
// Border of the EditText
private val borderShapeDrawable: MaterialShapeDrawable by lazy {
MaterialShapeDrawable().apply {
val typedData = TypedValue()
val lineColor = context.theme.obtainStyledAttributes(typedData.data, intArrayOf(R.attr.vctr_content_quaternary))
.getColor(0, 0)
strokeColor = ColorStateList.valueOf(lineColor)
strokeWidth = 1 * resources.displayMetrics.scaledDensity
fillColor = ColorStateList.valueOf(Color.TRANSPARENT)
val cornerSize = resources.getDimensionPixelSize(R.dimen.rich_text_composer_corner_radius_single_line)
setCornerSize(cornerSize.toFloat())
}
}
fun setFullScreen(isFullScreen: Boolean) {
editText.updateLayoutParams<ViewGroup.LayoutParams> {
height = if (isFullScreen) 0 else ViewGroup.LayoutParams.WRAP_CONTENT
}
updateTextFieldBorder(isFullScreen)
updateEditTextVisibility()
updateEditTextFullScreenState(views.richTextComposerEditText, isFullScreen)
updateEditTextFullScreenState(views.plainTextComposerEditText, isFullScreen)
views.composerFullScreenButton.setImageResource(
if (isFullScreen) R.drawable.ic_composer_collapse else R.drawable.ic_composer_full_screen
)
views.bottomSheetHandle.isVisible = isFullScreen
if (isFullScreen) {
editText.showKeyboard(true)
} else {
editText.hideKeyboard()
}
this.isFullScreen = isFullScreen
}
fun notifyIsBeingDragged(percentage: Float) {
// Calculate a new shape for the border according to the position in screen
val isSingleLine = editText.lineCount == 1
val cornerSize = if (!isSingleLine || hasRelatedMessage) {
resources.getDimensionPixelSize(R.dimen.rich_text_composer_corner_radius_expanded).toFloat()
} else {
val multilineCornerSize = resources.getDimensionPixelSize(R.dimen.rich_text_composer_corner_radius_expanded)
val singleLineCornerSize = resources.getDimensionPixelSize(R.dimen.rich_text_composer_corner_radius_single_line)
val diff = singleLineCornerSize - multilineCornerSize
multilineCornerSize + diff * (1 - percentage)
}
if (cornerSize != borderShapeDrawable.bottomLeftCornerResolvedSize) {
borderShapeDrawable.setCornerSize(cornerSize)
}
// Change maxLines while dragging, this should improve the smoothness of animations
val maxLines = if (percentage > 0.25f) {
Int.MAX_VALUE
} else {
MessageComposerView.MAX_LINES_WHEN_COLLAPSED
}
views.richTextComposerEditText.maxLines = maxLines
views.plainTextComposerEditText.maxLines = maxLines
views.bottomSheetHandle.isVisible = true
}
init {
inflate(context, R.layout.composer_rich_text_layout, this)
views = ComposerRichTextLayoutBinding.bind(this)
collapse(false)
// Workaround to avoid cut-off text caused by padding in scrolled TextView (there is no clipToPadding).
// In TextView, clipTop = padding, but also clipTop -= shadowRadius. So if we set the shadowRadius to padding, they cancel each other
views.richTextComposerEditText.setShadowLayer(views.richTextComposerEditText.paddingBottom.toFloat(), 0f, 0f, 0)
views.plainTextComposerEditText.setShadowLayer(views.richTextComposerEditText.paddingBottom.toFloat(), 0f, 0f, 0)
renderComposerMode(MessageComposerMode.Normal(null))
views.richTextComposerEditText.addTextChangedListener(
TextChangeListener({ callback?.onTextChanged(it) }, { updateTextFieldBorder() })
TextChangeListener({ callback?.onTextChanged(it) }, { updateTextFieldBorder(isFullScreen) })
)
views.plainTextComposerEditText.addTextChangedListener(
TextChangeListener({ callback?.onTextChanged(it) }, { updateTextFieldBorder() })
TextChangeListener({ callback?.onTextChanged(it) }, { updateTextFieldBorder(isFullScreen) })
)
views.composerRelatedMessageCloseButton.setOnClickListener {
collapse()
disallowParentInterceptTouchEvent(views.richTextComposerEditText)
disallowParentInterceptTouchEvent(views.plainTextComposerEditText)
views.composerModeCloseView.setOnClickListener {
callback?.onCloseRelatedMessage()
}
@ -125,11 +190,19 @@ class RichTextComposerLayout @JvmOverloads constructor(
callback?.onAddAttachment()
}
views.composerFullScreenButton.setOnClickListener {
callback?.onFullScreenModeChanged()
views.composerFullScreenButton.apply {
// There's no point in having full screen in landscape since there's almost no vertical space
isInvisible = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
setOnClickListener {
callback?.onFullScreenModeChanged()
}
}
views.composerEditTextOuterBorder.background = borderShapeDrawable
setupRichTextMenu()
updateTextFieldBorder(isFullScreen)
}
private fun setupRichTextMenu() {
@ -147,6 +220,21 @@ class RichTextComposerLayout @JvmOverloads constructor(
}
}
@SuppressLint("ClickableViewAccessibility")
private fun disallowParentInterceptTouchEvent(view: View) {
view.setOnTouchListener { v, event ->
if (v.hasFocus()) {
v.parent?.requestDisallowInterceptTouchEvent(true)
val action = event.actionMasked
if (action == MotionEvent.ACTION_SCROLL) {
v.parent?.requestDisallowInterceptTouchEvent(false)
return@setOnTouchListener true
}
}
false
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
@ -197,84 +285,99 @@ class RichTextComposerLayout @JvmOverloads constructor(
button.isSelected = menuState.reversedActions.contains(action)
}
private fun updateTextFieldBorder() {
val isExpanded = editText.editableText.lines().count() > 1
val borderResource = if (isExpanded || isFullScreen) {
R.drawable.bg_composer_rich_edit_text_expanded
fun estimateCollapsedHeight(): Int {
val editText = this.editText
val originalLines = editText.maxLines
val originalParamsHeight = editText.layoutParams.height
editText.maxLines = MessageComposerView.MAX_LINES_WHEN_COLLAPSED
editText.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.UNSPECIFIED,
)
val result = measuredHeight
editText.layoutParams.height = originalParamsHeight
editText.maxLines = originalLines
return result
}
private fun updateTextFieldBorder(isFullScreen: Boolean) {
val isMultiline = editText.editableText.lines().count() > 1 || isFullScreen || hasRelatedMessage
val cornerSize = if (isMultiline) {
resources.getDimensionPixelSize(R.dimen.rich_text_composer_corner_radius_expanded)
} else {
R.drawable.bg_composer_rich_edit_text_single_line
}
views.composerEditTextOuterBorder.setBackgroundResource(borderResource)
resources.getDimensionPixelSize(R.dimen.rich_text_composer_corner_radius_single_line)
}.toFloat()
borderShapeDrawable.setCornerSize(cornerSize)
}
override fun replaceFormattedContent(text: CharSequence) {
private fun replaceFormattedContent(text: CharSequence) {
views.richTextComposerEditText.setHtml(text.toString())
}
override fun collapse(animate: Boolean, transitionComplete: (() -> Unit)?) {
if (currentConstraintSetId == R.layout.composer_rich_text_layout_constraint_set_compact) {
// ignore we good
return
}
currentConstraintSetId = R.layout.composer_rich_text_layout_constraint_set_compact
applyNewConstraintSet(animate, transitionComplete)
updateEditTextVisibility()
}
override fun expand(animate: Boolean, transitionComplete: (() -> Unit)?) {
if (currentConstraintSetId == R.layout.composer_rich_text_layout_constraint_set_expanded) {
// ignore we good
return
}
currentConstraintSetId = R.layout.composer_rich_text_layout_constraint_set_expanded
applyNewConstraintSet(animate, transitionComplete)
updateEditTextVisibility()
updateTextFieldBorder(isFullScreen)
}
override fun setTextIfDifferent(text: CharSequence?): Boolean {
return editText.setTextIfDifferent(text)
}
override fun toggleFullScreen(newValue: Boolean) {
val constraintSetId = if (newValue) R.layout.composer_rich_text_layout_constraint_set_fullscreen else currentConstraintSetId
ConstraintSet().also {
it.clone(context, constraintSetId)
it.applyTo(this)
}
updateTextFieldBorder()
updateEditTextVisibility()
updateEditTextFullScreenState(views.richTextComposerEditText, newValue)
updateEditTextFullScreenState(views.plainTextComposerEditText, newValue)
val result = editText.setTextIfDifferent(text)
updateTextFieldBorder(isFullScreen)
return result
}
private fun updateEditTextFullScreenState(editText: EditText, isFullScreen: Boolean) {
if (isFullScreen) {
editText.maxLines = Int.MAX_VALUE
// This is a workaround to fix incorrect scroll position when maximised
post { editText.requestLayout() }
} else {
editText.maxLines = maxEditTextLinesWhenCollapsed
editText.maxLines = MessageComposerView.MAX_LINES_WHEN_COLLAPSED
}
}
private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) {
// val wasSendButtonInvisible = views.sendButton.isInvisible
if (animate) {
animateLayoutChange(animationDuration, transitionComplete)
}
ConstraintSet().also {
it.clone(context, currentConstraintSetId)
it.applyTo(this)
override fun renderComposerMode(mode: MessageComposerMode) {
if (mode is MessageComposerMode.Special) {
views.composerModeGroup.isVisible = true
replaceFormattedContent(mode.defaultContent)
hasRelatedMessage = true
editText.showKeyboard(andRequestFocus = true)
} else {
views.composerModeGroup.isGone = true
(mode as? MessageComposerMode.Normal)?.content?.let { text ->
if (isTextFormattingEnabled) {
replaceFormattedContent(text)
} else {
views.plainTextComposerEditText.setText(text)
}
}
views.sendButton.contentDescription = resources.getString(R.string.action_send)
hasRelatedMessage = false
}
// Might be updated by view state just after, but avoid blinks
// views.sendButton.isInvisible = wasSendButtonInvisible
}
views.sendButton.apply {
if (mode is MessageComposerMode.Edit) {
contentDescription = resources.getString(R.string.action_save)
setImageResource(R.drawable.ic_composer_rich_text_save)
} else {
contentDescription = resources.getString(R.string.action_send)
setImageResource(R.drawable.ic_rich_composer_send)
}
}
override fun setInvisible(isInvisible: Boolean) {
this.isInvisible = isInvisible
updateTextFieldBorder(isFullScreen)
when (mode) {
is MessageComposerMode.Edit -> {
views.composerModeTitleView.setText(R.string.editing)
views.composerModeIconView.setImageResource(R.drawable.ic_composer_rich_text_editor_edit)
}
is MessageComposerMode.Quote -> {
views.composerModeTitleView.setText(R.string.quoting)
views.composerModeIconView.setImageResource(R.drawable.ic_quote)
}
is MessageComposerMode.Reply -> {
val senderInfo = mode.event.senderInfo
val userName = senderInfo.displayName ?: senderInfo.disambiguatedDisplayName
views.composerModeTitleView.text = resources.getString(R.string.replying_to, userName)
views.composerModeIconView.setImageResource(R.drawable.ic_reply)
}
else -> Unit
}
}
private class TextChangeListener(

View file

@ -147,7 +147,8 @@ class VoiceMessageViews(
}
fun showRecordingViews() {
views.voiceMessageMicButton.setImageResource(R.drawable.ic_voice_mic_recording)
views.voiceMessageBackgroundView.isVisible = true
views.voiceMessageMicButton.setImageResource(R.drawable.ic_composer_rich_mic_pressed)
views.voiceMessageMicButton.setAttributeTintedBackground(R.drawable.circle_with_halo, R.attr.colorPrimary)
views.voiceMessageMicButton.updateLayoutParams<ViewGroup.MarginLayoutParams> {
setMargins(0, 0, 0, 0)
@ -172,6 +173,7 @@ class VoiceMessageViews(
fun hideRecordingViews(recordingState: RecordingUiState) {
// We need to animate the lock image first
views.voiceMessageBackgroundView.isVisible = false
if (recordingState !is RecordingUiState.Locked) {
views.voiceMessageLockImage.isVisible = false
views.voiceMessageLockImage.animate().translationY(0f).start()
@ -278,6 +280,7 @@ class VoiceMessageViews(
fun showDraftViews() {
hideRecordingViews(RecordingUiState.Idle)
views.voiceMessageBackgroundView.isVisible = true
views.voiceMessageMicButton.isVisible = false
views.voiceMessageSendButton.isVisible = true
views.voiceMessagePlaybackLayout.isVisible = true
@ -288,6 +291,7 @@ class VoiceMessageViews(
fun showRecordingLockedViews(recordingState: RecordingUiState) {
hideRecordingViews(recordingState)
views.voiceMessageBackgroundView.isVisible = true
views.voiceMessagePlaybackLayout.isVisible = true
views.voiceMessagePlaybackTimerIndicator.isVisible = true
views.voicePlaybackControlButton.isVisible = false

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?android:colorBackground"/>
<corners android:topLeftRadius="24dp" android:topRightRadius="24dp" />
</shape>

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<stroke
android:width="1dp"
android:color="?vctr_content_quaternary" />
<corners android:radius="@dimen/rich_text_composer_corner_radius_expanded" />
</shape>

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<stroke
android:width="1dp"
android:color="?vctr_content_quaternary" />
<corners android:radius="@dimen/rich_text_composer_corner_radius_single_line" />
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="?vctr_content_quinary"/>
<corners android:radius="4dp" />
</shape>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#C1C6CD"
android:pathData="M10.708,10Q10.438,10 10.219,9.781Q10,9.562 10,9.292V4.542Q10,4.354 10.146,4.219Q10.292,4.083 10.458,4.083Q10.646,4.083 10.781,4.219Q10.917,4.354 10.917,4.542V8.438L16.375,3Q16.5,2.854 16.688,2.854Q16.875,2.854 17,3Q17.146,3.125 17.146,3.312Q17.146,3.5 17,3.625L11.562,9.083H15.458Q15.646,9.083 15.781,9.229Q15.917,9.375 15.917,9.542Q15.917,9.729 15.781,9.865Q15.646,10 15.458,10ZM3,17Q2.854,16.875 2.854,16.688Q2.854,16.5 3,16.375L8.438,10.917H4.542Q4.354,10.917 4.219,10.771Q4.083,10.625 4.083,10.458Q4.083,10.271 4.219,10.135Q4.354,10 4.542,10H9.292Q9.562,10 9.781,10.219Q10,10.438 10,10.708V15.458Q10,15.646 9.854,15.781Q9.708,15.917 9.542,15.917Q9.354,15.917 9.219,15.781Q9.083,15.646 9.083,15.458V11.562L3.625,17Q3.5,17.146 3.312,17.146Q3.125,17.146 3,17Z"/>
</vector>

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M17.125,31.5C16.944,31.5 16.795,31.441 16.677,31.323C16.559,31.205 16.5,31.056 16.5,30.875V25.875C16.5,25.694 16.559,25.545 16.677,25.427C16.795,25.309 16.944,25.25 17.125,25.25C17.306,25.25 17.455,25.309 17.573,25.427C17.691,25.545 17.75,25.694 17.75,25.875V29.375L29.375,17.75H25.875C25.694,17.75 25.545,17.691 25.427,17.573C25.309,17.455 25.25,17.306 25.25,17.125C25.25,16.944 25.309,16.795 25.427,16.677C25.545,16.559 25.694,16.5 25.875,16.5H30.875C31.056,16.5 31.205,16.559 31.323,16.677C31.441,16.795 31.5,16.944 31.5,17.125V22.125C31.5,22.306 31.441,22.455 31.323,22.573C31.205,22.691 31.056,22.75 30.875,22.75C30.694,22.75 30.545,22.691 30.427,22.573C30.309,22.455 30.25,22.306 30.25,22.125V18.625L18.625,30.25H22.125C22.306,30.25 22.455,30.309 22.573,30.427C22.691,30.545 22.75,30.694 22.75,30.875C22.75,31.056 22.691,31.205 22.573,31.323C22.455,31.441 22.306,31.5 22.125,31.5H17.125Z"
android:fillColor="#C1C6CD"/>
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#C1C6CD"
android:pathData="M3.625,17.083Q3.354,17.083 3.135,16.865Q2.917,16.646 2.917,16.375V11.625Q2.917,11.438 3.062,11.302Q3.208,11.167 3.375,11.167Q3.562,11.167 3.698,11.302Q3.833,11.438 3.833,11.625V15.5L15.5,3.833H11.625Q11.438,3.833 11.302,3.688Q11.167,3.542 11.167,3.375Q11.167,3.188 11.302,3.052Q11.438,2.917 11.625,2.917H16.375Q16.646,2.917 16.865,3.135Q17.083,3.354 17.083,3.625V8.375Q17.083,8.562 16.938,8.698Q16.792,8.833 16.625,8.833Q16.438,8.833 16.302,8.698Q16.167,8.562 16.167,8.375V4.5L4.5,16.167H8.375Q8.562,16.167 8.698,16.312Q8.833,16.458 8.833,16.625Q8.833,16.812 8.698,16.948Q8.562,17.083 8.375,17.083Z"/>
</vector>

View file

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="52dp"
android:height="52dp"
android:viewportWidth="52"
android:viewportHeight="52">
<path
android:pathData="M26.173,26.169m-22.763,0a22.763,22.763 0,1 1,45.526 0a22.763,22.763 0,1 1,-45.526 0"
android:fillColor="#0DBD8B"/>
<path
android:pathData="M26,26m-26,0a26,26 0,1 1,52 0a26,26 0,1 1,-52 0"
android:strokeAlpha="0.2"
android:fillColor="#0DBD8B"
android:fillAlpha="0.2"/>
<path
android:pathData="M26,29.5C27.937,29.5 29.488,27.937 29.488,26L29.5,19C29.5,17.063 27.937,15.5 26,15.5C24.063,15.5 22.5,17.063 22.5,19V26C22.5,27.937 24.063,29.5 26,29.5ZM33.093,26C32.603,26 32.195,26.35 32.125,26.828C31.693,29.873 28.952,31.95 26,31.95C23.048,31.95 20.307,29.885 19.875,26.828C19.805,26.35 19.385,26 18.907,26C18.3,26 17.833,26.537 17.915,27.132C18.452,30.597 21.368,33.315 24.833,33.84V36.5C24.833,37.142 25.358,37.667 26,37.667C26.642,37.667 27.167,37.142 27.167,36.5V33.84C30.62,33.338 33.548,30.597 34.085,27.132C34.167,26.537 33.7,26 33.093,26Z"
android:fillColor="#ffffff"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:pathData="M10.403,2.53C10.696,2.237 10.696,1.763 10.403,1.47C10.111,1.177 9.636,1.177 9.343,1.47L5.946,4.867L2.549,1.47C2.256,1.177 1.781,1.177 1.488,1.47C1.195,1.763 1.195,2.237 1.488,2.53L4.885,5.927L1.343,9.47C1.05,9.763 1.05,10.237 1.343,10.53C1.636,10.823 2.11,10.823 2.403,10.53L5.946,6.988L9.488,10.53C9.781,10.823 10.256,10.823 10.549,10.53C10.842,10.237 10.842,9.763 10.549,9.47L7.006,5.927L10.403,2.53Z"
android:fillColor="#8D97A5"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:pathData="M2.649,7.355C2.655,7.316 2.672,7.28 2.699,7.251L8.404,1.064C8.479,0.983 8.605,0.978 8.686,1.053L9.863,2.138C9.944,2.213 9.949,2.339 9.874,2.42L4.169,8.607C4.143,8.636 4.108,8.656 4.069,8.665L2.668,9.005C2.529,9.039 2.401,8.92 2.423,8.779L2.649,7.355Z"
android:fillColor="#8D97A5"/>
<path
android:pathData="M1.75,9.443C1.336,9.443 1,9.779 1,10.193C1,10.608 1.336,10.943 1.75,10.943L10.75,10.943C11.164,10.943 11.5,10.608 11.5,10.193C11.5,9.779 11.164,9.443 10.75,9.443L1.75,9.443Z"
android:fillColor="#8D97A5"/>
</vector>

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M18,18m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
android:fillColor="#0DBD8B"/>
<path
android:pathData="M9.818,18.787L14.705,23.818L26.182,12"
android:strokeLineJoin="round"
android:strokeWidth="2.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M18,18m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
android:fillColor="#F4F6FA"/>
<path
android:pathData="M11.251,18H24.751M18.001,11.25V24.75"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#737D8C"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M18,18m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
android:fillColor="#0DBD8B"/>
<path
android:pathData="M27.83,19.085L12.26,26.867C11.21,27.391 10.119,26.266 10.632,25.24C10.632,25.24 12.561,21.343 13.092,20.322C13.623,19.301 14.231,19.124 19.874,18.395C20.083,18.368 20.253,18.21 20.253,18C20.253,17.79 20.083,17.632 19.874,17.605C14.231,16.876 13.623,16.699 13.092,15.678C12.561,14.658 10.632,10.76 10.632,10.76C10.119,9.734 11.21,8.609 12.26,9.133L27.83,16.915C28.725,17.362 28.725,18.638 27.83,19.085Z"
android:fillColor="#ffffff"/>
</vector>

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="52dp"
android:height="52dp"
android:viewportWidth="52"
android:viewportHeight="52">
<path
android:pathData="M21.2414,18.7749C21.2414,16.051 23.4496,13.8429 26.1734,13.8429C28.8973,13.8429 31.1054,16.051 31.1054,18.7749V26.1509C31.1054,28.8747 28.8973,31.0829 26.1734,31.0829C23.4496,31.0829 21.2414,28.8747 21.2414,26.1509V18.7749ZM17.542,24.2475C18.5968,24.2475 19.4518,25.1025 19.4518,26.1572C19.4518,29.8561 22.4509,32.8596 26.1586,32.8675C26.1637,32.8674 26.1689,32.8674 26.174,32.8674C26.179,32.8674 26.184,32.8674 26.189,32.8675C29.896,32.8589 32.8944,29.8556 32.8944,26.1572C32.8944,25.1025 33.7494,24.2475 34.8041,24.2475C35.8588,24.2475 36.7138,25.1025 36.7138,26.1572C36.7138,31.3227 32.9916,35.6165 28.0837,36.5143V37.24C28.0837,38.2947 27.2287,39.1497 26.174,39.1497C25.1193,39.1497 24.2643,38.2947 24.2643,37.24V36.5147C19.3555,35.6176 15.6323,31.3233 15.6323,26.1572C15.6323,25.1025 16.4873,24.2475 17.542,24.2475Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>

View file

@ -1,148 +1,210 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout 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/composer_layout_constraint_set_compact"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
android:orientation="vertical">
<!-- ========================
/!\ 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_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?colorSurface"
tools:ignore="MissingConstraints" />
<View
android:id="@+id/related_message_background_top_separator"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?vctr_list_separator"
tools:ignore="MissingConstraints" />
<ImageView
android:id="@+id/composerRelatedMessageAvatar"
android:layout_width="0dp"
android:layout_height="0dp"
android:importantForAccessibility="no"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/composerRelatedMessageTitle"
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/composerRelatedMessageContent"
android:layout_width="0dp"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/related_message_group"
android:layout_width="match_parent"
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" />
android:visibility="gone"
tools:visibility="visible">
<ImageView
android:id="@+id/composerRelatedMessageActionIcon"
android:layout_width="0dp"
android:layout_height="0dp"
android:importantForAccessibility="no"
app:tint="?vctr_content_primary"
tools:ignore="MissingConstraints,MissingPrefix" />
<View
android:id="@+id/related_message_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?colorSurface"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="@id/composer_preview_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/composerRelatedMessageImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:importantForAccessibility="no"
tools:ignore="MissingPrefix" />
<View
android:id="@+id/related_message_background_top_separator"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?vctr_list_separator"
app:layout_constraintEnd_toEndOf="@id/related_message_background"
app:layout_constraintStart_toStartOf="@id/related_message_background"
app:layout_constraintTop_toTopOf="@id/related_message_background" />
<ImageButton
android:id="@+id/composerRelatedMessageCloseButton"
android:layout_width="22dp"
android:layout_height="22dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/action_cancel"
android:src="@drawable/ic_close_round"
app:tint="?colorError"
tools:ignore="MissingConstraints,MissingPrefix" />
<ImageView
android:id="@+id/composerRelatedMessageAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="@id/composerRelatedMessageActionIcon"
app:layout_constraintEnd_toStartOf="@id/composerRelatedMessageTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/composerRelatedMessageTitle"
tools:src="@sample/user_round_avatars" />
<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="composerRelatedMessageContent,composerRelatedMessageActionIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/composerRelatedMessageTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/composerRelatedMessageCloseButton"
app:layout_constraintStart_toEndOf="@id/composerRelatedMessageAvatar"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/first_names" />
<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/option_send_files"
android:src="@drawable/ic_attachment"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/composerRelatedMessageContent"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?vctr_message_text_color"
app:layout_constrainedHeight="true"
app:layout_constraintEnd_toEndOf="@id/composerRelatedMessageTitle"
app:layout_constraintStart_toStartOf="@id/composerRelatedMessageTitle"
app:layout_constraintTop_toBottomOf="@id/composerRelatedMessageImage"
tools:text="@tools:sample/lorem/random" />
<FrameLayout
android:id="@+id/composerEditTextOuterBorder"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/bg_composer_edit_text" />
<ImageView
android:id="@+id/composerRelatedMessageActionIcon"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="38dp"
android:alpha="1"
android:importantForAccessibility="no"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="@id/composerRelatedMessageAvatar"
app:layout_constraintStart_toStartOf="@id/composerRelatedMessageAvatar"
app:layout_constraintTop_toBottomOf="@id/composerRelatedMessageAvatar"
app:tint="?vctr_content_primary"
tools:ignore="MissingPrefix"
tools:src="@drawable/ic_edit" />
<im.vector.app.features.home.room.detail.composer.ComposerEditText
android:id="@+id/composerEditText"
style="@style/Widget.Vector.EditText.Composer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
tools:hint="@string/room_message_placeholder"
tools:ignore="MissingConstraints" />
<ImageView
android:id="@+id/composerRelatedMessageImage"
android:layout_width="100dp"
android:layout_height="66dp"
android:layout_marginTop="6dp"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/composerRelatedMessageTitle"
app:layout_constraintTop_toBottomOf="@id/composerRelatedMessageTitle"
tools:ignore="MissingPrefix"
tools:src="@tools:sample/backgrounds/scenic"
tools:visibility="visible" />
<ImageButton
android:id="@+id/composerEmojiButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_open_emoji_picker"
android:src="@drawable/ic_insert_emoji"
android:visibility="invisible"
app:tint="?vctr_content_tertiary"
tools:ignore="MissingConstraints,MissingPrefix"
tools:visibility="visible" />
<ImageButton
android:id="@+id/composerRelatedMessageCloseButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/action_cancel"
android:src="@drawable/ic_close_round"
app:layout_constraintBottom_toBottomOf="@id/composer_preview_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?colorError"
tools:ignore="MissingPrefix" />
<ImageButton
android:id="@+id/sendButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/bg_send"
android:contentDescription="@string/action_send"
android:src="@drawable/ic_send"
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="composerRelatedMessageContent,composerRelatedMessageActionIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!--
<ImageButton
android:id="@+id/voiceMessageMicButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_start_voice_message"
android:src="@drawable/ic_voice_mic" />
-->
</androidx.constraintlayout.widget.ConstraintLayout>
</merge>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="@dimen/composer_attachment_size"
android:layout_height="@dimen/composer_attachment_size"
android:layout_margin="@dimen/composer_attachment_margin"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/option_send_files"
android:src="@drawable/ic_attachment"
app:layout_constraintBottom_toBottomOf="@id/sendButton"
app:layout_constraintEnd_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/sendButton"
app:layout_goneMarginBottom="57dp"
tools:ignore="MissingPrefix" />
<FrameLayout
android:id="@+id/composerEditTextOuterBorder"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="@drawable/bg_composer_edit_text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/sendButton"
app:layout_constraintStart_toEndOf="@id/attachmentButton"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginEnd="12dp" />
<im.vector.app.features.home.room.detail.composer.ComposerEditText
android:id="@+id/composerEditText"
style="@style/Widget.Vector.EditText.Composer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/composerEmojiButton"
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem/random" />
<ImageButton
android:id="@+id/composerEmojiButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_margin="1dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_open_emoji_picker"
android:src="@drawable/ic_insert_emoji"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@id/attachmentButton"
app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder"
app:layout_constraintStart_toEndOf="@id/composerEditText"
app:layout_constraintTop_toTopOf="@id/attachmentButton"
app:layout_goneMarginEnd="8dp"
app:tint="?vctr_content_quaternary"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<ImageButton
android:id="@+id/sendButton"
android:layout_width="56dp"
android:layout_height="@dimen/composer_min_height"
android:layout_marginEnd="2dp"
android:background="@drawable/bg_send"
android:contentDescription="@string/action_send"
android:scaleType="center"
android:src="@drawable/ic_send"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View file

@ -1,197 +0,0 @@
<?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_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?colorSurface"
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_list_separator"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/composerRelatedMessageAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:importantForAccessibility="no"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toStartOf="parent" />
<TextView
android:id="@+id/composerRelatedMessageTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textStyle="bold"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@id/composerRelatedMessageContent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="@tools:sample/first_names" />
<TextView
android:id="@+id/composerRelatedMessageContent"
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/composerRelatedMessageActionIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="38dp"
android:alpha="0"
android:importantForAccessibility="no"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent"
app:tint="?vctr_content_primary"
tools:ignore="MissingConstraints,MissingPrefix"
tools:src="@drawable/ic_edit" />
<ImageView
android:id="@+id/composerRelatedMessageImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintStart_toEndOf="parent"
tools:ignore="MissingPrefix"
tools:src="@tools:sample/backgrounds/scenic" />
<ImageButton
android:id="@+id/composerRelatedMessageCloseButton"
android:layout_width="22dp"
android:layout_height="22dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/action_cancel"
android:src="@drawable/ic_close_round"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:tint="?colorError"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<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="composerRelatedMessageContent,composerRelatedMessageActionIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="@dimen/composer_attachment_size"
android:layout_height="@dimen/composer_attachment_size"
android:layout_margin="@dimen/composer_attachment_margin"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/option_send_files"
android:src="@drawable/ic_attachment"
app:layout_constraintBottom_toBottomOf="@id/sendButton"
app:layout_constraintEnd_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/sendButton"
app:layout_goneMarginBottom="57dp"
tools:ignore="MissingPrefix" />
<FrameLayout
android:id="@+id/composerEditTextOuterBorder"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/sendButton"
app:layout_constraintStart_toEndOf="@id/attachmentButton"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginEnd="12dp" />
<im.vector.app.features.home.room.detail.composer.ComposerEditText
android:id="@+id/composerEditText"
style="@style/Widget.Vector.EditText.Composer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/composerEmojiButton"
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem/random" />
<ImageButton
android:id="@+id/composerEmojiButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_margin="1dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_open_emoji_picker"
android:src="@drawable/ic_insert_emoji"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@id/attachmentButton"
app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder"
app:layout_constraintStart_toEndOf="@id/composerEditText"
app:layout_constraintTop_toTopOf="@id/attachmentButton"
app:layout_goneMarginEnd="8dp"
app:tint="?vctr_content_quaternary"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<ImageButton
android:id="@+id/sendButton"
android:layout_width="56dp"
android:layout_height="@dimen/composer_min_height"
android:layout_marginEnd="2dp"
android:background="@drawable/bg_send"
android:contentDescription="@string/action_send"
android:scaleType="center"
android:src="@drawable/ic_send"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<!--
<ImageButton
android:id="@+id/voiceMessageMicButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_start_voice_message"
android:src="@drawable/ic_voice_mic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
-->
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,197 +0,0 @@
<?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_background"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?colorSurface"
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_list_separator"
app:layout_constraintEnd_toEndOf="@id/related_message_background"
app:layout_constraintStart_toStartOf="@id/related_message_background"
app:layout_constraintTop_toTopOf="@id/related_message_background" />
<ImageView
android:id="@+id/composerRelatedMessageAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="@id/composerRelatedMessageActionIcon"
app:layout_constraintEnd_toStartOf="@id/composerRelatedMessageTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/composerRelatedMessageTitle"
tools:src="@sample/user_round_avatars" />
<TextView
android:id="@+id/composerRelatedMessageTitle"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/composerRelatedMessageCloseButton"
app:layout_constraintStart_toEndOf="@id/composerRelatedMessageAvatar"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/first_names" />
<ImageView
android:id="@+id/composerRelatedMessageImage"
android:layout_width="100dp"
android:layout_height="66dp"
android:layout_marginTop="6dp"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/composerRelatedMessageTitle"
app:layout_constraintTop_toBottomOf="@id/composerRelatedMessageTitle"
tools:ignore="MissingPrefix"
tools:src="@tools:sample/backgrounds/scenic"
tools:visibility="visible" />
<TextView
android:id="@+id/composerRelatedMessageContent"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?vctr_message_text_color"
app:layout_constrainedHeight="true"
app:layout_constraintEnd_toEndOf="@id/composerRelatedMessageTitle"
app:layout_constraintStart_toStartOf="@id/composerRelatedMessageTitle"
app:layout_constraintTop_toBottomOf="@id/composerRelatedMessageImage"
tools:text="@tools:sample/lorem/random" />
<ImageView
android:id="@+id/composerRelatedMessageActionIcon"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="38dp"
android:alpha="1"
android:importantForAccessibility="no"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="@id/composerRelatedMessageAvatar"
app:layout_constraintStart_toStartOf="@id/composerRelatedMessageAvatar"
app:layout_constraintTop_toBottomOf="@id/composerRelatedMessageAvatar"
app:tint="?vctr_content_primary"
tools:ignore="MissingPrefix"
tools:src="@drawable/ic_edit" />
<ImageButton
android:id="@+id/composerRelatedMessageCloseButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/action_cancel"
android:src="@drawable/ic_close_round"
app:layout_constraintBottom_toBottomOf="@id/composer_preview_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?colorError"
tools:ignore="MissingPrefix" />
<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="composerRelatedMessageContent,composerRelatedMessageActionIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="@dimen/composer_attachment_size"
android:layout_height="@dimen/composer_attachment_size"
android:layout_margin="@dimen/composer_attachment_margin"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/option_send_files"
android:src="@drawable/ic_attachment"
app:layout_constraintBottom_toBottomOf="@id/sendButton"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/sendButton"
tools:ignore="MissingPrefix" />
<FrameLayout
android:id="@+id/composerEditTextOuterBorder"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/sendButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/composer_preview_barrier"
app:layout_goneMarginEnd="12dp" />
<im.vector.app.features.home.room.detail.composer.ComposerEditText
android:id="@+id/composerEditText"
style="@style/Widget.Vector.EditText.Composer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/composerEmojiButton"
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toBottomOf="@id/composer_preview_barrier"
tools:text="@tools:sample/lorem/random" />
<ImageButton
android:id="@+id/composerEmojiButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_margin="1dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_open_emoji_picker"
android:src="@drawable/ic_insert_emoji"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@id/sendButton"
app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder"
app:layout_constraintStart_toEndOf="@id/composerEditText"
app:layout_constraintTop_toTopOf="@id/sendButton"
app:layout_goneMarginBottom="52dp"
app:layout_goneMarginEnd="8dp"
app:tint="?vctr_content_quaternary"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<ImageButton
android:id="@+id/sendButton"
android:layout_width="56dp"
android:layout_height="@dimen/composer_min_height"
android:layout_marginEnd="2dp"
android:background="@drawable/bg_send"
android:contentDescription="@string/action_send"
android:scaleType="center"
android:src="@drawable/ic_send"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/composer_preview_barrier"
app:layout_constraintVertical_bias="1"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,183 +1,201 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout 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="match_parent"
tools:constraintSet="@layout/composer_rich_text_layout_constraint_set_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_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?colorSurface"
tools:ignore="MissingConstraints" />
<View
android:id="@+id/related_message_background_top_separator"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?vctr_list_separator"
tools:ignore="MissingConstraints" />
<ImageView
android:id="@+id/composerRelatedMessageAvatar"
android:layout_width="0dp"
android:layout_height="0dp"
android:importantForAccessibility="no"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/composerRelatedMessageTitle"
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/composerRelatedMessageContent"
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/composerRelatedMessageActionIcon"
android:layout_width="0dp"
android:layout_height="0dp"
android:importantForAccessibility="no"
app:tint="?vctr_content_primary"
tools:ignore="MissingConstraints,MissingPrefix" />
<ImageView
android:id="@+id/composerRelatedMessageImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:importantForAccessibility="no"
tools:ignore="MissingPrefix" />
<ImageButton
android:id="@+id/composerRelatedMessageCloseButton"
android:layout_width="22dp"
android:layout_height="22dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/action_cancel"
android:src="@drawable/ic_close_round"
app:tint="?colorError"
tools:ignore="MissingConstraints,MissingPrefix" />
<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="composerRelatedMessageContent,composerRelatedMessageActionIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:paddingTop="2dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/option_send_files"
android:src="@drawable/ic_attachment"
tools:ignore="MissingConstraints" />
android:orientation="vertical"
android:background="@drawable/bg_composer_rich_bottom_sheet">
<FrameLayout
android:id="@+id/composerEditTextOuterBorder"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/bg_composer_rich_edit_text_single_line" />
<io.element.android.wysiwyg.EditorEditText
android:id="@+id/richTextComposerEditText"
style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp"
android:id="@+id/bottomSheetHandle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:nextFocusLeft="@id/richTextComposerEditText"
android:nextFocusUp="@id/richTextComposerEditText"
tools:hint="@string/room_message_placeholder"
tools:text="@tools:sample/lorem/random"
tools:ignore="MissingConstraints" />
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<!-- Use a separate EditText for plain text editing while the rich text editor doesn't support this mode -->
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/plainTextComposerEditText"
style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="top"
android:nextFocusLeft="@id/plainTextComposerEditText"
android:nextFocusUp="@id/plainTextComposerEditText"
tools:hint="@string/room_message_placeholder"
tools:text="@tools:sample/lorem/random"
tools:ignore="MissingConstraints" />
<View
android:layout_width="36dp"
android:layout_height="5dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:background="@drawable/bottomsheet_handle" />
<ImageButton
android:id="@+id/composerFullScreenButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
android:src="@drawable/ic_composer_full_screen"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/rich_text_editor_full_screen_toggle" />
</FrameLayout>
<ImageButton
android:id="@+id/sendButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/bg_send"
android:contentDescription="@string/action_send"
android:src="@drawable/ic_send"
tools:ignore="MissingConstraints" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<HorizontalScrollView android:id="@+id/richTextMenuScrollView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:scrollbars="none"
app:layout_constraintTop_toTopOf="@id/sendButton"
app:layout_constraintStart_toEndOf="@id/attachmentButton"
app:layout_constraintEnd_toStartOf="@id/sendButton"
app:layout_constraintBottom_toBottomOf="parent"
android:fillViewport="true">
<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="56dp"
android:layout_height="60dp"
android:layout_margin="@dimen/composer_attachment_margin"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/option_send_files"
android:src="@drawable/ic_rich_composer_add"
android:paddingStart="4dp"
app:layout_constraintVertical_bias="1"
app:layout_constraintBottom_toBottomOf="@id/sendButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/sendButton"
app:layout_goneMarginBottom="57dp"
tools:ignore="MissingPrefix,RtlSymmetry" />
<LinearLayout
android:id="@+id/richTextMenu"
<FrameLayout
android:id="@+id/composerEditTextOuterBorder"
android:layout_width="0dp"
android:layout_height="0dp"
android:minHeight="40dp"
android:layout_marginTop="8dp"
android:layout_marginHorizontal="12dp"
app:layout_constraintVertical_bias="0"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/sendButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/composerModeIconView"
android:layout_width="11dp"
android:layout_height="11dp"
tools:src="@drawable/ic_quote"
android:layout_marginStart="12dp"
app:layout_constraintTop_toTopOf="@id/composerModeTitleView"
app:layout_constraintBottom_toBottomOf="@id/composerModeTitleView"
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:tint="?vctr_content_tertiary" />
<TextView android:id="@+id/composerModeTitleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:layout_marginStart="6dp"
android:layout_marginTop="8dp"
android:paddingBottom="2dp"
android:fontFamily="sans-serif-medium"
tools:text="Editing"
style="@style/BottomSheetItemTime"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
app:layout_constraintStart_toEndOf="@id/composerModeIconView" />
</LinearLayout>
<ImageButton android:id="@+id/composerModeCloseView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_composer_rich_text_editor_close"
android:background="?android:selectableItemBackground"
android:layout_marginEnd="12dp"
android:contentDescription="@string/action_close"
app:layout_constraintTop_toTopOf="@id/composerModeIconView"
app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder" />
</HorizontalScrollView>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/composerModeBarrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="bottom"
app:constraint_referenced_ids="composerModeIconView,composerModeTitleView,composerModeCloseView" />
<!--
<ImageButton
android:id="@+id/voiceMessageMicButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_start_voice_message"
android:src="@drawable/ic_voice_mic" />
-->
<androidx.constraintlayout.widget.Group
android:id="@+id/composerModeGroup"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
tools:visibility="visible"
app:constraint_referenced_ids="composerModeIconView,composerModeTitleView,composerModeCloseView" />
</merge>
<io.element.android.wysiwyg.EditorEditText
android:id="@+id/richTextComposerEditText"
style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintHeight_default="wrap"
android:gravity="top"
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/richTextComposerEditText"
android:nextFocusUp="@id/richTextComposerEditText"
android:layout_marginStart="12dp"
android:imeOptions="flagNoExtractUi"
app:layout_constraintVertical_bias="0"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toStartOf="@id/composerFullScreenButton"
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toBottomOf="@id/composerModeBarrier"
tools:text="@tools:sample/lorem/random" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/plainTextComposerEditText"
style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintHeight_default="wrap"
android:visibility="gone"
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/plainTextComposerEditText"
android:nextFocusUp="@id/plainTextComposerEditText"
android:layout_marginStart="12dp"
android:gravity="top"
android:imeOptions="flagNoExtractUi"
app:layout_constraintVertical_bias="0"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toStartOf="@id/composerFullScreenButton"
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toBottomOf="@id/composerModeBarrier"
tools:text="@tools:sample/lorem/random" />
<ImageButton
android:id="@+id/composerFullScreenButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="4dp"
app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toBottomOf="@id/composerModeBarrier"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintVertical_bias="0"
android:src="@drawable/ic_composer_full_screen"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/rich_text_editor_full_screen_toggle" />
<ImageButton
android:id="@+id/sendButton"
android:layout_width="56dp"
android:layout_height="60dp"
android:paddingEnd="4dp"
android:contentDescription="@string/action_send"
android:scaleType="center"
android:src="@drawable/ic_rich_composer_send"
android:visibility="invisible"
android:background="?android:selectableItemBackground"
app:layout_constraintTop_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintVertical_bias="1"
tools:ignore="MissingPrefix,RtlSymmetry"
tools:visibility="visible" />
<HorizontalScrollView android:id="@+id/richTextMenuScrollView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:minHeight="52dp"
app:layout_constraintTop_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintStart_toEndOf="@id/attachmentButton"
app:layout_constraintEnd_toStartOf="@id/sendButton"
app:layout_constraintBottom_toBottomOf="parent"
android:fillViewport="true">
<LinearLayout
android:id="@+id/richTextMenu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingVertical="4dp">
</LinearLayout>
</HorizontalScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View file

@ -1,233 +0,0 @@
<?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="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<View
android:id="@+id/related_message_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?colorSurface"
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_list_separator"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/composerRelatedMessageAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:importantForAccessibility="no"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toStartOf="parent" />
<TextView
android:id="@+id/composerRelatedMessageTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textStyle="bold"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@id/composerRelatedMessageContent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="@tools:sample/first_names" />
<TextView
android:id="@+id/composerRelatedMessageContent"
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/composerRelatedMessageActionIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="38dp"
android:alpha="0"
android:importantForAccessibility="no"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent"
app:tint="?vctr_content_primary"
tools:ignore="MissingConstraints,MissingPrefix"
tools:src="@drawable/ic_edit" />
<ImageView
android:id="@+id/composerRelatedMessageImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintStart_toEndOf="parent"
tools:ignore="MissingPrefix"
tools:src="@tools:sample/backgrounds/scenic" />
<ImageButton
android:id="@+id/composerRelatedMessageCloseButton"
android:layout_width="22dp"
android:layout_height="22dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/action_cancel"
android:src="@drawable/ic_close_round"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:tint="?colorError"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<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="composerRelatedMessageContent,composerRelatedMessageActionIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="@dimen/composer_attachment_size"
android:layout_height="@dimen/composer_attachment_size"
android:layout_margin="@dimen/composer_attachment_margin"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/option_send_files"
android:src="@drawable/ic_attachment"
app:layout_constraintVertical_bias="1"
app:layout_constraintBottom_toBottomOf="@id/sendButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/sendButton"
app:layout_goneMarginBottom="57dp"
tools:ignore="MissingPrefix" />
<FrameLayout
android:id="@+id/composerEditTextOuterBorder"
android:layout_width="0dp"
android:layout_height="0dp"
android:minHeight="40dp"
android:layout_marginTop="8dp"
android:layout_marginHorizontal="12dp"
android:background="@drawable/bg_composer_rich_edit_text_single_line"
app:layout_constraintVertical_bias="0"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/sendButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<io.element.android.wysiwyg.EditorEditText
android:id="@+id/richTextComposerEditText"
style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/richTextComposerEditText"
android:nextFocusUp="@id/richTextComposerEditText"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="10dp"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder"
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
tools:text="@tools:sample/lorem/random" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/plainTextComposerEditText"
style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/plainTextComposerEditText"
android:nextFocusUp="@id/plainTextComposerEditText"
android:layout_marginStart="12dp"
android:layout_marginVertical="10dp"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toStartOf="@id/composerFullScreenButton"
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
tools:text="@tools:sample/lorem/random" />
<ImageButton
android:id="@+id/composerFullScreenButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintVertical_bias="0"
android:src="@drawable/ic_composer_full_screen"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/rich_text_editor_full_screen_toggle" />
<ImageButton
android:id="@+id/sendButton"
android:layout_width="56dp"
android:layout_height="@dimen/composer_min_height"
android:layout_marginEnd="2dp"
android:background="@drawable/bg_send"
android:contentDescription="@string/action_send"
android:scaleType="center"
android:src="@drawable/ic_send"
android:visibility="invisible"
app:layout_constraintTop_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintVertical_bias="1"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<HorizontalScrollView android:id="@+id/richTextMenuScrollView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@id/sendButton"
app:layout_constraintStart_toEndOf="@id/attachmentButton"
app:layout_constraintEnd_toStartOf="@id/sendButton"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="1"
android:fillViewport="true">
<LinearLayout
android:id="@+id/richTextMenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
<!--
<ImageButton
android:id="@+id/voiceMessageMicButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_start_voice_message"
android:src="@drawable/ic_voice_mic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
-->
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,230 +0,0 @@
<?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="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<View
android:id="@+id/related_message_background"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?colorSurface"
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_list_separator"
app:layout_constraintEnd_toEndOf="@id/related_message_background"
app:layout_constraintStart_toStartOf="@id/related_message_background"
app:layout_constraintTop_toTopOf="@id/related_message_background" />
<ImageView
android:id="@+id/composerRelatedMessageAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="@id/composerRelatedMessageActionIcon"
app:layout_constraintEnd_toStartOf="@id/composerRelatedMessageTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/composerRelatedMessageTitle"
tools:src="@sample/user_round_avatars" />
<TextView
android:id="@+id/composerRelatedMessageTitle"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/composerRelatedMessageCloseButton"
app:layout_constraintStart_toEndOf="@id/composerRelatedMessageAvatar"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/first_names" />
<ImageView
android:id="@+id/composerRelatedMessageImage"
android:layout_width="100dp"
android:layout_height="66dp"
android:layout_marginTop="6dp"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/composerRelatedMessageTitle"
app:layout_constraintTop_toBottomOf="@id/composerRelatedMessageTitle"
tools:ignore="MissingPrefix"
tools:src="@tools:sample/backgrounds/scenic"
tools:visibility="visible" />
<TextView
android:id="@+id/composerRelatedMessageContent"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?vctr_message_text_color"
app:layout_constrainedHeight="true"
app:layout_constraintEnd_toEndOf="@id/composerRelatedMessageTitle"
app:layout_constraintStart_toStartOf="@id/composerRelatedMessageTitle"
app:layout_constraintTop_toBottomOf="@id/composerRelatedMessageImage"
tools:text="@tools:sample/lorem/random" />
<ImageView
android:id="@+id/composerRelatedMessageActionIcon"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="38dp"
android:alpha="1"
android:importantForAccessibility="no"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="@id/composerRelatedMessageAvatar"
app:layout_constraintStart_toStartOf="@id/composerRelatedMessageAvatar"
app:layout_constraintTop_toBottomOf="@id/composerRelatedMessageAvatar"
app:tint="?vctr_content_primary"
tools:ignore="MissingPrefix"
tools:src="@drawable/ic_edit" />
<ImageButton
android:id="@+id/composerRelatedMessageCloseButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/action_cancel"
android:src="@drawable/ic_close_round"
app:layout_constraintBottom_toBottomOf="@id/composer_preview_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?colorError"
tools:ignore="MissingPrefix" />
<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="composerRelatedMessageContent,composerRelatedMessageActionIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="@dimen/composer_attachment_size"
android:layout_height="@dimen/composer_attachment_size"
android:layout_margin="@dimen/composer_attachment_margin"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/option_send_files"
android:src="@drawable/ic_attachment"
app:layout_constraintBottom_toBottomOf="@id/sendButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/sendButton"
app:layout_goneMarginBottom="57dp"
tools:ignore="MissingPrefix" />
<FrameLayout
android:id="@+id/composerEditTextOuterBorder"
android:layout_width="0dp"
android:layout_height="0dp"
android:minHeight="40dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="0dp"
android:layout_marginHorizontal="12dp"
android:background="@drawable/bg_composer_rich_edit_text_single_line"
app:layout_constraintVertical_bias="0"
app:layout_constraintTop_toBottomOf="@id/composer_preview_barrier"
app:layout_constraintBottom_toTopOf="@id/sendButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<io.element.android.wysiwyg.EditorEditText
android:id="@+id/richTextComposerEditText"
style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/richTextComposerEditText"
android:nextFocusUp="@id/richTextComposerEditText"
android:layout_marginStart="12dp"
android:layout_marginVertical="10dp"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toStartOf="@id/composerFullScreenButton"
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
tools:text="@tools:sample/lorem/random" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/plainTextComposerEditText"
style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/plainTextComposerEditText"
android:nextFocusUp="@id/plainTextComposerEditText"
android:layout_marginStart="12dp"
android:layout_marginVertical="10dp"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toStartOf="@id/composerFullScreenButton"
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
tools:text="@tools:sample/lorem/random" />
<ImageButton
android:id="@+id/composerFullScreenButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintVertical_bias="0"
android:src="@drawable/ic_composer_full_screen"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/rich_text_editor_full_screen_toggle" />
<ImageButton
android:id="@+id/sendButton"
android:layout_width="56dp"
android:layout_height="@dimen/composer_min_height"
android:layout_marginEnd="2dp"
android:background="@drawable/bg_send"
android:contentDescription="@string/action_send"
android:scaleType="center"
android:src="@drawable/ic_send"
android:visibility="invisible"
app:layout_constraintTop_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<HorizontalScrollView android:id="@+id/richTextMenuScrollView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@id/sendButton"
app:layout_constraintStart_toEndOf="@id/attachmentButton"
app:layout_constraintEnd_toStartOf="@id/sendButton"
app:layout_constraintBottom_toBottomOf="parent"
android:fillViewport="true">
<LinearLayout
android:id="@+id/richTextMenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,234 +0,0 @@
<?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="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<View
android:id="@+id/related_message_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?colorSurface"
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_list_separator"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/composerRelatedMessageAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:importantForAccessibility="no"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toStartOf="parent" />
<TextView
android:id="@+id/composerRelatedMessageTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textStyle="bold"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@id/composerRelatedMessageContent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="@tools:sample/first_names" />
<TextView
android:id="@+id/composerRelatedMessageContent"
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/composerRelatedMessageActionIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="38dp"
android:alpha="0"
android:importantForAccessibility="no"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent"
app:tint="?vctr_content_primary"
tools:ignore="MissingConstraints,MissingPrefix"
tools:src="@drawable/ic_edit" />
<ImageView
android:id="@+id/composerRelatedMessageImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintStart_toEndOf="parent"
tools:ignore="MissingPrefix"
tools:src="@tools:sample/backgrounds/scenic" />
<ImageButton
android:id="@+id/composerRelatedMessageCloseButton"
android:layout_width="22dp"
android:layout_height="22dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/action_cancel"
android:src="@drawable/ic_close_round"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:tint="?colorError"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<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="composerRelatedMessageContent,composerRelatedMessageActionIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/attachmentButton"
android:layout_width="@dimen/composer_attachment_size"
android:layout_height="@dimen/composer_attachment_size"
android:layout_margin="@dimen/composer_attachment_margin"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/option_send_files"
android:src="@drawable/ic_attachment"
app:layout_constraintBottom_toBottomOf="@id/sendButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/sendButton"
app:layout_goneMarginBottom="57dp"
app:layout_constraintVertical_bias="1"
tools:ignore="MissingPrefix" />
<FrameLayout
android:id="@+id/composerEditTextOuterBorder"
android:layout_width="0dp"
android:layout_height="0dp"
android:minHeight="40dp"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="12dp"
android:background="@drawable/bg_composer_rich_edit_text_expanded"
app:layout_constraintVertical_bias="0"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/sendButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<io.element.android.wysiwyg.EditorEditText
android:id="@+id/richTextComposerEditText"
style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp"
android:layout_height="0dp"
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/richTextComposerEditText"
android:nextFocusUp="@id/richTextComposerEditText"
android:layout_marginStart="12dp"
android:layout_marginVertical="10dp"
android:gravity="top"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toStartOf="@id/composerFullScreenButton"
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
tools:text="@tools:sample/lorem/random" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/plainTextComposerEditText"
style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp"
android:layout_height="0dp"
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/plainTextComposerEditText"
android:nextFocusUp="@id/plainTextComposerEditText"
android:layout_marginStart="12dp"
android:layout_marginVertical="10dp"
android:gravity="top"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toStartOf="@id/composerFullScreenButton"
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
tools:text="@tools:sample/lorem/random" />
<ImageButton
android:id="@+id/composerFullScreenButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintVertical_bias="0"
android:src="@drawable/ic_composer_full_screen"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/rich_text_editor_full_screen_toggle" />
<ImageButton
android:id="@+id/sendButton"
android:layout_width="56dp"
android:layout_height="@dimen/composer_min_height"
android:layout_marginEnd="2dp"
android:background="@drawable/bg_send"
android:contentDescription="@string/action_send"
android:scaleType="center"
android:src="@drawable/ic_send"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintVertical_bias="1"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<HorizontalScrollView android:id="@+id/richTextMenuScrollView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@id/sendButton"
app:layout_constraintStart_toEndOf="@id/attachmentButton"
app:layout_constraintEnd_toStartOf="@id/sendButton"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="1"
android:fillViewport="true">
<LinearLayout
android:id="@+id/richTextMenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
<!--
<ImageButton
android:id="@+id/voiceMessageMicButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_start_voice_message"
android:src="@drawable/ic_voice_mic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
-->
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -4,12 +4,13 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@android:color/transparent">
<im.vector.app.features.home.room.detail.composer.PlainTextComposerLayout
android:id="@+id/composerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:background="?android:colorBackground"
android:minHeight="56dp"
android:transitionName="composer"
@ -20,7 +21,7 @@
android:id="@+id/richTextComposerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:colorBackground"
android:background="@drawable/bg_composer_rich_bottom_sheet"
android:minHeight="56dp"
android:transitionName="composer"
android:visibility="gone"

View file

@ -1,26 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- ========================
/!\ Constraints for this layout are defined in external layout files that are used as constraint set for animation.
/!\ These 2 files must be modified to stay coherent!
======================== -->
<View android:id="@+id/scrim"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone"
android:background="#44000000" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
@ -52,188 +37,187 @@
</com.google.android.material.appbar.AppBarLayout>
<im.vector.app.features.sync.widget.SyncStateView
android:id="@+id/syncStateView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
<im.vector.app.features.location.live.LiveLocationStatusView
android:id="@+id/liveLocationStatusIndicator"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/syncStateView"
tools:visibility="visible" />
<im.vector.app.features.call.conference.RemoveJitsiWidgetView
android:id="@+id/removeJitsiWidgetView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
android:minHeight="54dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/liveLocationStatusIndicator" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/timelineRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:overScrollMode="always"
app:layout_constraintBottom_toTopOf="@id/bottomBarrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView"
tools:listitem="@layout/item_timeline_event_base" />
<com.google.android.material.chip.Chip
android:id="@+id/jumpToReadMarkerView"
style="?vctr_jump_to_unread_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="24dp"
android:text="@string/room_jump_to_first_unread"
android:visibility="invisible"
app:chipIcon="@drawable/ic_jump_to_unread"
app:chipIconTint="?colorPrimary"
app:closeIcon="@drawable/ic_close_24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView" />
<im.vector.app.core.ui.views.NotificationAreaView
android:id="@+id/notificationAreaView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<ViewStub
android:id="@+id/failedMessagesWarningStub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/failedMessagesWarningStub"
android:layout="@layout/view_stub_failed_message_warning_layout"
app:layout_constraintBottom_toTopOf="@id/composerContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:layout_height="300dp" />
<FrameLayout
android:id="@+id/composerContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<FrameLayout
android:id="@+id/voiceMessageRecorderContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<ViewStub
android:id="@+id/inviteViewStub"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:colorBackground"
android:layout="@layout/view_stub_invite_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/bottomBarrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="top"
app:constraint_referenced_ids="composerContainer,notificationAreaView,failedMessagesWarningStub" />
<im.vector.app.core.platform.BadgeFloatingActionButton
android:id="@+id/jumpToBottomView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/a11y_jump_to_bottom"
android:src="@drawable/ic_expand_more"
android:visibility="gone"
app:backgroundTint="#FFFFFF"
app:badgeBackgroundColor="?colorPrimary"
app:badgeTextColor="?colorOnPrimary"
app:badgeTextPadding="2dp"
app:badgeTextSize="10sp"
app:layout_constraintBottom_toTopOf="@id/bottomBarrier"
app:layout_constraintEnd_toEndOf="parent"
app:tint="@android:color/black" />
<im.vector.app.core.ui.views.CompatKonfetti
android:id="@+id/viewKonfetti"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
<com.jetradarmobile.snowfall.SnowfallView
android:id="@+id/viewSnowFall"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?vctr_chat_effect_snow_background"
android:visibility="invisible" />
<!-- Room not found layout -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/roomNotFound"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:colorBackground"
android:elevation="10dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/rootConstraintLayout">
<ImageView
android:id="@+id/roomNotFoundIcon"
android:layout_width="60dp"
android:layout_height="60dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_alert_triangle"
app:layout_constraintBottom_toTopOf="@id/roomNotFoundText"
<im.vector.app.features.sync.widget.SyncStateView
android:id="@+id/syncStateView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/roomNotFoundText"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="wrap_content"
<im.vector.app.features.location.live.LiveLocationStatusView
android:id="@+id/liveLocationStatusIndicator"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:gravity="center"
android:paddingHorizontal="16dp"
android:text="@string/timeline_error_room_not_found"
android:textColor="?vctr_content_primary"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/syncStateView"
tools:visibility="visible" />
<im.vector.app.features.call.conference.RemoveJitsiWidgetView
android:id="@+id/removeJitsiWidgetView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
android:minHeight="54dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/liveLocationStatusIndicator" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/timelineRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:overScrollMode="always"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomNotFoundIcon" />
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView"
tools:listitem="@layout/item_timeline_event_base" />
<com.google.android.material.chip.Chip
android:id="@+id/jumpToReadMarkerView"
style="?vctr_jump_to_unread_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="24dp"
android:text="@string/room_jump_to_first_unread"
android:visibility="invisible"
app:chipIcon="@drawable/ic_jump_to_unread"
app:chipIconTint="?colorPrimary"
app:closeIcon="@drawable/ic_close_24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView" />
<im.vector.app.core.ui.views.NotificationAreaView
android:id="@+id/notificationAreaView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<ViewStub
android:id="@+id/failedMessagesWarningStub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/failedMessagesWarningStub"
android:layout="@layout/view_stub_failed_message_warning_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:layout_height="300dp" />
<ViewStub
android:id="@+id/inviteViewStub"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:colorBackground"
android:layout="@layout/view_stub_invite_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<im.vector.app.core.platform.BadgeFloatingActionButton
android:id="@+id/jumpToBottomView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/a11y_jump_to_bottom"
android:src="@drawable/ic_expand_more"
android:visibility="gone"
app:backgroundTint="#FFFFFF"
app:badgeBackgroundColor="?colorPrimary"
app:badgeTextColor="?colorOnPrimary"
app:badgeTextPadding="2dp"
app:badgeTextSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:tint="@android:color/black" />
<im.vector.app.core.ui.views.CompatKonfetti
android:id="@+id/viewKonfetti"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
<com.jetradarmobile.snowfall.SnowfallView
android:id="@+id/viewSnowFall"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?vctr_chat_effect_snow_background"
android:visibility="invisible" />
<!-- Room not found layout -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/roomNotFound"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:colorBackground"
android:elevation="10dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone">
<ImageView
android:id="@+id/roomNotFoundIcon"
android:layout_width="60dp"
android:layout_height="60dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_alert_triangle"
app:layout_constraintBottom_toTopOf="@id/roomNotFoundText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/roomNotFoundText"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:gravity="center"
android:paddingHorizontal="16dp"
android:text="@string/timeline_error_room_not_found"
android:textColor="?vctr_content_primary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomNotFoundIcon" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/composerContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:translationZ="10dp"
android:background="@android:color/transparent"
app:layout_behavior="im.vector.app.core.utils.ExpandingBottomSheetBehavior" />
<FrameLayout
android:id="@+id/voiceMessageRecorderContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:visibility="visible"
android:translationZ="10dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,258 +0,0 @@
<?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/rootConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- ========================
/!\ Constraints for this layout are defined in external layout files that are used as constraint set for animation.
/!\ These 2 files must be modified to stay coherent!
======================== -->
<View android:id="@+id/scrim"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:translationZ="10dp"
android:visibility="visible"
android:background="#44000000" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
<im.vector.app.core.ui.views.CurrentCallsView
android:id="@+id/currentCallsView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:visibility="gone" />
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/roomToolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:transitionName="toolbar">
<include
android:id="@+id/includeThreadToolbar"
layout="@layout/view_room_detail_thread_toolbar" />
<include
android:id="@+id/includeRoomToolbar"
layout="@layout/view_room_detail_toolbar" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<im.vector.app.features.sync.widget.SyncStateView
android:id="@+id/syncStateView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
<im.vector.app.features.location.live.LiveLocationStatusView
android:id="@+id/liveLocationStatusIndicator"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/syncStateView"
tools:visibility="visible" />
<im.vector.app.features.call.conference.RemoveJitsiWidgetView
android:id="@+id/removeJitsiWidgetView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
android:minHeight="54dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/liveLocationStatusIndicator" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/timelineRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:overScrollMode="always"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@id/typingMessageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView"
tools:listitem="@layout/item_timeline_event_base" />
<com.google.android.material.chip.Chip
android:id="@+id/jumpToReadMarkerView"
style="?vctr_jump_to_unread_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="24dp"
android:text="@string/room_jump_to_first_unread"
android:visibility="invisible"
app:chipIcon="@drawable/ic_jump_to_unread"
app:chipIconTint="?colorPrimary"
app:closeIcon="@drawable/ic_close_24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView" />
<im.vector.app.core.ui.views.TypingMessageView
android:id="@+id/typingMessageView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/bottomBarrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/timelineRecyclerView" />
<im.vector.app.core.ui.views.NotificationAreaView
android:id="@+id/notificationAreaView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<ViewStub
android:id="@+id/failedMessagesWarningStub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/failedMessagesWarningStub"
android:layout="@layout/view_stub_failed_message_warning_layout"
app:layout_constraintBottom_toTopOf="@id/composerContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:layout_height="300dp" />
<FrameLayout
android:id="@+id/composerContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:colorBackground"
android:translationZ="48dp"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<FrameLayout
android:id="@+id/voiceMessageRecorderContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:translationZ="48dp"
android:background="?android:colorBackground"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<ViewStub
android:id="@+id/inviteViewStub"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:colorBackground"
android:layout="@layout/view_stub_invite_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/bottomBarrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="top"
app:constraint_referenced_ids="notificationAreaView,failedMessagesWarningStub" />
<im.vector.app.core.platform.BadgeFloatingActionButton
android:id="@+id/jumpToBottomView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/a11y_jump_to_bottom"
android:src="@drawable/ic_expand_more"
android:visibility="gone"
app:backgroundTint="#FFFFFF"
app:badgeBackgroundColor="?colorPrimary"
app:badgeTextColor="?colorOnPrimary"
app:badgeTextPadding="2dp"
app:badgeTextSize="10sp"
app:layout_constraintBottom_toTopOf="@id/bottomBarrier"
app:layout_constraintEnd_toEndOf="parent"
app:tint="@android:color/black" />
<im.vector.app.core.ui.views.CompatKonfetti
android:id="@+id/viewKonfetti"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
<com.jetradarmobile.snowfall.SnowfallView
android:id="@+id/viewSnowFall"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?vctr_chat_effect_snow_background"
android:visibility="invisible" />
<!-- Room not found layout -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/roomNotFound"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:colorBackground"
android:elevation="10dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone">
<ImageView
android:id="@+id/roomNotFoundIcon"
android:layout_width="60dp"
android:layout_height="60dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_alert_triangle"
app:layout_constraintBottom_toTopOf="@id/roomNotFoundText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/roomNotFoundText"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:gravity="center"
android:paddingHorizontal="16dp"
android:text="@string/timeline_error_room_not_found"
android:textColor="?vctr_content_primary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomNotFoundIcon" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -2,8 +2,8 @@
<ImageButton 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="48dp"
android:layout_height="48dp"
android:layout_width="@dimen/rich_text_composer_menu_item_size"
android:layout_height="@dimen/rich_text_composer_menu_item_size"
android:layout_marginHorizontal="2dp"
android:background="@drawable/bg_rich_text_menu_button"
app:tint="@color/selector_rich_text_menu_icon"

View file

@ -7,13 +7,23 @@
android:layout_height="match_parent"
android:minHeight="200dp">
<View
android:id="@+id/voiceMessageBackgroundView"
android:layout_width="0dp"
android:layout_height="@dimen/composer_min_height"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="?android:colorBackground" />
<View
android:id="@+id/voiceMessageLockBackground"
android:layout_width="78dp"
android:layout_width="52dp"
android:layout_height="180dp"
android:background="@drawable/bg_voice_message_lock"
android:backgroundTint="?vctr_system"
android:visibility="gone"
android:layout_marginEnd="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/voiceMessageMicButton"
tools:translationY="-180dp"
@ -26,7 +36,7 @@
android:minWidth="32dp"
android:minHeight="32dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:layout_marginBottom="16dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_start_voice_message"
android:src="@drawable/ic_microphone"
@ -100,7 +110,6 @@
android:id="@+id/voiceMessageSlideToCancelDivider"
android:layout_width="48dp"
android:layout_height="0dp"
android:background="?android:colorBackground"
app:layout_constraintBottom_toBottomOf="@id/voiceMessageTimer"
app:layout_constraintStart_toEndOf="@id/voiceMessageTimer"
app:layout_constraintTop_toTopOf="@id/voiceMessageTimer" />
@ -109,7 +118,7 @@
android:id="@+id/voiceMessageLockImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:layout_marginTop="16dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_voice_message_unlocked"
android:visibility="gone"
@ -123,7 +132,6 @@
android:id="@+id/voiceMessageLockArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginBottom="14dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_voice_lock_arrow"