Add emoji search to CFv2.

This commit is contained in:
Cody Henthorne 2023-06-27 11:03:03 -04:00 committed by Nicholas
parent 2ee2d2883a
commit b6589637fa
8 changed files with 148 additions and 67 deletions

View file

@ -26,6 +26,9 @@ class InputAwareConstraintLayout @JvmOverloads constructor(
private var inputId: Int? = null
private var input: Fragment? = null
val isInputShowing: Boolean
get() = input != null
lateinit var fragmentManager: FragmentManager
var listener: Listener? = null

View file

@ -55,9 +55,11 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor(
private val parentEndGuideline: Guideline? by lazy { findViewById(R.id.parent_end_guideline) }
private val keyboardGuideline: Guideline? by lazy { findViewById(R.id.keyboard_guideline) }
private val listeners: MutableList<KeyboardStateListener> = mutableListOf()
private val keyboardAnimator = KeyboardInsetAnimator()
private val displayMetrics = DisplayMetrics()
private var overridingKeyboard: Boolean = false
private var previousKeyboardHeight: Int = 0
init {
ViewCompat.setOnApplyWindowInsetsListener(this) { _, windowInsetsCompat ->
@ -74,6 +76,14 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor(
}
}
fun addKeyboardStateListener(listener: KeyboardStateListener) {
listeners += listener
}
fun removeKeyboardStateListener(listener: KeyboardStateListener) {
listeners.remove(listener)
}
private fun applyInsets(windowInsets: Insets, keyboardInsets: Insets) {
val isLtr = ViewUtil.isLtr(this)
@ -96,6 +106,18 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor(
keyboardAnimator.endingGuidelineEnd = windowInsets.bottom
}
}
if (previousKeyboardHeight != keyboardInsets.bottom) {
listeners.forEach {
if (previousKeyboardHeight <= 0) {
it.onKeyboardShown()
} else {
it.onKeyboardHidden()
}
}
}
previousKeyboardHeight = keyboardInsets.bottom
}
protected fun overrideKeyboardGuidelineWithPreviousHeight() {
@ -157,6 +179,11 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor(
private val Guideline?.guidelineEnd: Int
get() = if (this == null) 0 else (layoutParams as LayoutParams).guideEnd
interface KeyboardStateListener {
fun onKeyboardShown()
fun onKeyboardHidden()
}
/**
* Adjusts the [keyboardGuideline] to move with the IME keyboard opening or closing.
*/

View file

@ -178,11 +178,13 @@ public final class ConversationReactionOverlay extends FrameLayout {
bottomNavigationBarHeight = 0;
}
toolbarShade.setVisibility(VISIBLE);
toolbarShade.setAlpha(1f);
if (!SignalStore.internalValues().useConversationFragmentV2()) {
toolbarShade.setVisibility(VISIBLE);
toolbarShade.setAlpha(1f);
inputShade.setVisibility(VISIBLE);
inputShade.setAlpha(1f);
inputShade.setVisibility(VISIBLE);
inputShade.setAlpha(1f);
}
Bitmap conversationItemSnapshot = selectedConversationModel.getBitmap();
@ -393,6 +395,13 @@ public final class ConversationReactionOverlay extends FrameLayout {
}
private void updateToolbarShade(@NonNull Activity activity) {
if (SignalStore.internalValues().useConversationFragmentV2()) {
LayoutParams layoutParams = (LayoutParams) toolbarShade.getLayoutParams();
layoutParams.height = 0;
toolbarShade.setLayoutParams(layoutParams);
return;
}
View toolbar = activity.findViewById(R.id.toolbar);
View bannerContainer = activity.findViewById(SignalStore.internalValues().useConversationFragmentV2() ? R.id.conversation_banner
: R.id.conversation_banner_container);
@ -403,6 +412,13 @@ public final class ConversationReactionOverlay extends FrameLayout {
}
private void updateInputShade(@NonNull Activity activity) {
if (SignalStore.internalValues().useConversationFragmentV2()) {
LayoutParams layoutParams = (LayoutParams) inputShade.getLayoutParams();
layoutParams.height = 0;
inputShade.setLayoutParams(layoutParams);
return;
}
LayoutParams layoutParams = (LayoutParams) inputShade.getLayoutParams();
layoutParams.bottomMargin = bottomNavigationBarHeight;
layoutParams.height = getInputPanelHeight(activity);
@ -899,19 +915,21 @@ public final class ConversationReactionOverlay extends FrameLayout {
itemYAnim.setDuration(duration);
animators.add(itemYAnim);
ObjectAnimator toolbarShadeAnim = new ObjectAnimator();
toolbarShadeAnim.setProperty(View.ALPHA);
toolbarShadeAnim.setFloatValues(0f);
toolbarShadeAnim.setTarget(toolbarShade);
toolbarShadeAnim.setDuration(duration);
animators.add(toolbarShadeAnim);
if (!SignalStore.internalValues().useConversationFragmentV2()) {
ObjectAnimator toolbarShadeAnim = new ObjectAnimator();
toolbarShadeAnim.setProperty(View.ALPHA);
toolbarShadeAnim.setFloatValues(0f);
toolbarShadeAnim.setTarget(toolbarShade);
toolbarShadeAnim.setDuration(duration);
animators.add(toolbarShadeAnim);
ObjectAnimator inputShadeAnim = new ObjectAnimator();
inputShadeAnim.setProperty(View.ALPHA);
inputShadeAnim.setFloatValues(0f);
inputShadeAnim.setTarget(inputShade);
inputShadeAnim.setDuration(duration);
animators.add(inputShadeAnim);
ObjectAnimator inputShadeAnim = new ObjectAnimator();
inputShadeAnim.setProperty(View.ALPHA);
inputShadeAnim.setFloatValues(0f);
inputShadeAnim.setTarget(inputShade);
inputShadeAnim.setDuration(duration);
animators.add(inputShadeAnim);
}
if (activity != null) {
ValueAnimator statusBarAnim = ValueAnimator.ofArgb(activity.getWindow().getStatusBarColor(), originalStatusBarColor);

View file

@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.conversation.ConversationAdapterBridge
import org.thoughtcrime.securesms.conversation.ConversationAdapterBridge.PulseRequest
import org.thoughtcrime.securesms.conversation.ConversationItem
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.ThemeUtil
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
@ -395,9 +396,11 @@ class MultiselectItemDecoration(
}
}
canvas.clipPath(path)
canvas.drawShade()
canvas.restore()
if (!SignalStore.internalValues().useConversationFragmentV2()) {
canvas.clipPath(path)
canvas.drawShade()
canvas.restore()
}
}
}
@ -413,9 +416,11 @@ class MultiselectItemDecoration(
}
}
canvas.clipPath(path, Region.Op.DIFFERENCE)
canvas.drawShade()
canvas.restore()
if (!SignalStore.internalValues().useConversationFragmentV2()) {
canvas.clipPath(path, Region.Op.DIFFERENCE)
canvas.drawShade()
canvas.restore()
}
}
}

View file

@ -53,6 +53,7 @@ import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentResultListener
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.commit
import androidx.fragment.app.viewModels
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
@ -102,6 +103,7 @@ import org.thoughtcrime.securesms.components.ConversationSearchBottomBar
import org.thoughtcrime.securesms.components.HidingLinearLayout
import org.thoughtcrime.securesms.components.InputAwareConstraintLayout
import org.thoughtcrime.securesms.components.InputPanel
import org.thoughtcrime.securesms.components.InsetAwareConstraintLayout
import org.thoughtcrime.securesms.components.ProgressCardDialogFragment
import org.thoughtcrime.securesms.components.ProgressCardDialogFragmentArgs
import org.thoughtcrime.securesms.components.ScrollToPositionDelegate
@ -114,7 +116,6 @@ import org.thoughtcrime.securesms.components.mention.MentionAnnotation
import org.thoughtcrime.securesms.components.menu.ActionItem
import org.thoughtcrime.securesms.components.menu.SignalBottomActionBar
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalFragment
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalType
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsActivity
@ -197,6 +198,7 @@ import org.thoughtcrime.securesms.keyboard.KeyboardPage
import org.thoughtcrime.securesms.keyboard.KeyboardPagerFragment
import org.thoughtcrime.securesms.keyboard.KeyboardPagerViewModel
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment
import org.thoughtcrime.securesms.keyboard.gif.GifKeyboardPageFragment
import org.thoughtcrime.securesms.keyboard.sticker.StickerKeyboardPageFragment
import org.thoughtcrime.securesms.keyboard.sticker.StickerSearchDialogFragment
@ -299,12 +301,14 @@ class ConversationFragment :
GifKeyboardPageFragment.Host,
StickerEventListener,
StickerKeyboardPageFragment.Callback,
MediaKeyboard.MediaKeyboardListener {
MediaKeyboard.MediaKeyboardListener,
EmojiSearchFragment.Callback {
companion object {
private val TAG = Log.tag(ConversationFragment::class.java)
private const val ACTION_PINNED_SHORTCUT = "action_pinned_shortcut"
private const val SAVED_STATE_IS_SEARCH_REQUESTED = "is_search_requested"
private const val EMOJI_SEARCH_FRAGMENT_TAG = "EmojiSearchFragment"
}
private val args: ConversationIntents.Args by lazy {
@ -391,6 +395,7 @@ class ConversationFragment :
private var pinnedShortcutReceiver: BroadcastReceiver? = null
private var searchMenuItem: MenuItem? = null
private var isSearchRequested: Boolean = false
private var previousPages: Set<KeyboardPage>? = null
private val jumpAndPulseScrollStrategy = object : ScrollToPositionDelegate.ScrollStrategy {
override fun performScroll(recyclerView: RecyclerView, layoutManager: LinearLayoutManager, position: Int, smooth: Boolean) {
@ -531,7 +536,21 @@ class ConversationFragment :
}
override fun openEmojiSearch() {
// TODO [cfv2] emoji search
val fragment = childFragmentManager.findFragmentByTag(EMOJI_SEARCH_FRAGMENT_TAG)
if (fragment == null) {
childFragmentManager.commit {
add(R.id.emoji_search_container, EmojiSearchFragment(), EMOJI_SEARCH_FRAGMENT_TAG)
}
}
}
override fun closeEmojiSearch() {
val fragment = childFragmentManager.findFragmentByTag(EMOJI_SEARCH_FRAGMENT_TAG)
if (fragment != null) {
childFragmentManager.commit(allowStateLoss = true) {
remove(fragment)
}
}
}
override fun onEmojiSelected(emoji: String?) {
@ -588,6 +607,7 @@ class ConversationFragment :
override fun onHidden() {
inputPanel.mediaKeyboardListener.onHidden()
closeEmojiSearch()
}
override fun onKeyboardChanged(page: KeyboardPage) {
@ -701,6 +721,7 @@ class ConversationFragment :
val keyboardEvents = KeyboardEvents()
container.listener = keyboardEvents
container.addKeyboardStateListener(keyboardEvents)
requireActivity()
.onBackPressedDispatcher
.addCallback(
@ -1106,9 +1127,7 @@ class ConversationFragment :
val callback = GiphyMp4ProjectionRecycler(holders)
GiphyMp4PlaybackController.attach(binding.conversationItemRecycler, callback, maxPlayback)
binding.conversationItemRecycler.addItemDecoration(
GiphyMp4ItemDecoration(callback) { translationY: Float ->
binding.reactionsShade.translationY = translationY + binding.conversationItemRecycler.height
},
GiphyMp4ItemDecoration(callback),
0
)
return callback
@ -1550,13 +1569,6 @@ class ConversationFragment :
reactionDelegate.setOnHideListener(onHideListener)
reactionDelegate.show(requireActivity(), viewModel.recipientSnapshot!!, conversationMessage, conversationGroupViewModel.isNonAdminInAnnouncementGroup(), selectedConversationModel)
composeText.clearFocus()
/*
// TODO [cfv2]
if (attachmentKeyboardStub.resolved()) {
attachmentKeyboardStub.get().hide(true);
}
*/
}
//region Message action handling
@ -2154,8 +2166,7 @@ class ConversationFragment :
val snapshot = ConversationItemSelection.snapshotView(itemView, binding.conversationItemRecycler, messageRecord, videoBitmap)
// TODO [cfv2] -- Should only have a focused view if the keyboard was open.
val focusedView = null // itemView.rootView.findFocus()
val focusedView = if (container.isInputShowing) null else itemView.rootView.findFocus()
val bodyBubble = itemView.bodyBubble!!
val selectedConversationModel = SelectedConversationModel(
snapshot,
@ -2185,7 +2196,6 @@ class ConversationFragment :
}
val conversationItem: ConversationItem = itemView
val isAttachmentKeyboardOpen = false // TODO [cfv2] -- isAttachmentKeyboardOpen
handleReaction(
item.conversationMessage,
ReactionsToolbarListener(item.conversationMessage),
@ -2224,10 +2234,6 @@ class ConversationFragment :
if (showScrollButtons) {
viewModel.setShowScrollButtons(true)
}
if (isAttachmentKeyboardOpen) {
// listener.openAttachmentKeyboard();
}
}
}
)
@ -2270,7 +2276,7 @@ class ConversationFragment :
val recipient: Recipient? = viewModel.recipientSnapshot
return ConversationOptionsMenu.Snapshot(
recipient = recipient,
isPushAvailable = true, // TODO [cfv2]
isPushAvailable = recipient?.isRegistered == true && Recipient.self().isRegistered,
canShowAsBubble = Observable.empty(),
isActiveGroup = recipient?.isActiveGroup == true,
isActiveV2Group = recipient?.let { it.isActiveGroup && it.isPushV2Group } == true,
@ -2572,7 +2578,7 @@ class ConversationFragment :
inner class ActionModeCallback : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.title = calculateSelectedItemCount()
// TODO [cfv2] listener.onMessageActionToolbarOpened();
// TODO [cfv2] scheduled message - listener.onMessageActionToolbarOpened();
setCorrectActionModeMenuVisibility()
return true
}
@ -2584,7 +2590,7 @@ class ConversationFragment :
override fun onDestroyActionMode(mode: ActionMode) {
adapter.clearSelection()
setBottomActionBarVisibility(false)
// TODO [cfv2] listener.onMessageActionToolbarClosed();
// TODO [cfv2] scheduled message - listener.onMessageActionToolbarClosed();
binding.conversationItemRecycler.invalidateItemDecorations()
actionMode = null
}
@ -2647,11 +2653,6 @@ class ConversationFragment :
}
private fun sendPreUploadMediaMessage(result: MediaSendActivityResult) {
if (ExpiredBuildReminder.isEligible()) {
/* TODO [cfv2] -- Show expired dialog */
return
}
if (SignalStore.uiHints().hasNotSeenTextFormattingAlert() && result.bodyRanges != null && result.bodyRanges.rangesCount > 0) {
Dialogs.showFormattedTextDialog(requireContext()) { sendPreUploadMediaMessage(result) }
return
@ -3073,13 +3074,18 @@ class ConversationFragment :
override fun onEnterEditMode() {
updateToggleButtonState()
// TODO [cfv2] -- Save keyboard pager state and force emoji
previousPages = keyboardPagerViewModel.pages().value
keyboardPagerViewModel.setOnlyPage(KeyboardPage.EMOJI)
onKeyboardChanged(KeyboardPage.EMOJI)
}
override fun onExitEditMode() {
updateToggleButtonState()
draftViewModel.deleteMessageEditDraft()
// TODO [cfv2] -- Restore keyboard pager pages
if (previousPages != null) {
keyboardPagerViewModel.setPages(previousPages!!)
previousPages = null
}
}
}
@ -3131,7 +3137,7 @@ class ConversationFragment :
override fun create(): Fragment = KeyboardPagerFragment()
}
private inner class KeyboardEvents : OnBackPressedCallback(false), InputAwareConstraintLayout.Listener {
private inner class KeyboardEvents : OnBackPressedCallback(false), InputAwareConstraintLayout.Listener, InsetAwareConstraintLayout.KeyboardStateListener {
override fun handleOnBackPressed() {
container.hideInput()
}
@ -3143,6 +3149,12 @@ class ConversationFragment :
override fun onInputHidden() {
isEnabled = false
}
override fun onKeyboardShown() = Unit
override fun onKeyboardHidden() {
closeEmojiSearch()
}
}
//endregion

View file

@ -12,8 +12,8 @@ import kotlin.math.min
* Decoration that will make the video display params update on each recycler redraw.
*/
class GiphyMp4ItemDecoration(
val callback: GiphyMp4PlaybackController.Callback,
val onRecyclerVerticalTranslationSet: (Float) -> Unit
private val callback: GiphyMp4PlaybackController.Callback,
private val onRecyclerVerticalTranslationSet: ((Float) -> Unit)? = null
) : RecyclerView.ItemDecoration() {
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
setParentRecyclerTranslationY(parent)
@ -26,7 +26,7 @@ class GiphyMp4ItemDecoration(
private fun setParentRecyclerTranslationY(parent: RecyclerView) {
if (parent.childCount == 0 || parent.canScrollVertically(-1) || parent.canScrollVertically(1)) {
parent.translationY = 0f
onRecyclerVerticalTranslationSet(parent.translationY)
onRecyclerVerticalTranslationSet?.invoke(parent.translationY)
} else {
val threadHeaderViewHolder = parent.children
.map { parent.getChildViewHolder(it) }
@ -35,7 +35,7 @@ class GiphyMp4ItemDecoration(
if (threadHeaderViewHolder == null) {
parent.translationY = 0f
onRecyclerVerticalTranslationSet(parent.translationY)
onRecyclerVerticalTranslationSet?.invoke(parent.translationY)
return
}
@ -51,7 +51,7 @@ class GiphyMp4ItemDecoration(
val childTop: Int = threadHeaderViewHolder.itemView.top - toolbarMargin
parent.translationY = min(0, -childTop).toFloat()
onRecyclerVerticalTranslationSet(parent.translationY)
onRecyclerVerticalTranslationSet?.invoke(parent.translationY)
}
}
}

View file

@ -84,6 +84,7 @@ class EmojiSearchFragment : Fragment(), EmojiPageViewGridAdapter.VariationSelect
private inner class SearchCallbacks : KeyboardPageSearchView.Callbacks {
override fun onNavigationClicked() {
ViewUtil.hideKeyboard(requireContext(), requireView())
callback.closeEmojiSearch()
}
override fun onQueryChanged(query: String) {

View file

@ -51,14 +51,6 @@
tools:itemCount="20"
tools:listitem="@layout/conversation_item_sent_text_only" />
<FrameLayout
android:id="@+id/reactions_shade"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/reactions_screen_light_shade_color"
android:foreground="@color/reactions_screen_dark_shade_color"
android:visibility="gone" />
<org.thoughtcrime.securesms.util.views.DarkOverflowToolbar
android:id="@+id/toolbar"
android:layout_width="0dp"
@ -208,6 +200,13 @@
app:layout_constraintEnd_toEndOf="@id/parent_end_guideline"
app:layout_constraintStart_toStartOf="@id/parent_start_guideline" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/conversation_input_panel_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top"
app:constraint_referenced_ids="emoji_search_container,keyboard_guideline" />
<TextView
android:id="@+id/conversation_input_space_left"
android:layout_width="0dp"
@ -216,7 +215,7 @@
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/keyboard_guideline"
app:layout_constraintBottom_toBottomOf="@id/conversation_input_panel_barrier"
app:layout_constraintEnd_toEndOf="@id/parent_end_guideline"
app:layout_constraintStart_toStartOf="@id/parent_start_guideline"
tools:text="160/160 (1)"
@ -231,6 +230,22 @@
app:layout_constraintStart_toStartOf="@id/parent_start_guideline"
app:layout_constraintTop_toTopOf="@id/keyboard_guideline" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/emoji_search_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/keyboard_guideline"
app:layout_constraintEnd_toEndOf="@id/parent_end_guideline"
app:layout_constraintStart_toStartOf="@id/parent_start_guideline" />
<FrameLayout
android:id="@+id/reactions_shade"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/reactions_screen_light_shade_color"
android:foreground="@color/reactions_screen_dark_shade_color"
android:visibility="gone" />
<org.thoughtcrime.securesms.components.menu.SignalBottomActionBar
android:id="@+id/conversation_bottom_action_bar"
android:layout_width="match_parent"