diff --git a/app/src/main/java/com/google/android/material/bottomsheet/BottomSheetBehaviorHack.kt b/app/src/main/java/com/google/android/material/bottomsheet/BottomSheetBehaviorHack.kt new file mode 100644 index 000000000..a9b644e87 --- /dev/null +++ b/app/src/main/java/com/google/android/material/bottomsheet/BottomSheetBehaviorHack.kt @@ -0,0 +1,14 @@ +package com.google.android.material.bottomsheet + +import android.view.View +import android.widget.FrameLayout +import java.lang.ref.WeakReference + +/** + * Manually adjust the nested scrolling child for a given [BottomSheetBehavior]. + */ +object BottomSheetBehaviorHack { + fun setNestedScrollingChild(behavior: BottomSheetBehavior, view: View) { + behavior.nestedScrollingChildRef = WeakReference(view) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java index 800a248aa..3babe8658 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java @@ -146,6 +146,10 @@ public class MediaKeyboard extends FrameLayout implements InputView { .commitAllowingStateLoss(); } + public boolean isEmojiSearchMode() { + return keyboardState == State.EMOJI_SEARCH; + } + private void initView() { if (!isInitialised) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/composer/StoryReplyComposer.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/composer/StoryReplyComposer.kt index d30a41d73..eb7bdc5e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/composer/StoryReplyComposer.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/composer/StoryReplyComposer.kt @@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.ComposeText import org.thoughtcrime.securesms.components.InputAwareLayout import org.thoughtcrime.securesms.components.QuoteView +import org.thoughtcrime.securesms.components.emoji.EmojiPageView import org.thoughtcrime.securesms.components.emoji.EmojiToggle import org.thoughtcrime.securesms.components.emoji.MediaKeyboard import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord @@ -42,6 +43,9 @@ class StoryReplyComposer @JvmOverloads constructor( var callback: Callback? = null + val emojiPageView: EmojiPageView? + get() = findViewById(R.id.emoji_page_view) + init { inflate(context, R.layout.stories_reply_to_story_composer, this) @@ -85,6 +89,12 @@ class StoryReplyComposer @JvmOverloads constructor( emojiDrawerToggle.setOnClickListener { onEmojiToggleClicked() } + + inputAwareLayout.addOnKeyboardShownListener { + if (inputAwareLayout.currentInput == emojiDrawer && !emojiDrawer.isEmojiSearchMode) { + onEmojiToggleClicked() + } + } } fun setQuote(messageRecord: MediaMmsMessageRecord) { @@ -136,11 +146,13 @@ class StoryReplyComposer @JvmOverloads constructor( if (inputAwareLayout.currentInput == emojiDrawer) { isRequestingEmojiDrawer = false inputAwareLayout.showSoftkey(input) + callback?.onHideEmojiKeyboard() } else { isRequestingEmojiDrawer = true inputAwareLayout.hideSoftkey(input) { inputAwareLayout.post { inputAwareLayout.show(input, emojiDrawer) + emojiDrawer.post { callback?.onShowEmojiKeyboard() } } } } @@ -150,6 +162,8 @@ class StoryReplyComposer @JvmOverloads constructor( fun onSendActionClicked() fun onPickReactionClicked() fun onInitializeEmojiDrawer(mediaKeyboard: MediaKeyboard) + fun onShowEmojiKeyboard() = Unit + fun onHideEmojiKeyboard() = Unit } companion object { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyBottomSheetDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyBottomSheetDialogFragment.kt index 5f84a65df..793fe132b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyBottomSheetDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyBottomSheetDialogFragment.kt @@ -37,6 +37,8 @@ class StoryGroupReplyBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDi override val peekHeightPercentage: Float = 1f private val lifecycleDisposable = LifecycleDisposable() + private var shouldShowFullScreen = false + private var initialParentHeight = 0 private val storyViewerPageViewModel: StoryViewerPageViewModel by viewModels( ownerProducer = { requireParentFragment() } @@ -70,7 +72,16 @@ class StoryGroupReplyBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDi view.viewTreeObserver.addOnGlobalLayoutListener { val parentHeight = requireCoordinatorLayout().height val desiredHeight = (resources.displayMetrics.heightPixels * 0.6f).roundToInt() - val targetHeight = if (parentHeight != 0) min(parentHeight, desiredHeight) else desiredHeight + + if (initialParentHeight == 0) { + initialParentHeight = parentHeight + } + + val targetHeight = when { + parentHeight == 0 -> desiredHeight + shouldShowFullScreen || parentHeight != initialParentHeight -> parentHeight + else -> min(parentHeight, desiredHeight) + } if (view.height != targetHeight) { view.updateLayoutParams { @@ -90,6 +101,11 @@ class StoryGroupReplyBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDi storyViewerPageViewModel.startDirectReply(storyId, recipientId) } + override fun requestFullScreen(fullscreen: Boolean) { + shouldShowFullScreen = fullscreen + requireView().invalidate() + } + companion object { private const val ARG_STORY_ID = "arg.story.id" private const val ARG_GROUP_RECIPIENT_ID = "arg.group.recipient.id" diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt index 5e25ae0dc..72b716ee4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt @@ -2,19 +2,22 @@ package org.thoughtcrime.securesms.stories.viewer.reply.group import android.content.ClipData import android.os.Bundle -import android.provider.Settings.System.getConfiguration import android.view.KeyEvent +import android.view.MotionEvent import android.view.View import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.bottomsheet.BottomSheetBehaviorHack +import com.google.android.material.bottomsheet.BottomSheetDialog import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.kotlin.subscribeBy import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment import org.thoughtcrime.securesms.components.emoji.MediaKeyboard import org.thoughtcrime.securesms.components.mention.MentionAnnotation import org.thoughtcrime.securesms.components.settings.DSLConfiguration @@ -72,6 +75,18 @@ class StoryGroupReplyFragment : ownerProducer = { requireActivity() } ) + private val recyclerListener: RecyclerView.OnItemTouchListener = object : RecyclerView.SimpleOnItemTouchListener() { + override fun onInterceptTouchEvent(view: RecyclerView, e: MotionEvent): Boolean { + recyclerView.isNestedScrollingEnabled = view == recyclerView + composer.emojiPageView?.isNestedScrollingEnabled = view == composer.emojiPageView + + val dialog = (parentFragment as FixedRoundedCornerBottomSheetDialogFragment).dialog as BottomSheetDialog + BottomSheetBehaviorHack.setNestedScrollingChild(dialog.behavior, view) + dialog.findViewById(R.id.design_bottom_sheet)?.invalidate() + return false + } + } + private val colorizer = Colorizer() private val lifecycleDisposable = LifecycleDisposable() @@ -245,6 +260,18 @@ class StoryGroupReplyFragment : mediaKeyboard.setFragmentManager(childFragmentManager) } + override fun onShowEmojiKeyboard() { + requireListener().requestFullScreen(true) + recyclerView.addOnItemTouchListener(recyclerListener) + composer.emojiPageView?.addOnItemTouchListener(recyclerListener) + } + + override fun onHideEmojiKeyboard() { + recyclerView.removeOnItemTouchListener(recyclerListener) + composer.emojiPageView?.removeOnItemTouchListener(recyclerListener) + requireListener().requestFullScreen(false) + } + override fun openEmojiSearch() { composer.openEmojiSearch() } @@ -359,5 +386,6 @@ class StoryGroupReplyFragment : interface Callback { fun onStartDirectReply(recipientId: RecipientId) + fun requestFullScreen(fullscreen: Boolean) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesDialogFragment.kt index 6a0f98c88..cc09411f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesDialogFragment.kt @@ -48,6 +48,9 @@ class StoryViewsAndRepliesDialogFragment : FixedRoundedCornerBottomSheetDialogFr private lateinit var pager: ViewPager2 + private var shouldShowFullScreen = false + private var initialParentHeight = 0 + private val storyViewerPageViewModel: StoryViewerPageViewModel by viewModels( ownerProducer = { requireParentFragment() } ) @@ -96,7 +99,16 @@ class StoryViewsAndRepliesDialogFragment : FixedRoundedCornerBottomSheetDialogFr view.viewTreeObserver.addOnGlobalLayoutListener { val parentHeight = requireCoordinatorLayout().height val desiredHeight = (resources.displayMetrics.heightPixels * 0.6f).roundToInt() - val targetHeight = if (parentHeight != 0) min(parentHeight, desiredHeight) else desiredHeight + + if (initialParentHeight == 0) { + initialParentHeight = parentHeight + } + + val targetHeight = when { + parentHeight == 0 -> desiredHeight + shouldShowFullScreen || parentHeight != initialParentHeight -> parentHeight + else -> min(parentHeight, desiredHeight) + } if (view.height != targetHeight) { view.updateLayoutParams { @@ -126,6 +138,11 @@ class StoryViewsAndRepliesDialogFragment : FixedRoundedCornerBottomSheetDialogFr storyViewerPageViewModel.startDirectReply(storyId, recipientId) } + override fun requestFullScreen(fullscreen: Boolean) { + shouldShowFullScreen = fullscreen + requireView().invalidate() + } + private inner class PageChangeCallback : ViewPager2.OnPageChangeCallback() { override fun onPageScrollStateChanged(state: Int) { if (state == ViewPager2.SCROLL_STATE_IDLE) {