Stickers in image editor.

master
Alan Evans 2019-08-30 17:45:35 -04:00 committed by GitHub
parent 15b650382e
commit 8d561ead21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 419 additions and 117 deletions

View File

@ -429,6 +429,10 @@
android:theme="@style/TextSecure.DarkTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".scribbles.NewStickerSelectActivity"
android:theme="@style/TextSecure.DarkTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:theme="@style/TextSecure.DarkTheme"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -75,13 +75,21 @@
android:src="@drawable/ic_brush_highlight_32" />
<ImageView
android:id="@+id/scribble_sticker_button"
android:id="@+id/old_scribble_sticker_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="8dp"
android:src="@drawable/ic_emoji_32" />
<ImageView
android:id="@+id/scribble_sticker_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="8dp"
android:src="@drawable/ic_sticker_32" />
<ImageView
android:id="@+id/scribble_crop_button"
android:layout_width="wrap_content"

View File

@ -35,7 +35,7 @@
android:orientation="horizontal"
app:layout_constraintEnd_toStartOf="@id/media_keyboard_backspace"
app:layout_constraintStart_toEndOf="@id/media_keyboard_search"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/media_keyboard_tabs_top" />
<org.thoughtcrime.securesms.components.RepeatableImageKey
android:id="@+id/media_keyboard_backspace"
@ -66,14 +66,27 @@
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_tabs"
app:layout_constraintTop_toTopOf="@+id/media_keyboard_tabs" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/media_keyboard_tabs_top"
android:layout_width="0dp"
android:layout_height="40dp"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@id/media_keyboard_add"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:layout_height="40dp"
tools:visibility="visible" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/media_keyboard_tabs"
android:layout_width="0dp"
android:layout_height="40dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/media_keyboard_add"
tools:layout_height="40dp"/>
app:layout_constraintStart_toStartOf="parent"
tools:layout_height="40dp"
tools:visibility="visible" />
<org.thoughtcrime.securesms.components.RepeatableImageKey
android:id="@+id/media_keyboard_backspace_backup"

View File

@ -1,23 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:background="?selectableItemBackground"
android:animateLayoutChanges="true">
<View
android:id="@+id/media_keyboard_bottom_tab_indicator"
android:id="@+id/media_keyboard_top_tab_indicator"
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="?emoji_tab_indicator"/>
android:background="?emoji_tab_indicator"
android:visibility="gone"
tools:visibility="visible" />
<ImageView
android:id="@+id/media_keyboard_bottom_tab_image"
android:layout_width="36dp"
android:layout_height="36dp"
android:padding="6dp"/>
android:padding="6dp" />
<View
android:id="@+id/media_keyboard_bottom_tab_indicator"
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="?emoji_tab_indicator"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.emoji.MediaKeyboard xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/emoji_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/core_grey_90"
app:tabs_gravity="top" />

View File

@ -355,4 +355,11 @@
<attr name="revealable_unopenedForegroundColor" format="color" />
<attr name="revealable_openedForegroundColor" format="color" />
</declare-styleable>
<declare-styleable name="MediaKeyboard">
<attr name="tabs_gravity" format="enum">
<enum name="bottom" value="0" />
<enum name="top" value="1" />
</attr>
</declare-styleable>
</resources>

View File

