Implement ability to react with any emoji behind a flag.
|
@ -133,7 +133,7 @@ public class EmojiKeyboardProvider implements MediaKeyboardProvider,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
|
public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
|
||||||
EmojiPageView page = new EmojiPageView(context, emojiSelectionListener, variationSelectorListener);
|
EmojiPageView page = new EmojiPageView(context, emojiSelectionListener, variationSelectorListener, true);
|
||||||
page.setModel(pages.get(position));
|
page.setModel(pages.get(position));
|
||||||
container.addView(page);
|
container.addView(page);
|
||||||
return page;
|
return page;
|
||||||
|
|
|
@ -26,7 +26,8 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe
|
||||||
|
|
||||||
public EmojiPageView(@NonNull Context context,
|
public EmojiPageView(@NonNull Context context,
|
||||||
@NonNull EmojiEventListener emojiSelectionListener,
|
@NonNull EmojiEventListener emojiSelectionListener,
|
||||||
@NonNull VariationSelectorListener variationSelectorListener)
|
@NonNull VariationSelectorListener variationSelectorListener,
|
||||||
|
boolean allowVariations)
|
||||||
{
|
{
|
||||||
super(context);
|
super(context);
|
||||||
final View view = LayoutInflater.from(getContext()).inflate(R.layout.emoji_grid_layout, this, true);
|
final View view = LayoutInflater.from(getContext()).inflate(R.layout.emoji_grid_layout, this, true);
|
||||||
|
@ -40,7 +41,8 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe
|
||||||
adapter = new EmojiPageViewGridAdapter(EmojiProvider.getInstance(context),
|
adapter = new EmojiPageViewGridAdapter(EmojiProvider.getInstance(context),
|
||||||
popup,
|
popup,
|
||||||
emojiSelectionListener,
|
emojiSelectionListener,
|
||||||
this);
|
this,
|
||||||
|
allowVariations);
|
||||||
|
|
||||||
recyclerView.setLayoutManager(layoutManager);
|
recyclerView.setLayoutManager(layoutManager);
|
||||||
recyclerView.setAdapter(adapter);
|
recyclerView.setAdapter(adapter);
|
||||||
|
@ -83,6 +85,10 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRecyclerNestedScrollingEnabled(boolean enabled) {
|
||||||
|
recyclerView.setNestedScrollingEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
private static class ScrollDisabler implements RecyclerView.OnItemTouchListener {
|
private static class ScrollDisabler implements RecyclerView.OnItemTouchListener {
|
||||||
@Override
|
@Override
|
||||||
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
|
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
|
||||||
|
|
|
@ -22,17 +22,20 @@ public class EmojiPageViewGridAdapter extends RecyclerView.Adapter<EmojiPageView
|
||||||
private final EmojiVariationSelectorPopup popup;
|
private final EmojiVariationSelectorPopup popup;
|
||||||
private final VariationSelectorListener variationSelectorListener;
|
private final VariationSelectorListener variationSelectorListener;
|
||||||
private final EmojiEventListener emojiEventListener;
|
private final EmojiEventListener emojiEventListener;
|
||||||
|
private final boolean allowVariations;
|
||||||
|
|
||||||
public EmojiPageViewGridAdapter(@NonNull EmojiProvider emojiProvider,
|
public EmojiPageViewGridAdapter(@NonNull EmojiProvider emojiProvider,
|
||||||
@NonNull EmojiVariationSelectorPopup popup,
|
@NonNull EmojiVariationSelectorPopup popup,
|
||||||
@NonNull EmojiEventListener emojiEventListener,
|
@NonNull EmojiEventListener emojiEventListener,
|
||||||
@NonNull VariationSelectorListener variationSelectorListener)
|
@NonNull VariationSelectorListener variationSelectorListener,
|
||||||
|
boolean allowVariations)
|
||||||
{
|
{
|
||||||
this.emojiList = new ArrayList<>();
|
this.emojiList = new ArrayList<>();
|
||||||
this.emojiProvider = emojiProvider;
|
this.emojiProvider = emojiProvider;
|
||||||
this.popup = popup;
|
this.popup = popup;
|
||||||
this.emojiEventListener = emojiEventListener;
|
this.emojiEventListener = emojiEventListener;
|
||||||
this.variationSelectorListener = variationSelectorListener;
|
this.variationSelectorListener = variationSelectorListener;
|
||||||
|
this.allowVariations = allowVariations;
|
||||||
|
|
||||||
popup.setOnDismissListener(this);
|
popup.setOnDismissListener(this);
|
||||||
}
|
}
|
||||||
|
@ -65,7 +68,7 @@ public class EmojiPageViewGridAdapter extends RecyclerView.Adapter<EmojiPageView
|
||||||
emojiEventListener.onEmojiSelected(emoji.getValue());
|
emojiEventListener.onEmojiSelected(emoji.getValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
if (emoji.getVariations().size() > 1) {
|
if (allowVariations && emoji.getVariations().size() > 1) {
|
||||||
viewHolder.itemView.setOnLongClickListener(v -> {
|
viewHolder.itemView.setOnLongClickListener(v -> {
|
||||||
popup.dismiss();
|
popup.dismiss();
|
||||||
popup.setVariations(emoji.getVariations());
|
popup.setVariations(emoji.getVariations());
|
||||||
|
|
|
@ -6,6 +6,7 @@ import org.whispersystems.libsignal.util.Pair;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -36,6 +37,10 @@ public final class EmojiUtil {
|
||||||
|
|
||||||
private EmojiUtil() {}
|
private EmojiUtil() {}
|
||||||
|
|
||||||
|
public static List<EmojiPageModel> getDisplayPages() {
|
||||||
|
return EmojiPages.DISPLAY_PAGES;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This will return all ways we know of expressing a singular emoji. This is to aid in search,
|
* This will return all ways we know of expressing a singular emoji. This is to aid in search,
|
||||||
* where some platforms may send an emoji we've locally marked as 'obsolete'.
|
* where some platforms may send an emoji we've locally marked as 'obsolete'.
|
||||||
|
|
|
@ -197,6 +197,7 @@ import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.profiles.GroupShareProfileView;
|
import org.thoughtcrime.securesms.profiles.GroupShareProfileView;
|
||||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
|
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
|
||||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientExporter;
|
import org.thoughtcrime.securesms.recipients.RecipientExporter;
|
||||||
|
@ -269,7 +270,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
ComposeText.CursorPositionChangedListener,
|
ComposeText.CursorPositionChangedListener,
|
||||||
ConversationSearchBottomBar.EventListener,
|
ConversationSearchBottomBar.EventListener,
|
||||||
StickerKeyboardProvider.StickerEventListener,
|
StickerKeyboardProvider.StickerEventListener,
|
||||||
AttachmentKeyboard.Callback
|
AttachmentKeyboard.Callback,
|
||||||
|
ConversationReactionOverlay.OnReactionSelectedListener,
|
||||||
|
ReactWithAnyEmojiBottomSheetDialogFragment.Callback
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final int SHORTCUT_ICON_SIZE = Build.VERSION.SDK_INT >= 26 ? ViewUtil.dpToPx(72) : ViewUtil.dpToPx(48 + 16 * 2);
|
private static final int SHORTCUT_ICON_SIZE = Build.VERSION.SDK_INT >= 26 ? ViewUtil.dpToPx(72) : ViewUtil.dpToPx(48 + 16 * 2);
|
||||||
|
@ -1714,7 +1717,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
|
|
||||||
inlineAttachmentButton.setOnClickListener(v -> handleAddAttachment());
|
inlineAttachmentButton.setOnClickListener(v -> handleAddAttachment());
|
||||||
|
|
||||||
reactionOverlay.setOnReactionSelectedListener(this::onReactionSelected);
|
reactionOverlay.setOnReactionSelectedListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initializeActionBar() {
|
protected void initializeActionBar() {
|
||||||
|
@ -1831,10 +1834,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
.show(TooltipPopup.POSITION_ABOVE);
|
.show(TooltipPopup.POSITION_ABOVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
private void onReactionSelected(MessageRecord messageRecord, String emoji) {
|
public void onReactionSelected(MessageRecord messageRecord, String emoji) {
|
||||||
final Context context = getApplicationContext();
|
final Context context = getApplicationContext();
|
||||||
|
|
||||||
|
reactionOverlay.hide();
|
||||||
|
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
ReactionRecord oldRecord = Stream.of(messageRecord.getReactions())
|
ReactionRecord oldRecord = Stream.of(messageRecord.getReactions())
|
||||||
.filter(record -> record.getAuthor().equals(Recipient.self().getId()))
|
.filter(record -> record.getAuthor().equals(Recipient.self().getId()))
|
||||||
|
@ -1849,6 +1854,35 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCustomReactionSelected(@NonNull MessageRecord messageRecord, boolean hasAddedCustomEmoji) {
|
||||||
|
ReactionRecord oldRecord = Stream.of(messageRecord.getReactions())
|
||||||
|
.filter(record -> record.getAuthor().equals(Recipient.self().getId()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (oldRecord != null && hasAddedCustomEmoji) {
|
||||||
|
final Context context = getApplicationContext();
|
||||||
|
|
||||||
|
reactionOverlay.hide();
|
||||||
|
|
||||||
|
SignalExecutors.BOUNDED.execute(() -> MessageSender.sendReactionRemoval(context,
|
||||||
|
messageRecord.getId(),
|
||||||
|
messageRecord.isMms(),
|
||||||
|
oldRecord));
|
||||||
|
} else {
|
||||||
|
reactionOverlay.hideAllButMask();
|
||||||
|
|
||||||
|
ReactWithAnyEmojiBottomSheetDialogFragment.createForMessageRecord(messageRecord)
|
||||||
|
.show(getSupportFragmentManager(), "BOTTOM");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReactWithAnyEmojiDialogDismissed() {
|
||||||
|
reactionOverlay.hideMask();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSearchMoveUpPressed() {
|
public void onSearchMoveUpPressed() {
|
||||||
searchViewModel.onMoveUp();
|
searchViewModel.onMoveUp();
|
||||||
|
|
|
@ -19,6 +19,7 @@ import android.widget.RelativeLayout;
|
||||||
import androidx.annotation.IdRes;
|
import androidx.annotation.IdRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.constraintlayout.widget.ConstraintSet;
|
import androidx.constraintlayout.widget.ConstraintSet;
|
||||||
|
@ -30,14 +31,17 @@ import com.annimon.stream.Stream;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||||
import org.thoughtcrime.securesms.components.MaskView;
|
import org.thoughtcrime.securesms.components.MaskView;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class ConversationReactionOverlay extends RelativeLayout {
|
public final class ConversationReactionOverlay extends RelativeLayout {
|
||||||
|
@ -60,12 +64,13 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||||
private boolean downIsOurs;
|
private boolean downIsOurs;
|
||||||
private boolean isToolbarTouch;
|
private boolean isToolbarTouch;
|
||||||
private int selected = -1;
|
private int selected = -1;
|
||||||
|
private int customEmojiIndex;
|
||||||
private int originalStatusBarColor;
|
private int originalStatusBarColor;
|
||||||
|
|
||||||
private View backgroundView;
|
private View backgroundView;
|
||||||
private ConstraintLayout foregroundView;
|
private ConstraintLayout foregroundView;
|
||||||
private View selectedView;
|
private View selectedView;
|
||||||
private View[] emojiViews;
|
private EmojiImageView[] emojiViews;
|
||||||
private MaskView maskView;
|
private MaskView maskView;
|
||||||
private Toolbar toolbar;
|
private Toolbar toolbar;
|
||||||
|
|
||||||
|
@ -87,6 +92,8 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||||
|
|
||||||
private AnimatorSet revealAnimatorSet = new AnimatorSet();
|
private AnimatorSet revealAnimatorSet = new AnimatorSet();
|
||||||
private AnimatorSet hideAnimatorSet = new AnimatorSet();
|
private AnimatorSet hideAnimatorSet = new AnimatorSet();
|
||||||
|
private AnimatorSet hideAllButMaskAnimatorSet = new AnimatorSet();
|
||||||
|
private AnimatorSet hideMaskAnimatorSet = new AnimatorSet();
|
||||||
|
|
||||||
public ConversationReactionOverlay(@NonNull Context context) {
|
public ConversationReactionOverlay(@NonNull Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
@ -111,7 +118,10 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||||
|
|
||||||
emojiViews = Stream.of(ReactionEmoji.values())
|
emojiViews = Stream.of(ReactionEmoji.values())
|
||||||
.map(e -> findViewById(e.viewId))
|
.map(e -> findViewById(e.viewId))
|
||||||
.toArray(View[]::new);
|
.toArray(EmojiImageView[]::new);
|
||||||
|
|
||||||
|
customEmojiIndex = FeatureFlags.reactWithAnyEmoji() ? ReactionEmoji.values().length - 1
|
||||||
|
: ReactionEmoji.values().length;
|
||||||
|
|
||||||
distanceFromTouchDownPointToTopOfScrubberDeadZone = getResources().getDimensionPixelSize(R.dimen.conversation_reaction_scrub_deadzone_distance_from_touch_top);
|
distanceFromTouchDownPointToTopOfScrubberDeadZone = getResources().getDimensionPixelSize(R.dimen.conversation_reaction_scrub_deadzone_distance_from_touch_top);
|
||||||
distanceFromTouchDownPointToBottomOfScrubberDeadZone = getResources().getDimensionPixelSize(R.dimen.conversation_reaction_scrub_deadzone_distance_from_touch_bottom);
|
distanceFromTouchDownPointToBottomOfScrubberDeadZone = getResources().getDimensionPixelSize(R.dimen.conversation_reaction_scrub_deadzone_distance_from_touch_bottom);
|
||||||
|
@ -144,7 +154,7 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||||
selected = -1;
|
selected = -1;
|
||||||
|
|
||||||
setupToolbarMenuItems();
|
setupToolbarMenuItems();
|
||||||
setupSelectedEmojiBackground();
|
setupSelectedEmoji();
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
View statusBarBackground = activity.findViewById(android.R.id.statusBarBackground);
|
View statusBarBackground = activity.findViewById(android.R.id.statusBarBackground);
|
||||||
|
@ -188,6 +198,22 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||||
|
|
||||||
public void hide() {
|
public void hide() {
|
||||||
maskView.setTarget(null);
|
maskView.setTarget(null);
|
||||||
|
hideInternal(hideAnimatorSet, onHideListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hideAllButMask() {
|
||||||
|
hideInternal(hideAllButMaskAnimatorSet, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hideMask() {
|
||||||
|
hideMaskAnimatorSet.start();
|
||||||
|
|
||||||
|
if (onHideListener != null) {
|
||||||
|
onHideListener.onHide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideInternal(@NonNull AnimatorSet hideAnimatorSet, @Nullable OnHideListener onHideListener) {
|
||||||
overlayState = OverlayState.HIDDEN;
|
overlayState = OverlayState.HIDDEN;
|
||||||
|
|
||||||
revealAnimatorSet.end();
|
revealAnimatorSet.end();
|
||||||
|
@ -316,20 +342,30 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSelectedEmojiBackground() {
|
private void setupSelectedEmoji() {
|
||||||
final String oldEmoji = getOldEmoji(messageRecord);
|
final String oldEmoji = getOldEmoji(messageRecord);
|
||||||
|
|
||||||
if (oldEmoji == null) {
|
if (oldEmoji == null) {
|
||||||
selectedView.setVisibility(View.GONE);
|
selectedView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean foundSelected = false;
|
||||||
|
|
||||||
for (int i = 0; i < emojiViews.length; i++) {
|
for (int i = 0; i < emojiViews.length; i++) {
|
||||||
final View view = emojiViews[i];
|
final EmojiImageView view = emojiViews[i];
|
||||||
|
|
||||||
view.setScaleX(1.0f);
|
view.setScaleX(1.0f);
|
||||||
view.setScaleY(1.0f);
|
view.setScaleY(1.0f);
|
||||||
view.setTranslationY(0);
|
view.setTranslationY(0);
|
||||||
|
|
||||||
if (ReactionEmoji.values()[i].emoji.equals(oldEmoji)) {
|
boolean isAtCustomIndex = i == customEmojiIndex;
|
||||||
|
boolean isNotAtCustomIndexAndOldEmojiMatches = !isAtCustomIndex && ReactionEmoji.values()[i].emoji.equals(oldEmoji);
|
||||||
|
boolean isAtCustomIndexAndOldEmojiExists = isAtCustomIndex && oldEmoji != null;
|
||||||
|
|
||||||
|
if (!foundSelected &&
|
||||||
|
(isNotAtCustomIndexAndOldEmojiMatches || isAtCustomIndexAndOldEmojiExists))
|
||||||
|
{
|
||||||
|
foundSelected = true;
|
||||||
selectedView.setVisibility(View.VISIBLE);
|
selectedView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
ConstraintSet constraintSet = new ConstraintSet();
|
ConstraintSet constraintSet = new ConstraintSet();
|
||||||
|
@ -339,6 +375,18 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||||
constraintSet.connect(selectedView.getId(), ConstraintSet.LEFT, view.getId(), ConstraintSet.LEFT);
|
constraintSet.connect(selectedView.getId(), ConstraintSet.LEFT, view.getId(), ConstraintSet.LEFT);
|
||||||
constraintSet.connect(selectedView.getId(), ConstraintSet.RIGHT, view.getId(), ConstraintSet.RIGHT);
|
constraintSet.connect(selectedView.getId(), ConstraintSet.RIGHT, view.getId(), ConstraintSet.RIGHT);
|
||||||
constraintSet.applyTo(foregroundView);
|
constraintSet.applyTo(foregroundView);
|
||||||
|
|
||||||
|
if (isAtCustomIndex) {
|
||||||
|
view.setImageEmoji(oldEmoji);
|
||||||
|
view.setTag(oldEmoji);
|
||||||
|
} else {
|
||||||
|
view.setImageEmoji(ReactionEmoji.values()[i].emoji);
|
||||||
|
}
|
||||||
|
} else if (isAtCustomIndex) {
|
||||||
|
view.setImageDrawable(AppCompatResources.getDrawable(getContext(), R.drawable.ic_any_emoji_32));
|
||||||
|
view.setTag(null);
|
||||||
|
} else {
|
||||||
|
view.setImageEmoji(ReactionEmoji.values()[i].emoji);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -396,10 +444,15 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleUpEvent() {
|
private void handleUpEvent() {
|
||||||
hide();
|
|
||||||
if (selected != -1 && onReactionSelectedListener != null) {
|
if (selected != -1 && onReactionSelectedListener != null) {
|
||||||
|
if (selected == customEmojiIndex) {
|
||||||
|
onReactionSelectedListener.onCustomReactionSelected(messageRecord, emojiViews[selected].getTag() != null);
|
||||||
|
} else {
|
||||||
onReactionSelectedListener.onReactionSelected(messageRecord, ReactionEmoji.values()[selected].emoji);
|
onReactionSelectedListener.onReactionSelected(messageRecord, ReactionEmoji.values()[selected].emoji);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnReactionSelectedListener(@Nullable OnReactionSelectedListener onReactionSelectedListener) {
|
public void setOnReactionSelectedListener(@Nullable OnReactionSelectedListener onReactionSelectedListener) {
|
||||||
|
@ -494,7 +547,6 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||||
Animator overlayHideAnim = AnimatorInflaterCompat.loadAnimator(getContext(), android.R.animator.fade_out);
|
Animator overlayHideAnim = AnimatorInflaterCompat.loadAnimator(getContext(), android.R.animator.fade_out);
|
||||||
overlayHideAnim.setTarget(maskView);
|
overlayHideAnim.setTarget(maskView);
|
||||||
overlayHideAnim.setDuration(duration);
|
overlayHideAnim.setDuration(duration);
|
||||||
hides.add(overlayHideAnim);
|
|
||||||
|
|
||||||
Animator backgroundHideAnim = AnimatorInflaterCompat.loadAnimator(getContext(), android.R.animator.fade_out);
|
Animator backgroundHideAnim = AnimatorInflaterCompat.loadAnimator(getContext(), android.R.animator.fade_out);
|
||||||
backgroundHideAnim.setTarget(backgroundView);
|
backgroundHideAnim.setTarget(backgroundView);
|
||||||
|
@ -511,15 +563,26 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||||
toolbarHideAnim.setDuration(duration);
|
toolbarHideAnim.setDuration(duration);
|
||||||
hides.add(toolbarHideAnim);
|
hides.add(toolbarHideAnim);
|
||||||
|
|
||||||
hideAnimatorSet.addListener(new AnimationCompleteListener() {
|
AnimationCompleteListener hideListener = new AnimationCompleteListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationEnd(Animator animation) {
|
public void onAnimationEnd(Animator animation) {
|
||||||
setVisibility(View.GONE);
|
setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
List<Animator> hideAllAnimators = new LinkedList<>(hides);
|
||||||
|
hideAllAnimators.add(overlayHideAnim);
|
||||||
|
|
||||||
|
hideAnimatorSet.addListener(hideListener);
|
||||||
hideAnimatorSet.setInterpolator(INTERPOLATOR);
|
hideAnimatorSet.setInterpolator(INTERPOLATOR);
|
||||||
hideAnimatorSet.playTogether(hides);
|
hideAnimatorSet.playTogether(hideAllAnimators);
|
||||||
|
|
||||||
|
hideAllButMaskAnimatorSet.setInterpolator(INTERPOLATOR);
|
||||||
|
hideAllButMaskAnimatorSet.playTogether(hides);
|
||||||
|
|
||||||
|
hideMaskAnimatorSet.addListener(hideListener);
|
||||||
|
hideMaskAnimatorSet.setInterpolator(INTERPOLATOR);
|
||||||
|
hideMaskAnimatorSet.playTogether(overlayHideAnim);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface OnHideListener {
|
public interface OnHideListener {
|
||||||
|
@ -528,6 +591,7 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||||
|
|
||||||
public interface OnReactionSelectedListener {
|
public interface OnReactionSelectedListener {
|
||||||
void onReactionSelected(@NonNull MessageRecord messageRecord, String emoji);
|
void onReactionSelected(@NonNull MessageRecord messageRecord, String emoji);
|
||||||
|
void onCustomReactionSelected(@NonNull MessageRecord messageRecord, boolean hasAddedCustomEmoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Boundary {
|
private static class Boundary {
|
||||||
|
|
|
@ -21,7 +21,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ClearProfileAvatarActivity;
|
import org.thoughtcrime.securesms.ClearProfileAvatarActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.groups.GroupId;
|
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -63,8 +62,8 @@ public class AvatarSelectionBottomSheetDialogFragment extends BottomSheetDialogF
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
setStyle(DialogFragment.STYLE_NORMAL,
|
setStyle(DialogFragment.STYLE_NORMAL,
|
||||||
ThemeUtil.isDarkTheme(requireContext()) ? R.style.Theme_Design_BottomSheetDialog_Fixed
|
ThemeUtil.isDarkTheme(requireContext()) ? R.style.Theme_Signal_BottomSheetDialog_Fixed
|
||||||
: R.style.Theme_Design_Light_BottomSheetDialog_Fixed);
|
: R.style.Theme_Signal_Light_BottomSheetDialog_Fixed);
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
|
|
@ -46,9 +46,9 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
|
||||||
if (ThemeUtil.isDarkTheme(requireContext())) {
|
if (ThemeUtil.isDarkTheme(requireContext())) {
|
||||||
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Design_BottomSheetDialog_Fixed);
|
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_BottomSheetDialog_Fixed);
|
||||||
} else {
|
} else {
|
||||||
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Design_Light_BottomSheetDialog_Fixed);
|
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_Light_BottomSheetDialog_Fixed);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
package org.thoughtcrime.securesms.reactions.any;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.util.Consumer;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiPageView;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
final class ReactWithAnyEmojiAdapter extends RecyclerView.Adapter<ReactWithAnyEmojiAdapter.ViewHolder> {
|
||||||
|
|
||||||
|
private final List<EmojiPageModel> models;
|
||||||
|
private final EmojiKeyboardProvider.EmojiEventListener emojiEventListener;
|
||||||
|
private final EmojiPageViewGridAdapter.VariationSelectorListener variationSelectorListener;
|
||||||
|
private final Callbacks callbacks;
|
||||||
|
|
||||||
|
ReactWithAnyEmojiAdapter(@NonNull List<EmojiPageModel> models,
|
||||||
|
@NonNull EmojiKeyboardProvider.EmojiEventListener emojiEventListener,
|
||||||
|
@NonNull EmojiPageViewGridAdapter.VariationSelectorListener variationSelectorListener,
|
||||||
|
@NonNull Callbacks callbacks)
|
||||||
|
{
|
||||||
|
this.models = models;
|
||||||
|
this.emojiEventListener = emojiEventListener;
|
||||||
|
this.variationSelectorListener = variationSelectorListener;
|
||||||
|
this.callbacks = callbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
return new ViewHolder(new EmojiPageView(parent.getContext(), emojiEventListener, variationSelectorListener, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
|
holder.bind(models.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return models.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewAttachedToWindow(@NonNull ViewHolder holder) {
|
||||||
|
callbacks.onViewHolderAttached(holder.getAdapterPosition(), holder.emojiPageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||||
|
recyclerView.setNestedScrollingEnabled(false);
|
||||||
|
ViewGroup.LayoutParams params = recyclerView.getLayoutParams();
|
||||||
|
params.height = (int) (recyclerView.getResources().getDisplayMetrics().heightPixels * 0.80);
|
||||||
|
recyclerView.setLayoutParams(params);
|
||||||
|
recyclerView.setHasFixedSize(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
private final EmojiPageView emojiPageView;
|
||||||
|
|
||||||
|
ViewHolder(@NonNull EmojiPageView itemView) {
|
||||||
|
super(itemView);
|
||||||
|
|
||||||
|
emojiPageView = itemView;
|
||||||
|
|
||||||
|
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
|
||||||
|
emojiPageView.setLayoutParams(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind(@NonNull EmojiPageModel model) {
|
||||||
|
emojiPageView.setModel(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callbacks {
|
||||||
|
void onViewHolderAttached(int adapterPosition, EmojiPageView pageView);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,268 @@
|
||||||
|
package org.thoughtcrime.securesms.reactions.any;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.TextSwitcher;
|
||||||
|
|
||||||
|
import androidx.annotation.AttrRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||||
|
import com.google.android.material.shape.CornerFamily;
|
||||||
|
import com.google.android.material.shape.MaterialShapeDrawable;
|
||||||
|
import com.google.android.material.shape.ShapeAppearanceModel;
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiPageView;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
|
import static org.thoughtcrime.securesms.R.layout.react_with_any_emoji_tab;
|
||||||
|
|
||||||
|
public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomSheetDialogFragment implements EmojiKeyboardProvider.EmojiEventListener, EmojiPageViewGridAdapter.VariationSelectorListener {
|
||||||
|
|
||||||
|
private static final String ARG_MESSAGE_ID = "arg_message_id";
|
||||||
|
private static final String ARG_IS_MMS = "arg_is_mms";
|
||||||
|
|
||||||
|
private ReactWithAnyEmojiViewModel viewModel;
|
||||||
|
private TextSwitcher categoryLabel;
|
||||||
|
private ViewPager2 categoryPager;
|
||||||
|
private ReactWithAnyEmojiAdapter adapter;
|
||||||
|
private OnPageChanged onPageChanged;
|
||||||
|
private SparseArray<EmojiPageView> pageArray = new SparseArray<>();
|
||||||
|
private Callback callback;
|
||||||
|
|
||||||
|
public static DialogFragment createForMessageRecord(@NonNull MessageRecord messageRecord) {
|
||||||
|
DialogFragment fragment = new ReactWithAnyEmojiBottomSheetDialogFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
|
args.putLong(ARG_MESSAGE_ID, messageRecord.getId());
|
||||||
|
args.putBoolean(ARG_IS_MMS, messageRecord.isMms());
|
||||||
|
fragment.setArguments(args);
|
||||||
|
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(@NonNull Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
|
||||||
|
callback = (Callback) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
if (ThemeUtil.isDarkTheme(requireContext())) {
|
||||||
|
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_BottomSheetDialog_Fixed_ReactWithAny);
|
||||||
|
} else {
|
||||||
|
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_Light_BottomSheetDialog_Fixed_ReactWithAny);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);
|
||||||
|
ShapeAppearanceModel shapeAppearanceModel = ShapeAppearanceModel.builder()
|
||||||
|
.setTopLeftCorner(CornerFamily.ROUNDED, ViewUtil.dpToPx(requireContext(), 8))
|
||||||
|
.setTopRightCorner(CornerFamily.ROUNDED, ViewUtil.dpToPx(requireContext(), 8))
|
||||||
|
.build();
|
||||||
|
MaterialShapeDrawable dialogBackground = new MaterialShapeDrawable(shapeAppearanceModel);
|
||||||
|
|
||||||
|
dialogBackground.setTint(ThemeUtil.getThemedColor(requireContext(), R.attr.dialog_background_color));
|
||||||
|
|
||||||
|
dialog.getBehavior().addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
|
||||||
|
@Override
|
||||||
|
public void onStateChanged(@NonNull View bottomSheet, int newState) {
|
||||||
|
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
|
||||||
|
ViewCompat.setBackground(bottomSheet, dialogBackground);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
return inflater.inflate(R.layout.react_with_any_emoji_bottom_sheet_dialog_fragment, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
initializeViewModel();
|
||||||
|
|
||||||
|
categoryLabel = view.findViewById(R.id.category_label);
|
||||||
|
categoryPager = view.findViewById(R.id.category_pager);
|
||||||
|
|
||||||
|
adapter = new ReactWithAnyEmojiAdapter(viewModel.getEmojiPageModels(), this, this, (position, pageView) -> {
|
||||||
|
pageArray.put(position, pageView);
|
||||||
|
|
||||||
|
if (categoryPager.getCurrentItem() == position) {
|
||||||
|
updateFocusedRecycler(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onPageChanged = new OnPageChanged();
|
||||||
|
|
||||||
|
categoryPager.setAdapter(adapter);
|
||||||
|
categoryPager.registerOnPageChangeCallback(onPageChanged);
|
||||||
|
|
||||||
|
int startPateIndex = viewModel.getStartIndex();
|
||||||
|
|
||||||
|
categoryPager.setCurrentItem(startPateIndex, false);
|
||||||
|
presentCategoryLabel(viewModel.getCategoryIconAttr(startPateIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
FrameLayout container = requireDialog().findViewById(R.id.container);
|
||||||
|
LayoutInflater layoutInflater = LayoutInflater.from(requireContext());
|
||||||
|
View statusBarShader = layoutInflater.inflate(R.layout.react_with_any_emoji_status_fade, container, false);
|
||||||
|
TabLayout categoryTabs = (TabLayout) layoutInflater.inflate(R.layout.react_with_any_emoji_tabs, container, false);
|
||||||
|
|
||||||
|
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtil.getStatusBarHeight(container));
|
||||||
|
|
||||||
|
statusBarShader.setLayoutParams(params);
|
||||||
|
container.addView(statusBarShader, 0);
|
||||||
|
container.addView(categoryTabs);
|
||||||
|
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(container, (v, insets) -> insets.consumeSystemWindowInsets());
|
||||||
|
|
||||||
|
new TabLayoutMediator(categoryTabs, categoryPager, (tab, position) -> {
|
||||||
|
tab.setCustomView(react_with_any_emoji_tab)
|
||||||
|
.setIcon(ThemeUtil.getThemedDrawable(requireContext(), viewModel.getCategoryIconAttr(position)));
|
||||||
|
}).attach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
|
||||||
|
categoryPager.unregisterOnPageChangeCallback(onPageChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDismiss(@NonNull DialogInterface dialog) {
|
||||||
|
super.onDismiss(dialog);
|
||||||
|
|
||||||
|
callback.onReactWithAnyEmojiDialogDismissed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeViewModel() {
|
||||||
|
Bundle args = requireArguments();
|
||||||
|
ReactWithAnyEmojiRepository repository = new ReactWithAnyEmojiRepository(requireContext());
|
||||||
|
ReactWithAnyEmojiViewModel.Factory factory = new ReactWithAnyEmojiViewModel.Factory(repository, args.getLong(ARG_MESSAGE_ID), args.getBoolean(ARG_IS_MMS));
|
||||||
|
|
||||||
|
viewModel = ViewModelProviders.of(this, factory).get(ReactWithAnyEmojiViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEmojiSelected(String emoji) {
|
||||||
|
viewModel.onEmojiSelected(emoji);
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onKeyEvent(KeyEvent keyEvent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVariationSelectorStateChanged(boolean open) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFocusedRecycler(int position) {
|
||||||
|
for (int i = 0; i < pageArray.size(); i++) {
|
||||||
|
pageArray.valueAt(i).setRecyclerNestedScrollingEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmojiPageView toFocus = pageArray.get(position);
|
||||||
|
if (toFocus != null) {
|
||||||
|
toFocus.setRecyclerNestedScrollingEnabled(true);
|
||||||
|
categoryPager.requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
presentCategoryLabel(viewModel.getCategoryIconAttr(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void presentCategoryLabel(@AttrRes int iconAttr) {
|
||||||
|
switch (iconAttr) {
|
||||||
|
case R.attr.emoji_category_recent:
|
||||||
|
categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__recently_used));
|
||||||
|
break;
|
||||||
|
case R.attr.emoji_category_people:
|
||||||
|
categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__smileys_and_people));
|
||||||
|
break;
|
||||||
|
case R.attr.emoji_category_nature:
|
||||||
|
categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__nature));
|
||||||
|
break;
|
||||||
|
case R.attr.emoji_category_foods:
|
||||||
|
categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__food));
|
||||||
|
break;
|
||||||
|
case R.attr.emoji_category_activity:
|
||||||
|
categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__activities));
|
||||||
|
break;
|
||||||
|
case R.attr.emoji_category_places:
|
||||||
|
categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__places));
|
||||||
|
break;
|
||||||
|
case R.attr.emoji_category_objects:
|
||||||
|
categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__objects));
|
||||||
|
break;
|
||||||
|
case R.attr.emoji_category_symbols:
|
||||||
|
categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__symbols));
|
||||||
|
break;
|
||||||
|
case R.attr.emoji_category_flags:
|
||||||
|
categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__flags));
|
||||||
|
break;
|
||||||
|
case R.attr.emoji_category_emoticons:
|
||||||
|
categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__emoticons));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OnPageChanged extends ViewPager2.OnPageChangeCallback {
|
||||||
|
@Override
|
||||||
|
public void onPageSelected(int position) {
|
||||||
|
updateFocusedRecycler(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void onReactWithAnyEmojiDialogDismissed();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.thoughtcrime.securesms.reactions.any;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
|
||||||
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
final class ReactWithAnyEmojiRepository {
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final RecentEmojiPageModel recentEmojiPageModel;
|
||||||
|
private final List<EmojiPageModel> emojiPageModels;
|
||||||
|
|
||||||
|
ReactWithAnyEmojiRepository(@NonNull Context context) {
|
||||||
|
this.context = context;
|
||||||
|
this.recentEmojiPageModel = new RecentEmojiPageModel(context);
|
||||||
|
this.emojiPageModels = new LinkedList<>();
|
||||||
|
|
||||||
|
emojiPageModels.add(recentEmojiPageModel);
|
||||||
|
emojiPageModels.addAll(EmojiUtil.getDisplayPages());
|
||||||
|
emojiPageModels.remove(emojiPageModels.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EmojiPageModel> getEmojiPageModels() {
|
||||||
|
return emojiPageModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addEmojiToMessage(@NonNull String emoji, long messageId, boolean isMms) {
|
||||||
|
recentEmojiPageModel.onCodePointSelected(emoji);
|
||||||
|
|
||||||
|
SignalExecutors.BOUNDED.execute(() -> MessageSender.sendNewReaction(context, messageId, isMms, emoji));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.thoughtcrime.securesms.reactions.any;
|
||||||
|
|
||||||
|
import androidx.annotation.AttrRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class ReactWithAnyEmojiViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private final ReactWithAnyEmojiRepository repository;
|
||||||
|
private final long messageId;
|
||||||
|
private final boolean isMms;
|
||||||
|
|
||||||
|
private ReactWithAnyEmojiViewModel(@NonNull ReactWithAnyEmojiRepository repository, long messageId, boolean isMms) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.isMms = isMms;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EmojiPageModel> getEmojiPageModels() {
|
||||||
|
return repository.getEmojiPageModels();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStartIndex() {
|
||||||
|
return repository.getEmojiPageModels().get(0).getEmoji().size() == 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onEmojiSelected(@NonNull String emoji) {
|
||||||
|
repository.addEmojiToMessage(emoji, messageId, isMms);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AttrRes int getCategoryIconAttr(int position) {
|
||||||
|
return repository.getEmojiPageModels().get(position).getIconAttr();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Factory implements ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
private final ReactWithAnyEmojiRepository repository;
|
||||||
|
private final long messageId;
|
||||||
|
private final boolean isMms;
|
||||||
|
|
||||||
|
Factory(@NonNull ReactWithAnyEmojiRepository repository, long messageId, boolean isMms) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.isMms = isMms;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
return modelClass.cast(new ReactWithAnyEmojiViewModel(repository, messageId, isMms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -59,6 +59,7 @@ public final class FeatureFlags {
|
||||||
private static final String PROFILE_FOR_CALLING = "android.profileForCalling";
|
private static final String PROFILE_FOR_CALLING = "android.profileForCalling";
|
||||||
private static final String CALLING_PIP = "android.callingPip";
|
private static final String CALLING_PIP = "android.callingPip";
|
||||||
private static final String NEW_GROUP_UI = "android.newGroupUI";
|
private static final String NEW_GROUP_UI = "android.newGroupUI";
|
||||||
|
private static final String REACT_WITH_ANY_EMOJI = "android.reactWithAnyEmoji";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||||
|
@ -76,7 +77,8 @@ public final class FeatureFlags {
|
||||||
REMOTE_DELETE,
|
REMOTE_DELETE,
|
||||||
PROFILE_FOR_CALLING,
|
PROFILE_FOR_CALLING,
|
||||||
CALLING_PIP,
|
CALLING_PIP,
|
||||||
NEW_GROUP_UI
|
NEW_GROUP_UI,
|
||||||
|
REACT_WITH_ANY_EMOJI
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,7 +100,8 @@ public final class FeatureFlags {
|
||||||
*/
|
*/
|
||||||
private static final Set<String> HOT_SWAPPABLE = Sets.newHashSet(
|
private static final Set<String> HOT_SWAPPABLE = Sets.newHashSet(
|
||||||
PINS_MEGAPHONE_KILL_SWITCH,
|
PINS_MEGAPHONE_KILL_SWITCH,
|
||||||
ATTACHMENTS_V3
|
ATTACHMENTS_V3,
|
||||||
|
REACT_WITH_ANY_EMOJI
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -247,6 +250,11 @@ public final class FeatureFlags {
|
||||||
return getBoolean(NEW_GROUP_UI, false);
|
return getBoolean(NEW_GROUP_UI, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** React with Any Emoji */
|
||||||
|
public static boolean reactWithAnyEmoji() {
|
||||||
|
return getBoolean(REACT_WITH_ANY_EMOJI, false);
|
||||||
|
}
|
||||||
|
|
||||||
/** Only for rendering debug info. */
|
/** Only for rendering debug info. */
|
||||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||||
return new TreeMap<>(REMOTE_VALUES);
|
return new TreeMap<>(REMOTE_VALUES);
|
||||||
|
|
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 24 KiB |
18
app/src/main/res/drawable/ic_any_emoji_32.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="32dp"
|
||||||
|
android:height="32dp"
|
||||||
|
android:viewportWidth="32"
|
||||||
|
android:viewportHeight="32">
|
||||||
|
<path
|
||||||
|
android:pathData="M16,16m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0"
|
||||||
|
android:fillColor="@color/core_grey_80"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M8,16m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
|
||||||
|
android:fillColor="@color/core_grey_05"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M16,16m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
|
||||||
|
android:fillColor="@color/core_grey_05"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M24,16m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
|
||||||
|
android:fillColor="@color/core_grey_05"/>
|
||||||
|
</vector>
|
|
@ -49,12 +49,11 @@
|
||||||
app:layout_constraintRight_toRightOf="@id/reaction_3"
|
app:layout_constraintRight_toRightOf="@id/reaction_3"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<ImageView
|
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||||
android:id="@+id/reaction_1"
|
android:id="@+id/reaction_1"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:alpha="0"
|
android:alpha="0"
|
||||||
android:src="@drawable/reaction_scrubber_love_48"
|
|
||||||
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/reaction_2"
|
app:layout_constraintEnd_toStartOf="@id/reaction_2"
|
||||||
|
@ -63,12 +62,11 @@
|
||||||
tools:alpha="1"
|
tools:alpha="1"
|
||||||
tools:translationY="0dp" />
|
tools:translationY="0dp" />
|
||||||
|
|
||||||
<ImageView
|
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||||
android:id="@+id/reaction_2"
|
android:id="@+id/reaction_2"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:alpha="0"
|
android:alpha="0"
|
||||||
android:src="@drawable/reaction_scrubber_like_48"
|
|
||||||
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/reaction_3"
|
app:layout_constraintEnd_toStartOf="@id/reaction_3"
|
||||||
|
@ -77,12 +75,11 @@
|
||||||
tools:alpha="1"
|
tools:alpha="1"
|
||||||
tools:translationY="0dp" />
|
tools:translationY="0dp" />
|
||||||
|
|
||||||
<ImageView
|
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||||
android:id="@+id/reaction_3"
|
android:id="@+id/reaction_3"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:alpha="0"
|
android:alpha="0"
|
||||||
android:src="@drawable/reaction_scrubber_dislike_48"
|
|
||||||
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/reaction_4"
|
app:layout_constraintEnd_toStartOf="@id/reaction_4"
|
||||||
|
@ -91,12 +88,11 @@
|
||||||
tools:alpha="1"
|
tools:alpha="1"
|
||||||
tools:translationY="0dp" />
|
tools:translationY="0dp" />
|
||||||
|
|
||||||
<ImageView
|
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||||
android:id="@+id/reaction_4"
|
android:id="@+id/reaction_4"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:alpha="0"
|
android:alpha="0"
|
||||||
android:src="@drawable/reaction_scrubber_haha_48"
|
|
||||||
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/reaction_5"
|
app:layout_constraintEnd_toStartOf="@id/reaction_5"
|
||||||
|
@ -105,12 +101,11 @@
|
||||||
tools:alpha="1"
|
tools:alpha="1"
|
||||||
tools:translationY="0dp" />
|
tools:translationY="0dp" />
|
||||||
|
|
||||||
<ImageView
|
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||||
android:id="@+id/reaction_5"
|
android:id="@+id/reaction_5"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:alpha="0"
|
android:alpha="0"
|
||||||
android:src="@drawable/reaction_scrubber_wow_48"
|
|
||||||
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/reaction_6"
|
app:layout_constraintEnd_toStartOf="@id/reaction_6"
|
||||||
|
@ -119,12 +114,11 @@
|
||||||
tools:alpha="1"
|
tools:alpha="1"
|
||||||
tools:translationY="0dp" />
|
tools:translationY="0dp" />
|
||||||
|
|
||||||
<ImageView
|
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||||
android:id="@+id/reaction_6"
|
android:id="@+id/reaction_6"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:alpha="0"
|
android:alpha="0"
|
||||||
android:src="@drawable/reaction_scrubber_sad_48"
|
|
||||||
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/reaction_7"
|
app:layout_constraintEnd_toStartOf="@id/reaction_7"
|
||||||
|
@ -133,12 +127,11 @@
|
||||||
tools:alpha="1"
|
tools:alpha="1"
|
||||||
tools:translationY="0dp" />
|
tools:translationY="0dp" />
|
||||||
|
|
||||||
<ImageView
|
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||||
android:id="@+id/reaction_7"
|
android:id="@+id/reaction_7"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:alpha="0"
|
android:alpha="0"
|
||||||
android:src="@drawable/reaction_scrubber_angry_48"
|
|
||||||
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
android:translationY="@dimen/reaction_scrubber_anim_start_translation_y"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:minHeight="@dimen/react_with_any_emoji_bottom_sheet_dialog_fragment_min_height">
|
||||||
|
|
||||||
|
<TextSwitcher
|
||||||
|
android:id="@+id/category_label"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:inAnimation="@anim/fade_in"
|
||||||
|
android:outAnimation="@anim/fade_out"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/TextAppearance.Signal.Subtitle2"
|
||||||
|
android:textColor="?attr/title_text_color_secondary"
|
||||||
|
tools:text="Smileys & People" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/TextAppearance.Signal.Subtitle2"
|
||||||
|
android:textColor="?attr/title_text_color_secondary" />
|
||||||
|
</TextSwitcher>
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/category_pager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginBottom="@dimen/react_with_any_emoji_bottom_sheet_dialog_fragment_tabs_height"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/category_label"
|
||||||
|
app:layout_constraintVertical_bias="0" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="?reactions_screen_shade_color"
|
||||||
|
android:fitsSystemWindows="true" />
|
9
app/src/main/res/layout/react_with_any_emoji_tab.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@android:id/icon"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
tools:src="?attr/emoji_category_recent" />
|
15
app/src/main/res/layout/react_with_any_emoji_tabs.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.tabs.TabLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/category_tabs"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/react_with_any_emoji_bottom_sheet_dialog_fragment_tabs_height"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:background="?attr/emoji_tab_strip_background"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:tabIndicatorColor="@color/core_ultramarine"
|
||||||
|
app:tabPadding="0dp"
|
||||||
|
app:tabPaddingBottom="0dp"
|
||||||
|
app:tabPaddingEnd="0dp"
|
||||||
|
app:tabPaddingStart="0dp"
|
||||||
|
app:tabPaddingTop="0dp" />
|
|
@ -35,12 +35,12 @@
|
||||||
<item name="android:navigationBarColor">@color/media_preview_bar_background</item>
|
<item name="android:navigationBarColor">@color/media_preview_bar_background</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Design.Light.BottomSheetDialog.Fixed">
|
<style name="Theme.Signal.Light.BottomSheetDialog.Fixed">
|
||||||
<item name="android:windowIsFloating">false</item>
|
<item name="android:windowIsFloating">false</item>
|
||||||
<item name="android:statusBarColor">@color/transparent</item>
|
<item name="android:statusBarColor">@color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Design.BottomSheetDialog.Fixed">
|
<style name="Theme.Signal.BottomSheetDialog.Fixed">
|
||||||
<item name="android:windowIsFloating">false</item>
|
<item name="android:windowIsFloating">false</item>
|
||||||
<item name="android:statusBarColor">@color/core_grey_95</item>
|
<item name="android:statusBarColor">@color/core_grey_95</item>
|
||||||
<item name="android:navigationBarColor">@color/transparent</item>
|
<item name="android:navigationBarColor">@color/transparent</item>
|
||||||
|
|
|
@ -34,13 +34,13 @@
|
||||||
<item name="android:navigationBarColor">@color/core_grey_95</item>
|
<item name="android:navigationBarColor">@color/core_grey_95</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Design.Light.BottomSheetDialog.Fixed">
|
<style name="Theme.Signal.Light.BottomSheetDialog.Fixed">
|
||||||
<item name="android:windowIsFloating">false</item>
|
<item name="android:windowIsFloating">false</item>
|
||||||
<item name="android:windowLightStatusBar">false</item>
|
<item name="android:windowLightStatusBar">false</item>
|
||||||
<item name="android:statusBarColor">@color/transparent</item>
|
<item name="android:statusBarColor">@color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Design.BottomSheetDialog.Fixed">
|
<style name="Theme.Signal.BottomSheetDialog.Fixed">
|
||||||
<item name="android:windowIsFloating">false</item>
|
<item name="android:windowIsFloating">false</item>
|
||||||
<item name="android:navigationBarColor">@color/core_grey_95</item>
|
<item name="android:navigationBarColor">@color/core_grey_95</item>
|
||||||
<item name="android:statusBarColor">@color/transparent</item>
|
<item name="android:statusBarColor">@color/transparent</item>
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<item name="android:windowLightNavigationBar">false</item>
|
<item name="android:windowLightNavigationBar">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Design.Light.BottomSheetDialog.Fixed">
|
<style name="Theme.Signal.Light.BottomSheetDialog.Fixed">
|
||||||
<item name="android:windowIsFloating">false</item>
|
<item name="android:windowIsFloating">false</item>
|
||||||
<item name="android:windowLightStatusBar">false</item>
|
<item name="android:windowLightStatusBar">false</item>
|
||||||
<item name="android:statusBarColor">@color/transparent</item>
|
<item name="android:statusBarColor">@color/transparent</item>
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
<item name="android:windowLightNavigationBar">true</item>
|
<item name="android:windowLightNavigationBar">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Design.BottomSheetDialog.Fixed">
|
<style name="Theme.Signal.BottomSheetDialog.Fixed">
|
||||||
<item name="android:windowIsFloating">false</item>
|
<item name="android:windowIsFloating">false</item>
|
||||||
<item name="android:statusBarColor">@color/transparent</item>
|
<item name="android:statusBarColor">@color/transparent</item>
|
||||||
<item name="android:windowLightStatusBar">false</item>
|
<item name="android:windowLightStatusBar">false</item>
|
||||||
|
|
|
@ -156,4 +156,7 @@
|
||||||
<dimen name="picture_in_picture_gesture_helper_pip_width">90dp</dimen>
|
<dimen name="picture_in_picture_gesture_helper_pip_width">90dp</dimen>
|
||||||
<dimen name="picture_in_picture_gesture_helper_pip_height">160dp</dimen>
|
<dimen name="picture_in_picture_gesture_helper_pip_height">160dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="react_with_any_emoji_bottom_sheet_dialog_fragment_min_height">340dp</dimen>
|
||||||
|
<dimen name="react_with_any_emoji_bottom_sheet_dialog_fragment_tabs_height">36dp</dimen>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1647,6 +1647,18 @@
|
||||||
<string name="HelpFragment__please_be_as_descriptive_as_possible">Please be as descriptive as possible to help us understand the issue.</string>
|
<string name="HelpFragment__please_be_as_descriptive_as_possible">Please be as descriptive as possible to help us understand the issue.</string>
|
||||||
<string name="HelpFragment__no_email_app_found">No email app found.</string>
|
<string name="HelpFragment__no_email_app_found">No email app found.</string>
|
||||||
|
|
||||||
|
<!-- ReactWithAnyEmojiBottomSheetDialogFragment -->
|
||||||
|
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__recently_used">Recently Used</string>
|
||||||
|
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__smileys_and_people">Smileys & People</string>
|
||||||
|
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__nature">Nature</string>
|
||||||
|
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__food">Food</string>
|
||||||
|
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__activities">Activities</string>
|
||||||
|
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__places">Places</string>
|
||||||
|
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__objects">Objects</string>
|
||||||
|
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__symbols">Symbols</string>
|
||||||
|
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__flags">Flags</string>
|
||||||
|
<string name="ReactWithAnyEmojiBottomSheetDialogFragment__emoticons">Emoticons</string>
|
||||||
|
|
||||||
<!-- arrays.xml -->
|
<!-- arrays.xml -->
|
||||||
<string name="arrays__import_export">Import</string>
|
<string name="arrays__import_export">Import</string>
|
||||||
<string name="arrays__use_default">Use default</string>
|
<string name="arrays__use_default">Use default</string>
|
||||||
|
|
|
@ -789,14 +789,41 @@
|
||||||
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">false</item>
|
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Design.Light.BottomSheetDialog.Fixed">
|
<style name="Theme.Signal.Light.BottomSheetDialog" parent="Theme.Design.Light.BottomSheetDialog">
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Signal.BottomSheetDialog" parent="Theme.Design.BottomSheetDialog">
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Signal.Light.BottomSheetDialog.Fixed">
|
||||||
<item name="android:windowIsFloating">false</item>
|
<item name="android:windowIsFloating">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Design.BottomSheetDialog.Fixed">
|
<style name="Theme.Signal.BottomSheetDialog.Fixed" parent="Theme.Design.BottomSheetDialog">
|
||||||
<item name="android:windowIsFloating">false</item>
|
<item name="android:windowIsFloating">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Signal.BottomSheetDialog.Fixed.ReactWithAny">
|
||||||
|
<item name="bottomSheetStyle">@style/Widget.Signal.BottomSheet.ReactWithAny</item>
|
||||||
|
<item name="android:backgroundDimEnabled">false</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Signal.Light.BottomSheetDialog.Fixed.ReactWithAny">
|
||||||
|
<item name="bottomSheetStyle">@style/Widget.Signal.BottomSheet.ReactWithAny.Light</item>
|
||||||
|
<item name="android:backgroundDimEnabled">false</item>
|
||||||
|
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Signal.BottomSheet.Rounded</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Signal.BottomSheet.ReactWithAny" parent="Widget.MaterialComponents.BottomSheet">
|
||||||
|
<item name="backgroundTint">@color/core_grey_75</item>
|
||||||
|
<item name="behavior_peekHeight">@dimen/react_with_any_emoji_bottom_sheet_dialog_fragment_min_height</item>
|
||||||
|
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Signal.BottomSheet.Rounded</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Signal.BottomSheet.ReactWithAny.Light">
|
||||||
|
<item name="backgroundTint">@color/white</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="ShapeAppearanceOverlay.Signal.BottomSheet.Rounded" parent="">
|
<style name="ShapeAppearanceOverlay.Signal.BottomSheet.Rounded" parent="">
|
||||||
<item name="cornerFamily">rounded</item>
|
<item name="cornerFamily">rounded</item>
|
||||||
<item name="cornerSizeTopRight">8dp</item>
|
<item name="cornerSizeTopRight">8dp</item>
|
||||||
|
|