diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/InputAwareConstraintLayout.kt b/app/src/main/java/org/thoughtcrime/securesms/components/InputAwareConstraintLayout.kt index 52a365e5f2..7adc9fef5c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/InputAwareConstraintLayout.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/InputAwareConstraintLayout.kt @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.kt b/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.kt index 846cc565ca..217b7d1c37 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.kt @@ -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 = 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. */ diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java index cc28f52d3f..1ed865f15b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java @@ -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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration.kt index 748bef2950..52e3fd1287 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration.kt @@ -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() + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index 539e45cd91..67ff61494f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -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? = 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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ItemDecoration.kt b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ItemDecoration.kt index 5f78d1e2a2..bd4f901856 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ItemDecoration.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ItemDecoration.kt @@ -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) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/search/EmojiSearchFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/search/EmojiSearchFragment.kt index 3f0734669f..25a69a0f13 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/search/EmojiSearchFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/search/EmojiSearchFragment.kt @@ -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) { diff --git a/app/src/main/res/layout/v2_conversation_fragment.xml b/app/src/main/res/layout/v2_conversation_fragment.xml index 56ea30ac2f..8199744950 100644 --- a/app/src/main/res/layout/v2_conversation_fragment.xml +++ b/app/src/main/res/layout/v2_conversation_fragment.xml @@ -51,14 +51,6 @@ tools:itemCount="20" tools:listitem="@layout/conversation_item_sent_text_only" /> - - + + + + + +