kopia lustrzana https://github.com/ryukoposting/Signal-Android
Add ability to edit default reactions.
rodzic
811bef8c35
commit
e5b0941d30
|
@ -595,6 +595,10 @@
|
|||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.Signal.WallpaperCropper" />
|
||||
|
||||
<activity android:name=".reactions.edit.EditReactionsActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<service android:enabled="true" android:name=".service.webrtc.WebRtcCallService"/>
|
||||
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
||||
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
||||
|
|
|
@ -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>)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="200"
|
||||
android:fromDegrees="-8"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toDegrees="8" />
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="100"
|
||||
android:fromDegrees="0"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toDegrees="-8" />
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/transparent_black_80" />
|
||||
<solid android:color="@color/core_grey_80" />
|
||||
<corners android:radius="30dp" />
|
||||
</shape>
|
|
@ -5,7 +5,7 @@
|
|||
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"/>
|
||||
android:fillColor="@color/core_grey_70"/>
|
||||
<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"/>
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/settings_ripple_color" />
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<color android:color="@color/settings_ripple_color" />
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,155 @@
|
|||
<?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">
|
||||
|
||||
<include layout="@layout/dsl_settings_toolbar" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/edit_reactions_one_third_guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.2" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/edit_reactions_reset_emoji"
|
||||
style="@style/Signal.Widget.Button.Medium.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="@string/EditReactionsFragment__reset_emoji"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/edit_reactions_fragment_save"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/edit_reactions_fragment_save" />
|
||||
|
||||
<com.dd.CircularProgressButton
|
||||
android:id="@+id/edit_reactions_fragment_save"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
app:cornerRadius="80dp"
|
||||
app:cpb_colorIndicator="@color/white"
|
||||
app:cpb_colorProgress="?colorAccent"
|
||||
app:cpb_cornerRadius="28dp"
|
||||
app:cpb_selectorIdle="@drawable/progress_button_state"
|
||||
app:cpb_textIdle="@string/EditReactionsFragment_save"
|
||||
app:elevation="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/edit_reactions_fragment_tap_to_replace"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="@string/EditReactionsFragment__tap_to_replace_an_emoji"
|
||||
android:textAppearance="@style/Signal.Text.Caption"
|
||||
android:textColor="@color/signal_text_secondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/edit_reactions_fragment_scrubber" />
|
||||
|
||||
<View
|
||||
android:id="@+id/edit_reactions_fragment_reaction_mask"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:alpha="0"
|
||||
android:background="@color/reactions_screen_shade_color"
|
||||
app:elevation="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/edit_reactions_fragment_scrubber"
|
||||
android:layout_width="@dimen/reaction_scrubber_width"
|
||||
android:clickable="false"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@drawable/conversation_reaction_overlay_background"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:elevation="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/edit_reactions_one_third_guideline"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||
android:id="@+id/reaction_1"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:foreground="@drawable/dsl_preference_item_background_borderless"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/reaction_2"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||
android:id="@+id/reaction_2"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:foreground="@drawable/dsl_preference_item_background_borderless"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/reaction_3"
|
||||
app:layout_constraintStart_toEndOf="@id/reaction_1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||
android:id="@+id/reaction_3"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:foreground="@drawable/dsl_preference_item_background_borderless"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/reaction_4"
|
||||
app:layout_constraintStart_toEndOf="@id/reaction_2"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||
android:id="@+id/reaction_4"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:foreground="@drawable/dsl_preference_item_background_borderless"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/reaction_5"
|
||||
app:layout_constraintStart_toEndOf="@id/reaction_3"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||
android:id="@+id/reaction_5"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:foreground="@drawable/dsl_preference_item_background_borderless"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/reaction_6"
|
||||
app:layout_constraintStart_toEndOf="@id/reaction_4"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||
android:id="@+id/reaction_6"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:foreground="@drawable/dsl_preference_item_background_borderless"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/reaction_5"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -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" />
|
||||
|
||||
<TextView
|
||||
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" />
|
||||
</TextSwitcher>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/edit_reactions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/EditReactionsFragment__edit_reactions"
|
||||
android:foreground="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="12dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_settings_outline_24"
|
||||
app:tint="@color/signal_icon_tint_primary"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/category_pager"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -46,4 +59,4 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/category_label"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -206,6 +206,13 @@
|
|||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
<action
|
||||
android:id="@+id/action_chatsSettingsFragment_to_editReactionsFragment"
|
||||
app:destination="@id/editReactionsFragment"
|
||||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
@ -233,6 +240,12 @@
|
|||
android:name="org.thoughtcrime.securesms.components.settings.app.wrapped.WrappedMmsPreferencesFragment"
|
||||
android:label="mms_preferences_fragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/editReactionsFragment"
|
||||
android:name="org.thoughtcrime.securesms.reactions.edit.EditReactionsFragment"
|
||||
android:label="edit_reactions_fragment"
|
||||
tools:layout="@layout/edit_reactions_fragment" />
|
||||
|
||||
<!-- endregion -->
|
||||
|
||||
<!-- Notifications -->
|
||||
|
|
|
@ -3502,6 +3502,12 @@
|
|||
<string name="ChatColorGradientTool_top_edge_selector">Top edge selector</string>
|
||||
<string name="ChatColorGradientTool_bottom_edge_selector">Bottom edge selector</string>
|
||||
|
||||
<!-- EditReactionsFragment -->
|
||||
<string name="EditReactionsFragment__edit_reactions">Edit Reactions</string>
|
||||
<string name="EditReactionsFragment__tap_to_replace_an_emoji">Tap to replace an emoji</string>
|
||||
<string name="EditReactionsFragment__reset_emoji">Reset emoji</string>
|
||||
<string name="EditReactionsFragment_save">Save</string>
|
||||
|
||||
<!-- EOF -->
|
||||
|
||||
</resources>
|
||||
|
|
Ładowanie…
Reference in New Issue