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 +