Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/7436.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Rich text editor: add full screen mode.
1 change: 1 addition & 0 deletions library/ui-strings/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3423,5 +3423,6 @@
<string name="rich_text_editor_format_italic">Apply italic format</string>
<string name="rich_text_editor_format_strikethrough">Apply strikethrough format</string>
<string name="rich_text_editor_format_underline">Apply underline format</string>
<string name="rich_text_editor_full_screen_toggle">Toggle full screen mode</string>

</resources>
3 changes: 2 additions & 1 deletion vector/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@

<activity
android:name=".features.home.room.detail.RoomDetailActivity"
android:parentActivityName=".features.home.HomeActivity">
android:parentActivityName=".features.home.HomeActivity"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".features.home.HomeActivity" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -43,7 +45,7 @@ class JumpToBottomViewVisibilityManager(

if (scrollingToPast) {
jumpToBottomView.hide()
} else {
} else if (canShowButtonOnScroll) {
maybeShowJumpToBottomViewVisibility()
}
}
Expand All @@ -66,7 +68,13 @@ class JumpToBottomViewVisibilityManager(
}
}

fun hideAndPreventVisibilityChangesWithScrolling() {
jumpToBottomView.hide()
canShowButtonOnScroll = false
}

private fun maybeShowJumpToBottomViewVisibility() {
canShowButtonOnScroll = true
if (layoutManager.findFirstVisibleItemPosition() > 1) {
jumpToBottomView.show()
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.addCallback
import androidx.appcompat.view.menu.MenuBuilder
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.net.toUri
Expand Down Expand Up @@ -64,6 +66,7 @@ import im.vector.app.core.dialogs.ConfirmationDialogBuilder
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
import im.vector.app.core.extensions.animateLayoutChange
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.containsRtLOverride
Expand Down Expand Up @@ -183,7 +186,9 @@ import im.vector.app.features.widgets.WidgetArgs
import im.vector.app.features.widgets.WidgetKind
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -337,6 +342,7 @@ class TimelineFragment :
setupJumpToBottomView()
setupRemoveJitsiWidgetView()
setupLiveLocationIndicator()
setupBackPressHandling()

views.includeRoomToolbar.roomToolbarContentView.debouncedClicks {
navigator.openRoomProfile(requireActivity(), timelineArgs.roomId)
Expand Down Expand Up @@ -414,6 +420,31 @@ class TimelineFragment :
if (savedInstanceState == null) {
handleSpaceShare()
}

views.scrim.setOnClickListener {
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false))
}

messageComposerViewModel.stateFlow.map { it.isFullScreen }
.distinctUntilChanged()
.onEach { isFullScreen ->
toggleFullScreenEditor(isFullScreen)
}
.launchIn(viewLifecycleOwner.lifecycleScope)
}

private fun setupBackPressHandling() {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
withState(messageComposerViewModel) { state ->
if (state.isFullScreen) {
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false))
} else {
remove() // Remove callback to avoid infinite loop
@Suppress("DEPRECATION")
requireActivity().onBackPressed()
}
}
}
}

private fun setupRemoveJitsiWidgetView() {
Expand Down Expand Up @@ -1016,7 +1047,13 @@ class TimelineFragment :
override fun onLayoutCompleted(state: RecyclerView.State) {
super.onLayoutCompleted(state)
updateJumpToReadMarkerViewVisibility()
jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay()
withState(messageComposerViewModel) { composerState ->
if (!composerState.isFullScreen) {
jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay()
} else {
jumpToBottomViewVisibilityManager.hideAndPreventVisibilityChangesWithScrolling()
}
}
}
}.apply {
// For local rooms, pin the view's content to the top edge (the layout is reversed)
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ sealed class MessageComposerAction : VectorViewModelAction {
data class SlashCommandConfirmed(val parsedCommand: ParsedCommand) : MessageComposerAction()
data class InsertUserDisplayName(val userId: String) : MessageComposerAction()

data class SetFullScreen(val isFullScreen: Boolean) : MessageComposerAction()

// Voice Message
data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : MessageComposerAction()
data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : MessageComposerAction()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -219,6 +220,13 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
}
}

messageComposerViewModel.stateFlow.map { it.isFullScreen }
.distinctUntilChanged()
.onEach { isFullScreen ->
composer.toggleFullScreen(isFullScreen)
}
.launchIn(viewLifecycleOwner.lifecycleScope)

if (savedInstanceState != null) {
handleShareData()
}
Expand Down Expand Up @@ -297,7 +305,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
// Show keyboard when the user started a thread
composerEditText.showKeyboard(andRequestFocus = true)
}
composer.callback = object : PlainTextComposerLayout.Callback {
composer.callback = object : Callback {
override fun onAddAttachment() {
if (!::attachmentTypeSelector.isInitialized) {
attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment)
Expand All @@ -320,8 +328,12 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
composer.emojiButton?.isVisible = isEmojiKeyboardVisible
}

override fun onSendMessage(text: CharSequence) {
override fun onSendMessage(text: CharSequence) = withState(messageComposerViewModel) { state ->
sendTextMessage(text, composer.formattedText)

if (state.isFullScreen) {
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false))
}
}

override fun onCloseRelatedMessage() {
Expand All @@ -335,6 +347,10 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
override fun onTextChanged(text: CharSequence) {
messageComposerViewModel.handle(MessageComposerAction.OnTextChanged(text))
}

override fun onFullScreenModeChanged() = withState(messageComposerViewModel) { state ->
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(!state.isFullScreen))
}
}
}

Expand Down Expand Up @@ -461,7 +477,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
composer.sendButton.alpha = 0f
composer.sendButton.isVisible = true
composer.sendButton.animate().alpha(1f).setDuration(150).start()
} else {
} else if (!event.isVisible) {
composer.sendButton.isInvisible = true
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,30 @@ 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

fun collapse(animate: Boolean = true, transitionComplete: (() -> Unit)? = null)
fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null)
fun setTextIfDifferent(text: CharSequence?): Boolean
fun replaceFormattedContent(text: CharSequence)
fun toggleFullScreen(newValue: Boolean)

fun setInvisible(isInvisible: Boolean)
}

interface Callback : ComposerEditText.Callback {
fun onCloseRelatedMessage()
fun onSendMessage(text: CharSequence)
fun onAddAttachment()
fun onExpandOrCompactChange()
fun onFullScreenModeChanged()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -122,6 +123,7 @@ class MessageComposerViewModel @AssistedInject constructor(
is MessageComposerAction.AudioSeekBarMovedTo -> handleAudioSeekBarMovedTo(action)
is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(action)
is MessageComposerAction.InsertUserDisplayName -> handleInsertUserDisplayName(action)
is MessageComposerAction.SetFullScreen -> handleSetFullScreen(action)
}
}

Expand All @@ -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() {
Expand Down Expand Up @@ -163,6 +164,10 @@ class MessageComposerViewModel @AssistedInject constructor(
}
}

private fun handleSetFullScreen(action: MessageComposerAction.SetFullScreen) {
setState { copy(isFullScreen = action.isFullScreen) }
}

private fun observePowerLevelAndEncryption() {
combine(
PowerLevelsFlowFactory(room).createFlow(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Loading