@ -1,18 +1,20 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
import org.thoughtcrime.securesms.components.RepeatableImageKey;
@ -29,16 +31,18 @@ public class MediaKeyboard extends FrameLayout implements InputView,
private static final String TAG = Log.tag(MediaKeyboard.class);
private RecyclerView categoryTabs;
private ViewPager categoryPager;
private ViewGroup providerTabs;
private RepeatableImageKey backspaceButton;
private RepeatableImageKey backspaceButtonBackup;
private View searchButton;
private View addButton;
private MediaKeyboardListener keyboardListener;
private MediaKeyboardProvider[] providers;
private int providerIndex;
private RecyclerView categoryTabs;
private ViewPager categoryPager;
private ViewGroup providerTabs;
private RepeatableImageKey backspaceButton;
private RepeatableImageKey backspaceButtonBackup;
private View searchButton;
private View addButton;
@Nullable private MediaKeyboardListener keyboardListener;
private MediaKeyboardProvider[] providers;
private int providerIndex;
private final boolean tabsAtBottom;
private MediaKeyboardBottomTabAdapter categoryTabAdapter;
@ -48,6 +52,14 @@ public class MediaKeyboard extends FrameLayout implements InputView,
public MediaKeyboard(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MediaKeyboard, 0, 0);
try {
tabsAtBottom = typedArray.getInt(R.styleable.MediaKeyboard_tabs_gravity, 0) == 0;
} finally {
typedArray.recycle();
}
}
public void setProviders(int startIndex, MediaKeyboardProvider... providers) {
@ -59,7 +71,7 @@ public class MediaKeyboard extends FrameLayout implements InputView,
}
}
public void setKeyboardListener(MediaKeyboardListener listener) {
public void setKeyboardListener(@Nullable MediaKeyboardListener listener) {
this.keyboardListener = listener;
}
@ -76,8 +88,14 @@ public class MediaKeyboard extends FrameLayout implements InputView,
params.height = height;
Log.i(TAG, "showing emoji drawer with height " + params.height);
setLayoutParams(params);
setVisibility(VISIBLE);
show();
}
public void show() {
if (this.categoryPager == null) initView();
setVisibility(VISIBLE);
if (keyboardListener != null) keyboardListener.onShown();
requestPresent(providers, providerIndex);
@ -122,7 +140,7 @@ public class MediaKeyboard extends FrameLayout implements InputView,
public void requestDismissal() {
hide(true);
providerIndex = 0;
keyboardListener.onKeyboardProviderChanged(providers[providerIndex]);
if (keyboardListener != null) keyboardListener.onKeyboardProviderChanged(providers[providerIndex]);
}
@Override
@ -148,7 +166,10 @@ public class MediaKeyboard extends FrameLayout implements InputView,
private void initView() {
final View view = LayoutInflater.from(getContext()).inflate(R.layout.media_keyboard, this, true);
this.categoryTabs = view.findViewById(R.id.media_keyboard_tabs);
RecyclerView categoryTabsTop = view.findViewById(R.id.media_keyboard_tabs_top);
RecyclerView categoryTabsBottom = view.findViewById(R.id.media_keyboard_tabs);
this.categoryTabs = tabsAtBottom ? categoryTabsBottom : categoryTabsTop;
this.categoryPager = view.findViewById(R.id.media_keyboard_pager);
this.providerTabs = view.findViewById(R.id.media_keyboard_provider_tabs);
this.backspaceButton = view.findViewById(R.id.media_keyboard_backspace);
@ -156,10 +177,11 @@ public class MediaKeyboard extends FrameLayout implements InputView,
this.searchButton = view.findViewById(R.id.media_keyboard_search);
this.addButton = view.findViewById(R.id.media_keyboard_add);
this.categoryTabAdapter = new MediaKeyboardBottomTabAdapter(GlideApp.with(this), this);
this.categoryTabAdapter = new MediaKeyboardBottomTabAdapter(GlideApp.with(this), this, tabsAtBottom);
categoryTabs.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
categoryTabs.setAdapter(categoryTabAdapter);
categoryTabs.setVisibility(VISIBLE);
}
private void requestPresent(@NonNull MediaKeyboardProvider[] providers, int newIndex) {

View File

@ -15,19 +15,22 @@ public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter<MediaKey
private final GlideRequests glideRequests;
private final EventListener eventListener;
private final boolean highlightTop;
private TabIconProvider tabIconProvider;
private int activePosition;
private int count;
public MediaKeyboardBottomTabAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) {
public MediaKeyboardBottomTabAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener, boolean highlightTop) {
this.glideRequests = glideRequests;
this.eventListener = eventListener;
this.highlightTop = highlightTop;
}
@Override
public @NonNull MediaKeyboardBottomTabViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new MediaKeyboardBottomTabViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.media_keyboard_bottom_tab_item, viewGroup, false));
return new MediaKeyboardBottomTabViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.media_keyboard_bottom_tab_item, viewGroup, false),
highlightTop);
}
@Override
@ -62,11 +65,16 @@ public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter<MediaKey
private final ImageView image;
private final View indicator;
public MediaKeyboardBottomTabViewHolder(@NonNull View itemView) {
public MediaKeyboardBottomTabViewHolder(@NonNull View itemView, boolean highlightTop) {
super(itemView);
View indicatorTop = itemView.findViewById(R.id.media_keyboard_top_tab_indicator);
View indicatorBottom = itemView.findViewById(R.id.media_keyboard_bottom_tab_indicator);
this.image = itemView.findViewById(R.id.media_keyboard_bottom_tab_image);
this.indicator = itemView.findViewById(R.id.media_keyboard_bottom_tab_indicator);
this.indicator = highlightTop ? indicatorTop : indicatorBottom;
this.indicator.setVisibility(View.VISIBLE);
}
void bind(@NonNull GlideRequests glideRequests,

View File

@ -19,7 +19,6 @@ package org.thoughtcrime.securesms.conversation;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import androidx.lifecycle.ViewModelProviders;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -42,16 +41,6 @@ import android.os.Vibrator;
import android.provider.Browser;
import android.provider.ContactsContract;
import android.provider.Telephony;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import androidx.core.view.MenuItemCompat;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@ -71,6 +60,18 @@ import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import androidx.core.view.MenuItemCompat;
import androidx.lifecycle.ViewModelProviders;
import com.annimon.stream.Stream;
import org.greenrobot.eventbus.EventBus;
@ -217,6 +218,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.views.Stub;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional;
@ -2491,9 +2493,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void sendSticker(@NonNull StickerRecord stickerRecord, boolean clearCompose) {
sendSticker(new StickerLocator(stickerRecord.getPackId(), stickerRecord.getPackKey(), stickerRecord.getStickerId()), stickerRecord.getUri(), stickerRecord.getSize(), clearCompose);
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
DatabaseFactory.getStickerDatabase(this).updateStickerLastUsedTime(stickerRecord.getRowId(), System.currentTimeMillis());
});
SignalExecutors.BOUNDED.execute(() ->
DatabaseFactory.getStickerDatabase(getApplicationContext())
.updateStickerLastUsedTime(stickerRecord.getRowId(), System.currentTimeMillis())
);
}
private void sendSticker(@NonNull StickerLocator stickerLocator, @NonNull Uri uri, long size, boolean clearCompose) {

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.database.model;
import android.net.Uri;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.mms.PartAuthority;

View File

@ -1,10 +1,5 @@
package org.thoughtcrime.securesms.mediasend;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.lifecycle.ViewModelProviders;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
@ -15,12 +10,6 @@ import android.graphics.Rect;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
@ -32,6 +21,16 @@ import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.TransportOption;
@ -438,10 +437,13 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
}
@Override
public void onRequestFullScreen(boolean fullScreen) {
public void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard) {
if (captionAndRail != null) {
captionAndRail.setVisibility(fullScreen ? View.GONE : View.VISIBLE);
}
if (hideKeyboard && hud.isKeyboardOpen()) {
hud.hideSoftkey(composeText, null);
}
}
@Override

View File

@ -6,14 +6,16 @@ import android.graphics.Bitmap;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProviders;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.imageeditor.ColorableRenderer;
import org.thoughtcrime.securesms.imageeditor.ImageEditorView;
@ -28,6 +30,7 @@ import org.thoughtcrime.securesms.mms.PushMediaConstraints;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.ParcelUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
@ -36,7 +39,6 @@ import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import static android.app.Activity.RESULT_OK;
@ -48,14 +50,15 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
private static final String KEY_IMAGE_URI = "image_uri";
public static final int SELECT_STICKER_REQUEST_CODE = 123;
private static final int SELECT_OLD_STICKER_REQUEST_CODE = 123;
private static final int SELECT_NEW_STICKER_REQUEST_CODE = 124;
private EditorModel restoredModel;
@Nullable
private EditorElement currentSelection;
private int imageMaxHeight;
private int imageMaxWidth;
@Nullable private EditorElement currentSelection;
private int imageMaxHeight;
private int imageMaxWidth;
private ImageEditorFragmentViewModel viewModel;
public static class Data {
private final Bundle bundle;
@ -118,6 +121,13 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
imageMaxWidth = mediaConstraints.getImageMaxWidth(requireContext());
imageMaxHeight = mediaConstraints.getImageMaxHeight(requireContext());
StickerSearchRepository repository = new StickerSearchRepository(requireContext());
viewModel = ViewModelProviders.of(this, new ImageEditorFragmentViewModel.Factory(requireActivity().getApplication(), repository))
.get(ImageEditorFragmentViewModel.class);
viewModel.getStickersAvailability().observe(this, isAvailable -> imageEditorHud.setStickersAvailable(isAvailable));
}
@Nullable
@ -233,15 +243,26 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == SELECT_STICKER_REQUEST_CODE && data != null) {
final String stickerFile = data.getStringExtra(StickerSelectActivity.EXTRA_STICKER_FILE);
UriGlideRenderer renderer = new UriGlideRenderer(Uri.parse("file:///android_asset/" + stickerFile), false, imageMaxWidth, imageMaxHeight);
EditorElement element = new EditorElement(renderer);
imageEditorView.getModel().addElementCentered(element, 0.2f);
currentSelection = element;
if (resultCode == RESULT_OK && requestCode == SELECT_NEW_STICKER_REQUEST_CODE && data != null) {
final Uri uri = data.getData();
if (uri != null) {
UriGlideRenderer renderer = new UriGlideRenderer(uri, true, imageMaxWidth, imageMaxHeight);
EditorElement element = new EditorElement(renderer);
imageEditorView.getModel().addElementCentered(element, 0.2f);
currentSelection = element;
imageEditorHud.setMode(ImageEditorHud.Mode.MOVE_DELETE);
}
} else if (resultCode == RESULT_OK && requestCode == SELECT_OLD_STICKER_REQUEST_CODE && data != null) {
final Uri uri = data.getData();
if (uri != null) {
UriGlideRenderer renderer = new UriGlideRenderer(uri, false, imageMaxWidth, imageMaxHeight);
EditorElement element = new EditorElement(renderer);
imageEditorView.getModel().addElementCentered(element, 0.2f);
currentSelection = element;
imageEditorHud.setMode(ImageEditorHud.Mode.MOVE_DELETE);
}
} else {
imageEditorHud.enterMode(ImageEditorHud.Mode.NONE);
imageEditorHud.setMode(ImageEditorHud.Mode.NONE);
}
}
@ -253,31 +274,46 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
controller.onTouchEventsNeeded(mode != ImageEditorHud.Mode.NONE);
switch (mode) {
case CROP:
case CROP: {
imageEditorView.getModel().startCrop();
break;
break;
}
case DRAW:
case DRAW: {
imageEditorView.startDrawing(0.01f, Paint.Cap.ROUND);
break;
}
case HIGHLIGHT:
case HIGHLIGHT: {
imageEditorView.startDrawing(0.03f, Paint.Cap.SQUARE);
break;
}
case TEXT:
case TEXT: {
addText();
break;
}
case INSERT_ASSET_STICKER: {
Intent intent = new Intent(getContext(), StickerSelectActivity.class);
startActivityForResult(intent, SELECT_OLD_STICKER_REQUEST_CODE);
break;
}
case INSERT_STICKER: {
Intent intent = new Intent(getContext(), NewStickerSelectActivity.class);
startActivityForResult(intent, SELECT_NEW_STICKER_REQUEST_CODE);
break;
}
case MOVE_DELETE:
Intent intent = new Intent(getContext(), StickerSelectActivity.class);
startActivityForResult(intent, SELECT_STICKER_REQUEST_CODE);
break;
case NONE:
case NONE: {
imageEditorView.getModel().doneCrop();
currentSelection = null;
break;
}
}
}
@ -350,8 +386,8 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
}
@Override
public void onRequestFullScreen(boolean fullScreen) {
controller.onRequestFullScreen(fullScreen);
public void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard) {
controller.onRequestFullScreen(fullScreen, hideKeyboard);
}
private void refreshUniqueColors() {
@ -371,8 +407,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
} else {
currentSelection = null;
controller.onTouchEventsNeeded(false);
imageEditorHud.enterMode(ImageEditorHud.Mode.NONE);
imageEditorView.doneTextEditing();
imageEditorHud.setMode(ImageEditorHud.Mode.NONE);
}
}
@ -383,7 +418,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
if (editorElement.getRenderer() instanceof MultiLineTextRenderer) {
setTextElement(editorElement, (ColorableRenderer) editorElement.getRenderer(), imageEditorView.isTextEditing());
} else {
imageEditorHud.enterMode(ImageEditorHud.Mode.MOVE_DELETE);
imageEditorHud.setMode(ImageEditorHud.Mode.MOVE_DELETE);
}
}
}
@ -412,6 +447,6 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
public interface Controller {
void onTouchEventsNeeded(boolean needed);
void onRequestFullScreen(boolean fullScreen);
void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard);
}
}

View File

@ -0,0 +1,65 @@
package org.thoughtcrime.securesms.scribbles;
import android.app.Application;
import android.database.ContentObserver;
import android.os.Handler;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
import org.thoughtcrime.securesms.util.Throttler;
public final class ImageEditorFragmentViewModel extends ViewModel {
private final Application application;
private final StickerSearchRepository repository;
private final MutableLiveData<Boolean> stickersAvailable;
private final Throttler availabilityThrottler;
private final ContentObserver packObserver;
private ImageEditorFragmentViewModel(@NonNull Application application, @NonNull StickerSearchRepository repository) {
this.application = application;
this.repository = repository;
this.stickersAvailable = new MutableLiveData<>();
this.availabilityThrottler = new Throttler(500);
this.packObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
availabilityThrottler.publish(() -> repository.getStickerFeatureAvailability(stickersAvailable::postValue));
}
};
application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, packObserver);
}
@NonNull LiveData<Boolean> getStickersAvailability() {
repository.getStickerFeatureAvailability(stickersAvailable::postValue);
return stickersAvailable;
}
@Override
protected void onCleared() {
application.getContentResolver().unregisterContentObserver(packObserver);
}
static class Factory extends ViewModelProvider.NewInstanceFactory {
private final Application application;
private final StickerSearchRepository repository;
public Factory(@NonNull Application application, @NonNull StickerSearchRepository repository) {
this.application = application;
this.repository = repository;
}
@Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection ConstantConditions
return modelClass.cast(new ImageEditorFragmentViewModel(application, repository));
}
}
}

View File

@ -2,15 +2,17 @@ package org.thoughtcrime.securesms.scribbles;
import android.content.Context;
import android.graphics.Color;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.scribbles.widget.ColorPaletteAdapter;
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
@ -34,7 +36,8 @@ public final class ImageEditorHud extends LinearLayout {
private View drawButton;
private View highlightButton;
private View textButton;
private View stickerButton;
private View oldStickerButton;
private View newStickerButton;
private View undoButton;
private View saveButton;
private View deleteButton;
@ -80,7 +83,8 @@ public final class ImageEditorHud extends LinearLayout {
drawButton = findViewById(R.id.scribble_draw_button);
highlightButton = findViewById(R.id.scribble_highlight_button);
textButton = findViewById(R.id.scribble_text_button);
stickerButton = findViewById(R.id.scribble_sticker_button);
oldStickerButton = findViewById(R.id.old_scribble_sticker_button);
newStickerButton = findViewById(R.id.scribble_sticker_button);
undoButton = findViewById(R.id.scribble_undo_button);
saveButton = findViewById(R.id.scribble_save_button);
deleteButton = findViewById(R.id.scribble_delete_button);
@ -102,7 +106,7 @@ public final class ImageEditorHud extends LinearLayout {
}
private void initializeVisibilityMap() {
setVisibleViewsWhenInMode(Mode.NONE, drawButton, highlightButton, textButton, stickerButton, cropButton, undoButton, saveButton);
setStickersAvailable(false);
setVisibleViewsWhenInMode(Mode.DRAW, confirmButton, undoButton, colorPicker, colorPalette);
@ -112,17 +116,34 @@ public final class ImageEditorHud extends LinearLayout {
setVisibleViewsWhenInMode(Mode.MOVE_DELETE, confirmButton, deleteButton);
setVisibleViewsWhenInMode(Mode.INSERT_STICKER, confirmButton);
setVisibleViewsWhenInMode(Mode.INSERT_ASSET_STICKER, confirmButton);
setVisibleViewsWhenInMode(Mode.CROP, confirmButton, cropFlipButton, cropRotateButton, cropAspectLock, undoButton);
for (Set<View> views : visibilityModeMap.values()) {
allViews.addAll(views);
}
allViews.add(newStickerButton);
allViews.add(oldStickerButton);
}
private void setVisibleViewsWhenInMode(Mode mode, View... views) {
visibilityModeMap.put(mode, new HashSet<>(Arrays.asList(views)));
}
@MainThread
public void setStickersAvailable(boolean stickersAvailable) {
if (stickersAvailable) {
setVisibleViewsWhenInMode(Mode.NONE, drawButton, highlightButton, textButton, newStickerButton, cropButton, undoButton, saveButton);
} else {
setVisibleViewsWhenInMode(Mode.NONE, drawButton, highlightButton, textButton, oldStickerButton, cropButton, undoButton, saveButton);
}
updateButtonVisibility(currentMode);
}
private void initializeViews() {
undoButton.setOnClickListener(v -> eventListener.onUndo());
@ -146,7 +167,8 @@ public final class ImageEditorHud extends LinearLayout {
drawButton.setOnClickListener(v -> setMode(Mode.DRAW));
highlightButton.setOnClickListener(v -> setMode(Mode.HIGHLIGHT));
textButton.setOnClickListener(v -> setMode(Mode.TEXT));
stickerButton.setOnClickListener(v -> setMode(Mode.MOVE_DELETE));
oldStickerButton.setOnClickListener(v -> setMode(Mode.INSERT_ASSET_STICKER));
newStickerButton.setOnClickListener(v -> setMode(Mode.INSERT_STICKER));
saveButton.setOnClickListener(v -> eventListener.onSave());
}
@ -172,16 +194,13 @@ public final class ImageEditorHud extends LinearLayout {
setMode(mode, false);
}
private void setMode(@NonNull Mode mode) {
public void setMode(@NonNull Mode mode) {
setMode(mode, true);
}
private void setMode(@NonNull Mode mode, boolean notify) {
this.currentMode = mode;
Set<View> visibleButtons = visibilityModeMap.get(mode);
for (View button : allViews) {
button.setVisibility(buttonIsVisible(visibleButtons, button) ? VISIBLE : GONE);
}
updateButtonVisibility(mode);
switch (mode) {
case CROP: presentModeCrop(); break;
@ -193,7 +212,14 @@ public final class ImageEditorHud extends LinearLayout {
if (notify) {
eventListener.onModeStarted(mode);
}
eventListener.onRequestFullScreen(mode != Mode.NONE);
eventListener.onRequestFullScreen(mode != Mode.NONE, mode != Mode.TEXT);
}
private void updateButtonVisibility(@NonNull Mode mode) {
Set<View> visibleButtons = visibilityModeMap.get(mode);
for (View button : allViews) {
button.setVisibility(buttonIsVisible(visibleButtons, button) ? VISIBLE : GONE);
}
}
private boolean buttonIsVisible(@Nullable Set<View> visibleButtons, @NonNull View button) {
@ -236,7 +262,14 @@ public final class ImageEditorHud extends LinearLayout {
}
public enum Mode {
NONE, DRAW, HIGHLIGHT, TEXT, MOVE_DELETE, CROP
NONE,
CROP,
TEXT,
DRAW,
HIGHLIGHT,
MOVE_DELETE,
INSERT_STICKER,
INSERT_ASSET_STICKER
}
public interface EventListener {
@ -249,7 +282,7 @@ public final class ImageEditorHud extends LinearLayout {
void onRotate90AntiClockwise();
void onCropAspectLock(boolean locked);
boolean isCropAspectLocked();
void onRequestFullScreen(boolean fullScreen);
void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard);
}
private static final EventListener NULL_EVENT_LISTENER = new EventListener() {
@ -292,7 +325,7 @@ public final class ImageEditorHud extends LinearLayout {
}
@Override
public void onRequestFullScreen(boolean fullScreen) {
public void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard) {
}
};
}

View File

@ -0,0 +1,82 @@
package org.thoughtcrime.securesms.scribbles;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.stickers.StickerKeyboardProvider;
import org.thoughtcrime.securesms.stickers.StickerManagementActivity;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
public final class NewStickerSelectActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.scribble_select_new_sticker_activity);
MediaKeyboard mediaKeyboard = findViewById(R.id.emoji_drawer);
mediaKeyboard.setProviders(0, new StickerKeyboardProvider(this, new StickerKeyboardProvider.StickerEventListener() {
@Override
public void onStickerSelected(@NonNull StickerRecord sticker) {
Intent intent = new Intent();
intent.setData(sticker.getUri());
setResult(RESULT_OK, intent);
SignalExecutors.BOUNDED.execute(() ->
DatabaseFactory.getStickerDatabase(getApplicationContext())
.updateStickerLastUsedTime(sticker.getRowId(), System.currentTimeMillis())
);
finish();
}
@Override
public void onStickerManagementClicked() {
startActivity(StickerManagementActivity.getIntent(NewStickerSelectActivity.this));
}
}
));
mediaKeyboard.setKeyboardListener(new MediaKeyboard.MediaKeyboardListener() {
@Override
public void onShown() {
}
@Override
public void onHidden() {
finish();
}
@Override
public void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider) {
}
});
mediaKeyboard.show();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -63,9 +63,7 @@ final class UriGlideRenderer implements Renderer {
try {
Bitmap bitmap = getBitmapGlideRequest(rendererContext.context, false).submit().get();
setBitmap(rendererContext, bitmap);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
} else {
@ -73,6 +71,8 @@ final class UriGlideRenderer implements Renderer {
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
setBitmap(rendererContext, resource);
rendererContext.invalidate.onInvalidate(UriGlideRenderer.this);
}
@Override
@ -99,7 +99,7 @@ final class UriGlideRenderer implements Renderer {
paint.setAlpha(alpha);
rendererContext.restore();
} else {
} else if (rendererContext.isBlockingLoad()) {
// If failed to load, we draw a black out, in case image was sticker positioned to cover private info.
rendererContext.canvas.drawRect(Bounds.FULL_BOUNDS, paint);
}

View File

@ -1,16 +1,17 @@
package org.thoughtcrime.securesms.stickers;
import androidx.lifecycle.ViewModelProviders;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.ImageView;
import androidx.lifecycle.ViewModelProviders;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider;
@ -48,7 +49,7 @@ public final class StickerKeyboardProvider implements MediaKeyboardProvider,
private boolean isSoloProvider;
private StickerKeyboardViewModel viewModel;
public StickerKeyboardProvider(@NonNull AppCompatActivity activity,
public StickerKeyboardProvider(@NonNull FragmentActivity activity,
@NonNull StickerEventListener eventListener)
{
this.context = activity;
@ -109,7 +110,7 @@ public final class StickerKeyboardProvider implements MediaKeyboardProvider,
}
}
private void initViewModel(@NonNull AppCompatActivity activity) {
private void initViewModel(@NonNull FragmentActivity activity) {
StickerKeyboardRepository repository = new StickerKeyboardRepository(DatabaseFactory.getStickerDatabase(activity));
viewModel = ViewModelProviders.of(activity, new StickerKeyboardViewModel.Factory(activity.getApplication(), repository)).get(StickerKeyboardViewModel.class);