diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 75cd3b706f..527fc7b339 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -429,6 +429,10 @@ android:theme="@style/TextSecure.DarkTheme" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> + + diff --git a/res/drawable-hdpi/ic_sticker_32.webp b/res/drawable-hdpi/ic_sticker_32.webp new file mode 100644 index 0000000000..09d338c31e Binary files /dev/null and b/res/drawable-hdpi/ic_sticker_32.webp differ diff --git a/res/drawable-mdpi/sticker_32.webp b/res/drawable-mdpi/sticker_32.webp new file mode 100644 index 0000000000..020d81c54c Binary files /dev/null and b/res/drawable-mdpi/sticker_32.webp differ diff --git a/res/drawable-xhdpi/sticker_32.webp b/res/drawable-xhdpi/sticker_32.webp new file mode 100644 index 0000000000..5924355998 Binary files /dev/null and b/res/drawable-xhdpi/sticker_32.webp differ diff --git a/res/drawable-xxhdpi/sticker_32.webp b/res/drawable-xxhdpi/sticker_32.webp new file mode 100644 index 0000000000..f97e9d2d66 Binary files /dev/null and b/res/drawable-xxhdpi/sticker_32.webp differ diff --git a/res/drawable-xxxhdpi/sticker_32.webp b/res/drawable-xxxhdpi/sticker_32.webp new file mode 100644 index 0000000000..7bda7018b7 Binary files /dev/null and b/res/drawable-xxxhdpi/sticker_32.webp differ diff --git a/res/layout/image_editor_hud.xml b/res/layout/image_editor_hud.xml index 4ebafacaca..8601d9919a 100644 --- a/res/layout/image_editor_hud.xml +++ b/res/layout/image_editor_hud.xml @@ -75,13 +75,21 @@ android:src="@drawable/ic_brush_highlight_32" /> + + + app:layout_constraintTop_toBottomOf="@id/media_keyboard_tabs_top" /> + + + app:layout_constraintStart_toStartOf="parent" + tools:layout_height="40dp" + tools:visibility="visible" /> - + android:background="?emoji_tab_indicator" + android:visibility="gone" + tools:visibility="visible" /> + android:padding="6dp" /> + + diff --git a/res/layout/scribble_select_new_sticker_activity.xml b/res/layout/scribble_select_new_sticker_activity.xml new file mode 100644 index 0000000000..a99e01fd22 --- /dev/null +++ b/res/layout/scribble_select_new_sticker_activity.xml @@ -0,0 +1,8 @@ + + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 92d2670708..c2ed44653b 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -355,4 +355,11 @@ + + + + + + + diff --git a/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java b/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java index 29e8b47671..3443725dd3 100644 --- a/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java +++ b/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java @@ -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) { diff --git a/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboardBottomTabAdapter.java b/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboardBottomTabAdapter.java index 440ce88e1e..80fd336813 100644 --- a/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboardBottomTabAdapter.java +++ b/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboardBottomTabAdapter.java @@ -15,19 +15,22 @@ public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter { - 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) { diff --git a/src/org/thoughtcrime/securesms/database/model/StickerRecord.java b/src/org/thoughtcrime/securesms/database/model/StickerRecord.java index 76ad74fbdf..8db2525d3d 100644 --- a/src/org/thoughtcrime/securesms/database/model/StickerRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/StickerRecord.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.database.model; import android.net.Uri; + import androidx.annotation.NonNull; import org.thoughtcrime.securesms.mms.PartAuthority; diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java index 1329e5330c..481c01ac80 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java @@ -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 diff --git a/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java b/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java index 50ab3041ad..93703a83fe 100644 --- a/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java +++ b/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java @@ -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); } } diff --git a/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragmentViewModel.java b/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragmentViewModel.java new file mode 100644 index 0000000000..8f843d8d44 --- /dev/null +++ b/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragmentViewModel.java @@ -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 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 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 create(@NonNull Class modelClass) { + //noinspection ConstantConditions + return modelClass.cast(new ImageEditorFragmentViewModel(application, repository)); + } + } +} diff --git a/src/org/thoughtcrime/securesms/scribbles/ImageEditorHud.java b/src/org/thoughtcrime/securesms/scribbles/ImageEditorHud.java index efc062b14d..81f37d5f9d 100644 --- a/src/org/thoughtcrime/securesms/scribbles/ImageEditorHud.java +++ b/src/org/thoughtcrime/securesms/scribbles/ImageEditorHud.java @@ -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 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 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 visibleButtons = visibilityModeMap.get(mode); + for (View button : allViews) { + button.setVisibility(buttonIsVisible(visibleButtons, button) ? VISIBLE : GONE); + } } private boolean buttonIsVisible(@Nullable Set 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) { } }; } diff --git a/src/org/thoughtcrime/securesms/scribbles/NewStickerSelectActivity.java b/src/org/thoughtcrime/securesms/scribbles/NewStickerSelectActivity.java new file mode 100644 index 0000000000..ae6c81d773 --- /dev/null +++ b/src/org/thoughtcrime/securesms/scribbles/NewStickerSelectActivity.java @@ -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); + } +} diff --git a/src/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java b/src/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java index 429136917f..37f1a206c7 100644 --- a/src/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java +++ b/src/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java @@ -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 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); } diff --git a/src/org/thoughtcrime/securesms/stickers/StickerKeyboardProvider.java b/src/org/thoughtcrime/securesms/stickers/StickerKeyboardProvider.java index df9ab953bc..5b47fc8ade 100644 --- a/src/org/thoughtcrime/securesms/stickers/StickerKeyboardProvider.java +++ b/src/org/thoughtcrime/securesms/stickers/StickerKeyboardProvider.java @@ -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);