[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_italic">Apply italic format</string>
<string name="rich_text_editor_format_strikethrough">Apply strikethrough 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_format_underline">Apply underline format</string>
<string name="rich_text_editor_full_screen_toggle">Toggle full screen mode</string>
</resources> </resources>

View File

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

View File

@ -29,7 +29,13 @@ import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.isVisible 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.R
import im.vector.app.core.animations.SimpleTransitionListener
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
/** /**
@ -90,3 +96,18 @@ fun View.setAttributeBackground(@AttrRes attributeId: Int) {
val attribute = ThemeUtils.getAttribute(context, attributeId)!! val attribute = ThemeUtils.getAttribute(context, attributeId)!!
setBackgroundResource(attribute.resourceId) 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 val layoutManager: LinearLayoutManager
) { ) {
private var canShowButtonOnScroll = true
init { init {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
@ -43,7 +45,7 @@ class JumpToBottomViewVisibilityManager(
if (scrollingToPast) { if (scrollingToPast) {
jumpToBottomView.hide() jumpToBottomView.hide()
} else { } else if (canShowButtonOnScroll) {
maybeShowJumpToBottomViewVisibility() maybeShowJumpToBottomViewVisibility()
} }
} }
@ -66,7 +68,13 @@ class JumpToBottomViewVisibilityManager(
} }
} }
fun hideAndPreventVisibilityChangesWithScrolling() {
jumpToBottomView.hide()
canShowButtonOnScroll = false
}
private fun maybeShowJumpToBottomViewVisibility() { private fun maybeShowJumpToBottomViewVisibility() {
canShowButtonOnScroll = true
if (layoutManager.findFirstVisibleItemPosition() > 1) { if (layoutManager.findFirstVisibleItemPosition() > 1) {
jumpToBottomView.show() jumpToBottomView.show()
} else { } else {

View File

@ -32,7 +32,9 @@ import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.activity.addCallback
import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuBuilder
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.net.toUri 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.GalleryOrCameraDialogHelper
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory
import im.vector.app.core.epoxy.LayoutManagerStateRestorer 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.cleanup
import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.containsRtLOverride 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.WidgetKind
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -337,6 +342,7 @@ class TimelineFragment :
setupJumpToBottomView() setupJumpToBottomView()
setupRemoveJitsiWidgetView() setupRemoveJitsiWidgetView()
setupLiveLocationIndicator() setupLiveLocationIndicator()
setupBackPressHandling()
views.includeRoomToolbar.roomToolbarContentView.debouncedClicks { views.includeRoomToolbar.roomToolbarContentView.debouncedClicks {
navigator.openRoomProfile(requireActivity(), timelineArgs.roomId) navigator.openRoomProfile(requireActivity(), timelineArgs.roomId)
@ -414,6 +420,31 @@ class TimelineFragment :
if (savedInstanceState == null) { if (savedInstanceState == null) {
handleSpaceShare() 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() { private fun setupRemoveJitsiWidgetView() {
@ -1016,7 +1047,13 @@ class TimelineFragment :
override fun onLayoutCompleted(state: RecyclerView.State) { override fun onLayoutCompleted(state: RecyclerView.State) {
super.onLayoutCompleted(state) super.onLayoutCompleted(state)
updateJumpToReadMarkerViewVisibility() updateJumpToReadMarkerViewVisibility()
jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay() withState(messageComposerViewModel) { composerState ->
if (!composerState.isFullScreen) {
jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay()
} else {
jumpToBottomViewVisibilityManager.hideAndPreventVisibilityChangesWithScrolling()
}
}
} }
}.apply { }.apply {
// For local rooms, pin the view's content to the top edge (the layout is reversed) // 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. * 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 SlashCommandConfirmed(val parsedCommand: ParsedCommand) : MessageComposerAction()
data class InsertUserDisplayName(val userId: String) : MessageComposerAction() data class InsertUserDisplayName(val userId: String) : MessageComposerAction()
data class SetFullScreen(val isFullScreen: Boolean) : MessageComposerAction()
// Voice Message // Voice Message
data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : MessageComposerAction() data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : MessageComposerAction()
data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : 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.share.SharedData
import im.vector.app.features.voice.VoiceFailure import im.vector.app.features.voice.VoiceFailure
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach 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) { if (savedInstanceState != null) {
handleShareData() handleShareData()
} }
@ -297,7 +305,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
// Show keyboard when the user started a thread // Show keyboard when the user started a thread
composerEditText.showKeyboard(andRequestFocus = true) composerEditText.showKeyboard(andRequestFocus = true)
} }
composer.callback = object : PlainTextComposerLayout.Callback { composer.callback = object : Callback {
override fun onAddAttachment() { override fun onAddAttachment() {
if (!::attachmentTypeSelector.isInitialized) { if (!::attachmentTypeSelector.isInitialized) {
attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment) attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment)
@ -320,8 +328,12 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
composer.emojiButton?.isVisible = isEmojiKeyboardVisible composer.emojiButton?.isVisible = isEmojiKeyboardVisible
} }
override fun onSendMessage(text: CharSequence) { override fun onSendMessage(text: CharSequence) = withState(messageComposerViewModel) { state ->
sendTextMessage(text, composer.formattedText) sendTextMessage(text, composer.formattedText)
if (state.isFullScreen) {
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false))
}
} }
override fun onCloseRelatedMessage() { override fun onCloseRelatedMessage() {
@ -335,6 +347,10 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
override fun onTextChanged(text: CharSequence) { override fun onTextChanged(text: CharSequence) {
messageComposerViewModel.handle(MessageComposerAction.OnTextChanged(text)) 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.alpha = 0f
composer.sendButton.isVisible = true composer.sendButton.isVisible = true
composer.sendButton.animate().alpha(1f).setDuration(150).start() composer.sendButton.animate().alpha(1f).setDuration(150).start()
} else { } else if (!event.isVisible) {
composer.sendButton.isInvisible = true composer.sendButton.isInvisible = true
} }
} }

View File

@ -30,13 +30,14 @@ interface MessageComposerView {
val emojiButton: ImageButton? val emojiButton: ImageButton?
val sendButton: ImageButton val sendButton: ImageButton
val attachmentButton: ImageButton val attachmentButton: ImageButton
val fullScreenButton: ImageButton?
val composerRelatedMessageTitle: TextView val composerRelatedMessageTitle: TextView
val composerRelatedMessageContent: TextView val composerRelatedMessageContent: TextView
val composerRelatedMessageImage: ImageView val composerRelatedMessageImage: ImageView
val composerRelatedMessageActionIcon: ImageView val composerRelatedMessageActionIcon: ImageView
val composerRelatedMessageAvatar: ImageView val composerRelatedMessageAvatar: ImageView
var callback: PlainTextComposerLayout.Callback? var callback: Callback?
var isVisible: Boolean var isVisible: Boolean
@ -44,6 +45,15 @@ interface MessageComposerView {
fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null)
fun setTextIfDifferent(text: CharSequence?): Boolean fun setTextIfDifferent(text: CharSequence?): Boolean
fun replaceFormattedContent(text: CharSequence) fun replaceFormattedContent(text: CharSequence)
fun toggleFullScreen(newValue: Boolean)
fun setInvisible(isInvisible: 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 package im.vector.app.features.home.room.detail.composer
import android.text.SpannableString
import androidx.lifecycle.asFlow import androidx.lifecycle.asFlow
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted import dagger.assisted.Assisted
@ -122,6 +123,7 @@ class MessageComposerViewModel @AssistedInject constructor(
is MessageComposerAction.AudioSeekBarMovedTo -> handleAudioSeekBarMovedTo(action) is MessageComposerAction.AudioSeekBarMovedTo -> handleAudioSeekBarMovedTo(action)
is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(action) is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(action)
is MessageComposerAction.InsertUserDisplayName -> handleInsertUserDisplayName(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) { private fun handleOnTextChanged(action: MessageComposerAction.OnTextChanged) {
setState { val needsSendButtonVisibilityUpdate = currentComposerText.isEmpty() != action.text.isEmpty()
// Makes sure currentComposerText is upToDate when accessing further setState currentComposerText = SpannableString(action.text)
currentComposerText = action.text if (needsSendButtonVisibilityUpdate) {
this updateIsSendButtonVisibility(true)
} }
updateIsSendButtonVisibility(true)
} }
private fun subscribeToStateInternal() { 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() { private fun observePowerLevelAndEncryption() {
combine( combine(
PowerLevelsFlowFactory(room).createFlow(), PowerLevelsFlowFactory(room).createFlow(),

View File

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

View File

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

View File

@ -21,7 +21,6 @@ import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.EditText import android.widget.EditText
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.ImageView import android.widget.ImageView
@ -33,13 +32,8 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible 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.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.core.extensions.setTextIfDifferent
import im.vector.app.databinding.ComposerRichTextLayoutBinding import im.vector.app.databinding.ComposerRichTextLayoutBinding
import im.vector.app.databinding.ViewRichTextMenuButtonBinding import im.vector.app.databinding.ViewRichTextMenuButtonBinding
@ -56,12 +50,13 @@ class RichTextComposerLayout @JvmOverloads constructor(
private val views: ComposerRichTextLayoutBinding private val views: ComposerRichTextLayoutBinding
override var callback: PlainTextComposerLayout.Callback? = null override var callback: Callback? = null
private var currentConstraintSetId: Int = -1 private var currentConstraintSetId: Int = -1
private val animationDuration = 100L private val animationDuration = 100L
private var isFullScreen = false
override val text: Editable? override val text: Editable?
get() = views.composerEditText.text get() = views.composerEditText.text
override val formattedText: String? override val formattedText: String?
@ -74,6 +69,8 @@ class RichTextComposerLayout @JvmOverloads constructor(
get() = views.sendButton get() = views.sendButton
override val attachmentButton: ImageButton override val attachmentButton: ImageButton
get() = views.attachmentButton get() = views.attachmentButton
override val fullScreenButton: ImageButton?
get() = views.composerFullScreenButton
override val composerRelatedMessageActionIcon: ImageView override val composerRelatedMessageActionIcon: ImageView
get() = views.composerRelatedMessageActionIcon get() = views.composerRelatedMessageActionIcon
override val composerRelatedMessageAvatar: ImageView override val composerRelatedMessageAvatar: ImageView
@ -124,6 +121,10 @@ class RichTextComposerLayout @JvmOverloads constructor(
callback?.onAddAttachment() callback?.onAddAttachment()
} }
views.composerFullScreenButton.setOnClickListener {
callback?.onFullScreenModeChanged()
}
setupRichTextMenu() setupRichTextMenu()
} }
@ -205,34 +206,30 @@ class RichTextComposerLayout @JvmOverloads constructor(
return views.composerEditText.setTextIfDifferent(text) 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)?) { private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) {
// val wasSendButtonInvisible = views.sendButton.isInvisible // val wasSendButtonInvisible = views.sendButton.isInvisible
if (animate) { if (animate) {
configureAndBeginTransition(transitionComplete) animateLayoutChange(animationDuration, transitionComplete)
} }
ConstraintSet().also { ConstraintSet().also {
it.clone(context, currentConstraintSetId) it.clone(context, currentConstraintSetId)
it.applyTo(this) it.applyTo(this)
} }
// Might be updated by view state just after, but avoid blinks // Might be updated by view state just after, but avoid blinks
// views.sendButton.isInvisible = wasSendButtonInvisible // 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) { override fun setInvisible(isInvisible: Boolean) {
this.isInvisible = isInvisible 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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" 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:constraintSet="@layout/composer_rich_text_layout_constraint_set_compact"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
@ -108,12 +108,24 @@
style="@style/Widget.Vector.EditText.RichTextComposer" style="@style/Widget.Vector.EditText.RichTextComposer"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="top"
android:nextFocusLeft="@id/composerEditText" android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText" android:nextFocusUp="@id/composerEditText"
tools:hint="@string/room_message_placeholder" tools:hint="@string/room_message_placeholder"
tools:text="@tools:sample/lorem/random" tools:text="@tools:sample/lorem/random"
tools:ignore="MissingConstraints" /> 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 <ImageButton
android:id="@+id/sendButton" android:id="@+id/sendButton"
android:layout_width="0dp" android:layout_width="0dp"

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/composerLayout" android:id="@+id/composerLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"> app:layout_constraintStart_toStartOf="parent">
@ -114,6 +114,7 @@
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/option_send_files" android:contentDescription="@string/option_send_files"
android:src="@drawable/ic_attachment" android:src="@drawable/ic_attachment"
app:layout_constraintVertical_bias="1"
app:layout_constraintBottom_toBottomOf="@id/sendButton" app:layout_constraintBottom_toBottomOf="@id/sendButton"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/sendButton" app:layout_constraintTop_toTopOf="@id/sendButton"
@ -142,14 +143,26 @@
android:hint="@string/room_message_placeholder" android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/composerEditText" android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText" android:nextFocusUp="@id/composerEditText"
android:layout_marginHorizontal="12dp" android:layout_marginStart="12dp"
android:layout_marginVertical="10dp" android:layout_marginVertical="10dp"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder" 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_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder" app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
tools:text="@tools:sample/lorem/random" /> 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 <ImageButton
android:id="@+id/sendButton" android:id="@+id/sendButton"
android:layout_width="56dp" android:layout_width="56dp"
@ -163,6 +176,7 @@
app:layout_constraintTop_toBottomOf="@id/composerEditTextOuterBorder" app:layout_constraintTop_toBottomOf="@id/composerEditTextOuterBorder"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintVertical_bias="1"
tools:ignore="MissingPrefix" tools:ignore="MissingPrefix"
tools:visibility="visible" /> tools:visibility="visible" />
@ -173,6 +187,7 @@
app:layout_constraintStart_toEndOf="@id/attachmentButton" app:layout_constraintStart_toEndOf="@id/attachmentButton"
app:layout_constraintEnd_toStartOf="@id/sendButton" app:layout_constraintEnd_toStartOf="@id/sendButton"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="1"
android:fillViewport="true"> android:fillViewport="true">
<LinearLayout <LinearLayout

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/composerLayout" android:id="@+id/composerLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"> app:layout_constraintStart_toStartOf="parent">
@ -156,14 +156,26 @@
android:hint="@string/room_message_placeholder" android:hint="@string/room_message_placeholder"
android:nextFocusLeft="@id/composerEditText" android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText" android:nextFocusUp="@id/composerEditText"
android:layout_marginHorizontal="12dp" android:layout_marginStart="12dp"
android:layout_marginVertical="10dp" android:layout_marginVertical="10dp"
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder" 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_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder" app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder"
tools:text="@tools:sample/lorem/random" /> 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 <ImageButton
android:id="@+id/sendButton" android:id="@+id/sendButton"
android:layout_width="56dp" 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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="match_parent">
<im.vector.app.features.home.room.detail.composer.PlainTextComposerLayout <im.vector.app.features.home.room.detail.composer.PlainTextComposerLayout
android:id="@+id/composerLayout" android:id="@+id/composerLayout"
@ -19,7 +19,7 @@
<im.vector.app.features.home.room.detail.composer.RichTextComposerLayout <im.vector.app.features.home.room.detail.composer.RichTextComposerLayout
android:id="@+id/richTextComposerLayout" android:id="@+id/richTextComposerLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:background="?android:colorBackground" android:background="?android:colorBackground"
android:minHeight="56dp" android:minHeight="56dp"
android:transitionName="composer" android:transitionName="composer"

View File

@ -6,6 +6,21 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout" android:id="@+id/appBarLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -98,6 +113,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="20dp" android:paddingStart="20dp"
android:paddingEnd="20dp" android:paddingEnd="20dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/bottomBarrier" app:layout_constraintBottom_toTopOf="@id/bottomBarrier"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -128,6 +144,7 @@
android:id="@+id/composerContainer" android:id="@+id/composerContainer"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:colorBackground"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" /> app:layout_constraintBottom_toBottomOf="parent" />
@ -165,6 +182,7 @@
android:layout_margin="16dp" android:layout_margin="16dp"
android:contentDescription="@string/a11y_jump_to_bottom" android:contentDescription="@string/a11y_jump_to_bottom"
android:src="@drawable/ic_expand_more" android:src="@drawable/ic_expand_more"
android:visibility="gone"
app:backgroundTint="#FFFFFF" app:backgroundTint="#FFFFFF"
app:badgeBackgroundColor="?colorPrimary" app:badgeBackgroundColor="?colorPrimary"
app:badgeTextColor="?colorOnPrimary" 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>