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:
Greyson Parrelli
2018-09-20 13:27:18 -07:00
parent e9a38bab1e
commit 08ace15f95
102 changed files with 1798 additions and 350 deletions

View File

@@ -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();
}
};
}

View 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();
}
}

View File

@@ -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);
}
}