Show long-press-magnify in sticker preview screen.

This commit is contained in:
Greyson Parrelli 2020-01-03 00:41:03 -05:00 committed by Alan Evans
parent 4e7b4da941
commit 277c9e22f1
8 changed files with 226 additions and 91 deletions

View File

@ -4,4 +4,5 @@
android:id="@+id/sticker_install_item_image"
android:layout_width="match_parent"
android:layout_height="@dimen/sticker_preview_sticker_size"
android:layout_marginTop="6dp"
android:scaleType="centerInside"/>

View File

@ -1,14 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?sticker_popup_background">
<ImageView
android:id="@+id/sticker_popup_image"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:gravity="center">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/sticker_popup_emoji"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:gravity="center"
android:maxLines="1"
android:textSize="22sp"
tools:text=":)"/>
<ImageView
android:id="@+id/sticker_popup_image"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center"
tools:src="@drawable/ic_contact_picture"/>
</LinearLayout>
</FrameLayout>

View File

@ -45,8 +45,7 @@ final class StickerKeyboardPageAdapter extends RecyclerView.Adapter<StickerKeybo
}
@Override
public @NonNull
StickerKeyboardPageViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
public @NonNull StickerKeyboardPageViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new StickerKeyboardPageViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.sticker_keyboard_page_list_item, viewGroup, false));
}

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.stickers;
import androidx.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.os.Bundle;
@ -11,9 +10,7 @@ import androidx.annotation.Px;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@ -21,15 +18,18 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.stickers.StickerKeyboardPageAdapter.StickerKeyboardPageViewHolder;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libsignal.util.Pair;
/**
* An individual page of stickers in the {@link StickerKeyboardProvider}.
*/
public final class StickerKeyboardPageFragment extends Fragment implements StickerKeyboardPageAdapter.EventListener {
public final class StickerKeyboardPageFragment extends Fragment implements StickerKeyboardPageAdapter.EventListener,
StickerRolloverTouchListener.RolloverStickerRetriever
{
private static final String TAG = Log.tag(StickerKeyboardPageFragment.class);
@ -43,7 +43,7 @@ public final class StickerKeyboardPageFragment extends Fragment implements Stick
private StickerKeyboardPageViewModel viewModel;
private EventListener eventListener;
private ListTouchListener listTouchListener;
private StickerRolloverTouchListener listTouchListener;
private String packId;
@ -70,7 +70,7 @@ public final class StickerKeyboardPageFragment extends Fragment implements Stick
this.list = view.findViewById(R.id.sticker_keyboard_list);
this.adapter = new StickerKeyboardPageAdapter(glideRequests, this);
this.layoutManager = new GridLayoutManager(requireContext(), 2);
this.listTouchListener = new ListTouchListener(requireContext(), glideRequests);
this.listTouchListener = new StickerRolloverTouchListener(requireContext(), glideRequests, eventListener, this);
this.packId = getArguments().getString(KEY_PACK_ID);
list.setLayoutManager(layoutManager);
@ -101,6 +101,18 @@ public final class StickerKeyboardPageFragment extends Fragment implements Stick
}
}
@Override
public @Nullable Pair<Object, String> getStickerDataFromView(@NonNull View view) {
if (list != null) {
StickerKeyboardPageViewHolder holder = (StickerKeyboardPageViewHolder) list.getChildViewHolder(view);
if (holder != null && holder.getCurrentSticker() != null) {
return new Pair<>(new DecryptableStreamUriLoader.DecryptableUri(holder.getCurrentSticker().getUri()),
holder.getCurrentSticker().getEmoji());
}
}
return null;
}
public void setEventListener(@NonNull EventListener eventListener) {
this.eventListener = eventListener;
}
@ -146,69 +158,7 @@ public final class StickerKeyboardPageFragment extends Fragment implements Stick
return (int) ((screenWidth - ((columnCount + 1) * multiplier)) / columnCount);
}
private final class ListTouchListener implements RecyclerView.OnItemTouchListener {
private final StickerPreviewPopup popup;
private boolean hoverMode;
ListTouchListener(@NonNull Context context, @NonNull GlideRequests glideRequests) {
this.popup = new StickerPreviewPopup(context, glideRequests);
popup.setAnimationStyle(R.style.StickerPopupAnimation);
}
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
return hoverMode;
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
hoverMode = false;
popup.dismiss();
eventListener.onStickerPopupEnded();
break;
default:
for (int i = 0, len = recyclerView.getChildCount(); i < len; i++) {
View child = recyclerView.getChildAt(i);
if (ViewUtil.isPointInsideView(recyclerView, motionEvent.getRawX(), motionEvent.getRawY()) &&
ViewUtil.isPointInsideView(child, motionEvent.getRawX(), motionEvent.getRawY()))
{
showStickerForView(recyclerView, child);
}
}
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) {
}
void enterHoverMode(@NonNull RecyclerView recyclerView, View targetView) {
this.hoverMode = true;
showStickerForView(recyclerView, targetView);
}
private void showStickerForView(@NonNull RecyclerView recyclerView, @NonNull View view) {
StickerKeyboardPageViewHolder holder = (StickerKeyboardPageViewHolder) recyclerView.getChildViewHolder(view);
if (holder != null && holder.getCurrentSticker() != null) {
if (!popup.isShowing()) {
popup.showAtLocation(recyclerView, Gravity.NO_GRAVITY, 0, 0);
eventListener.onStickerPopupStarted();
}
popup.presentSticker(holder.getCurrentSticker());
}
}
}
interface EventListener {
interface EventListener extends StickerRolloverTouchListener.RolloverEventListener {
void onStickerSelected(@NonNull StickerRecord sticker);
void onStickerPopupStarted();
void onStickerPopupEnded();
}
}

View File

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.stickers;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.content.res.Configuration;
@ -22,11 +23,13 @@ import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.ShareActivity;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.stickers.StickerManifest.Sticker;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
@ -40,7 +43,11 @@ import org.whispersystems.libsignal.util.guava.Optional;
* Shows the contents of a pack and allows the user to install it (if not installed) or remove it
* (if installed). This is also the handler for sticker pack deep links.
*/
public final class StickerPackPreviewActivity extends PassphraseRequiredActionBarActivity {
public final class StickerPackPreviewActivity extends PassphraseRequiredActionBarActivity
implements StickerRolloverTouchListener.RolloverEventListener,
StickerRolloverTouchListener.RolloverStickerRetriever,
StickerPackPreviewAdapter.EventListener
{
private static final String TAG = Log.tag(StickerPackPreviewActivity.class);
@ -57,8 +64,9 @@ public final class StickerPackPreviewActivity extends PassphraseRequiredActionBa
private View shareButton;
private View shareButtonImage;
private StickerPackPreviewAdapter adapter;
private GridLayoutManager layoutManager;
private StickerPackPreviewAdapter adapter;
private GridLayoutManager layoutManager;
private StickerRolloverTouchListener touchListener;
public static Intent getIntent(@NonNull String packId, @NonNull String packKey) {
Intent intent = new Intent(Intent.ACTION_VIEW, StickerUrl.createActionUri(packId, packKey));
@ -105,6 +113,32 @@ public final class StickerPackPreviewActivity extends PassphraseRequiredActionBa
onScreenWidthChanged(getScreenWidth());
}
@Override
public void onStickerLongPress(@NonNull View view) {
if (touchListener != null) {
touchListener.enterHoverMode(stickerList, view);
}
}
@Override
public void onStickerPopupStarted() {
}
@Override
public void onStickerPopupEnded() {
}
@Override
public @Nullable Pair<Object, String> getStickerDataFromView(@NonNull View view) {
if (stickerList != null) {
StickerPackPreviewAdapter.StickerViewHolder holder = (StickerPackPreviewAdapter.StickerViewHolder) stickerList.getChildViewHolder(view);
if (holder != null) {
return new Pair<>(holder.getCurrentGlideModel(), holder.getCurrentEmoji());
}
}
return null;
}
private void initView() {
this.coverImage = findViewById(R.id.sticker_install_cover);
this.stickerTitle = findViewById(R.id.sticker_install_title);
@ -115,11 +149,13 @@ public final class StickerPackPreviewActivity extends PassphraseRequiredActionBa
this.shareButton = findViewById(R.id.sticker_install_share_button);
this.shareButtonImage = findViewById(R.id.sticker_install_share_button_image);
this.adapter = new StickerPackPreviewAdapter(GlideApp.with(this));
this.adapter = new StickerPackPreviewAdapter(GlideApp.with(this), this);
this.layoutManager = new GridLayoutManager(this, 2);
this.touchListener = new StickerRolloverTouchListener(this, GlideApp.with(this), this, this);
onScreenWidthChanged(getScreenWidth());
stickerList.setLayoutManager(layoutManager);
stickerList.addOnItemTouchListener(touchListener);
stickerList.setAdapter(adapter);
}

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.stickers;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@ -19,10 +20,12 @@ import java.util.List;
public final class StickerPackPreviewAdapter extends RecyclerView.Adapter<StickerPackPreviewAdapter.StickerViewHolder> {
private final GlideRequests glideRequests;
private final EventListener eventListener;
private final List<StickerManifest.Sticker> list;
public StickerPackPreviewAdapter(@NonNull GlideRequests glideRequests) {
public StickerPackPreviewAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) {
this.glideRequests = glideRequests;
this.eventListener = eventListener;
this.list = new ArrayList<>();
}
@ -33,7 +36,7 @@ public final class StickerPackPreviewAdapter extends RecyclerView.Adapter<Sticke
@Override
public void onBindViewHolder(@NonNull StickerViewHolder stickerViewHolder, int i) {
stickerViewHolder.bind(glideRequests, list.get(i));
stickerViewHolder.bind(glideRequests, list.get(i), eventListener);
}
@Override
@ -41,6 +44,11 @@ public final class StickerPackPreviewAdapter extends RecyclerView.Adapter<Sticke
return list.size();
}
@Override
public void onViewRecycled(@NonNull StickerViewHolder holder) {
holder.recycle();
}
void setStickers(List<StickerManifest.Sticker> stickers) {
list.clear();
list.addAll(stickers);
@ -51,17 +59,42 @@ public final class StickerPackPreviewAdapter extends RecyclerView.Adapter<Sticke
private final ImageView image;
private Object currentGlideModel;
private String currentEmoji;
private StickerViewHolder(@NonNull View itemView) {
super(itemView);
this.image = itemView.findViewById(R.id.sticker_install_item_image);
}
void bind(@NonNull GlideRequests glideRequests, @NonNull StickerManifest.Sticker sticker) {
Object model = sticker.getUri().isPresent() ? new DecryptableStreamUriLoader.DecryptableUri(sticker.getUri().get())
: new StickerRemoteUri(sticker.getPackId(), sticker.getPackKey(), sticker.getId());
glideRequests.load(model)
void bind(@NonNull GlideRequests glideRequests, @NonNull StickerManifest.Sticker sticker, @NonNull EventListener eventListener) {
currentEmoji = sticker.getEmoji();
currentGlideModel = sticker.getUri().isPresent() ? new DecryptableStreamUriLoader.DecryptableUri(sticker.getUri().get())
: new StickerRemoteUri(sticker.getPackId(), sticker.getPackKey(), sticker.getId());
glideRequests.load(currentGlideModel)
.transition(DrawableTransitionOptions.withCrossFade())
.into(image);
image.setOnLongClickListener(v -> {
eventListener.onStickerLongPress(v);
return true;
});
}
void recycle() {
image.setOnLongClickListener(null);
}
@Nullable Object getCurrentGlideModel() {
return currentGlideModel;
}
@Nullable String getCurrentEmoji() {
return currentEmoji;
}
}
interface EventListener {
void onStickerLongPress(@NonNull View view);
}
}

View File

@ -2,14 +2,15 @@ package org.thoughtcrime.securesms.stickers;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.GlideRequests;
@ -20,6 +21,7 @@ final class StickerPreviewPopup extends PopupWindow {
private final GlideRequests glideRequests;
private final ImageView image;
private final TextView emojiText;
StickerPreviewPopup(@NonNull Context context, @NonNull GlideRequests glideRequests) {
super(LayoutInflater.from(context).inflate(R.layout.sticker_preview_popup, null),
@ -27,12 +29,14 @@ final class StickerPreviewPopup extends PopupWindow {
ViewGroup.LayoutParams.MATCH_PARENT);
this.glideRequests = glideRequests;
this.image = getContentView().findViewById(R.id.sticker_popup_image);
this.emojiText = getContentView().findViewById(R.id.sticker_popup_emoji);
setTouchable(false);
}
void presentSticker(@NonNull StickerRecord stickerRecord) {
glideRequests.load(new DecryptableUri(stickerRecord.getUri()))
void presentSticker(@NonNull Object stickerGlideModel, @Nullable String emoji) {
emojiText.setText(emoji);
glideRequests.load(stickerGlideModel)
.into(image);
}
}

View File

@ -0,0 +1,91 @@
package org.thoughtcrime.securesms.stickers;
import android.content.Context;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libsignal.util.Pair;
public class StickerRolloverTouchListener implements RecyclerView.OnItemTouchListener {
private final StickerPreviewPopup popup;
private final RolloverEventListener eventListener;
private final RolloverStickerRetriever stickerRetriever;
private boolean hoverMode;
StickerRolloverTouchListener(@NonNull Context context,
@NonNull GlideRequests glideRequests,
@NonNull RolloverEventListener eventListener,
@NonNull RolloverStickerRetriever stickerRetriever)
{
this.eventListener = eventListener;
this.stickerRetriever = stickerRetriever;
this.popup = new StickerPreviewPopup(context, glideRequests);
popup.setAnimationStyle(R.style.StickerPopupAnimation);
}
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
return hoverMode;
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
hoverMode = false;
popup.dismiss();
eventListener.onStickerPopupEnded();
break;
default:
for (int i = 0, len = recyclerView.getChildCount(); i < len; i++) {
View child = recyclerView.getChildAt(i);
if (ViewUtil.isPointInsideView(recyclerView, motionEvent.getRawX(), motionEvent.getRawY()) &&
ViewUtil.isPointInsideView(child, motionEvent.getRawX(), motionEvent.getRawY()))
{
showStickerForView(recyclerView, child);
}
}
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) {
}
void enterHoverMode(@NonNull RecyclerView recyclerView, View targetView) {
this.hoverMode = true;
showStickerForView(recyclerView, targetView);
}
private void showStickerForView(@NonNull RecyclerView recyclerView, @NonNull View view) {
Pair<Object, String> stickerData = stickerRetriever.getStickerDataFromView(view);
if (stickerData != null) {
if (!popup.isShowing()) {
popup.showAtLocation(recyclerView, Gravity.NO_GRAVITY, 0, 0);
eventListener.onStickerPopupStarted();
}
popup.presentSticker(stickerData.first(), stickerData.second());
}
}
public interface RolloverEventListener {
void onStickerPopupStarted();
void onStickerPopupEnded();
}
public interface RolloverStickerRetriever {
@Nullable Pair<Object, String> getStickerDataFromView(@NonNull View view);
}
}