From 742ace48979aac79f17de749704e2996e01cbdcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 21 Oct 2022 13:02:17 +0200 Subject: [PATCH 1/6] Rich text editor: implement full screen editor mode using ConstraintSets --- vector/src/main/AndroidManifest.xml | 3 +- .../app/core/extensions/ViewExtensions.kt | 21 ++ .../home/room/detail/TimelineFragment.kt | 28 ++ .../detail/composer/MessageComposerAction.kt | 2 + .../composer/MessageComposerFragment.kt | 20 +- .../detail/composer/MessageComposerView.kt | 12 +- .../composer/MessageComposerViewModel.kt | 15 +- .../composer/MessageComposerViewState.kt | 1 + .../composer/PlainTextComposerLayout.kt | 12 +- .../detail/composer/RichTextComposerLayout.kt | 49 ++-- .../res/drawable/ic_composer_full_screen.xml | 9 + .../res/layout/composer_rich_text_layout.xml | 13 +- ...ich_text_layout_constraint_set_compact.xml | 20 +- ...ch_text_layout_constraint_set_expanded.xml | 17 +- ..._text_layout_constraint_set_fullscreen.xml | 216 +++++++++++++++ .../src/main/res/layout/fragment_composer.xml | 4 +- .../src/main/res/layout/fragment_timeline.xml | 12 + .../layout/fragment_timeline_fullscreen.xml | 252 ++++++++++++++++++ 18 files changed, 656 insertions(+), 50 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_composer_full_screen.xml create mode 100644 vector/src/main/res/layout/composer_rich_text_layout_constraint_set_fullscreen.xml create mode 100644 vector/src/main/res/layout/fragment_timeline_fullscreen.xml diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index b0cd202d122..11a54e9f824 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -150,7 +150,8 @@ + android:parentActivityName=".features.home.HomeActivity" + android:windowSoftInputMode="adjustResize"> diff --git a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt index 625ff15ef76..156809d5ad9 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt @@ -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) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 9d50cdb0706..74100b30ff1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -33,6 +33,7 @@ import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView 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 +65,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 +185,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 @@ -414,6 +418,17 @@ class TimelineFragment : if (savedInstanceState == null) { handleSpaceShare() } + + views.scrim.setOnClickListener { + messageComposerViewModel.handle(MessageComposerAction.ToggleFullScreen) + } + + messageComposerViewModel.stateFlow.map { it.isFullScreen } + .distinctUntilChanged() + .onEach { isFullScreen -> + toggleFullScreenEditor(isFullScreen) + } + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun setupRemoveJitsiWidgetView() { @@ -2002,6 +2017,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. */ diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt index 82adcd014aa..ba4e2d4a4c2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt @@ -34,6 +34,8 @@ sealed class MessageComposerAction : VectorViewModelAction { data class SlashCommandConfirmed(val parsedCommand: ParsedCommand) : MessageComposerAction() data class InsertUserDisplayName(val userId: String) : MessageComposerAction() + object ToggleFullScreen : MessageComposerAction() + // Voice Message data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : MessageComposerAction() data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : MessageComposerAction() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 55ec922a578..7bbcbe1bf4b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -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(), 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(), 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) @@ -322,6 +330,10 @@ class MessageComposerFragment : VectorBaseFragment(), A override fun onSendMessage(text: CharSequence) { sendTextMessage(text, composer.formattedText) + + if (withState(messageComposerViewModel) { it.isFullScreen }) { + messageComposerViewModel.handle(MessageComposerAction.ToggleFullScreen) + } } override fun onCloseRelatedMessage() { @@ -335,6 +347,10 @@ class MessageComposerFragment : VectorBaseFragment(), A override fun onTextChanged(text: CharSequence) { messageComposerViewModel.handle(MessageComposerAction.OnTextChanged(text)) } + + override fun onFullScreenModeChanged(isFullScreen: Boolean) { + messageComposerViewModel.handle(MessageComposerAction.ToggleFullScreen) + } } } @@ -461,7 +477,7 @@ class MessageComposerFragment : VectorBaseFragment(), 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 } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt index 09357191b44..c8f76ea34b6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt @@ -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(isFullScreen: Boolean) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 1a9f9e62910..1836db68a22 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -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) + MessageComposerAction.ToggleFullScreen -> handleToggleFullScreen() } } @@ -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 handleToggleFullScreen() { + setState { copy(isFullScreen = !isFullScreen) } + } + private fun observePowerLevelAndEncryption() { combine( PowerLevelsFlowFactory(room).createFlow(), diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt index 0df1dbebd86..7bb9509599c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt @@ -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) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt index acb5a1b42aa..939a59fccaf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt @@ -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) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt index 07b7d151adb..05058e7acb3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt @@ -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(!isFullScreen) + } + setupRichTextMenu() } @@ -205,34 +206,32 @@ class RichTextComposerLayout @JvmOverloads constructor( return views.composerEditText.setTextIfDifferent(text) } + override fun toggleFullScreen(newValue: Boolean) { + isFullScreen = newValue + + val constraintSetId = if (isFullScreen) R.layout.composer_rich_text_layout_constraint_set_fullscreen else currentConstraintSetId + ConstraintSet().also { + it.clone(context, constraintSetId) + it.applyTo(this) + } + + updateTextFieldBorder(isFullScreen) + } + 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 } diff --git a/vector/src/main/res/drawable/ic_composer_full_screen.xml b/vector/src/main/res/drawable/ic_composer_full_screen.xml new file mode 100644 index 00000000000..394dc522798 --- /dev/null +++ b/vector/src/main/res/drawable/ic_composer_full_screen.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/layout/composer_rich_text_layout.xml b/vector/src/main/res/layout/composer_rich_text_layout.xml index 09e4b038877..17f1e658ae7 100644 --- a/vector/src/main/res/layout/composer_rich_text_layout.xml +++ b/vector/src/main/res/layout/composer_rich_text_layout.xml @@ -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,23 @@ 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" /> + + @@ -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,25 @@ 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" /> + + @@ -173,6 +186,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"> @@ -156,14 +156,25 @@ 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" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_composer.xml b/vector/src/main/res/layout/fragment_composer.xml index 8703af74719..41c052367a3 100644 --- a/vector/src/main/res/layout/fragment_composer.xml +++ b/vector/src/main/res/layout/fragment_composer.xml @@ -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"> + + diff --git a/vector/src/main/res/layout/fragment_timeline_fullscreen.xml b/vector/src/main/res/layout/fragment_timeline_fullscreen.xml new file mode 100644 index 00000000000..63563a78e1e --- /dev/null +++ b/vector/src/main/res/layout/fragment_timeline_fullscreen.xml @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 14c3cda68031b2e9c853dd18b2db50a8e1047729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 25 Oct 2022 18:32:06 +0200 Subject: [PATCH 2/6] Fix danger issues --- changelog.d/7436.feature | 1 + library/ui-strings/src/main/res/values/strings.xml | 1 + .../home/room/detail/composer/MessageComposerFragment.kt | 1 + vector/src/main/res/layout/composer_rich_text_layout.xml | 3 ++- .../composer_rich_text_layout_constraint_set_compact.xml | 3 ++- .../composer_rich_text_layout_constraint_set_expanded.xml | 3 ++- .../composer_rich_text_layout_constraint_set_fullscreen.xml | 3 ++- 7 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 changelog.d/7436.feature diff --git a/changelog.d/7436.feature b/changelog.d/7436.feature new file mode 100644 index 00000000000..b038c975e1f --- /dev/null +++ b/changelog.d/7436.feature @@ -0,0 +1 @@ +Rich text editor: add full screen mode. diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index ea9b4b59998..450dcab1f7e 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3423,5 +3423,6 @@ Apply italic format Apply strikethrough format Apply underline format + Toggle full screen mode diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 7bbcbe1bf4b..7491639892a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -93,6 +93,7 @@ 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.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach diff --git a/vector/src/main/res/layout/composer_rich_text_layout.xml b/vector/src/main/res/layout/composer_rich_text_layout.xml index 17f1e658ae7..9f49b8f9d63 100644 --- a/vector/src/main/res/layout/composer_rich_text_layout.xml +++ b/vector/src/main/res/layout/composer_rich_text_layout.xml @@ -123,7 +123,8 @@ 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:background="?android:attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/rich_text_editor_full_screen_toggle" /> + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/rich_text_editor_full_screen_toggle" /> + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/rich_text_editor_full_screen_toggle" /> + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/rich_text_editor_full_screen_toggle" /> Date: Tue, 25 Oct 2022 19:06:25 +0200 Subject: [PATCH 3/6] Add back press handler --- .../home/room/detail/TimelineFragment.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 74100b30ff1..3b3737a82b5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -32,6 +32,7 @@ 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 @@ -341,6 +342,7 @@ class TimelineFragment : setupJumpToBottomView() setupRemoveJitsiWidgetView() setupLiveLocationIndicator() + setupBackPressHandling() views.includeRoomToolbar.roomToolbarContentView.debouncedClicks { navigator.openRoomProfile(requireActivity(), timelineArgs.roomId) @@ -431,6 +433,20 @@ class TimelineFragment : .launchIn(viewLifecycleOwner.lifecycleScope) } + private fun setupBackPressHandling() { + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { + withState(messageComposerViewModel) { state -> + if (state.isFullScreen) { + messageComposerViewModel.handle(MessageComposerAction.ToggleFullScreen) + } else { + remove() // Remove callback to avoid infinite loop + @Suppress("DEPRECATION") + requireActivity().onBackPressed() + } + } + } + } + private fun setupRemoveJitsiWidgetView() { views.removeJitsiWidgetView.onCompleteSliding = { withState(timelineViewModel) { From b7dbd8c19900f43d7bbef5c389c3c7c74b6f8d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 26 Oct 2022 11:49:04 +0200 Subject: [PATCH 4/6] Change ToggleFullScreen to SetFullScreen, fix rebase issues --- .../app/features/home/room/detail/TimelineFragment.kt | 4 ++-- .../room/detail/composer/MessageComposerAction.kt | 2 +- .../room/detail/composer/MessageComposerFragment.kt | 11 +++++------ .../home/room/detail/composer/MessageComposerView.kt | 2 +- .../room/detail/composer/MessageComposerViewModel.kt | 6 +++--- .../room/detail/composer/RichTextComposerLayout.kt | 8 +++----- 6 files changed, 15 insertions(+), 18 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 3b3737a82b5..e7f01bec81c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -422,7 +422,7 @@ class TimelineFragment : } views.scrim.setOnClickListener { - messageComposerViewModel.handle(MessageComposerAction.ToggleFullScreen) + messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false)) } messageComposerViewModel.stateFlow.map { it.isFullScreen } @@ -437,7 +437,7 @@ class TimelineFragment : requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { withState(messageComposerViewModel) { state -> if (state.isFullScreen) { - messageComposerViewModel.handle(MessageComposerAction.ToggleFullScreen) + messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false)) } else { remove() // Remove callback to avoid infinite loop @Suppress("DEPRECATION") diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt index ba4e2d4a4c2..30437a016d3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt @@ -34,7 +34,7 @@ sealed class MessageComposerAction : VectorViewModelAction { data class SlashCommandConfirmed(val parsedCommand: ParsedCommand) : MessageComposerAction() data class InsertUserDisplayName(val userId: String) : MessageComposerAction() - object ToggleFullScreen : MessageComposerAction() + data class SetFullScreen(val isFullScreen: Boolean) : MessageComposerAction() // Voice Message data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : MessageComposerAction() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 7491639892a..beb7215c220 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -93,7 +93,6 @@ 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.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -329,11 +328,11 @@ class MessageComposerFragment : VectorBaseFragment(), A composer.emojiButton?.isVisible = isEmojiKeyboardVisible } - override fun onSendMessage(text: CharSequence) { + override fun onSendMessage(text: CharSequence) = withState(messageComposerViewModel) { state -> sendTextMessage(text, composer.formattedText) - if (withState(messageComposerViewModel) { it.isFullScreen }) { - messageComposerViewModel.handle(MessageComposerAction.ToggleFullScreen) + if (state.isFullScreen) { + messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false)) } } @@ -349,8 +348,8 @@ class MessageComposerFragment : VectorBaseFragment(), A messageComposerViewModel.handle(MessageComposerAction.OnTextChanged(text)) } - override fun onFullScreenModeChanged(isFullScreen: Boolean) { - messageComposerViewModel.handle(MessageComposerAction.ToggleFullScreen) + override fun onFullScreenModeChanged() = withState(messageComposerViewModel) { state -> + messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(!state.isFullScreen)) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt index c8f76ea34b6..b7e0e29679d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt @@ -55,5 +55,5 @@ interface Callback : ComposerEditText.Callback { fun onSendMessage(text: CharSequence) fun onAddAttachment() fun onExpandOrCompactChange() - fun onFullScreenModeChanged(isFullScreen: Boolean) + fun onFullScreenModeChanged() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 1836db68a22..23d6e71114a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -123,7 +123,7 @@ class MessageComposerViewModel @AssistedInject constructor( is MessageComposerAction.AudioSeekBarMovedTo -> handleAudioSeekBarMovedTo(action) is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(action) is MessageComposerAction.InsertUserDisplayName -> handleInsertUserDisplayName(action) - MessageComposerAction.ToggleFullScreen -> handleToggleFullScreen() + is MessageComposerAction.SetFullScreen -> handleSetFullScreen(action) } } @@ -164,8 +164,8 @@ class MessageComposerViewModel @AssistedInject constructor( } } - private fun handleToggleFullScreen() { - setState { copy(isFullScreen = !isFullScreen) } + private fun handleSetFullScreen(action: MessageComposerAction.SetFullScreen) { + setState { copy(isFullScreen = action.isFullScreen) } } private fun observePowerLevelAndEncryption() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt index 05058e7acb3..cac8f8bed49 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt @@ -122,7 +122,7 @@ class RichTextComposerLayout @JvmOverloads constructor( } views.composerFullScreenButton.setOnClickListener { - callback?.onFullScreenModeChanged(!isFullScreen) + callback?.onFullScreenModeChanged() } setupRichTextMenu() @@ -207,15 +207,13 @@ class RichTextComposerLayout @JvmOverloads constructor( } override fun toggleFullScreen(newValue: Boolean) { - isFullScreen = newValue - - val constraintSetId = if (isFullScreen) R.layout.composer_rich_text_layout_constraint_set_fullscreen else currentConstraintSetId + 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(isFullScreen) + updateTextFieldBorder(newValue) } private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) { From 826902d5c29b693322ee870a0d8b3b31510138f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 26 Oct 2022 12:50:28 +0200 Subject: [PATCH 5/6] Fix some review issues --- .../room/detail/JumpToBottomViewVisibilityManager.kt | 10 +++++++++- .../app/features/home/room/detail/TimelineFragment.kt | 8 +++++++- vector/src/main/res/layout/fragment_timeline.xml | 1 + .../main/res/layout/fragment_timeline_fullscreen.xml | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/JumpToBottomViewVisibilityManager.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/JumpToBottomViewVisibilityManager.kt index 0f7dc251ae0..1368b71ec63 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/JumpToBottomViewVisibilityManager.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/JumpToBottomViewVisibilityManager.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index e7f01bec81c..4f51922a621 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1047,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) diff --git a/vector/src/main/res/layout/fragment_timeline.xml b/vector/src/main/res/layout/fragment_timeline.xml index 93ba7972451..6271e07d583 100644 --- a/vector/src/main/res/layout/fragment_timeline.xml +++ b/vector/src/main/res/layout/fragment_timeline.xml @@ -177,6 +177,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" diff --git a/vector/src/main/res/layout/fragment_timeline_fullscreen.xml b/vector/src/main/res/layout/fragment_timeline_fullscreen.xml index 63563a78e1e..6fd49c9751f 100644 --- a/vector/src/main/res/layout/fragment_timeline_fullscreen.xml +++ b/vector/src/main/res/layout/fragment_timeline_fullscreen.xml @@ -184,6 +184,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" From b492158baf6f0d2a9cdaed83d5523462901e4cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 26 Oct 2022 13:07:45 +0200 Subject: [PATCH 6/6] Add warning to fragment_timeline* files --- vector/src/main/res/layout/fragment_timeline.xml | 5 +++++ vector/src/main/res/layout/fragment_timeline_fullscreen.xml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/vector/src/main/res/layout/fragment_timeline.xml b/vector/src/main/res/layout/fragment_timeline.xml index 6271e07d583..100cf694e0c 100644 --- a/vector/src/main/res/layout/fragment_timeline.xml +++ b/vector/src/main/res/layout/fragment_timeline.xml @@ -6,6 +6,11 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + +