diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2b9ff89c3..f56143f9f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -595,6 +595,10 @@
android:screenOrientation="portrait"
android:theme="@style/Theme.Signal.WallpaperCropper" />
+
+
diff --git a/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/AlphaTransition.kt b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/AlphaTransition.kt
new file mode 100644
index 000000000..f678762e8
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/AlphaTransition.kt
@@ -0,0 +1,44 @@
+package org.thoughtcrime.securesms.animation.transitions
+
+import android.animation.Animator
+import android.animation.ObjectAnimator
+import android.view.View
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.transition.Transition
+import androidx.transition.TransitionValues
+
+private const val ALPHA = "signal.alpha_transition.alpha"
+
+/**
+ * Alpha transition that can be used with [ConstraintLayout]
+ */
+class AlphaTransition : Transition() {
+
+ override fun captureStartValues(transitionValues: TransitionValues) {
+ captureValues(transitionValues)
+ }
+
+ override fun captureEndValues(transitionValues: TransitionValues) {
+ captureValues(transitionValues)
+ }
+
+ private fun captureValues(transitionValues: TransitionValues) {
+ val view: View = transitionValues.view
+ if (view !is ConstraintLayout) {
+ transitionValues.values[ALPHA] = view.alpha
+ }
+ }
+
+ override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
+ if (startValues == null || endValues == null) {
+ return null
+ }
+
+ val view: View = endValues.view
+ val startAlpha: Float = startValues.values[ALPHA] as? Float ?: view.alpha
+ val endAlpha: Float = endValues.values[ALPHA] as? Float ?: view.alpha
+
+ return ObjectAnimator.ofFloat(view, "alpha", startAlpha, endAlpha)
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java
index b6593b5ee..2a739231b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java
@@ -118,11 +118,15 @@ public final class ConversationReactionOverlay extends RelativeLayout {
toolbar.setOnMenuItemClickListener(this::handleToolbarItemClicked);
toolbar.setNavigationOnClickListener(view -> hide());
- emojiViews = Stream.of(ReactionEmoji.values())
- .map(e -> findViewById(e.viewId))
- .toArray(EmojiImageView[]::new);
+ emojiViews = new EmojiImageView[] { findViewById(R.id.reaction_1),
+ findViewById(R.id.reaction_2),
+ findViewById(R.id.reaction_3),
+ findViewById(R.id.reaction_4),
+ findViewById(R.id.reaction_5),
+ findViewById(R.id.reaction_6),
+ findViewById(R.id.reaction_7) };
- customEmojiIndex = ReactionEmoji.values().length - 1;
+ customEmojiIndex = emojiViews.length - 1;
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);
@@ -364,7 +368,8 @@ public final class ConversationReactionOverlay extends RelativeLayout {
}
private void setupSelectedEmoji() {
- final String oldEmoji = getOldEmoji(messageRecord);
+ final List emojis = SignalStore.emojiValues().getReactions();
+ final String oldEmoji = getOldEmoji(messageRecord);
if (oldEmoji == null) {
selectedView.setVisibility(View.GONE);
@@ -380,7 +385,7 @@ public final class ConversationReactionOverlay extends RelativeLayout {
view.setTranslationY(0);
boolean isAtCustomIndex = i == customEmojiIndex;
- boolean isNotAtCustomIndexAndOldEmojiMatches = !isAtCustomIndex && oldEmoji != null && ReactionEmoji.values()[i].emoji.equals(EmojiUtil.getCanonicalRepresentation(oldEmoji));
+ boolean isNotAtCustomIndexAndOldEmojiMatches = !isAtCustomIndex && oldEmoji != null && emojis.get(i).equals(EmojiUtil.getCanonicalRepresentation(oldEmoji));
boolean isAtCustomIndexAndOldEmojiExists = isAtCustomIndex && oldEmoji != null;
if (!foundSelected &&
@@ -401,13 +406,13 @@ public final class ConversationReactionOverlay extends RelativeLayout {
view.setImageEmoji(oldEmoji);
view.setTag(oldEmoji);
} else {
- view.setImageEmoji(SignalStore.emojiValues().getPreferredVariation(ReactionEmoji.values()[i].emoji));
+ view.setImageEmoji(SignalStore.emojiValues().getPreferredVariation(emojis.get(i)));
}
} else if (isAtCustomIndex) {
view.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.ic_any_emoji_32));
view.setTag(null);
} else {
- view.setImageEmoji(SignalStore.emojiValues().getPreferredVariation(ReactionEmoji.values()[i].emoji));
+ view.setImageEmoji(SignalStore.emojiValues().getPreferredVariation(emojis.get(i)));
}
}
}
@@ -469,7 +474,7 @@ public final class ConversationReactionOverlay extends RelativeLayout {
if (selected == customEmojiIndex) {
onReactionSelectedListener.onCustomReactionSelected(messageRecord, emojiViews[selected].getTag() != null);
} else {
- onReactionSelectedListener.onReactionSelected(messageRecord, SignalStore.emojiValues().getPreferredVariation(ReactionEmoji.values()[selected].emoji));
+ onReactionSelectedListener.onReactionSelected(messageRecord, SignalStore.emojiValues().getPreferredVariation(SignalStore.emojiValues().getReactions().get(selected)));
}
} else {
hide();
@@ -642,24 +647,6 @@ public final class ConversationReactionOverlay extends RelativeLayout {
}
}
- private enum ReactionEmoji {
- HEART(R.id.reaction_1, "\u2764\ufe0f"),
- THUMBS_UP(R.id.reaction_2, "\ud83d\udc4d"),
- THUMBS_DOWN(R.id.reaction_3, "\ud83d\udc4e"),
- LAUGH(R.id.reaction_4, "\ud83d\ude02"),
- SURPRISE(R.id.reaction_5, "\ud83d\ude2e"),
- SAD(R.id.reaction_6, "\ud83d\ude22"),
- ANGRY(R.id.reaction_7, "\ud83d\ude21");
-
- final @IdRes int viewId;
- final String emoji;
-
- ReactionEmoji(int viewId, String emoji) {
- this.viewId = viewId;
- this.emoji = emoji;
- }
- }
-
private enum OverlayState {
HIDDEN,
UNINITAILIZED,
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java
index 54babdfa5..11253490c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java
@@ -116,7 +116,6 @@ import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity;
import org.thoughtcrime.securesms.payments.preferences.details.PaymentDetailsFragmentArgs;
import org.thoughtcrime.securesms.payments.preferences.details.PaymentDetailsParcelable;
import org.thoughtcrime.securesms.permissions.Permissions;
-import org.thoughtcrime.securesms.ratelimit.RecaptchaProofActivity;
import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.KeyCachingService;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java
index d597a9a6a..0b8b93a89 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java
@@ -1,16 +1,28 @@
package org.thoughtcrime.securesms.keyvalue;
+import android.text.TextUtils;
+
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
+import org.thoughtcrime.securesms.util.Util;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class EmojiValues extends SignalStoreValues {
- private static final String PREFIX = "emojiPref__";
+ public static final List DEFAULT_REACTIONS_LIST = Arrays.asList("\u2764\ufe0f",
+ "\ud83d\udc4d",
+ "\ud83d\udc4e",
+ "\ud83d\ude02",
+ "\ud83d\ude2e",
+ "\ud83d\ude22");
+
+ private static final String PREFIX = "emojiPref__";
private static final String NEXT_SCHEDULED_CHECK = PREFIX + "next_scheduled_check";
+ private static final String REACTIONS_LIST = PREFIX + "reactions_list";
EmojiValues(@NonNull KeyValueStore store) {
super(store);
@@ -23,7 +35,7 @@ public class EmojiValues extends SignalStoreValues {
@Override
@NonNull List getKeysToIncludeInBackup() {
- return Collections.emptyList();
+ return Collections.singletonList(REACTIONS_LIST);
}
public long getNextScheduledCheck() {
@@ -49,4 +61,17 @@ public class EmojiValues extends SignalStoreValues {
return getString(PREFIX + canonical, emoji);
}
+
+ public @NonNull List getReactions() {
+ String list = getString(REACTIONS_LIST, "");
+ if (TextUtils.isEmpty(list)) {
+ return DEFAULT_REACTIONS_LIST;
+ } else {
+ return Arrays.asList(list.split(","));
+ }
+ }
+
+ public void setReactions(List reactions) {
+ putString(REACTIONS_LIST, Util.join(reactions, ","));
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiBottomSheetDialogFragment.java
index 518a580f8..c2e200b99 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiBottomSheetDialogFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiBottomSheetDialogFragment.java
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.reactions.any;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
import android.os.Bundle;
import android.util.SparseArray;
import android.view.KeyEvent;
@@ -35,6 +36,7 @@ import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.reactions.ReactionsLoader;
+import org.thoughtcrime.securesms.reactions.edit.EditReactionsActivity;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -53,6 +55,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
private static final String ARG_START_PAGE = "arg_start_page";
private static final String ARG_SHADOWS = "arg_shadows";
private static final String ARG_RECENT_KEY = "arg_recent_key";
+ private static final String ARG_EDIT = "arg_edit";
private ReactWithAnyEmojiViewModel viewModel;
private TextSwitcher categoryLabel;
@@ -62,6 +65,8 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
private SparseArray pageArray = new SparseArray<>();
private Callback callback;
private ReactionsLoader reactionsLoader;
+ private View editReactions;
+ private boolean showEditReactions;
public static DialogFragment createForMessageRecord(@NonNull MessageRecord messageRecord, int startingPage) {
DialogFragment fragment = new ReactWithAnyEmojiBottomSheetDialogFragment();
@@ -72,6 +77,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
args.putInt(ARG_START_PAGE, startingPage);
args.putBoolean(ARG_SHADOWS, false);
args.putString(ARG_RECENT_KEY, REACTION_STORAGE_KEY);
+ args.putBoolean(ARG_EDIT, true);
fragment.setArguments(args);
return fragment;
@@ -91,11 +97,29 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
return fragment;
}
+ public static DialogFragment createForEditReactions() {
+ DialogFragment fragment = new ReactWithAnyEmojiBottomSheetDialogFragment();
+ Bundle args = new Bundle();
+
+ args.putLong(ARG_MESSAGE_ID, -1);
+ args.putBoolean(ARG_IS_MMS, false);
+ args.putInt(ARG_START_PAGE, -1);
+ args.putBoolean(ARG_SHADOWS, false);
+ args.putString(ARG_RECENT_KEY, REACTION_STORAGE_KEY);
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
- callback = (Callback) context;
+ if (getParentFragment() instanceof Callback) {
+ callback = (Callback) getParentFragment();
+ } else {
+ callback = (Callback) context;
+ }
}
@Override
@@ -159,6 +183,12 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
categoryLabel = view.findViewById(R.id.category_label);
categoryPager = view.findViewById(R.id.category_pager);
+ editReactions = view.findViewById(R.id.edit_reactions);
+
+ showEditReactions = requireArguments().getBoolean(ARG_EDIT, false);
+ if (showEditReactions) {
+ editReactions.setOnClickListener(v -> startActivity(new Intent(requireContext(), EditReactionsActivity.class)));
+ }
adapter = new ReactWithAnyEmojiAdapter(this, this, (position, pageView) -> {
pageArray.put(position, pageView);
@@ -264,6 +294,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
}
categoryLabel.setText(getString(adapter.getItem(position).getLabel()));
+ editReactions.setVisibility(showEditReactions && position == 0 ? View.VISIBLE : View.GONE);
}
private int getStartingPage(boolean firstPageHasContent) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsActivity.kt
new file mode 100644
index 000000000..cd8bd07e2
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsActivity.kt
@@ -0,0 +1,27 @@
+package org.thoughtcrime.securesms.reactions.edit
+
+import android.os.Bundle
+import org.thoughtcrime.securesms.PassphraseRequiredActivity
+import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
+import org.thoughtcrime.securesms.util.DynamicTheme
+
+class EditReactionsActivity : PassphraseRequiredActivity() {
+
+ private val theme: DynamicTheme = DynamicNoActionBarTheme()
+
+ override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
+ super.onCreate(savedInstanceState, ready)
+ theme.onCreate(this)
+
+ if (savedInstanceState == null) {
+ supportFragmentManager.beginTransaction()
+ .replace(android.R.id.content, EditReactionsFragment())
+ .commit()
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ theme.onResume(this)
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsFragment.kt
new file mode 100644
index 000000000..70bea4598
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsFragment.kt
@@ -0,0 +1,176 @@
+package org.thoughtcrime.securesms.reactions.edit
+
+import android.animation.ObjectAnimator
+import android.os.Bundle
+import android.view.View
+import android.view.animation.Animation
+import android.view.animation.AnimationUtils
+import androidx.appcompat.widget.Toolbar
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.ViewModelProviders
+import androidx.transition.ChangeBounds
+import androidx.transition.Transition
+import androidx.transition.TransitionManager
+import androidx.transition.TransitionSet
+import org.thoughtcrime.securesms.LoggingFragment
+import org.thoughtcrime.securesms.R
+import org.thoughtcrime.securesms.animation.transitions.AlphaTransition
+import org.thoughtcrime.securesms.components.emoji.EmojiImageView
+import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment
+import org.thoughtcrime.securesms.util.ViewUtil
+
+private val SELECTED_SIZE = ViewUtil.dpToPx(36)
+private val UNSELECTED_SIZE = ViewUtil.dpToPx(26)
+
+/**
+ * Edit default reactions that show when long pressing.
+ */
+class EditReactionsFragment : LoggingFragment(R.layout.edit_reactions_fragment), ReactWithAnyEmojiBottomSheetDialogFragment.Callback {
+
+ private lateinit var toolbar: Toolbar
+ private lateinit var reactionViews: List
+ private lateinit var scrubber: ConstraintLayout
+ private lateinit var mask: View
+
+ private lateinit var defaultSet: ConstraintSet
+
+ private lateinit var viewModel: EditReactionsViewModel
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ toolbar = view.findViewById(R.id.toolbar)
+ toolbar.setTitle(R.string.EditReactionsFragment__edit_reactions)
+ toolbar.setNavigationOnClickListener {
+ requireActivity().onBackPressed()
+ }
+
+ reactionViews = listOf(
+ view.findViewById(R.id.reaction_1),
+ view.findViewById(R.id.reaction_2),
+ view.findViewById(R.id.reaction_3),
+ view.findViewById(R.id.reaction_4),
+ view.findViewById(R.id.reaction_5),
+ view.findViewById(R.id.reaction_6)
+ )
+ reactionViews.forEach { it.setOnClickListener(this::onEmojiClick) }
+
+ scrubber = view.findViewById(R.id.edit_reactions_fragment_scrubber)
+ defaultSet = ConstraintSet().apply { clone(scrubber) }
+
+ mask = view.findViewById(R.id.edit_reactions_fragment_reaction_mask)
+
+ view.findViewById(R.id.edit_reactions_reset_emoji).setOnClickListener { viewModel.resetToDefaults() }
+ view.findViewById(R.id.edit_reactions_fragment_save).setOnClickListener {
+ viewModel.save()
+ requireActivity().onBackPressed()
+ }
+
+ viewModel = ViewModelProviders.of(this).get(EditReactionsViewModel::class.java)
+
+ viewModel.reactions.observe(viewLifecycleOwner) { emojis ->
+ emojis.forEachIndexed { index, emoji -> reactionViews[index].setImageEmoji(emoji) }
+ }
+
+ viewModel.selection.observe(viewLifecycleOwner) { selection ->
+ if (selection == EditReactionsViewModel.NO_SELECTION) {
+ deselectAll()
+ ObjectAnimator.ofFloat(mask, "alpha", 0f).start()
+ } else {
+ ObjectAnimator.ofFloat(mask, "alpha", 1f).start()
+ select(reactionViews[selection])
+ ReactWithAnyEmojiBottomSheetDialogFragment.createForEditReactions().show(childFragmentManager, REACT_SHEET_TAG)
+ }
+ }
+
+ view.setOnClickListener { viewModel.setSelection(EditReactionsViewModel.NO_SELECTION) }
+ }
+
+ private fun select(emojiImageView: EmojiImageView) {
+ val set = ConstraintSet()
+ set.clone(scrubber)
+ reactionViews.forEach { view ->
+ view.clearAnimation()
+ view.rotation = 0f
+ if (view.id == emojiImageView.id) {
+ set.constrainWidth(view.id, SELECTED_SIZE)
+ set.constrainHeight(view.id, SELECTED_SIZE)
+ set.setAlpha(view.id, 1f)
+ } else {
+ set.constrainWidth(view.id, UNSELECTED_SIZE)
+ set.constrainHeight(view.id, UNSELECTED_SIZE)
+ set.setAlpha(view.id, 0.3f)
+ }
+ }
+
+ TransitionManager.beginDelayedTransition(scrubber, createSelectTransitionSet(emojiImageView))
+ set.applyTo(scrubber)
+ }
+
+ private fun deselectAll() {
+ reactionViews.forEach { it.clearAnimation() }
+
+ TransitionManager.beginDelayedTransition(scrubber, createTransitionSet())
+ defaultSet.applyTo(scrubber)
+ }
+
+ private fun onEmojiClick(view: View) {
+ viewModel.setSelection(reactionViews.indexOf(view))
+ }
+
+ override fun onReactWithAnyEmojiDialogDismissed() {
+ viewModel.setSelection(EditReactionsViewModel.NO_SELECTION)
+ }
+
+ override fun onReactWithAnyEmojiPageChanged(page: Int) {
+ }
+
+ override fun onReactWithAnyEmojiSelected(emoji: String) {
+ viewModel.onEmojiSelected(emoji)
+ }
+
+ companion object {
+
+ private const val REACT_SHEET_TAG = "REACT_SHEET_TAG"
+
+ private fun createTransitionSet(): Transition {
+ return TransitionSet().apply {
+ ordering = TransitionSet.ORDERING_TOGETHER
+ duration = 250
+ addTransition(AlphaTransition())
+ addTransition(ChangeBounds())
+ }
+ }
+
+ private fun createSelectTransitionSet(target: View): Transition {
+ return createTransitionSet().addListener(object : Transition.TransitionListener {
+ override fun onTransitionEnd(transition: Transition) {
+ startRockingAnimation(target)
+ }
+
+ override fun onTransitionStart(transition: Transition) = Unit
+ override fun onTransitionCancel(transition: Transition) = Unit
+ override fun onTransitionPause(transition: Transition) = Unit
+ override fun onTransitionResume(transition: Transition) = Unit
+ })
+ }
+
+ private fun startRockingAnimation(target: View) {
+ val startRocking: Animation = AnimationUtils.loadAnimation(target.context, R.anim.rock_start)
+ startRocking.setAnimationListener(object : Animation.AnimationListener {
+ override fun onAnimationEnd(animation: Animation?) {
+ val continualRocking: Animation = AnimationUtils.loadAnimation(target.context, R.anim.rock)
+ continualRocking.repeatCount = Animation.INFINITE
+ continualRocking.repeatMode = Animation.REVERSE
+ target.startAnimation(continualRocking)
+ }
+
+ override fun onAnimationStart(animation: Animation?) = Unit
+ override fun onAnimationRepeat(animation: Animation?) = Unit
+ })
+
+ target.clearAnimation()
+ target.startAnimation(startRocking)
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsViewModel.kt
new file mode 100644
index 000000000..1fb0d01dd
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/edit/EditReactionsViewModel.kt
@@ -0,0 +1,47 @@
+package org.thoughtcrime.securesms.reactions.edit
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.ViewModel
+import org.thoughtcrime.securesms.keyvalue.EmojiValues
+import org.thoughtcrime.securesms.keyvalue.SignalStore
+import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
+import org.thoughtcrime.securesms.util.livedata.Store
+
+class EditReactionsViewModel : ViewModel() {
+
+ private val emojiValues: EmojiValues = SignalStore.emojiValues()
+ private val store: Store = Store(State(reactions = emojiValues.reactions.map { emojiValues.getPreferredVariation(it) }))
+
+ val reactions: LiveData> = LiveDataUtil.mapDistinct(store.stateLiveData, State::reactions)
+ val selection: LiveData = LiveDataUtil.mapDistinct(store.stateLiveData, State::selection)
+
+ fun setSelection(selection: Int) {
+ store.update { it.copy(selection = selection) }
+ }
+
+ fun onEmojiSelected(emoji: String) {
+ store.update { state ->
+ if (state.selection != NO_SELECTION && state.selection in state.reactions.indices) {
+ val preferredEmoji: String = emojiValues.getPreferredVariation(emoji)
+ val newReactions: List = state.reactions.toMutableList().apply { set(state.selection, preferredEmoji) }
+ state.copy(reactions = newReactions)
+ } else {
+ state
+ }
+ }
+ }
+
+ fun resetToDefaults() {
+ store.update { it.copy(reactions = EmojiValues.DEFAULT_REACTIONS_LIST) }
+ }
+
+ fun save() {
+ emojiValues.reactions = store.state.reactions
+ }
+
+ companion object {
+ const val NO_SELECTION: Int = -1
+ }
+
+ data class State(val selection: Int = NO_SELECTION, val reactions: List)
+}
diff --git a/app/src/main/res/anim/rock.xml b/app/src/main/res/anim/rock.xml
new file mode 100644
index 000000000..5ec7ce836
--- /dev/null
+++ b/app/src/main/res/anim/rock.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/app/src/main/res/anim/rock_start.xml b/app/src/main/res/anim/rock_start.xml
new file mode 100644
index 000000000..74c8759ba
--- /dev/null
+++ b/app/src/main/res/anim/rock_start.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/app/src/main/res/drawable-night/conversation_reaction_overlay_background.xml b/app/src/main/res/drawable-night/conversation_reaction_overlay_background.xml
index 0ff9bdd27..5cdbdaea4 100644
--- a/app/src/main/res/drawable-night/conversation_reaction_overlay_background.xml
+++ b/app/src/main/res/drawable-night/conversation_reaction_overlay_background.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-night/ic_any_emoji_32.xml b/app/src/main/res/drawable-night/ic_any_emoji_32.xml
index 3e9a813a1..459675901 100644
--- a/app/src/main/res/drawable-night/ic_any_emoji_32.xml
+++ b/app/src/main/res/drawable-night/ic_any_emoji_32.xml
@@ -5,7 +5,7 @@
android:viewportHeight="32">
+ android:fillColor="@color/core_grey_70"/>
diff --git a/app/src/main/res/drawable-v21/dsl_preference_item_background_borderless.xml b/app/src/main/res/drawable-v21/dsl_preference_item_background_borderless.xml
new file mode 100644
index 000000000..a12f00fd6
--- /dev/null
+++ b/app/src/main/res/drawable-v21/dsl_preference_item_background_borderless.xml
@@ -0,0 +1,3 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/dsl_preference_item_background_borderless.xml b/app/src/main/res/drawable/dsl_preference_item_background_borderless.xml
new file mode 100644
index 000000000..b17ebdd4f
--- /dev/null
+++ b/app/src/main/res/drawable/dsl_preference_item_background_borderless.xml
@@ -0,0 +1,6 @@
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/edit_reactions_fragment.xml b/app/src/main/res/layout/edit_reactions_fragment.xml
new file mode 100644
index 000000000..56854d865
--- /dev/null
+++ b/app/src/main/res/layout/edit_reactions_fragment.xml
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/react_with_any_emoji_bottom_sheet_dialog_fragment.xml b/app/src/main/res/layout/react_with_any_emoji_bottom_sheet_dialog_fragment.xml
index fd3b88834..4b7e2c295 100644
--- a/app/src/main/res/layout/react_with_any_emoji_bottom_sheet_dialog_fragment.xml
+++ b/app/src/main/res/layout/react_with_any_emoji_bottom_sheet_dialog_fragment.xml
@@ -15,7 +15,7 @@
android:layout_marginEnd="10dp"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/edit_reactions"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@@ -23,16 +23,29 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Signal.Subtitle2"
- android:textColor="@color/signal_text_secondary"
+ android:textColor="@color/signal_icon_tint_primary"
tools:text="Smileys & People" />
+ android:textColor="@color/signal_icon_tint_primary" />
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/navigation/app_settings.xml b/app/src/main/res/navigation/app_settings.xml
index 342f82e90..fa869f697 100644
--- a/app/src/main/res/navigation/app_settings.xml
+++ b/app/src/main/res/navigation/app_settings.xml
@@ -206,6 +206,13 @@
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 988b84c86..bf9d50c64 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -3502,6 +3502,12 @@
Top edge selector
Bottom edge selector
+
+ Edit Reactions
+ Tap to replace an emoji
+ Reset emoji
+ Save
+