mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 10:20:25 +01:00
Add ability to edit default reactions.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<String> 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<String> 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<String> getReactions() {
|
||||
String list = getString(REACTIONS_LIST, "");
|
||||
if (TextUtils.isEmpty(list)) {
|
||||
return DEFAULT_REACTIONS_LIST;
|
||||
} else {
|
||||
return Arrays.asList(list.split(","));
|
||||
}
|
||||
}
|
||||
|
||||
public void setReactions(List<String> reactions) {
|
||||
putString(REACTIONS_LIST, Util.join(reactions, ","));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ReactWithAnyEmojiAdapter.ScrollableChild> 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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<EmojiImageView>
|
||||
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<View>(R.id.edit_reactions_reset_emoji).setOnClickListener { viewModel.resetToDefaults() }
|
||||
view.findViewById<View>(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<State> = Store(State(reactions = emojiValues.reactions.map { emojiValues.getPreferredVariation(it) }))
|
||||
|
||||
val reactions: LiveData<List<String>> = LiveDataUtil.mapDistinct(store.stateLiveData, State::reactions)
|
||||
val selection: LiveData<Int> = 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<String> = 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<String>)
|
||||
}
|
||||
Reference in New Issue
Block a user