mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-01 22:25:46 +01:00
Implemented new camera capture flow.
A new, fullscreen camera capture flow that easily allows you to capture and edit a photo before sending it. Replaces the current half-screen camera button.
This commit is contained in:
@@ -1,272 +1,69 @@
|
||||
package org.thoughtcrime.securesms.scribbles;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.PointF;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.scribbles.viewmodel.Font;
|
||||
import org.thoughtcrime.securesms.scribbles.viewmodel.Layer;
|
||||
import org.thoughtcrime.securesms.scribbles.viewmodel.TextLayer;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.MotionView;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.ScribbleView;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.entity.ImageEntity;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.entity.MotionEntity;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.entity.TextEntity;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import org.thoughtcrime.securesms.TransportOption;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public class ScribbleActivity extends PassphraseRequiredActionBarActivity implements ScribbleHud.EventListener, VerticalSlideColorPicker.OnColorChangeListener {
|
||||
public class ScribbleActivity extends PassphraseRequiredActionBarActivity implements ScribbleFragment.Controller {
|
||||
|
||||
private static final String TAG = ScribbleActivity.class.getName();
|
||||
|
||||
public static final int SELECT_STICKER_REQUEST_CODE = 123;
|
||||
public static final int SCRIBBLE_REQUEST_CODE = 31424;
|
||||
public static final int SCRIBBLE_REQUEST_CODE = 31424;
|
||||
|
||||
private ScribbleHud scribbleHud;
|
||||
private ScribbleView scribbleView;
|
||||
private GlideRequests glideRequests;
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
dynamicLanguage.onCreate(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||
setContentView(R.layout.scribble_activity);
|
||||
|
||||
this.glideRequests = GlideApp.with(this);
|
||||
this.scribbleHud = findViewById(R.id.scribble_hud);
|
||||
this.scribbleView = findViewById(R.id.scribble_view);
|
||||
|
||||
scribbleHud.setEventListener(this);
|
||||
|
||||
scribbleView.setMotionViewCallback(motionViewCallback);
|
||||
scribbleView.setDrawingChangedListener(() -> scribbleHud.setColorPalette(scribbleView.getUniqueColors()));
|
||||
scribbleView.setDrawingMode(false);
|
||||
scribbleView.setImage(glideRequests, getIntent().getData());
|
||||
if (savedInstanceState == null) {
|
||||
ScribbleFragment fragment = ScribbleFragment.newInstance(getIntent().getData(), dynamicLanguage.getCurrentLocale(), Optional.absent());
|
||||
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, fragment).commit();
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
||||
}
|
||||
}
|
||||
|
||||
private void addSticker(final Bitmap pica) {
|
||||
Util.runOnMain(() -> {
|
||||
Layer layer = new Layer();
|
||||
ImageEntity entity = new ImageEntity(layer, pica, scribbleView.getWidth(), scribbleView.getHeight());
|
||||
|
||||
scribbleView.addEntityAndPosition(entity);
|
||||
});
|
||||
}
|
||||
|
||||
private void changeTextEntityColor(int selectedColor) {
|
||||
TextEntity textEntity = currentTextEntity();
|
||||
|
||||
if (textEntity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
textEntity.getLayer().getFont().setColor(selectedColor);
|
||||
textEntity.updateEntity();
|
||||
scribbleView.invalidate();
|
||||
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
|
||||
}
|
||||
|
||||
private void startTextEntityEditing() {
|
||||
TextEntity textEntity = currentTextEntity();
|
||||
if (textEntity != null) {
|
||||
scribbleView.startEditing(textEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TextEntity currentTextEntity() {
|
||||
if (scribbleView != null && scribbleView.getSelectedEntity() instanceof TextEntity) {
|
||||
return ((TextEntity) scribbleView.getSelectedEntity());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void addTextSticker() {
|
||||
TextLayer textLayer = createTextLayer();
|
||||
TextEntity textEntity = new TextEntity(textLayer, scribbleView.getWidth(), scribbleView.getHeight());
|
||||
scribbleView.addEntityAndPosition(textEntity);
|
||||
|
||||
PointF center = textEntity.absoluteCenter();
|
||||
center.y = center.y * 0.5F;
|
||||
textEntity.moveCenterTo(center);
|
||||
|
||||
scribbleView.invalidate();
|
||||
|
||||
startTextEntityEditing();
|
||||
changeTextEntityColor(scribbleHud.getActiveColor());
|
||||
}
|
||||
|
||||
private TextLayer createTextLayer() {
|
||||
TextLayer textLayer = new TextLayer();
|
||||
Font font = new Font();
|
||||
|
||||
font.setColor(scribbleHud.getActiveColor());
|
||||
font.setSize(TextLayer.Limits.INITIAL_FONT_SIZE);
|
||||
|
||||
textLayer.setFont(font);
|
||||
|
||||
return textLayer;
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCode == RESULT_OK) {
|
||||
if (requestCode == SELECT_STICKER_REQUEST_CODE) {
|
||||
if (data != null) {
|
||||
final String stickerFile = data.getStringExtra(StickerSelectActivity.EXTRA_STICKER_FILE);
|
||||
|
||||
new AsyncTask<Void, Void, Bitmap>() {
|
||||
@Override
|
||||
protected @Nullable
|
||||
Bitmap doInBackground(Void... params) {
|
||||
try {
|
||||
return BitmapFactory.decodeStream(getAssets().open(stickerFile));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(@Nullable Bitmap bitmap) {
|
||||
addSticker(bitmap);
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
}
|
||||
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModeStarted(@NonNull ScribbleHud.Mode mode) {
|
||||
switch (mode) {
|
||||
case DRAW:
|
||||
scribbleView.setDrawingMode(true);
|
||||
scribbleView.setDrawingBrushWidth(ScribbleView.DEFAULT_BRUSH_WIDTH);
|
||||
break;
|
||||
|
||||
case HIGHLIGHT:
|
||||
scribbleView.setDrawingMode(true);
|
||||
scribbleView.setDrawingBrushWidth(ScribbleView.DEFAULT_BRUSH_WIDTH * 3);
|
||||
break;
|
||||
|
||||
case TEXT:
|
||||
scribbleView.setDrawingMode(false);
|
||||
addTextSticker();
|
||||
break;
|
||||
|
||||
case STICKER:
|
||||
scribbleView.setDrawingMode(false);
|
||||
Intent intent = new Intent(this, StickerSelectActivity.class);
|
||||
startActivityForResult(intent, SELECT_STICKER_REQUEST_CODE);
|
||||
break;
|
||||
|
||||
case NONE:
|
||||
scribbleView.clearSelection();
|
||||
scribbleView.setDrawingMode(false);
|
||||
break;
|
||||
}
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
dynamicLanguage.onResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorChange(int color) {
|
||||
scribbleView.setDrawingBrushColor(color);
|
||||
changeTextEntityColor(color);
|
||||
public void onImageEditComplete(@NonNull Uri uri, int width, int height, long size, @NonNull Optional<String> message, @NonNull Optional<TransportOption> transport) {
|
||||
Intent intent = new Intent();
|
||||
intent.setData(uri);
|
||||
setResult(RESULT_OK, intent);
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUndo() {
|
||||
scribbleView.undoDrawing();
|
||||
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
|
||||
public void onImageEditFailure() {
|
||||
Toast.makeText(ScribbleActivity.this, R.string.ScribbleActivity_save_failure, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDelete() {
|
||||
scribbleView.deleteSelected();
|
||||
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSave() {
|
||||
ListenableFuture<Bitmap> future = scribbleView.getRenderedImage(glideRequests);
|
||||
|
||||
future.addListener(new ListenableFuture.Listener<Bitmap>() {
|
||||
@Override
|
||||
public void onSuccess(Bitmap result) {
|
||||
PersistentBlobProvider provider = PersistentBlobProvider.getInstance(ScribbleActivity.this);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
result.compress(Bitmap.CompressFormat.JPEG, 80, baos);
|
||||
|
||||
byte[] data = baos.toByteArray();
|
||||
baos = null;
|
||||
result = null;
|
||||
|
||||
Uri uri = provider.create(ScribbleActivity.this, data, MediaUtil.IMAGE_JPEG, null);
|
||||
Intent intent = new Intent();
|
||||
intent.setData(uri);
|
||||
setResult(RESULT_OK, intent);
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ExecutionException e) {
|
||||
Log.w(TAG, e);
|
||||
Toast.makeText(ScribbleActivity.this, R.string.ScribbleActivity_save_failure, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final MotionView.MotionViewCallback motionViewCallback = new MotionView.MotionViewCallback() {
|
||||
@Override
|
||||
public void onEntitySelected(@Nullable MotionEntity entity) {
|
||||
if (entity == null) {
|
||||
scribbleHud.enterMode(ScribbleHud.Mode.NONE);
|
||||
} else if (entity instanceof TextEntity) {
|
||||
int textColor = ((TextEntity) entity).getLayer().getFont().getColor();
|
||||
|
||||
scribbleHud.enterMode(ScribbleHud.Mode.TEXT);
|
||||
scribbleHud.setActiveColor(textColor);
|
||||
} else {
|
||||
scribbleHud.enterMode(ScribbleHud.Mode.STICKER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntityDoubleTap(@NonNull MotionEntity entity) {
|
||||
startTextEntityEditing();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
306
src/org/thoughtcrime/securesms/scribbles/ScribbleFragment.java
Normal file
306
src/org/thoughtcrime/securesms/scribbles/ScribbleFragment.java
Normal file
@@ -0,0 +1,306 @@
|
||||
package org.thoughtcrime.securesms.scribbles;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.PointF;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.TransportOption;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.scribbles.viewmodel.Font;
|
||||
import org.thoughtcrime.securesms.scribbles.viewmodel.Layer;
|
||||
import org.thoughtcrime.securesms.scribbles.viewmodel.TextLayer;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.MotionView;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.ScribbleView;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.entity.ImageEntity;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.entity.MotionEntity;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.entity.TextEntity;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.LifecycleBoundTask;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public class ScribbleFragment extends Fragment implements ScribbleHud.EventListener, VerticalSlideColorPicker.OnColorChangeListener {
|
||||
|
||||
private static final String TAG = ScribbleFragment.class.getName();
|
||||
|
||||
private static final String KEY_IMAGE_URI = "image_uri";
|
||||
private static final String KEY_LOCALE = "locale";
|
||||
private static final String KEY_TRANSPORT = "compose_mode";
|
||||
|
||||
public static final int SELECT_STICKER_REQUEST_CODE = 123;
|
||||
|
||||
private Controller controller;
|
||||
private ScribbleHud scribbleHud;
|
||||
private ScribbleView scribbleView;
|
||||
private GlideRequests glideRequests;
|
||||
|
||||
public static ScribbleFragment newInstance(@NonNull Uri imageUri, @NonNull Locale locale, Optional<TransportOption> transport) {
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(KEY_IMAGE_URI, imageUri);
|
||||
args.putSerializable(KEY_LOCALE, locale);
|
||||
args.putParcelable(KEY_TRANSPORT, transport.orNull());
|
||||
|
||||
ScribbleFragment fragment = new ScribbleFragment();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (!(getActivity() instanceof Controller)) {
|
||||
throw new IllegalStateException("Parent activity must implement Controller interface.");
|
||||
}
|
||||
controller = (Controller) getActivity();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.scribble_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
this.glideRequests = GlideApp.with(this);
|
||||
this.scribbleHud = view.findViewById(R.id.scribble_hud);
|
||||
this.scribbleView = view.findViewById(R.id.scribble_view);
|
||||
|
||||
scribbleHud.setEventListener(this);
|
||||
scribbleHud.setTransport(Optional.fromNullable(getArguments().getParcelable(KEY_TRANSPORT)));
|
||||
scribbleHud.setFullscreen((getActivity().getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) > 0);
|
||||
|
||||
scribbleView.setMotionViewCallback(motionViewCallback);
|
||||
scribbleView.setDrawingChangedListener(() -> scribbleHud.setColorPalette(scribbleView.getUniqueColors()));
|
||||
scribbleView.setDrawingMode(false);
|
||||
scribbleView.setImage(glideRequests, getArguments().getParcelable(KEY_IMAGE_URI));
|
||||
}
|
||||
|
||||
public boolean isEmojiKeyboardVisible() {
|
||||
return scribbleHud.isInputOpen();
|
||||
}
|
||||
|
||||
public void dismissEmojiKeyboard() {
|
||||
scribbleHud.dismissEmojiKeyboard();
|
||||
}
|
||||
|
||||
private void addSticker(final Bitmap pica) {
|
||||
Util.runOnMain(() -> {
|
||||
Layer layer = new Layer();
|
||||
ImageEntity entity = new ImageEntity(layer, pica, scribbleView.getWidth(), scribbleView.getHeight());
|
||||
|
||||
scribbleView.addEntityAndPosition(entity);
|
||||
});
|
||||
}
|
||||
|
||||
private void changeTextEntityColor(int selectedColor) {
|
||||
TextEntity textEntity = currentTextEntity();
|
||||
|
||||
if (textEntity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
textEntity.getLayer().getFont().setColor(selectedColor);
|
||||
textEntity.updateEntity();
|
||||
scribbleView.invalidate();
|
||||
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
|
||||
}
|
||||
|
||||
private void startTextEntityEditing() {
|
||||
TextEntity textEntity = currentTextEntity();
|
||||
if (textEntity != null) {
|
||||
scribbleView.startEditing(textEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TextEntity currentTextEntity() {
|
||||
if (scribbleView != null && scribbleView.getSelectedEntity() instanceof TextEntity) {
|
||||
return ((TextEntity) scribbleView.getSelectedEntity());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void addTextSticker() {
|
||||
TextLayer textLayer = createTextLayer();
|
||||
TextEntity textEntity = new TextEntity(textLayer, scribbleView.getWidth(), scribbleView.getHeight());
|
||||
scribbleView.addEntityAndPosition(textEntity);
|
||||
|
||||
PointF center = textEntity.absoluteCenter();
|
||||
center.y = center.y * 0.5F;
|
||||
textEntity.moveCenterTo(center);
|
||||
|
||||
scribbleView.invalidate();
|
||||
|
||||
startTextEntityEditing();
|
||||
changeTextEntityColor(scribbleHud.getActiveColor());
|
||||
}
|
||||
|
||||
private TextLayer createTextLayer() {
|
||||
TextLayer textLayer = new TextLayer();
|
||||
Font font = new Font();
|
||||
|
||||
font.setColor(scribbleHud.getActiveColor());
|
||||
font.setSize(TextLayer.Limits.INITIAL_FONT_SIZE);
|
||||
|
||||
textLayer.setFont(font);
|
||||
|
||||
return textLayer;
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@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);
|
||||
|
||||
LifecycleBoundTask.run(getLifecycle(), () -> {
|
||||
try {
|
||||
return BitmapFactory.decodeStream(getContext().getAssets().open(stickerFile));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}, bitmap -> {
|
||||
if (bitmap != null) {
|
||||
addSticker(bitmap);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModeStarted(@NonNull ScribbleHud.Mode mode) {
|
||||
switch (mode) {
|
||||
case DRAW:
|
||||
scribbleView.setDrawingMode(true);
|
||||
scribbleView.setDrawingBrushWidth(ScribbleView.DEFAULT_BRUSH_WIDTH);
|
||||
break;
|
||||
|
||||
case HIGHLIGHT:
|
||||
scribbleView.setDrawingMode(true);
|
||||
scribbleView.setDrawingBrushWidth(ScribbleView.DEFAULT_BRUSH_WIDTH * 3);
|
||||
break;
|
||||
|
||||
case TEXT:
|
||||
scribbleView.setDrawingMode(false);
|
||||
addTextSticker();
|
||||
break;
|
||||
|
||||
case STICKER:
|
||||
scribbleView.setDrawingMode(false);
|
||||
Intent intent = new Intent(getContext(), StickerSelectActivity.class);
|
||||
startActivityForResult(intent, SELECT_STICKER_REQUEST_CODE);
|
||||
break;
|
||||
|
||||
case NONE:
|
||||
scribbleView.clearSelection();
|
||||
scribbleView.setDrawingMode(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorChange(int color) {
|
||||
scribbleView.setDrawingBrushColor(color);
|
||||
changeTextEntityColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUndo() {
|
||||
scribbleView.undoDrawing();
|
||||
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDelete() {
|
||||
scribbleView.deleteSelected();
|
||||
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEditComplete(@NonNull Optional<String> message, Optional<TransportOption> transport) {
|
||||
ListenableFuture<Bitmap> future = scribbleView.getRenderedImage(glideRequests);
|
||||
|
||||
future.addListener(new ListenableFuture.Listener<Bitmap>() {
|
||||
@Override
|
||||
public void onSuccess(Bitmap result) {
|
||||
PersistentBlobProvider provider = PersistentBlobProvider.getInstance(getContext());
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
result.compress(Bitmap.CompressFormat.JPEG, 80, baos);
|
||||
|
||||
byte[] data = baos.toByteArray();
|
||||
|
||||
controller.onImageEditComplete(provider.create(getContext(), data, MediaUtil.IMAGE_JPEG, null),
|
||||
result.getWidth(),
|
||||
result.getHeight(),
|
||||
data.length,
|
||||
message,
|
||||
transport);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ExecutionException e) {
|
||||
Log.w(TAG, e);
|
||||
controller.onImageEditFailure();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final MotionView.MotionViewCallback motionViewCallback = new MotionView.MotionViewCallback() {
|
||||
@Override
|
||||
public void onEntitySelected(@Nullable MotionEntity entity) {
|
||||
if (entity == null) {
|
||||
scribbleHud.enterMode(ScribbleHud.Mode.NONE);
|
||||
} else if (entity instanceof TextEntity) {
|
||||
int textColor = ((TextEntity) entity).getLayer().getFont().getColor();
|
||||
|
||||
scribbleHud.enterMode(ScribbleHud.Mode.TEXT);
|
||||
scribbleHud.setActiveColor(textColor);
|
||||
} else {
|
||||
scribbleHud.enterMode(ScribbleHud.Mode.STICKER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntityDoubleTap(@NonNull MotionEntity entity) {
|
||||
startTextEntityEditing();
|
||||
}
|
||||
};
|
||||
|
||||
public interface Controller {
|
||||
void onImageEditComplete(@NonNull Uri uri, int width, int height, long size, @NonNull Optional<String> message, @NonNull Optional<TransportOption> transport);
|
||||
void onImageEditFailure();
|
||||
}
|
||||
}
|
||||
@@ -2,26 +2,43 @@ package org.thoughtcrime.securesms.scribbles;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.TransportOption;
|
||||
import org.thoughtcrime.securesms.components.ComposeText;
|
||||
import org.thoughtcrime.securesms.components.InputAwareLayout;
|
||||
import org.thoughtcrime.securesms.components.SendButton;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.ColorPaletteAdapter;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
||||
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The HUD (heads-up display) that contains all of the tools for interacting with
|
||||
* {@link org.thoughtcrime.securesms.scribbles.widget.ScribbleView}
|
||||
*/
|
||||
public class ScribbleHud extends FrameLayout {
|
||||
public class ScribbleHud extends InputAwareLayout implements ViewTreeObserver.OnGlobalLayoutListener {
|
||||
|
||||
private View drawButton;
|
||||
private View highlightButton;
|
||||
@@ -32,9 +49,20 @@ public class ScribbleHud extends FrameLayout {
|
||||
private View saveButton;
|
||||
private VerticalSlideColorPicker colorPicker;
|
||||
private RecyclerView colorPalette;
|
||||
private ViewGroup inputContainer;
|
||||
private ComposeText composeText;
|
||||
private SendButton sendButton;
|
||||
private ViewGroup sendButtonBkg;
|
||||
private EmojiToggle emojiToggle;
|
||||
private Stub<EmojiDrawer> emojiDrawer;
|
||||
private TextView charactersLeft;
|
||||
|
||||
private EventListener eventListener;
|
||||
private ColorPaletteAdapter colorPaletteAdapter;
|
||||
private int visibleHeight;
|
||||
private Locale locale;
|
||||
|
||||
private final Rect visibleBounds = new Rect();
|
||||
|
||||
public ScribbleHud(@NonNull Context context) {
|
||||
super(context);
|
||||
@@ -51,8 +79,36 @@ public class ScribbleHud extends FrameLayout {
|
||||
initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
getRootView().getViewTreeObserver().addOnGlobalLayoutListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
getRootView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
getRootView().getWindowVisibleDisplayFrame(visibleBounds);
|
||||
|
||||
int currentVisibleHeight = visibleBounds.height();
|
||||
|
||||
if (currentVisibleHeight != visibleHeight) {
|
||||
getLayoutParams().height = currentVisibleHeight;
|
||||
layout(visibleBounds.left, visibleBounds.top, visibleBounds.right, visibleBounds.bottom);
|
||||
requestLayout();
|
||||
|
||||
visibleHeight = currentVisibleHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
inflate(getContext(), R.layout.scribble_hud, this);
|
||||
setOrientation(VERTICAL);
|
||||
|
||||
drawButton = findViewById(R.id.scribble_draw_button);
|
||||
highlightButton = findViewById(R.id.scribble_highlight_button);
|
||||
@@ -63,7 +119,20 @@ public class ScribbleHud extends FrameLayout {
|
||||
saveButton = findViewById(R.id.scribble_save_button);
|
||||
colorPicker = findViewById(R.id.scribble_color_picker);
|
||||
colorPalette = findViewById(R.id.scribble_color_palette);
|
||||
inputContainer = findViewById(R.id.scribble_compose_container);
|
||||
composeText = findViewById(R.id.scribble_compose_text);
|
||||
sendButton = findViewById(R.id.scribble_send_button);
|
||||
sendButtonBkg = findViewById(R.id.scribble_send_button_bkg);
|
||||
emojiToggle = findViewById(R.id.scribble_emoji_toggle);
|
||||
emojiDrawer = new Stub<>(findViewById(R.id.scribble_emoji_drawer_stub));
|
||||
charactersLeft = findViewById(R.id.scribble_characters_left);
|
||||
|
||||
initializeViews();
|
||||
setMode(Mode.NONE);
|
||||
setTransport(Optional.absent());
|
||||
}
|
||||
|
||||
private void initializeViews() {
|
||||
undoButton.setOnClickListener(v -> {
|
||||
if (eventListener != null) {
|
||||
eventListener.onUndo();
|
||||
@@ -79,24 +148,79 @@ public class ScribbleHud extends FrameLayout {
|
||||
|
||||
saveButton.setOnClickListener(v -> {
|
||||
if (eventListener != null) {
|
||||
eventListener.onSave();
|
||||
eventListener.onEditComplete(Optional.absent(), Optional.absent());
|
||||
}
|
||||
setMode(Mode.NONE);
|
||||
});
|
||||
|
||||
sendButton.setOnClickListener(v -> {
|
||||
if (eventListener != null) {
|
||||
if (isKeyboardOpen()) {
|
||||
hideSoftkey(composeText, null);
|
||||
}
|
||||
eventListener.onEditComplete(Optional.of(composeText.getTextTrimmed()), Optional.of(sendButton.getSelectedTransport()));
|
||||
}
|
||||
setMode(Mode.NONE);
|
||||
});
|
||||
|
||||
sendButton.addOnTransportChangedListener((newTransport, manuallySelected) -> {
|
||||
presentCharactersRemaining();
|
||||
composeText.setTransport(newTransport);
|
||||
sendButtonBkg.getBackground().setColorFilter(newTransport.getBackgroundColor(), PorterDuff.Mode.MULTIPLY);
|
||||
sendButtonBkg.getBackground().invalidateSelf();
|
||||
});
|
||||
|
||||
ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener();
|
||||
|
||||
composeText.setOnKeyListener(composeKeyPressedListener);
|
||||
composeText.addTextChangedListener(composeKeyPressedListener);
|
||||
composeText.setOnClickListener(composeKeyPressedListener);
|
||||
composeText.setOnFocusChangeListener(composeKeyPressedListener);
|
||||
|
||||
emojiToggle.setOnClickListener(this::onEmojiToggleClicked);
|
||||
|
||||
colorPaletteAdapter = new ColorPaletteAdapter();
|
||||
colorPaletteAdapter.setEventListener(colorPicker::setActiveColor);
|
||||
|
||||
colorPalette.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
colorPalette.setAdapter(colorPaletteAdapter);
|
||||
}
|
||||
|
||||
setMode(Mode.NONE);
|
||||
public void setLocale(@NonNull Locale locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public void setTransport(@NonNull Optional<TransportOption> transport) {
|
||||
if (transport.isPresent()) {
|
||||
saveButton.setVisibility(GONE);
|
||||
inputContainer.setVisibility(VISIBLE);
|
||||
sendButton.setTransport(transport.get());
|
||||
} else {
|
||||
saveButton.setVisibility(VISIBLE);
|
||||
inputContainer.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void dismissEmojiKeyboard() {
|
||||
hideCurrentInput(composeText);
|
||||
}
|
||||
|
||||
public void setColorPalette(@NonNull Set<Integer> colors) {
|
||||
colorPaletteAdapter.setColors(colors);
|
||||
}
|
||||
|
||||
public int getActiveColor() {
|
||||
return colorPicker.getActiveColor();
|
||||
}
|
||||
|
||||
public void setActiveColor(int color) {
|
||||
colorPicker.setActiveColor(color);
|
||||
}
|
||||
|
||||
public void setEventListener(@Nullable EventListener eventListener) {
|
||||
this.eventListener = eventListener;
|
||||
}
|
||||
|
||||
public void enterMode(@NonNull Mode mode) {
|
||||
setMode(mode, false);
|
||||
}
|
||||
@@ -201,16 +325,44 @@ public class ScribbleHud extends FrameLayout {
|
||||
stickerButton.setOnClickListener(v -> setMode(Mode.NONE));
|
||||
}
|
||||
|
||||
public int getActiveColor() {
|
||||
return colorPicker.getActiveColor();
|
||||
private void presentCharactersRemaining() {
|
||||
String messageBody = composeText.getTextTrimmed();
|
||||
TransportOption transportOption = sendButton.getSelectedTransport();
|
||||
CharacterState characterState = transportOption.calculateCharacters(messageBody);
|
||||
|
||||
if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) {
|
||||
charactersLeft.setText(String.format(locale,
|
||||
"%d/%d (%d)",
|
||||
characterState.charactersRemaining,
|
||||
characterState.maxMessageSize,
|
||||
characterState.messagesSpent));
|
||||
charactersLeft.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
charactersLeft.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setActiveColor(int color) {
|
||||
colorPicker.setActiveColor(color);
|
||||
}
|
||||
private void onEmojiToggleClicked(View v) {
|
||||
if (!emojiDrawer.resolved()) {
|
||||
emojiToggle.attach(emojiDrawer.get());
|
||||
emojiDrawer.get().setEmojiEventListener(new EmojiDrawer.EmojiEventListener() {
|
||||
@Override
|
||||
public void onKeyEvent(KeyEvent keyEvent) {
|
||||
composeText.dispatchKeyEvent(keyEvent);
|
||||
}
|
||||
|
||||
public void setEventListener(@Nullable EventListener eventListener) {
|
||||
this.eventListener = eventListener;
|
||||
@Override
|
||||
public void onEmojiSelected(String emoji) {
|
||||
composeText.insertEmoji(emoji);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (getCurrentInput() == emojiDrawer.get()) {
|
||||
showSoftkey(composeText);
|
||||
} else {
|
||||
hideSoftkey(composeText, () -> post(() -> show(composeText, emojiDrawer.get())));
|
||||
}
|
||||
}
|
||||
|
||||
private final VerticalSlideColorPicker.OnColorChangeListener standardOnColorChangeListener = new VerticalSlideColorPicker.OnColorChangeListener() {
|
||||
@@ -236,6 +388,46 @@ public class ScribbleHud extends FrameLayout {
|
||||
}
|
||||
};
|
||||
|
||||
private class ComposeKeyPressedListener implements OnKeyListener, OnClickListener, TextWatcher, OnFocusChangeListener {
|
||||
|
||||
int beforeLength;
|
||||
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||
if (TextSecurePreferences.isEnterSendsEnabled(getContext())) {
|
||||
sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
|
||||
sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
showSoftkey(composeText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,int after) {
|
||||
beforeLength = composeText.getTextTrimmed().length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
presentCharactersRemaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before,int count) {}
|
||||
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {}
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
NONE, DRAW, HIGHLIGHT, TEXT, STICKER
|
||||
}
|
||||
@@ -245,6 +437,6 @@ public class ScribbleHud extends FrameLayout {
|
||||
void onColorChange(int color);
|
||||
void onUndo();
|
||||
void onDelete();
|
||||
void onSave();
|
||||
void onEditComplete(@NonNull Optional<String> message, @NonNull Optional<TransportOption> transport);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user