Add new story reaction bar.

main
Clark 2023-01-30 16:14:16 -05:00 zatwierdzone przez Greyson Parrelli
rodzic 4677f207e7
commit ef9cd2515e
6 zmienionych plików z 201 dodań i 171 usunięć

Wyświetl plik

@ -48,7 +48,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends FixedRound
EmojiPageViewGridAdapter.VariationSelectorListener EmojiPageViewGridAdapter.VariationSelectorListener
{ {
private static final String REACTION_STORAGE_KEY = "reactions_recent_emoji"; public static final String REACTION_STORAGE_KEY = "reactions_recent_emoji";
private static final String ABOUT_STORAGE_KEY = TextSecurePreferences.RECENT_STORAGE_KEY; private static final String ABOUT_STORAGE_KEY = TextSecurePreferences.RECENT_STORAGE_KEY;
private static final String ARG_MESSAGE_ID = "arg_message_id"; private static final String ARG_MESSAGE_ID = "arg_message_id";

Wyświetl plik

@ -1,27 +1,40 @@
package org.thoughtcrime.securesms.stories.viewer.reply.composer package org.thoughtcrime.securesms.stories.viewer.reply.composer
import android.content.Context import android.content.Context
import android.graphics.Rect
import android.net.Uri
import android.util.AttributeSet import android.util.AttributeSet
import android.view.KeyEvent
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.animation.OvershootInterpolator
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.TextView import androidx.core.view.marginEnd
import android.widget.ViewSwitcher
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import org.signal.core.util.dp
import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ComposeText import org.thoughtcrime.securesms.components.ComposeText
import org.thoughtcrime.securesms.components.InputAwareLayout import org.thoughtcrime.securesms.components.InputAwareLayout
import org.thoughtcrime.securesms.components.QuoteView import org.thoughtcrime.securesms.components.emoji.Emoji
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel
import org.thoughtcrime.securesms.components.emoji.EmojiPageView import org.thoughtcrime.securesms.components.emoji.EmojiPageView
import org.thoughtcrime.securesms.components.emoji.EmojiToggle import org.thoughtcrime.securesms.components.emoji.EmojiToggle
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard import org.thoughtcrime.securesms.components.emoji.MediaKeyboard
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel
import org.thoughtcrime.securesms.database.model.Mention import org.thoughtcrime.securesms.database.model.Mention
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.emoji.EmojiSource
import org.thoughtcrime.securesms.mms.QuoteModel import org.thoughtcrime.securesms.keyboard.emoji.toMappingModels
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.visible import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
class StoryReplyComposer @JvmOverloads constructor( class StoryReplyComposer @JvmOverloads constructor(
context: Context, context: Context,
@ -30,13 +43,15 @@ class StoryReplyComposer @JvmOverloads constructor(
) : FrameLayout(context, attrs, defStyleAttr) { ) : FrameLayout(context, attrs, defStyleAttr) {
private val inputAwareLayout: InputAwareLayout private val inputAwareLayout: InputAwareLayout
private val quoteView: QuoteView
private val privacyChrome: TextView
private val emojiDrawerToggle: EmojiToggle private val emojiDrawerToggle: EmojiToggle
private val emojiDrawer: MediaKeyboard private val emojiDrawer: MediaKeyboard
private val reactionEmojiView: EmojiPageView
private val anyReactionView: View
private val emojiBar: View
private val bubbleView: ViewGroup
val reactionButton: View
val input: ComposeText val input: ComposeText
val decoration: SpacingDecoration
var isRequestingEmojiDrawer: Boolean = false var isRequestingEmojiDrawer: Boolean = false
private set private set
@ -51,19 +66,18 @@ class StoryReplyComposer @JvmOverloads constructor(
inputAwareLayout = findViewById(R.id.input_aware_layout) inputAwareLayout = findViewById(R.id.input_aware_layout)
emojiDrawerToggle = findViewById(R.id.emoji_toggle) emojiDrawerToggle = findViewById(R.id.emoji_toggle)
quoteView = findViewById(R.id.quote_view)
input = findViewById(R.id.compose_text) input = findViewById(R.id.compose_text)
reactionButton = findViewById(R.id.reaction)
privacyChrome = findViewById(R.id.private_reply_recipient)
emojiDrawer = findViewById(R.id.emoji_drawer) emojiDrawer = findViewById(R.id.emoji_drawer)
anyReactionView = findViewById(R.id.any_reaction)
reactionEmojiView = findViewById(R.id.reaction_emoji_view)
emojiBar = findViewById(R.id.emoji_bar)
bubbleView = findViewById(R.id.bubble)
val viewSwitcher: ViewSwitcher = findViewById(R.id.reply_reaction_switch)
val reply: View = findViewById(R.id.reply) val reply: View = findViewById(R.id.reply)
reply.setOnClickListener { reply.setOnClickListener {
callback?.onSendActionClicked() callback?.onSendActionClicked()
} }
input.setOnEditorActionListener { _, actionId, _ -> input.setOnEditorActionListener { _, actionId, _ ->
when (actionId) { when (actionId) {
EditorInfo.IME_ACTION_SEND -> { EditorInfo.IME_ACTION_SEND -> {
@ -74,16 +88,21 @@ class StoryReplyComposer @JvmOverloads constructor(
} }
} }
input.doAfterTextChanged { anyReactionView.setOnClickListener {
if (it.isNullOrEmpty()) { callback?.onPickAnyReactionClicked()
viewSwitcher.displayedChild = 0
} else {
viewSwitcher.displayedChild = 1
}
} }
reactionButton.setOnClickListener { input.doAfterTextChanged {
callback?.onPickReactionClicked() val notEmpty = !it.isNullOrEmpty()
reply.isEnabled = notEmpty
if (notEmpty && reply.visibility != View.VISIBLE) {
val transition = AutoTransition().setDuration(200L).setInterpolator(OvershootInterpolator(1f))
TransitionManager.beginDelayedTransition(bubbleView, transition)
reply.visibility = View.VISIBLE
reply.scaleX = 0f
reply.scaleY = 0f
reply.animate().setDuration(150).scaleX(1f).scaleY(1f).setInterpolator(OvershootInterpolator(1f)).start()
}
} }
emojiDrawerToggle.setOnClickListener { emojiDrawerToggle.setOnClickListener {
@ -95,6 +114,29 @@ class StoryReplyComposer @JvmOverloads constructor(
onEmojiToggleClicked() onEmojiToggleClicked()
} }
} }
val emojiEventListener: EmojiEventListener = object : EmojiEventListener {
override fun onEmojiSelected(emoji: String?) {
if (emoji != null) {
callback?.onReactionClicked(emoji)
}
}
override fun onKeyEvent(keyEvent: KeyEvent?) = Unit
}
reactionEmojiView.initialize(
emojiEventListener,
{ },
false,
LinearLayoutManager(context, RecyclerView.HORIZONTAL, false),
R.layout.emoji_display_item_list,
R.layout.emoji_text_display_item_list
)
decoration = SpacingDecoration()
reactionEmojiView.addItemDecoration(decoration)
reactionEmojiView.setList(getReactionEmojis()) {
updateEmojiSpacing()
}
} }
var hint: CharSequence var hint: CharSequence
@ -105,24 +147,8 @@ class StoryReplyComposer @JvmOverloads constructor(
input.hint = value input.hint = value
} }
fun setQuote(messageRecord: MediaMmsMessageRecord) { fun displayReplyHint(recipient: Recipient) {
quoteView.setQuote( input.hint = (context.getString(R.string.StoryReplyComposer__reply_to_s, recipient.getDisplayName(context)))
GlideApp.with(this),
messageRecord.dateSent,
messageRecord.recipient,
messageRecord.body,
false,
messageRecord.slideDeck,
null,
QuoteModel.Type.NORMAL
)
quoteView.visible = true
}
fun displayPrivacyChrome(recipient: Recipient) {
privacyChrome.text = context.getString(R.string.StoryReplyComposer__replying_privately_to_s, recipient.getDisplayName(context))
privacyChrome.visible = true
} }
fun consumeInput(): Input { fun consumeInput(): Input {
@ -151,6 +177,17 @@ class StoryReplyComposer @JvmOverloads constructor(
inputAwareLayout.hideCurrentInput(input) inputAwareLayout.hideCurrentInput(input)
} }
private fun getReactionEmojis(): List<MappingModel<*>> {
val reactionEmoji = SignalStore.emojiValues().reactions
val recentEmoji = RecentEmojiPageModel(context, ReactWithAnyEmojiBottomSheetDialogFragment.REACTION_STORAGE_KEY).emoji
val emoji = (reactionEmoji + recentEmoji).distinct()
val displayEmoji: List<Emoji> = emoji
.mapNotNull { canonical -> EmojiSource.latest.canonicalToVariations[canonical] }
.map { Emoji(it) }
return EmojiReactionsPageModel(emoji, displayEmoji).toMappingModels()
}
private fun onEmojiToggleClicked() { private fun onEmojiToggleClicked() {
if (!emojiDrawer.isInitialised) { if (!emojiDrawer.isInitialised) {
callback?.onInitializeEmojiDrawer(emojiDrawer) callback?.onInitializeEmojiDrawer(emojiDrawer)
@ -168,13 +205,60 @@ class StoryReplyComposer @JvmOverloads constructor(
} }
} }
private fun updateEmojiSpacing() {
val emojiItemWidth = 44.dp
val availableWidth = reactionEmojiView.width - anyReactionView.marginEnd
val maxNumItems = availableWidth / emojiItemWidth
val numItems = reactionEmojiView.adapter?.itemCount ?: 0
decoration.firstItemOffset = anyReactionView.marginEnd
if (numItems > maxNumItems) {
decoration.horizontalSpacing = 0
reactionEmojiView.invalidateItemDecorations()
} else {
decoration.horizontalSpacing = (availableWidth - (numItems * emojiItemWidth)) / numItems
reactionEmojiView.invalidateItemDecorations()
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
updateEmojiSpacing()
}
interface Callback { interface Callback {
fun onSendActionClicked() fun onSendActionClicked()
fun onPickReactionClicked() fun onPickAnyReactionClicked()
fun onReactionClicked(emoji: String)
fun onInitializeEmojiDrawer(mediaKeyboard: MediaKeyboard) fun onInitializeEmojiDrawer(mediaKeyboard: MediaKeyboard)
fun onShowEmojiKeyboard() = Unit fun onShowEmojiKeyboard() = Unit
fun onHideEmojiKeyboard() = Unit fun onHideEmojiKeyboard() = Unit
} }
class SpacingDecoration : RecyclerView.ItemDecoration() {
var horizontalSpacing: Int = 0
var firstItemOffset: Int = 0
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
outRect.right = horizontalSpacing
if (parent.getChildAdapterPosition(view) == 0) {
outRect.left = firstItemOffset
} else {
outRect.left = 0
}
}
}
private class EmojiReactionsPageModel(private val emoji: List<String>, private val displayEmoji: List<Emoji>) : EmojiPageModel {
override fun getKey(): String = ""
override fun getIconAttr(): Int = -1
override fun getEmoji(): List<String> = emoji
override fun getDisplayEmoji(): List<Emoji> = displayEmoji
override fun getSpriteUri(): Uri? = null
override fun isDynamic(): Boolean = false
}
data class Input(val body: String, val mentions: List<Mention>, val bodyRanges: BodyRangeList?) data class Input(val body: String, val mentions: List<Mention>, val bodyRanges: BodyRangeList?)
} }

Wyświetl plik

@ -13,7 +13,6 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.KeyboardEntryDialogFragment import org.thoughtcrime.securesms.components.KeyboardEntryDialogFragment
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener import org.thoughtcrime.securesms.components.emoji.EmojiEventListener
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard import org.thoughtcrime.securesms.components.emoji.MediaKeyboard
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.keyboard.KeyboardPage import org.thoughtcrime.securesms.keyboard.KeyboardPage
import org.thoughtcrime.securesms.keyboard.KeyboardPagerViewModel import org.thoughtcrime.securesms.keyboard.KeyboardPagerViewModel
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment
@ -21,9 +20,7 @@ import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageViewModel import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageViewModel
import org.thoughtcrime.securesms.stories.viewer.reply.composer.StoryReactionBar
import org.thoughtcrime.securesms.stories.viewer.reply.composer.StoryReplyComposer import org.thoughtcrime.securesms.stories.viewer.reply.composer.StoryReplyComposer
import org.thoughtcrime.securesms.util.FragmentDialogs.displayInDialogAboveAnchor
import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.LifecycleDisposable
import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.ViewUtil
@ -82,28 +79,13 @@ class StoryDirectReplyDialogFragment :
} }
} }
override fun onPickReactionClicked() { override fun onReactionClicked(emoji: String) {
displayInDialogAboveAnchor(composer.reactionButton, R.layout.stories_reaction_bar_layout) { dialog, view -> sendReaction(emoji)
view.findViewById<StoryReactionBar>(R.id.reaction_bar).apply { }
callback = object : StoryReactionBar.Callback {
override fun onTouchOutsideOfReactionBar() {
dialog.dismiss()
}
override fun onReactionSelected(emoji: String) { override fun onPickAnyReactionClicked() {
dialog.dismiss() isRequestingReactWithAny = true
sendReaction(emoji) ReactWithAnyEmojiBottomSheetDialogFragment.createForStory().show(childFragmentManager, null)
}
override fun onOpenReactionPicker() {
dialog.dismiss()
isRequestingReactWithAny = true
ReactWithAnyEmojiBottomSheetDialogFragment.createForStory().show(childFragmentManager, null)
}
}
animateIn()
}
}
} }
override fun onInitializeEmojiDrawer(mediaKeyboard: MediaKeyboard) { override fun onInitializeEmojiDrawer(mediaKeyboard: MediaKeyboard) {
@ -114,13 +96,9 @@ class StoryDirectReplyDialogFragment :
viewModel.state.observe(viewLifecycleOwner) { state -> viewModel.state.observe(viewLifecycleOwner) { state ->
if (state.groupDirectReplyRecipient != null) { if (state.groupDirectReplyRecipient != null) {
composer.displayPrivacyChrome(state.groupDirectReplyRecipient) composer.displayReplyHint(state.groupDirectReplyRecipient)
} else if (state.storyRecord != null) { } else if (state.storyRecord != null) {
composer.displayPrivacyChrome(state.storyRecord.recipient) composer.displayReplyHint(state.storyRecord.recipient)
}
if (state.storyRecord != null) {
composer.setQuote(state.storyRecord as MediaMmsMessageRecord)
} }
} }
} }

Wyświetl plik

@ -53,10 +53,8 @@ import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.stories.viewer.reply.StoryViewsAndRepliesPagerChild import org.thoughtcrime.securesms.stories.viewer.reply.StoryViewsAndRepliesPagerChild
import org.thoughtcrime.securesms.stories.viewer.reply.StoryViewsAndRepliesPagerParent import org.thoughtcrime.securesms.stories.viewer.reply.StoryViewsAndRepliesPagerParent
import org.thoughtcrime.securesms.stories.viewer.reply.composer.StoryReactionBar
import org.thoughtcrime.securesms.stories.viewer.reply.composer.StoryReplyComposer import org.thoughtcrime.securesms.stories.viewer.reply.composer.StoryReplyComposer
import org.thoughtcrime.securesms.util.DeleteDialog import org.thoughtcrime.securesms.util.DeleteDialog
import org.thoughtcrime.securesms.util.FragmentDialogs.displayInDialogAboveAnchor
import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.LifecycleDisposable
import org.thoughtcrime.securesms.util.ServiceUtil import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.ViewUtil
@ -355,27 +353,12 @@ class StoryGroupReplyFragment :
performSend(body, mentions, bodyRanges) performSend(body, mentions, bodyRanges)
} }
override fun onPickReactionClicked() { override fun onPickAnyReactionClicked() {
displayInDialogAboveAnchor(composer.reactionButton, R.layout.stories_reaction_bar_layout) { dialog, view -> ReactWithAnyEmojiBottomSheetDialogFragment.createForStory().show(childFragmentManager, null)
view.findViewById<StoryReactionBar>(R.id.reaction_bar).apply { }
callback = object : StoryReactionBar.Callback {
override fun onTouchOutsideOfReactionBar() {
dialog.dismiss()
}
override fun onReactionSelected(emoji: String) { override fun onReactionClicked(emoji: String) {
dialog.dismiss() sendReaction(emoji)
sendReaction(emoji)
}
override fun onOpenReactionPicker() {
dialog.dismiss()
ReactWithAnyEmojiBottomSheetDialogFragment.createForStory().show(childFragmentManager, null)
}
}
animateIn()
}
}
} }
override fun onEmojiSelected(emoji: String?) { override fun onEmojiSelected(emoji: String?) {

Wyświetl plik

@ -6,62 +6,73 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/signal_colorSurface" android:background="@color/signal_colorSurface"
android:paddingTop="12dp"> android:paddingTop="10dp">
<org.thoughtcrime.securesms.components.FromTextView <LinearLayout
android:id="@+id/private_reply_recipient" android:id="@+id/emoji_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginEnd="6dp"
android:textAppearance="@style/Signal.Text.Caption"
android:textColor="@color/signal_colorOnSurfaceVariant"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/bubble"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="Replying privately to Miles Morales" android:orientation="horizontal">
tools:visibility="visible" />
<androidx.constraintlayout.widget.ConstraintLayout <org.thoughtcrime.securesms.components.emoji.EmojiPageView
android:id="@+id/reaction_emoji_view"
android:layout_width="0dp"
android:layout_weight="1"
android:requiresFadingEdge="horizontal"
android:fadingEdgeLength="8dp"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/any_reaction"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="16dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_any_emoji_32"
/>
</LinearLayout>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/bubble" android:id="@+id/bubble"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="18dp" android:layout_marginStart="16dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:background="@drawable/rounded_rectangle_surface_variant_18" android:background="@drawable/rounded_rectangle_surface_variant_32"
android:padding="8dp" android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/reply_reaction_switch" app:layout_constraintEnd_toStartOf="@id/reply"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/private_reply_recipient" app:layout_constraintTop_toBottomOf="@id/emoji_bar"
app:layout_goneMarginTop="0dp"> app:layout_goneMarginTop="0dp"
app:layout_goneMarginEnd="16dp"
android:orientation="horizontal">
<org.thoughtcrime.securesms.components.QuoteView <org.thoughtcrime.securesms.components.emoji.EmojiToggle
android:id="@+id/quote_view" android:id="@+id/emoji_toggle"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp" android:background="?selectableItemBackgroundBorderless"
android:visibility="gone" app:force_outline="true"
app:layout_constraintEnd_toEndOf="parent" android:layout_gravity="bottom"
app:layout_constraintStart_toStartOf="parent" app:tint="@color/signal_colorOnSurface"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:message_type="story_reply_preview" app:layout_constraintStart_toStartOf="parent" />
app:quote_colorPrimary="@color/signal_text_primary"
app:quote_colorSecondary="@color/signal_text_primary"
tools:visibility="gone" />
<org.thoughtcrime.securesms.components.ComposeText <org.thoughtcrime.securesms.components.ComposeText
android:id="@+id/compose_text" android:id="@+id/compose_text"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:layout_weight="1"
android:background="@null" android:background="@null"
android:hint="@string/StoryViewerPageFragment__reply" android:hint="@string/StoryViewerPageFragment__reply"
android:layout_gravity="center_vertical"
android:imeOptions="flagNoEnterAction|actionSend" android:imeOptions="flagNoEnterAction|actionSend"
android:inputType="textAutoCorrect|textCapSentences|textMultiLine" android:inputType="textAutoCorrect|textCapSentences|textMultiLine"
android:maxLength="65536" android:maxLength="65536"
@ -71,53 +82,25 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/emoji_toggle" app:layout_constraintStart_toEndOf="@id/emoji_toggle"
app:layout_constraintTop_toBottomOf="@id/quote_view" app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginTop="0dp" app:layout_goneMarginTop="0dp"
tools:text="hello\nasdf" /> tools:text="hello\nasdf" />
<org.thoughtcrime.securesms.components.emoji.EmojiToggle </androidx.appcompat.widget.LinearLayoutCompat>
android:id="@+id/emoji_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="?selectableItemBackgroundBorderless"
app:force_outline="true"
app:tint="@color/signal_colorOnSurface"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_goneMarginTop="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout> <ImageView
android:id="@+id/reply"
<ViewSwitcher android:layout_width="40dp"
android:id="@+id/reply_reaction_switch" android:layout_height="40dp"
android:layout_width="36dp" android:layout_gravity="bottom"
android:layout_height="36dp" android:background="@drawable/circle_tintable"
android:layout_marginEnd="6dp" android:contentDescription="@string/StoryReplyComposer__react_to_this_story"
android:layout_marginBottom="2dp" android:padding="8dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:backgroundTint="@color/signal_light_colorPrimary"
app:srcCompat="@drawable/ic_send_24"
app:layout_constraintBottom_toBottomOf="@+id/bubble" app:layout_constraintBottom_toBottomOf="@+id/bubble"
app:layout_constraintEnd_toEndOf="parent"> app:layout_constraintEnd_toEndOf="parent"/>
<ImageView
android:id="@+id/reaction"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/StoryReplyComposer__react_to_this_story"
android:padding="6dp"
app:srcCompat="@drawable/ic_add_reaction_outline_24"
app:tint="@color/signal_colorOnSurface" />
<ImageView
android:id="@+id/reply"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="bottom"
android:background="@drawable/circle_tintable"
android:contentDescription="@string/StoryReplyComposer__react_to_this_story"
android:padding="6dp"
app:backgroundTint="@color/signal_light_colorPrimary"
app:srcCompat="@drawable/ic_send_24" />
</ViewSwitcher>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -4861,6 +4861,8 @@
<string name="StoryReplyComposer__react_to_this_story">React to this story</string> <string name="StoryReplyComposer__react_to_this_story">React to this story</string>
<!-- Displayed when the user is replying privately to someone who replied to one of their stories --> <!-- Displayed when the user is replying privately to someone who replied to one of their stories -->
<string name="StoryReplyComposer__replying_privately_to_s">Replying privately to %1$s</string> <string name="StoryReplyComposer__replying_privately_to_s">Replying privately to %1$s</string>
<!-- Displayed when the user is replying privately to someone who replied to one of their stories -->
<string name="StoryReplyComposer__reply_to_s">Reply to %1$s</string>
<!-- Context menu item to privately reply to a story response --> <!-- Context menu item to privately reply to a story response -->
<!-- Context menu item to copy a story response --> <!-- Context menu item to copy a story response -->
<string name="StoryGroupReplyItem__copy">Copy</string> <string name="StoryGroupReplyItem__copy">Copy</string>