[Rich text editor] Implement full screen editor mode (simple approach) (#7436)

* Rich text editor: implement full screen editor mode using ConstraintSets

* Add back press handler

* Change ToggleFullScreen to SetFullScreen, fix rebase issues

* Add warning to fragment_timeline* files
This commit is contained in:
Jorge Martin Espinosa 2022-10-26 15:15:48 +02:00 committed by GitHub
parent 65a5ae9d3d
commit d242ab049b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 705 additions and 53 deletions

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

@ -0,0 +1 @@
Rich text editor: add full screen mode.

View file

@ -3423,5 +3423,6 @@
<string name="rich_text_editor_format_italic">Apply italic format</string>
<string name="rich_text_editor_format_strikethrough">Apply strikethrough format</string>
<string name="rich_text_editor_format_underline">Apply underline format</string>
<string name="rich_text_editor_full_screen_toggle">Toggle full screen mode</string>
</resources>

View file

@ -150,7 +150,8 @@
<activity
android:name=".features.home.room.detail.RoomDetailActivity"
android:parentActivityName=".features.home.HomeActivity">
android:parentActivityName=".features.home.HomeActivity"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".features.home.HomeActivity" />

View file

@ -29,7 +29,13 @@ import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
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 im.vector.app.R
import im.vector.app.core.animations.SimpleTransitionListener
import im.vector.app.features.themes.ThemeUtils
/**
@ -90,3 +96,18 @@ fun View.setAttributeBackground(@AttrRes attributeId: Int) {
val attribute = ThemeUtils.getAttribute(context, attributeId)!!
setBackgroundResource(attribute.resourceId)
}
fun ViewGroup.animateLayoutChange(animationDuration: Long, 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()
}
})
}
TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition)
}

View file

@ -34,6 +34,8 @@ 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) {
@ -43,7 +45,7 @@ class JumpToBottomViewVisibilityManager(
if (scrollingToPast) {
jumpToBottomView.hide()
} else {
} else if (canShowButtonOnScroll) {
maybeShowJumpToBottomViewVisibility()
}
}
@ -66,7 +68,13 @@ class JumpToBottomViewVisibilityManager(
}
}
fun hideAndPreventVisibilityChangesWithScrolling() {
jumpToBottomView.hide()
canShowButtonOnScroll = false
}
private fun maybeShowJumpToBottomViewVisibility() {
canShowButtonOnScroll = true
if (layoutManager.findFirstVisibleItemPosition() > 1) {
jumpToBottomView.show()
} else {

View file

@ -32,7 +32,9 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.addCallback
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
@ -64,6 +66,7 @@ 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
@ -183,7 +186,9 @@ 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
@ -337,6 +342,7 @@ class TimelineFragment :
setupJumpToBottomView()
setupRemoveJitsiWidgetView()
setupLiveLocationIndicator()
setupBackPressHandling()
views.includeRoomToolbar.roomToolbarContentView.debouncedClicks {
navigator.openRoomProfile(requireActivity(), timelineArgs.roomId)
@ -414,6 +420,31 @@ class TimelineFragment :
if (savedInstanceState == null) {
handleSpaceShare()
}
views.scrim.setOnClickListener {
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false))
}
messageComposerViewModel.stateFlow.map { it.isFullScreen }
.distinctUntilChanged()
.onEach { isFullScreen ->
toggleFullScreenEditor(isFullScreen)
}
.launchIn(viewLifecycleOwner.lifecycleScope)
}
private fun setupBackPressHandling() {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
withState(messageComposerViewModel) { state ->
if (state.isFullScreen) {
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false))
} else {
remove() // Remove callback to avoid infinite loop
@Suppress("DEPRECATION")
requireActivity().onBackPressed()
}
}
}
}
private fun setupRemoveJitsiWidgetView() {
@ -1016,7 +1047,13 @@ class TimelineFragment :
override fun onLayoutCompleted(state: RecyclerView.State) {
super.onLayoutCompleted(state)
updateJumpToReadMarkerViewVisibility()
jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay()
withState(messageComposerViewModel) { composerState ->
if (!composerState.isFullScreen) {
jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay()
} else {
jumpToBottomViewVisibilityManager.hideAndPreventVisibilityChangesWithScrolling()
}
}
}
}.apply {
// For local rooms, pin the view's content to the top edge (the layout is reversed)
@ -2002,6 +2039,19 @@ 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

@ -34,6 +34,8 @@ sealed class MessageComposerAction : VectorViewModelAction {
data class SlashCommandConfirmed(val parsedCommand: ParsedCommand) : MessageComposerAction()
data class InsertUserDisplayName(val userId: String) : MessageComposerAction()
data class SetFullScreen(val isFullScreen: Boolean) : MessageComposerAction()
// Voice Message
data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : MessageComposerAction()
data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : MessageComposerAction()

View file

@ -92,6 +92,7 @@ import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.share.SharedData
import im.vector.app.features.voice.VoiceFailure
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@ -219,6 +220,13 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
}
}
messageComposerViewModel.stateFlow.map { it.isFullScreen }
.distinctUntilChanged()
.onEach { isFullScreen ->
composer.toggleFullScreen(isFullScreen)
}
.launchIn(viewLifecycleOwner.lifecycleScope)
if (savedInstanceState != null) {
handleShareData()
}
@ -297,7 +305,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
// Show keyboard when the user started a thread
composerEditText.showKeyboard(andRequestFocus = true)
}
composer.callback = object : PlainTextComposerLayout.Callback {
composer.callback = object : Callback {
override fun onAddAttachment() {
if (!::attachmentTypeSelector.isInitialized) {
attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment)
@ -320,8 +328,12 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
composer.emojiButton?.isVisible = isEmojiKeyboardVisible
}
override fun onSendMessage(text: CharSequence) {
override fun onSendMessage(text: CharSequence) = withState(messageComposerViewModel) { state ->
sendTextMessage(text, composer.formattedText)
if (state.isFullScreen) {
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false))
}
}
override fun onCloseRelatedMessage() {
@ -335,6 +347,10 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
override fun onTextChanged(text: CharSequence) {
messageComposerViewModel.handle(MessageComposerAction.OnTextChanged(text))
}
override fun onFullScreenModeChanged() = withState(messageComposerViewModel) { state ->
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(!state.isFullScreen))
}
}
}
@ -461,7 +477,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
composer.sendButton.alpha = 0f
composer.sendButton.isVisible = true
composer.sendButton.animate().alpha(1f).setDuration(150).start()
} else {
} else if (!event.isVisible) {
composer.sendButton.isInvisible = true
}
}

View file

@ -30,13 +30,14 @@ interface MessageComposerView {
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: PlainTextComposerLayout.Callback?
var callback: Callback?
var isVisible: Boolean
@ -44,6 +45,15 @@ interface MessageComposerView {
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)
}
interface Callback : ComposerEditText.Callback {
fun onCloseRelatedMessage()
fun onSendMessage(text: CharSequence)
fun onAddAttachment()
fun onExpandOrCompactChange()
fun onFullScreenModeChanged()
}

View file

@ -16,6 +16,7 @@
package im.vector.app.features.home.room.detail.composer
import android.text.SpannableString
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
@ -122,6 +123,7 @@ class MessageComposerViewModel @AssistedInject constructor(
is MessageComposerAction.AudioSeekBarMovedTo -> handleAudioSeekBarMovedTo(action)
is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(action)
is MessageComposerAction.InsertUserDisplayName -> handleInsertUserDisplayName(action)
is MessageComposerAction.SetFullScreen -> handleSetFullScreen(action)
}
}
@ -130,12 +132,11 @@ class MessageComposerViewModel @AssistedInject constructor(
}
private fun handleOnTextChanged(action: MessageComposerAction.OnTextChanged) {
setState {
// Makes sure currentComposerText is upToDate when accessing further setState
currentComposerText = action.text
this
val needsSendButtonVisibilityUpdate = currentComposerText.isEmpty() != action.text.isEmpty()
currentComposerText = SpannableString(action.text)
if (needsSendButtonVisibilityUpdate) {
updateIsSendButtonVisibility(true)
}
updateIsSendButtonVisibility(true)
}
private fun subscribeToStateInternal() {
@ -163,6 +164,10 @@ class MessageComposerViewModel @AssistedInject constructor(
}
}
private fun handleSetFullScreen(action: MessageComposerAction.SetFullScreen) {
setState { copy(isFullScreen = action.isFullScreen) }
}
private fun observePowerLevelAndEncryption() {
combine(
PowerLevelsFlowFactory(room).createFlow(),

View file

@ -70,6 +70,7 @@ data class MessageComposerViewState(
val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle,
val voiceBroadcastState: VoiceBroadcastState? = null,
val text: CharSequence? = null,
val isFullScreen: Boolean = false,
) : MavericksState {
val isVoiceRecording = when (voiceRecordingUiState) {

View file

@ -49,13 +49,6 @@ class PlainTextComposerLayout @JvmOverloads constructor(
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), MessageComposerView {
interface Callback : ComposerEditText.Callback {
fun onCloseRelatedMessage()
fun onSendMessage(text: CharSequence)
fun onAddAttachment()
fun onExpandOrCompactChange()
}
private val views: ComposerLayoutBinding
override var callback: Callback? = null
@ -83,6 +76,7 @@ class PlainTextComposerLayout @JvmOverloads constructor(
}
override val attachmentButton: ImageButton
get() = views.attachmentButton
override val fullScreenButton: ImageButton? = null
override val composerRelatedMessageActionIcon: ImageView
get() = views.composerRelatedMessageActionIcon
override val composerRelatedMessageAvatar: ImageView
@ -155,6 +149,10 @@ class PlainTextComposerLayout @JvmOverloads constructor(
return views.composerEditText.setTextIfDifferent(text)
}
override fun toggleFullScreen(newValue: Boolean) {
// Plain text composer has no full screen
}
private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) {
// val wasSendButtonInvisible = views.sendButton.isInvisible
if (animate) {

View file

@ -21,7 +21,6 @@ import android.text.Editable
import android.text.TextWatcher
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageButton
import android.widget.ImageView
@ -33,13 +32,8 @@ import androidx.constraintlayout.widget.ConstraintSet
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 im.vector.app.R
import im.vector.app.core.animations.SimpleTransitionListener
import im.vector.app.core.extensions.animateLayoutChange
import im.vector.app.core.extensions.setTextIfDifferent
import im.vector.app.databinding.ComposerRichTextLayoutBinding
import im.vector.app.databinding.ViewRichTextMenuButtonBinding
@ -56,12 +50,13 @@ class RichTextComposerLayout @JvmOverloads constructor(
private val views: ComposerRichTextLayoutBinding
override var callback: PlainTextComposerLayout.Callback? = null
override var callback: Callback? = null
private var currentConstraintSetId: Int = -1
private val animationDuration = 100L
private var isFullScreen = false
override val text: Editable?
get() = views.composerEditText.text
override val formattedText: String?
@ -74,6 +69,8 @@ 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
@ -124,6 +121,10 @@ class RichTextComposerLayout @JvmOverloads constructor(
callback?.onAddAttachment()
}
views.composerFullScreenButton.setOnClickListener {
callback?.onFullScreenModeChanged()
}
setupRichTextMenu()
}
@ -205,34 +206,30 @@ class RichTextComposerLayout @JvmOverloads constructor(
return views.composerEditText.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(newValue)
}
private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) {
// val wasSendButtonInvisible = views.sendButton.isInvisible
if (animate) {
configureAndBeginTransition(transitionComplete)
animateLayoutChange(animationDuration, transitionComplete)
}
ConstraintSet().also {
it.clone(context, currentConstraintSetId)
it.applyTo(this)
}
// 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()
}
})
}
TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition)
}
override fun setInvisible(isInvisible: Boolean) {
this.isInvisible = isInvisible
}

View file

@ -0,0 +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"/>
</vector>

View file

@ -3,7 +3,7 @@
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"
android:layout_height="match_parent"
tools:constraintSet="@layout/composer_rich_text_layout_constraint_set_compact"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
@ -108,12 +108,24 @@
style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="top"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
tools:hint="@string/room_message_placeholder"
tools:text="@tools:sample/lorem/random"
tools:ignore="MissingConstraints" />
<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" />
<ImageButton
android:id="@+id/sendButton"
android:layout_width="0dp"

View file

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/composerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
@ -114,6 +114,7 @@
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"
@ -142,14 +143,26 @@
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
android:layout_marginHorizontal="12dp"
android:layout_marginStart="12dp"
android:layout_marginVertical="10dp"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toEndOf="@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"
@ -163,6 +176,7 @@
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" />
@ -173,6 +187,7 @@
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

View file

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/composerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
@ -156,14 +156,26 @@
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
android:layout_marginHorizontal="12dp"
android:layout_marginStart="12dp"
android:layout_marginVertical="10dp"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintEnd_toEndOf="@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"

View file

@ -0,0 +1,217 @@
<?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/composerEditText"
style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp"
android:layout_height="0dp"
android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
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,7 +4,7 @@
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">
android:layout_height="match_parent">
<im.vector.app.features.home.room.detail.composer.PlainTextComposerLayout
android:id="@+id/composerLayout"
@ -19,7 +19,7 @@
<im.vector.app.features.home.room.detail.composer.RichTextComposerLayout
android:id="@+id/richTextComposerLayout"
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"

View file

@ -6,6 +6,21 @@
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"
@ -98,6 +113,7 @@
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"
@ -128,6 +144,7 @@
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" />
@ -165,6 +182,7 @@
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"

View file

@ -0,0 +1,258 @@
<?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>