Stickers in image editor.
This commit is contained in:
parent
15b650382e
commit
8d561ead21
22 changed files with 419 additions and 117 deletions
|
@ -429,6 +429,10 @@
|
||||||
android:theme="@style/TextSecure.DarkTheme"
|
android:theme="@style/TextSecure.DarkTheme"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
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"
|
<activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
|
||||||
android:theme="@style/TextSecure.DarkTheme"/>
|
android:theme="@style/TextSecure.DarkTheme"/>
|
||||||
|
|
||||||
|
|
BIN
res/drawable-hdpi/ic_sticker_32.webp
Normal file
BIN
res/drawable-hdpi/ic_sticker_32.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-mdpi/sticker_32.webp
Normal file
BIN
res/drawable-mdpi/sticker_32.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 804 B |
BIN
res/drawable-xhdpi/sticker_32.webp
Normal file
BIN
res/drawable-xhdpi/sticker_32.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
BIN
res/drawable-xxhdpi/sticker_32.webp
Normal file
BIN
res/drawable-xxhdpi/sticker_32.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
BIN
res/drawable-xxxhdpi/sticker_32.webp
Normal file
BIN
res/drawable-xxxhdpi/sticker_32.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
|
@ -75,13 +75,21 @@
|
||||||
android:src="@drawable/ic_brush_highlight_32" />
|
android:src="@drawable/ic_brush_highlight_32" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/scribble_sticker_button"
|
android:id="@+id/old_scribble_sticker_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:src="@drawable/ic_emoji_32" />
|
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
|
<ImageView
|
||||||
android:id="@+id/scribble_crop_button"
|
android:id="@+id/scribble_crop_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
app:layout_constraintEnd_toStartOf="@id/media_keyboard_backspace"
|
app:layout_constraintEnd_toStartOf="@id/media_keyboard_backspace"
|
||||||
app:layout_constraintStart_toEndOf="@id/media_keyboard_search"
|
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
|
<org.thoughtcrime.securesms.components.RepeatableImageKey
|
||||||
android:id="@+id/media_keyboard_backspace"
|
android:id="@+id/media_keyboard_backspace"
|
||||||
|
@ -66,14 +66,27 @@
|
||||||
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_tabs"
|
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_tabs"
|
||||||
app:layout_constraintTop_toTopOf="@+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
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/media_keyboard_tabs"
|
android:id="@+id/media_keyboard_tabs"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/media_keyboard_add"
|
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
|
<org.thoughtcrime.securesms.components.RepeatableImageKey
|
||||||
android:id="@+id/media_keyboard_backspace_backup"
|
android:id="@+id/media_keyboard_backspace_backup"
|
||||||
|
|
|
@ -1,23 +1,33 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="?selectableItemBackground"
|
android:background="?selectableItemBackground"
|
||||||
android:animateLayoutChanges="true">
|
android:animateLayoutChanges="true">
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/media_keyboard_bottom_tab_indicator"
|
android:id="@+id/media_keyboard_top_tab_indicator"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="2dp"
|
android:layout_height="2dp"
|
||||||
android:background="?emoji_tab_indicator"/>
|
android:background="?emoji_tab_indicator"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/media_keyboard_bottom_tab_image"
|
android:id="@+id/media_keyboard_bottom_tab_image"
|
||||||
android:layout_width="36dp"
|
android:layout_width="36dp"
|
||||||
android:layout_height="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>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
8
res/layout/scribble_select_new_sticker_activity.xml
Normal file
8
res/layout/scribble_select_new_sticker_activity.xml
Normal 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" />
|
|
@ -355,4 +355,11 @@
|
||||||
<attr name="revealable_unopenedForegroundColor" format="color" />
|
<attr name="revealable_unopenedForegroundColor" format="color" />
|
||||||
<attr name="revealable_openedForegroundColor" format="color" />
|
<attr name="revealable_openedForegroundColor" format="color" />
|
||||||
</declare-styleable>
|
</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>
|
</resources>
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
package org.thoughtcrime.securesms.components.emoji;
|
package org.thoughtcrime.securesms.components.emoji;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.NonNull;
|
import android.content.res.TypedArray;
|
||||||
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.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
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.R;
|
||||||
import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
|
import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
|
||||||
import org.thoughtcrime.securesms.components.RepeatableImageKey;
|
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 static final String TAG = Log.tag(MediaKeyboard.class);
|
||||||
|
|
||||||
private RecyclerView categoryTabs;
|
private RecyclerView categoryTabs;
|
||||||
private ViewPager categoryPager;
|
private ViewPager categoryPager;
|
||||||
private ViewGroup providerTabs;
|
private ViewGroup providerTabs;
|
||||||
private RepeatableImageKey backspaceButton;
|
private RepeatableImageKey backspaceButton;
|
||||||
private RepeatableImageKey backspaceButtonBackup;
|
private RepeatableImageKey backspaceButtonBackup;
|
||||||
private View searchButton;
|
private View searchButton;
|
||||||
private View addButton;
|
private View addButton;
|
||||||
private MediaKeyboardListener keyboardListener;
|
@Nullable private MediaKeyboardListener keyboardListener;
|
||||||
private MediaKeyboardProvider[] providers;
|
private MediaKeyboardProvider[] providers;
|
||||||
private int providerIndex;
|
private int providerIndex;
|
||||||
|
|
||||||
|
private final boolean tabsAtBottom;
|
||||||
|
|
||||||
private MediaKeyboardBottomTabAdapter categoryTabAdapter;
|
private MediaKeyboardBottomTabAdapter categoryTabAdapter;
|
||||||
|
|
||||||
|
@ -48,6 +52,14 @@ public class MediaKeyboard extends FrameLayout implements InputView,
|
||||||
|
|
||||||
public MediaKeyboard(Context context, AttributeSet attrs) {
|
public MediaKeyboard(Context context, AttributeSet attrs) {
|
||||||
super(context, 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) {
|
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;
|
this.keyboardListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,8 +88,14 @@ public class MediaKeyboard extends FrameLayout implements InputView,
|
||||||
params.height = height;
|
params.height = height;
|
||||||
Log.i(TAG, "showing emoji drawer with height " + params.height);
|
Log.i(TAG, "showing emoji drawer with height " + params.height);
|
||||||
setLayoutParams(params);
|
setLayoutParams(params);
|
||||||
setVisibility(VISIBLE);
|
|
||||||
|
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
if (this.categoryPager == null) initView();
|
||||||
|
|
||||||
|
setVisibility(VISIBLE);
|
||||||
if (keyboardListener != null) keyboardListener.onShown();
|
if (keyboardListener != null) keyboardListener.onShown();
|
||||||
|
|
||||||
requestPresent(providers, providerIndex);
|
requestPresent(providers, providerIndex);
|
||||||
|
@ -122,7 +140,7 @@ public class MediaKeyboard extends FrameLayout implements InputView,
|
||||||
public void requestDismissal() {
|
public void requestDismissal() {
|
||||||
hide(true);
|
hide(true);
|
||||||
providerIndex = 0;
|
providerIndex = 0;
|
||||||
keyboardListener.onKeyboardProviderChanged(providers[providerIndex]);
|
if (keyboardListener != null) keyboardListener.onKeyboardProviderChanged(providers[providerIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -148,7 +166,10 @@ public class MediaKeyboard extends FrameLayout implements InputView,
|
||||||
private void initView() {
|
private void initView() {
|
||||||
final View view = LayoutInflater.from(getContext()).inflate(R.layout.media_keyboard, this, true);
|
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.categoryPager = view.findViewById(R.id.media_keyboard_pager);
|
||||||
this.providerTabs = view.findViewById(R.id.media_keyboard_provider_tabs);
|
this.providerTabs = view.findViewById(R.id.media_keyboard_provider_tabs);
|
||||||
this.backspaceButton = view.findViewById(R.id.media_keyboard_backspace);
|
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.searchButton = view.findViewById(R.id.media_keyboard_search);
|
||||||
this.addButton = view.findViewById(R.id.media_keyboard_add);
|
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.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
|
||||||
categoryTabs.setAdapter(categoryTabAdapter);
|
categoryTabs.setAdapter(categoryTabAdapter);
|
||||||
|
categoryTabs.setVisibility(VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestPresent(@NonNull MediaKeyboardProvider[] providers, int newIndex) {
|
private void requestPresent(@NonNull MediaKeyboardProvider[] providers, int newIndex) {
|
||||||
|
|
|
@ -15,19 +15,22 @@ public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter<MediaKey
|
||||||
|
|
||||||
private final GlideRequests glideRequests;
|
private final GlideRequests glideRequests;
|
||||||
private final EventListener eventListener;
|
private final EventListener eventListener;
|
||||||
|
private final boolean highlightTop;
|
||||||
|
|
||||||
private TabIconProvider tabIconProvider;
|
private TabIconProvider tabIconProvider;
|
||||||
private int activePosition;
|
private int activePosition;
|
||||||
private int count;
|
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.glideRequests = glideRequests;
|
||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
|
this.highlightTop = highlightTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull MediaKeyboardBottomTabViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
|
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
|
@Override
|
||||||
|
@ -62,11 +65,16 @@ public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter<MediaKey
|
||||||
private final ImageView image;
|
private final ImageView image;
|
||||||
private final View indicator;
|
private final View indicator;
|
||||||
|
|
||||||
public MediaKeyboardBottomTabViewHolder(@NonNull View itemView) {
|
public MediaKeyboardBottomTabViewHolder(@NonNull View itemView, boolean highlightTop) {
|
||||||
super(itemView);
|
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.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,
|
void bind(@NonNull GlideRequests glideRequests,
|
||||||
|
|
|
@ -19,7 +19,6 @@ package org.thoughtcrime.securesms.conversation;
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -42,16 +41,6 @@ import android.os.Vibrator;
|
||||||
import android.provider.Browser;
|
import android.provider.Browser;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
import android.provider.Telephony;
|
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.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
@ -71,6 +60,18 @@ import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
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 com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
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.AssertedSuccessListener;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
import org.thoughtcrime.securesms.util.views.Stub;
|
import org.thoughtcrime.securesms.util.views.Stub;
|
||||||
import org.whispersystems.libsignal.InvalidMessageException;
|
import org.whispersystems.libsignal.InvalidMessageException;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
@ -2491,9 +2493,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
private void sendSticker(@NonNull StickerRecord stickerRecord, boolean clearCompose) {
|
private void sendSticker(@NonNull StickerRecord stickerRecord, boolean clearCompose) {
|
||||||
sendSticker(new StickerLocator(stickerRecord.getPackId(), stickerRecord.getPackKey(), stickerRecord.getStickerId()), stickerRecord.getUri(), stickerRecord.getSize(), clearCompose);
|
sendSticker(new StickerLocator(stickerRecord.getPackId(), stickerRecord.getPackKey(), stickerRecord.getStickerId()), stickerRecord.getUri(), stickerRecord.getSize(), clearCompose);
|
||||||
|
|
||||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
SignalExecutors.BOUNDED.execute(() ->
|
||||||
DatabaseFactory.getStickerDatabase(this).updateStickerLastUsedTime(stickerRecord.getRowId(), System.currentTimeMillis());
|
DatabaseFactory.getStickerDatabase(getApplicationContext())
|
||||||
});
|
.updateStickerLastUsedTime(stickerRecord.getRowId(), System.currentTimeMillis())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendSticker(@NonNull StickerLocator stickerLocator, @NonNull Uri uri, long size, boolean clearCompose) {
|
private void sendSticker(@NonNull StickerLocator stickerLocator, @NonNull Uri uri, long size, boolean clearCompose) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.thoughtcrime.securesms.database.model;
|
package org.thoughtcrime.securesms.database.model;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
package org.thoughtcrime.securesms.mediasend;
|
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.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -15,12 +10,6 @@ import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
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.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
@ -32,6 +21,16 @@ import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
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.PassphraseRequiredActionBarActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.TransportOption;
|
import org.thoughtcrime.securesms.TransportOption;
|
||||||
|
@ -438,10 +437,13 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestFullScreen(boolean fullScreen) {
|
public void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard) {
|
||||||
if (captionAndRail != null) {
|
if (captionAndRail != null) {
|
||||||
captionAndRail.setVisibility(fullScreen ? View.GONE : View.VISIBLE);
|
captionAndRail.setVisibility(fullScreen ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
if (hideKeyboard && hud.isKeyboardOpen()) {
|
||||||
|
hud.hideSoftkey(composeText, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -6,14 +6,16 @@ import android.graphics.Bitmap;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
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.R;
|
||||||
import org.thoughtcrime.securesms.imageeditor.ColorableRenderer;
|
import org.thoughtcrime.securesms.imageeditor.ColorableRenderer;
|
||||||
import org.thoughtcrime.securesms.imageeditor.ImageEditorView;
|
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.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
||||||
|
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
import org.thoughtcrime.securesms.util.ParcelUtil;
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
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 org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
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";
|
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;
|
private EditorModel restoredModel;
|
||||||
|
|
||||||
@Nullable
|
@Nullable private EditorElement currentSelection;
|
||||||
private EditorElement currentSelection;
|
private int imageMaxHeight;
|
||||||
private int imageMaxHeight;
|
private int imageMaxWidth;
|
||||||
private int imageMaxWidth;
|
private ImageEditorFragmentViewModel viewModel;
|
||||||
|
|
||||||
public static class Data {
|
public static class Data {
|
||||||
private final Bundle bundle;
|
private final Bundle bundle;
|
||||||
|
@ -118,6 +121,13 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
||||||
|
|
||||||
imageMaxWidth = mediaConstraints.getImageMaxWidth(requireContext());
|
imageMaxWidth = mediaConstraints.getImageMaxWidth(requireContext());
|
||||||
imageMaxHeight = mediaConstraints.getImageMaxHeight(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
|
@Nullable
|
||||||
|
@ -233,15 +243,26 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
if (resultCode == RESULT_OK && requestCode == SELECT_STICKER_REQUEST_CODE && data != null) {
|
if (resultCode == RESULT_OK && requestCode == SELECT_NEW_STICKER_REQUEST_CODE && data != null) {
|
||||||
final String stickerFile = data.getStringExtra(StickerSelectActivity.EXTRA_STICKER_FILE);
|
final Uri uri = data.getData();
|
||||||
|
if (uri != null) {
|
||||||
UriGlideRenderer renderer = new UriGlideRenderer(Uri.parse("file:///android_asset/" + stickerFile), false, imageMaxWidth, imageMaxHeight);
|
UriGlideRenderer renderer = new UriGlideRenderer(uri, true, imageMaxWidth, imageMaxHeight);
|
||||||
EditorElement element = new EditorElement(renderer);
|
EditorElement element = new EditorElement(renderer);
|
||||||
imageEditorView.getModel().addElementCentered(element, 0.2f);
|
imageEditorView.getModel().addElementCentered(element, 0.2f);
|
||||||
currentSelection = element;
|
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 {
|
} 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);
|
controller.onTouchEventsNeeded(mode != ImageEditorHud.Mode.NONE);
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case CROP:
|
case CROP: {
|
||||||
imageEditorView.getModel().startCrop();
|
imageEditorView.getModel().startCrop();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case DRAW:
|
case DRAW: {
|
||||||
imageEditorView.startDrawing(0.01f, Paint.Cap.ROUND);
|
imageEditorView.startDrawing(0.01f, Paint.Cap.ROUND);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case HIGHLIGHT:
|
case HIGHLIGHT: {
|
||||||
imageEditorView.startDrawing(0.03f, Paint.Cap.SQUARE);
|
imageEditorView.startDrawing(0.03f, Paint.Cap.SQUARE);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case TEXT:
|
case TEXT: {
|
||||||
addText();
|
addText();
|
||||||
break;
|
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:
|
case MOVE_DELETE:
|
||||||
Intent intent = new Intent(getContext(), StickerSelectActivity.class);
|
|
||||||
startActivityForResult(intent, SELECT_STICKER_REQUEST_CODE);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NONE:
|
case NONE: {
|
||||||
imageEditorView.getModel().doneCrop();
|
imageEditorView.getModel().doneCrop();
|
||||||
currentSelection = null;
|
currentSelection = null;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,8 +386,8 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestFullScreen(boolean fullScreen) {
|
public void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard) {
|
||||||
controller.onRequestFullScreen(fullScreen);
|
controller.onRequestFullScreen(fullScreen, hideKeyboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshUniqueColors() {
|
private void refreshUniqueColors() {
|
||||||
|
@ -371,8 +407,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
||||||
} else {
|
} else {
|
||||||
currentSelection = null;
|
currentSelection = null;
|
||||||
controller.onTouchEventsNeeded(false);
|
controller.onTouchEventsNeeded(false);
|
||||||
imageEditorHud.enterMode(ImageEditorHud.Mode.NONE);
|
imageEditorHud.setMode(ImageEditorHud.Mode.NONE);
|
||||||
imageEditorView.doneTextEditing();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,7 +418,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
||||||
if (editorElement.getRenderer() instanceof MultiLineTextRenderer) {
|
if (editorElement.getRenderer() instanceof MultiLineTextRenderer) {
|
||||||
setTextElement(editorElement, (ColorableRenderer) editorElement.getRenderer(), imageEditorView.isTextEditing());
|
setTextElement(editorElement, (ColorableRenderer) editorElement.getRenderer(), imageEditorView.isTextEditing());
|
||||||
} else {
|
} 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 {
|
public interface Controller {
|
||||||
void onTouchEventsNeeded(boolean needed);
|
void onTouchEventsNeeded(boolean needed);
|
||||||
|
|
||||||
void onRequestFullScreen(boolean fullScreen);
|
void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,15 +2,17 @@ package org.thoughtcrime.securesms.scribbles;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Color;
|
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.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
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.R;
|
||||||
import org.thoughtcrime.securesms.scribbles.widget.ColorPaletteAdapter;
|
import org.thoughtcrime.securesms.scribbles.widget.ColorPaletteAdapter;
|
||||||
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
||||||
|
@ -34,7 +36,8 @@ public final class ImageEditorHud extends LinearLayout {
|
||||||
private View drawButton;
|
private View drawButton;
|
||||||
private View highlightButton;
|
private View highlightButton;
|
||||||
private View textButton;
|
private View textButton;
|
||||||
private View stickerButton;
|
private View oldStickerButton;
|
||||||
|
private View newStickerButton;
|
||||||
private View undoButton;
|
private View undoButton;
|
||||||
private View saveButton;
|
private View saveButton;
|
||||||
private View deleteButton;
|
private View deleteButton;
|
||||||
|
@ -80,7 +83,8 @@ public final class ImageEditorHud extends LinearLayout {
|
||||||
drawButton = findViewById(R.id.scribble_draw_button);
|
drawButton = findViewById(R.id.scribble_draw_button);
|
||||||
highlightButton = findViewById(R.id.scribble_highlight_button);
|
highlightButton = findViewById(R.id.scribble_highlight_button);
|
||||||
textButton = findViewById(R.id.scribble_text_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);
|
undoButton = findViewById(R.id.scribble_undo_button);
|
||||||
saveButton = findViewById(R.id.scribble_save_button);
|
saveButton = findViewById(R.id.scribble_save_button);
|
||||||
deleteButton = findViewById(R.id.scribble_delete_button);
|
deleteButton = findViewById(R.id.scribble_delete_button);
|
||||||
|
@ -102,7 +106,7 @@ public final class ImageEditorHud extends LinearLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeVisibilityMap() {
|
private void initializeVisibilityMap() {
|
||||||
setVisibleViewsWhenInMode(Mode.NONE, drawButton, highlightButton, textButton, stickerButton, cropButton, undoButton, saveButton);
|
setStickersAvailable(false);
|
||||||
|
|
||||||
setVisibleViewsWhenInMode(Mode.DRAW, confirmButton, undoButton, colorPicker, colorPalette);
|
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.MOVE_DELETE, confirmButton, deleteButton);
|
||||||
|
|
||||||
|
setVisibleViewsWhenInMode(Mode.INSERT_STICKER, confirmButton);
|
||||||
|
|
||||||
|
setVisibleViewsWhenInMode(Mode.INSERT_ASSET_STICKER, confirmButton);
|
||||||
|
|
||||||
setVisibleViewsWhenInMode(Mode.CROP, confirmButton, cropFlipButton, cropRotateButton, cropAspectLock, undoButton);
|
setVisibleViewsWhenInMode(Mode.CROP, confirmButton, cropFlipButton, cropRotateButton, cropAspectLock, undoButton);
|
||||||
|
|
||||||
for (Set<View> views : visibilityModeMap.values()) {
|
for (Set<View> views : visibilityModeMap.values()) {
|
||||||
allViews.addAll(views);
|
allViews.addAll(views);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allViews.add(newStickerButton);
|
||||||
|
allViews.add(oldStickerButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setVisibleViewsWhenInMode(Mode mode, View... views) {
|
private void setVisibleViewsWhenInMode(Mode mode, View... views) {
|
||||||
visibilityModeMap.put(mode, new HashSet<>(Arrays.asList(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() {
|
private void initializeViews() {
|
||||||
undoButton.setOnClickListener(v -> eventListener.onUndo());
|
undoButton.setOnClickListener(v -> eventListener.onUndo());
|
||||||
|
|
||||||
|
@ -146,7 +167,8 @@ public final class ImageEditorHud extends LinearLayout {
|
||||||
drawButton.setOnClickListener(v -> setMode(Mode.DRAW));
|
drawButton.setOnClickListener(v -> setMode(Mode.DRAW));
|
||||||
highlightButton.setOnClickListener(v -> setMode(Mode.HIGHLIGHT));
|
highlightButton.setOnClickListener(v -> setMode(Mode.HIGHLIGHT));
|
||||||
textButton.setOnClickListener(v -> setMode(Mode.TEXT));
|
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());
|
saveButton.setOnClickListener(v -> eventListener.onSave());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,16 +194,13 @@ public final class ImageEditorHud extends LinearLayout {
|
||||||
setMode(mode, false);
|
setMode(mode, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setMode(@NonNull Mode mode) {
|
public void setMode(@NonNull Mode mode) {
|
||||||
setMode(mode, true);
|
setMode(mode, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setMode(@NonNull Mode mode, boolean notify) {
|
private void setMode(@NonNull Mode mode, boolean notify) {
|
||||||
this.currentMode = mode;
|
this.currentMode = mode;
|
||||||
Set<View> visibleButtons = visibilityModeMap.get(mode);
|
updateButtonVisibility(mode);
|
||||||
for (View button : allViews) {
|
|
||||||
button.setVisibility(buttonIsVisible(visibleButtons, button) ? VISIBLE : GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case CROP: presentModeCrop(); break;
|
case CROP: presentModeCrop(); break;
|
||||||
|
@ -193,7 +212,14 @@ public final class ImageEditorHud extends LinearLayout {
|
||||||
if (notify) {
|
if (notify) {
|
||||||
eventListener.onModeStarted(mode);
|
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) {
|
private boolean buttonIsVisible(@Nullable Set<View> visibleButtons, @NonNull View button) {
|
||||||
|
@ -236,7 +262,14 @@ public final class ImageEditorHud extends LinearLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Mode {
|
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 {
|
public interface EventListener {
|
||||||
|
@ -249,7 +282,7 @@ public final class ImageEditorHud extends LinearLayout {
|
||||||
void onRotate90AntiClockwise();
|
void onRotate90AntiClockwise();
|
||||||
void onCropAspectLock(boolean locked);
|
void onCropAspectLock(boolean locked);
|
||||||
boolean isCropAspectLocked();
|
boolean isCropAspectLocked();
|
||||||
void onRequestFullScreen(boolean fullScreen);
|
void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final EventListener NULL_EVENT_LISTENER = new EventListener() {
|
private static final EventListener NULL_EVENT_LISTENER = new EventListener() {
|
||||||
|
@ -292,7 +325,7 @@ public final class ImageEditorHud extends LinearLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestFullScreen(boolean fullScreen) {
|
public void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,9 +63,7 @@ final class UriGlideRenderer implements Renderer {
|
||||||
try {
|
try {
|
||||||
Bitmap bitmap = getBitmapGlideRequest(rendererContext.context, false).submit().get();
|
Bitmap bitmap = getBitmapGlideRequest(rendererContext.context, false).submit().get();
|
||||||
setBitmap(rendererContext, bitmap);
|
setBitmap(rendererContext, bitmap);
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -73,6 +71,8 @@ final class UriGlideRenderer implements Renderer {
|
||||||
@Override
|
@Override
|
||||||
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
|
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
|
||||||
setBitmap(rendererContext, resource);
|
setBitmap(rendererContext, resource);
|
||||||
|
|
||||||
|
rendererContext.invalidate.onInvalidate(UriGlideRenderer.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -99,7 +99,7 @@ final class UriGlideRenderer implements Renderer {
|
||||||
paint.setAlpha(alpha);
|
paint.setAlpha(alpha);
|
||||||
|
|
||||||
rendererContext.restore();
|
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.
|
// 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);
|
rendererContext.canvas.drawRect(Bounds.FULL_BOUNDS, paint);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package org.thoughtcrime.securesms.stickers;
|
package org.thoughtcrime.securesms.stickers;
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider;
|
import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider;
|
||||||
|
@ -48,7 +49,7 @@ public final class StickerKeyboardProvider implements MediaKeyboardProvider,
|
||||||
private boolean isSoloProvider;
|
private boolean isSoloProvider;
|
||||||
private StickerKeyboardViewModel viewModel;
|
private StickerKeyboardViewModel viewModel;
|
||||||
|
|
||||||
public StickerKeyboardProvider(@NonNull AppCompatActivity activity,
|
public StickerKeyboardProvider(@NonNull FragmentActivity activity,
|
||||||
@NonNull StickerEventListener eventListener)
|
@NonNull StickerEventListener eventListener)
|
||||||
{
|
{
|
||||||
this.context = activity;
|
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));
|
StickerKeyboardRepository repository = new StickerKeyboardRepository(DatabaseFactory.getStickerDatabase(activity));
|
||||||
viewModel = ViewModelProviders.of(activity, new StickerKeyboardViewModel.Factory(activity.getApplication(), repository)).get(StickerKeyboardViewModel.class);
|
viewModel = ViewModelProviders.of(activity, new StickerKeyboardViewModel.Factory(activity.getApplication(), repository)).get(StickerKeyboardViewModel.class);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue