diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 4434022adb..7478ebc6cb 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -375,7 +375,7 @@ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> diff --git a/res/drawable-hdpi/baseline_highlight_white_24.png b/res/drawable-hdpi/baseline_highlight_white_24.png new file mode 100644 index 0000000000..2491e9ecb1 Binary files /dev/null and b/res/drawable-hdpi/baseline_highlight_white_24.png differ diff --git a/res/drawable-hdpi/ic_brush_white_24dp.png b/res/drawable-hdpi/ic_brush_white_24dp.png index c8aa20ca2b..f813722efe 100644 Binary files a/res/drawable-hdpi/ic_brush_white_24dp.png and b/res/drawable-hdpi/ic_brush_white_24dp.png differ diff --git a/res/drawable-hdpi/ic_check_white_24dp.png b/res/drawable-hdpi/ic_check_white_24dp.png index c12d44d1db..468ea5acd0 100644 Binary files a/res/drawable-hdpi/ic_check_white_24dp.png and b/res/drawable-hdpi/ic_check_white_24dp.png differ diff --git a/res/drawable-hdpi/ic_delete_white_24dp.png b/res/drawable-hdpi/ic_delete_white_24dp.png index 6522b421a9..b1b2ed1b36 100644 Binary files a/res/drawable-hdpi/ic_delete_white_24dp.png and b/res/drawable-hdpi/ic_delete_white_24dp.png differ diff --git a/res/drawable-hdpi/ic_replay_white_24dp.png b/res/drawable-hdpi/ic_replay_white_24dp.png index 5ef425a170..fc59ca0d51 100644 Binary files a/res/drawable-hdpi/ic_replay_white_24dp.png and b/res/drawable-hdpi/ic_replay_white_24dp.png differ diff --git a/res/drawable-hdpi/ic_tag_faces_white_24dp.png b/res/drawable-hdpi/ic_tag_faces_white_24dp.png index 3aeae65b14..54d12d1b15 100644 Binary files a/res/drawable-hdpi/ic_tag_faces_white_24dp.png and b/res/drawable-hdpi/ic_tag_faces_white_24dp.png differ diff --git a/res/drawable-hdpi/ic_text_fields_white_24dp.png b/res/drawable-hdpi/ic_text_fields_white_24dp.png index a675d51c6e..a1ff66d673 100644 Binary files a/res/drawable-hdpi/ic_text_fields_white_24dp.png and b/res/drawable-hdpi/ic_text_fields_white_24dp.png differ diff --git a/res/drawable-mdpi/baseline_highlight_white_24.png b/res/drawable-mdpi/baseline_highlight_white_24.png new file mode 100644 index 0000000000..b48342dd71 Binary files /dev/null and b/res/drawable-mdpi/baseline_highlight_white_24.png differ diff --git a/res/drawable-mdpi/ic_brush_white_24dp.png b/res/drawable-mdpi/ic_brush_white_24dp.png index ae4dd03dc9..b06a66460e 100644 Binary files a/res/drawable-mdpi/ic_brush_white_24dp.png and b/res/drawable-mdpi/ic_brush_white_24dp.png differ diff --git a/res/drawable-mdpi/ic_check_white_24dp.png b/res/drawable-mdpi/ic_check_white_24dp.png index 14747f601d..6a93d69194 100644 Binary files a/res/drawable-mdpi/ic_check_white_24dp.png and b/res/drawable-mdpi/ic_check_white_24dp.png differ diff --git a/res/drawable-mdpi/ic_delete_white_24dp.png b/res/drawable-mdpi/ic_delete_white_24dp.png index 66cc721793..a98c3d9424 100644 Binary files a/res/drawable-mdpi/ic_delete_white_24dp.png and b/res/drawable-mdpi/ic_delete_white_24dp.png differ diff --git a/res/drawable-mdpi/ic_replay_white_24dp.png b/res/drawable-mdpi/ic_replay_white_24dp.png index 5a79970a98..0b90fb1339 100644 Binary files a/res/drawable-mdpi/ic_replay_white_24dp.png and b/res/drawable-mdpi/ic_replay_white_24dp.png differ diff --git a/res/drawable-mdpi/ic_tag_faces_white_24dp.png b/res/drawable-mdpi/ic_tag_faces_white_24dp.png index e6cc505f9f..01088fa437 100644 Binary files a/res/drawable-mdpi/ic_tag_faces_white_24dp.png and b/res/drawable-mdpi/ic_tag_faces_white_24dp.png differ diff --git a/res/drawable-mdpi/ic_text_fields_white_24dp.png b/res/drawable-mdpi/ic_text_fields_white_24dp.png index 1b45ea0b41..d41ed201e1 100644 Binary files a/res/drawable-mdpi/ic_text_fields_white_24dp.png and b/res/drawable-mdpi/ic_text_fields_white_24dp.png differ diff --git a/res/drawable-xhdpi/baseline_highlight_white_24.png b/res/drawable-xhdpi/baseline_highlight_white_24.png new file mode 100644 index 0000000000..3ada66c2d1 Binary files /dev/null and b/res/drawable-xhdpi/baseline_highlight_white_24.png differ diff --git a/res/drawable-xhdpi/ic_brush_white_24dp.png b/res/drawable-xhdpi/ic_brush_white_24dp.png index 6a7239478f..4d5cc6e12b 100644 Binary files a/res/drawable-xhdpi/ic_brush_white_24dp.png and b/res/drawable-xhdpi/ic_brush_white_24dp.png differ diff --git a/res/drawable-xhdpi/ic_check_white_24dp.png b/res/drawable-xhdpi/ic_check_white_24dp.png index 2df770d103..9868d19a42 100644 Binary files a/res/drawable-xhdpi/ic_check_white_24dp.png and b/res/drawable-xhdpi/ic_check_white_24dp.png differ diff --git a/res/drawable-xhdpi/ic_delete_white_24dp.png b/res/drawable-xhdpi/ic_delete_white_24dp.png index 20ae6d313d..d337127653 100644 Binary files a/res/drawable-xhdpi/ic_delete_white_24dp.png and b/res/drawable-xhdpi/ic_delete_white_24dp.png differ diff --git a/res/drawable-xhdpi/ic_replay_white_24dp.png b/res/drawable-xhdpi/ic_replay_white_24dp.png index 3b41913257..72d1d9d45c 100644 Binary files a/res/drawable-xhdpi/ic_replay_white_24dp.png and b/res/drawable-xhdpi/ic_replay_white_24dp.png differ diff --git a/res/drawable-xhdpi/ic_tag_faces_white_24dp.png b/res/drawable-xhdpi/ic_tag_faces_white_24dp.png index c97abc49f1..4aac3940c6 100644 Binary files a/res/drawable-xhdpi/ic_tag_faces_white_24dp.png and b/res/drawable-xhdpi/ic_tag_faces_white_24dp.png differ diff --git a/res/drawable-xhdpi/ic_text_fields_white_24dp.png b/res/drawable-xhdpi/ic_text_fields_white_24dp.png index 612d143869..bd6051f8c8 100644 Binary files a/res/drawable-xhdpi/ic_text_fields_white_24dp.png and b/res/drawable-xhdpi/ic_text_fields_white_24dp.png differ diff --git a/res/drawable-xxhdpi/baseline_highlight_white_24.png b/res/drawable-xxhdpi/baseline_highlight_white_24.png new file mode 100644 index 0000000000..57a8a0ff08 Binary files /dev/null and b/res/drawable-xxhdpi/baseline_highlight_white_24.png differ diff --git a/res/drawable-xxhdpi/ic_brush_white_24dp.png b/res/drawable-xxhdpi/ic_brush_white_24dp.png index 300529d20e..071b38eee7 100644 Binary files a/res/drawable-xxhdpi/ic_brush_white_24dp.png and b/res/drawable-xxhdpi/ic_brush_white_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_check_white_24dp.png b/res/drawable-xxhdpi/ic_check_white_24dp.png index 6e03d54cf4..2a7c32de61 100644 Binary files a/res/drawable-xxhdpi/ic_check_white_24dp.png and b/res/drawable-xxhdpi/ic_check_white_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_delete_white_24dp.png b/res/drawable-xxhdpi/ic_delete_white_24dp.png index 0e95e9b1d0..57f96b268d 100644 Binary files a/res/drawable-xxhdpi/ic_delete_white_24dp.png and b/res/drawable-xxhdpi/ic_delete_white_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_replay_white_24dp.png b/res/drawable-xxhdpi/ic_replay_white_24dp.png index fcddcf02dd..5df9d2b99e 100644 Binary files a/res/drawable-xxhdpi/ic_replay_white_24dp.png and b/res/drawable-xxhdpi/ic_replay_white_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_tag_faces_white_24dp.png b/res/drawable-xxhdpi/ic_tag_faces_white_24dp.png index 4bfd751867..6105b8637e 100644 Binary files a/res/drawable-xxhdpi/ic_tag_faces_white_24dp.png and b/res/drawable-xxhdpi/ic_tag_faces_white_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_text_fields_white_24dp.png b/res/drawable-xxhdpi/ic_text_fields_white_24dp.png index 2d76a1da72..0f2f0a7ed9 100644 Binary files a/res/drawable-xxhdpi/ic_text_fields_white_24dp.png and b/res/drawable-xxhdpi/ic_text_fields_white_24dp.png differ diff --git a/res/drawable-xxxhdpi/baseline_highlight_white_24.png b/res/drawable-xxxhdpi/baseline_highlight_white_24.png new file mode 100644 index 0000000000..5e2f24bb90 Binary files /dev/null and b/res/drawable-xxxhdpi/baseline_highlight_white_24.png differ diff --git a/res/drawable-xxxhdpi/ic_brush_white_24dp.png b/res/drawable-xxxhdpi/ic_brush_white_24dp.png index 8297819ffd..d26893d2dc 100644 Binary files a/res/drawable-xxxhdpi/ic_brush_white_24dp.png and b/res/drawable-xxxhdpi/ic_brush_white_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_check_white_24dp.png b/res/drawable-xxxhdpi/ic_check_white_24dp.png new file mode 100644 index 0000000000..d601ca823c Binary files /dev/null and b/res/drawable-xxxhdpi/ic_check_white_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_delete_white_24dp.png b/res/drawable-xxxhdpi/ic_delete_white_24dp.png new file mode 100644 index 0000000000..08042108df Binary files /dev/null and b/res/drawable-xxxhdpi/ic_delete_white_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_replay_white_24dp.png b/res/drawable-xxxhdpi/ic_replay_white_24dp.png index 1573fb111b..c3d5f96adb 100644 Binary files a/res/drawable-xxxhdpi/ic_replay_white_24dp.png and b/res/drawable-xxxhdpi/ic_replay_white_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_tag_faces_white_24dp.png b/res/drawable-xxxhdpi/ic_tag_faces_white_24dp.png index 319a13a387..fcbaca3025 100644 Binary files a/res/drawable-xxxhdpi/ic_tag_faces_white_24dp.png and b/res/drawable-xxxhdpi/ic_tag_faces_white_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_text_fields_white_24dp.png b/res/drawable-xxxhdpi/ic_text_fields_white_24dp.png index f4e597a8e2..69ec59a99a 100644 Binary files a/res/drawable-xxxhdpi/ic_text_fields_white_24dp.png and b/res/drawable-xxxhdpi/ic_text_fields_white_24dp.png differ diff --git a/res/drawable/circle_white.xml b/res/drawable/circle_white.xml new file mode 100644 index 0000000000..631057c468 --- /dev/null +++ b/res/drawable/circle_white.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/res/layout/item_color.xml b/res/layout/item_color.xml new file mode 100644 index 0000000000..83507eba1c --- /dev/null +++ b/res/layout/item_color.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/res/layout/scribble_activity.xml b/res/layout/scribble_activity.xml index cd2b89c6a1..644b4936de 100644 --- a/res/layout/scribble_activity.xml +++ b/res/layout/scribble_activity.xml @@ -1,44 +1,23 @@ - + - + - + - - - - - - - + diff --git a/res/layout/scribble_hud.xml b/res/layout/scribble_hud.xml new file mode 100644 index 0000000000..b827fbcdf0 --- /dev/null +++ b/res/layout/scribble_hud.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/scribble_view.xml b/res/layout/scribble_view.xml index 5dbd35fbfa..29abcf4429 100644 --- a/res/layout/scribble_view.xml +++ b/res/layout/scribble_view.xml @@ -1,9 +1,7 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> + + + diff --git a/res/values/arrays.xml b/res/values/arrays.xml index e9e6832d98..8c10c34e7a 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -273,11 +273,15 @@ - #000000 + #ffffff #ff0000 - #ffff00 - #00ffff #ff00ff + #0000ff + #00ffff + #00ff00 + #ffff00 + #ff5500 + #000000 diff --git a/res/values/colors.xml b/res/values/colors.xml index 891f5942ad..5cd5e555d7 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -54,7 +54,7 @@ #400099cc #40ffffff - #8cf437 + #99ffffff #00FFFFFF #88000000 diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 82a34404ef..0c5eceb268 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -58,7 +58,7 @@ 34sp 20sp - 3dp + 2dp 16dp diff --git a/res/values/themes.xml b/res/values/themes.xml index c47aa779eb..45a58f1f8b 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -373,4 +373,7 @@ @drawable/permission_rationale_dialog_corners + + diff --git a/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java b/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java index 9b3953fdc6..ec9d083218 100644 --- a/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java +++ b/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java @@ -5,7 +5,6 @@ import android.annotation.TargetApi; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Color; import android.graphics.PointF; import android.net.Uri; import android.os.AsyncTask; @@ -18,7 +17,6 @@ import android.view.View; import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; @@ -40,41 +38,37 @@ import java.io.IOException; import java.util.concurrent.ExecutionException; @TargetApi(Build.VERSION_CODES.JELLY_BEAN) -public class ScribbleActivity extends PassphraseRequiredActionBarActivity implements ScribbleToolbar.ScribbleToolbarListener, VerticalSlideColorPicker.OnColorChangeListener { +public class ScribbleActivity extends PassphraseRequiredActionBarActivity implements ScribbleHud.EventListener, VerticalSlideColorPicker.OnColorChangeListener { 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; - private VerticalSlideColorPicker colorPicker; - private ScribbleToolbar toolbar; - private ScribbleView scribbleView; - private GlideRequests glideRequests; + private ScribbleHud scribbleHud; + private ScribbleView scribbleView; + private GlideRequests glideRequests; @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); - this.toolbar = findViewById(R.id.toolbar); - this.colorPicker = findViewById(R.id.scribble_color_picker); - this.toolbar.setListener(this); - this.toolbar.setToolColor(Color.RED); + scribbleHud.setEventListener(this); scribbleView.setMotionViewCallback(motionViewCallback); + scribbleView.setDrawingChangedListener(() -> scribbleHud.setColorPalette(scribbleView.getUniqueColors())); scribbleView.setDrawingMode(false); scribbleView.setImage(glideRequests, getIntent().getData()); - colorPicker.setOnColorChangeListener(this); - colorPicker.setVisibility(View.GONE); - - setSupportActionBar(toolbar); - - getSupportActionBar().setDisplayHomeAsUpEnabled(false); - getSupportActionBar().setTitle(null); + 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) { @@ -96,6 +90,7 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem textEntity.getLayer().getFont().setColor(selectedColor); textEntity.updateEntity(); scribbleView.invalidate(); + scribbleHud.setColorPalette(scribbleView.getUniqueColors()); } private void startTextEntityEditing() { @@ -119,23 +114,21 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem TextEntity textEntity = new TextEntity(textLayer, scribbleView.getWidth(), scribbleView.getHeight()); scribbleView.addEntityAndPosition(textEntity); - // move text sticker up so that its not hidden under keyboard PointF center = textEntity.absoluteCenter(); center.y = center.y * 0.5F; textEntity.moveCenterTo(center); - // redraw scribbleView.invalidate(); startTextEntityEditing(); - changeTextEntityColor(toolbar.getToolColor()); + changeTextEntityColor(scribbleHud.getActiveColor()); } private TextLayer createTextLayer() { TextLayer textLayer = new TextLayer(); Font font = new Font(); - font.setColor(TextLayer.Limits.INITIAL_FONT_COLOR); + font.setColor(scribbleHud.getActiveColor()); font.setSize(TextLayer.Limits.INITIAL_FONT_SIZE); textLayer.setFont(font); @@ -150,7 +143,6 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem if (resultCode == RESULT_OK) { if (requestCode == SELECT_STICKER_REQUEST_CODE) { if (data != null) { - toolbar.setStickerSelected(true); final String stickerFile = data.getStringExtra(StickerSelectActivity.EXTRA_STICKER_FILE); new AsyncTask() { @@ -176,44 +168,52 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem } @Override - public void onBrushSelected(boolean enabled) { - scribbleView.setDrawingMode(enabled); - colorPicker.setVisibility(enabled ? View.VISIBLE : View.GONE); + 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; + } } @Override - public void onPaintUndo() { + public void onColorChange(int color) { + scribbleView.setDrawingBrushColor(color); + changeTextEntityColor(color); + } + + @Override + public void onUndo() { scribbleView.undoDrawing(); + scribbleHud.setColorPalette(scribbleView.getUniqueColors()); } @Override - public void onTextSelected(boolean enabled) { - if (enabled) { - addTextSticker(); - scribbleView.setDrawingMode(false); - colorPicker.setVisibility(View.VISIBLE); - } else { - scribbleView.clearSelection(); - colorPicker.setVisibility(View.GONE); - } - } - - @Override - public void onStickerSelected(boolean enabled) { - colorPicker.setVisibility(View.GONE); - - if (!enabled) { - scribbleView.clearSelection(); - } else { - scribbleView.setDrawingMode(false); - Intent intent = new Intent(this, StickerSelectActivity.class); - startActivityForResult(intent, SELECT_STICKER_REQUEST_CODE); - } - } - - public void onDeleteSelected() { + public void onDelete() { scribbleView.deleteSelected(); - colorPicker.setVisibility(View.GONE); + scribbleHud.setColorPalette(scribbleView.getUniqueColors()); } @Override @@ -250,14 +250,14 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem @Override public void onEntitySelected(@Nullable MotionEntity entity) { if (entity == null) { - toolbar.setNoneSelected(); - colorPicker.setVisibility(View.GONE); + scribbleHud.enterMode(ScribbleHud.Mode.NONE); } else if (entity instanceof TextEntity) { - toolbar.setTextSelected(true); - colorPicker.setVisibility(View.VISIBLE); + int textColor = ((TextEntity) entity).getLayer().getFont().getColor(); + + scribbleHud.enterMode(ScribbleHud.Mode.TEXT); + scribbleHud.setActiveColor(textColor); } else { - toolbar.setStickerSelected(true); - colorPicker.setVisibility(View.GONE); + scribbleHud.enterMode(ScribbleHud.Mode.STICKER); } } @@ -266,14 +266,4 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem startTextEntityEditing(); } }; - - @Override - public void onColorChange(int color) { - if (color == 0) color = Color.RED; - - toolbar.setToolColor(color); - scribbleView.setDrawingBrushColor(color); - - changeTextEntityColor(color); - } } diff --git a/src/org/thoughtcrime/securesms/scribbles/ScribbleHud.java b/src/org/thoughtcrime/securesms/scribbles/ScribbleHud.java new file mode 100644 index 0000000000..ed2f996da9 --- /dev/null +++ b/src/org/thoughtcrime/securesms/scribbles/ScribbleHud.java @@ -0,0 +1,250 @@ +package org.thoughtcrime.securesms.scribbles; + +import android.content.Context; +import android.graphics.Color; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.scribbles.widget.ColorPaletteAdapter; +import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker; + +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 { + + private View drawButton; + private View highlightButton; + private View textButton; + private View stickerButton; + private View undoButton; + private View deleteButton; + private View saveButton; + private VerticalSlideColorPicker colorPicker; + private RecyclerView colorPalette; + + private EventListener eventListener; + private ColorPaletteAdapter colorPaletteAdapter; + + public ScribbleHud(@NonNull Context context) { + super(context); + initialize(); + } + + public ScribbleHud(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public ScribbleHud(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(); + } + + private void initialize() { + inflate(getContext(), R.layout.scribble_hud, this); + + drawButton = findViewById(R.id.scribble_draw_button); + highlightButton = findViewById(R.id.scribble_highlight_button); + textButton = findViewById(R.id.scribble_text_button); + stickerButton = findViewById(R.id.scribble_sticker_button); + undoButton = findViewById(R.id.scribble_undo_button); + deleteButton = findViewById(R.id.scribble_delete_button); + saveButton = findViewById(R.id.scribble_save_button); + colorPicker = findViewById(R.id.scribble_color_picker); + colorPalette = findViewById(R.id.scribble_color_palette); + + undoButton.setOnClickListener(v -> { + if (eventListener != null) { + eventListener.onUndo(); + } + }); + + deleteButton.setOnClickListener(v -> { + if (eventListener != null) { + eventListener.onDelete(); + } + setMode(Mode.NONE); + }); + + saveButton.setOnClickListener(v -> { + if (eventListener != null) { + eventListener.onSave(); + } + setMode(Mode.NONE); + }); + + colorPaletteAdapter = new ColorPaletteAdapter(); + colorPaletteAdapter.setEventListener(colorPicker::setActiveColor); + + colorPalette.setLayoutManager(new LinearLayoutManager(getContext())); + colorPalette.setAdapter(colorPaletteAdapter); + + setMode(Mode.NONE); + } + + public void setColorPalette(@NonNull Set colors) { + colorPaletteAdapter.setColors(colors); + } + + public void enterMode(@NonNull Mode mode) { + setMode(mode, false); + } + + private void setMode(@NonNull Mode mode) { + setMode(mode, true); + } + + private void setMode(@NonNull Mode mode, boolean notify) { + switch (mode) { + case NONE: presentModeNone(); break; + case DRAW: presentModeDraw(); break; + case HIGHLIGHT: presentModeHighlight(); break; + case TEXT: presentModeText(); break; + case STICKER: presentModeSticker(); break; + } + + if (notify && eventListener != null) { + eventListener.onModeStarted(mode); + } + } + + private void presentModeNone() { + drawButton.setVisibility(VISIBLE); + highlightButton.setVisibility(VISIBLE); + textButton.setVisibility(VISIBLE); + stickerButton.setVisibility(VISIBLE); + + undoButton.setVisibility(GONE); + deleteButton.setVisibility(GONE); + colorPicker.setVisibility(GONE); + colorPalette.setVisibility(GONE); + + drawButton.setOnClickListener(v -> setMode(Mode.DRAW)); + highlightButton.setOnClickListener(v -> setMode(Mode.HIGHLIGHT)); + textButton.setOnClickListener(v -> setMode(Mode.TEXT)); + stickerButton.setOnClickListener(v -> setMode(Mode.STICKER)); + } + + private void presentModeDraw() { + drawButton.setVisibility(VISIBLE); + undoButton.setVisibility(VISIBLE); + colorPicker.setVisibility(VISIBLE); + colorPalette.setVisibility(VISIBLE); + + highlightButton.setVisibility(GONE); + textButton.setVisibility(GONE); + stickerButton.setVisibility(GONE); + deleteButton.setVisibility(GONE); + + drawButton.setOnClickListener(v -> setMode(Mode.NONE)); + + colorPicker.setOnColorChangeListener(standardOnColorChangeListener); + colorPicker.setActiveColor(Color.RED); + } + + private void presentModeHighlight() { + highlightButton.setVisibility(VISIBLE); + undoButton.setVisibility(VISIBLE); + colorPicker.setVisibility(VISIBLE); + colorPalette.setVisibility(VISIBLE); + + drawButton.setVisibility(GONE); + textButton.setVisibility(GONE); + stickerButton.setVisibility(GONE); + deleteButton.setVisibility(GONE); + + highlightButton.setOnClickListener(v -> setMode(Mode.NONE)); + + colorPicker.setOnColorChangeListener(highlightOnColorChangeListener); + colorPicker.setActiveColor(Color.YELLOW); + } + + private void presentModeText() { + textButton.setVisibility(VISIBLE); + deleteButton.setVisibility(VISIBLE); + colorPicker.setVisibility(VISIBLE); + colorPalette.setVisibility(VISIBLE); + + drawButton.setVisibility(GONE); + highlightButton.setVisibility(GONE); + stickerButton.setVisibility(GONE); + undoButton.setVisibility(GONE); + + textButton.setOnClickListener(v -> setMode(Mode.NONE)); + + colorPicker.setOnColorChangeListener(standardOnColorChangeListener); + colorPicker.setActiveColor(Color.WHITE); + } + + private void presentModeSticker() { + stickerButton.setVisibility(VISIBLE); + deleteButton.setVisibility(VISIBLE); + + drawButton.setVisibility(GONE); + highlightButton.setVisibility(GONE); + textButton.setVisibility(GONE); + undoButton.setVisibility(GONE); + colorPicker.setVisibility(GONE); + colorPalette.setVisibility(GONE); + + stickerButton.setOnClickListener(v -> setMode(Mode.NONE)); + } + + public int getActiveColor() { + return colorPicker.getActiveColor(); + } + + public void setActiveColor(int color) { + colorPicker.setActiveColor(color); + } + + public void setEventListener(@Nullable EventListener eventListener) { + this.eventListener = eventListener; + } + + private final VerticalSlideColorPicker.OnColorChangeListener standardOnColorChangeListener = new VerticalSlideColorPicker.OnColorChangeListener() { + @Override + public void onColorChange(int selectedColor) { + if (eventListener != null) { + eventListener.onColorChange(selectedColor); + } + } + }; + + private final VerticalSlideColorPicker.OnColorChangeListener highlightOnColorChangeListener = new VerticalSlideColorPicker.OnColorChangeListener() { + @Override + public void onColorChange(int selectedColor) { + if (eventListener != null) { + int r = Color.red(selectedColor); + int g = Color.green(selectedColor); + int b = Color.blue(selectedColor); + int a = 128; + + eventListener.onColorChange(Color.argb(a, r, g, b)); + } + } + }; + + public enum Mode { + NONE, DRAW, HIGHLIGHT, TEXT, STICKER + } + + public interface EventListener { + void onModeStarted(@NonNull Mode mode); + void onColorChange(int color); + void onUndo(); + void onDelete(); + void onSave(); + } +} diff --git a/src/org/thoughtcrime/securesms/scribbles/ScribbleToolbar.java b/src/org/thoughtcrime/securesms/scribbles/ScribbleToolbar.java deleted file mode 100644 index 75718b0acb..0000000000 --- a/src/org/thoughtcrime/securesms/scribbles/ScribbleToolbar.java +++ /dev/null @@ -1,240 +0,0 @@ -/** - * Copyright (C) 2016 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.scribbles; - -import android.animation.LayoutTransition; -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.support.annotation.Nullable; -import android.support.v7.widget.Toolbar; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import org.thoughtcrime.securesms.R; - -@TargetApi(Build.VERSION_CODES.JELLY_BEAN) -public class ScribbleToolbar extends Toolbar implements View.OnClickListener { - - private enum Selected { - NONE, - STICKER, - TEXT, - BRUSH - } - - private int foregroundSelectedTint; - private int foregroundUnselectedTint; - - private LinearLayout toolsView; - - private ImageView saveView; - private ImageView brushView; - private ImageView textView; - private ImageView stickerView; - - private ImageView separatorView; - - private ImageView undoView; - private ImageView deleteView; - - private Drawable background; - - @Nullable - private ScribbleToolbarListener listener; - - private int toolColor = Color.RED; - private Selected selected = Selected.NONE; - - public ScribbleToolbar(Context context) { - super(context); - init(context); - } - - public ScribbleToolbar(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(context); - } - - public ScribbleToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context); - } - - private void init(Context context) { - inflate(context, R.layout.scribble_toolbar, this); - - this.toolsView = (LinearLayout) findViewById(R.id.tools_view); - this.brushView = (ImageView) findViewById(R.id.brush_button); - this.textView = (ImageView) findViewById(R.id.text_button); - this.stickerView = (ImageView) findViewById(R.id.sticker_button); - this.separatorView = (ImageView) findViewById(R.id.separator); - this.saveView = (ImageView) findViewById(R.id.save); - - this.undoView = (ImageView) findViewById(R.id.undo); - this.deleteView = (ImageView) findViewById(R.id.delete); - - this.background = getResources().getDrawable(R.drawable.circle_tintable); - this.foregroundSelectedTint = getResources().getColor(R.color.white); - this.foregroundUnselectedTint = getResources().getColor(R.color.grey_800); - - this.undoView.setOnClickListener(this); - this.brushView.setOnClickListener(this); - this.textView.setOnClickListener(this); - this.stickerView.setOnClickListener(this); - this.separatorView.setOnClickListener(this); - this.deleteView.setOnClickListener(this); - this.saveView.setOnClickListener(this); - } - - public void setListener(@Nullable ScribbleToolbarListener listener) { - this.listener = listener; - } - - public void setToolColor(int toolColor) { - this.toolColor = toolColor; - this.background.setColorFilter(new PorterDuffColorFilter(toolColor, PorterDuff.Mode.MULTIPLY)); - } - - public int getToolColor() { - return this.toolColor; - } - - @Override - public void onClick(View v) { - this.toolsView.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); - - if (v == this.brushView) { - boolean enabled = selected != Selected.BRUSH; - setBrushSelected(enabled); - if (listener != null) listener.onBrushSelected(enabled); - } else if (v == this.stickerView) { - setNoneSelected(); - if (listener != null) listener.onStickerSelected(true); - } else if (v == this.textView) { - boolean enabled = selected != Selected.TEXT; - setTextSelected(enabled); - if (listener != null) listener.onTextSelected(enabled); - } else if (v == this.deleteView) { - setNoneSelected(); - if (listener != null) listener.onDeleteSelected(); - } else if (v == this.undoView) { - if (listener != null) listener.onPaintUndo(); - } else if (v == this.saveView) { - if (listener != null) listener.onSave(); - } - } - - private void setBrushSelected(boolean enabled) { - if (enabled) { - - this.textView.setBackground(null); - this.textView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY)); - - this.brushView.setBackground(background); - this.brushView.setColorFilter(new PorterDuffColorFilter(foregroundSelectedTint, PorterDuff.Mode.MULTIPLY)); - - this.stickerView.setBackground(null); - this.stickerView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY)); - - this.separatorView.setVisibility(View.VISIBLE); - this.undoView.setVisibility(View.VISIBLE); - this.deleteView.setVisibility(View.GONE); - - this.selected = Selected.BRUSH; - } else { - this.brushView.setBackground(null); - this.brushView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY)); - this.separatorView.setVisibility(View.GONE); - this.undoView.setVisibility(View.GONE); - - this.selected = Selected.NONE; - } - } - - public void setTextSelected(boolean enabled) { - if (enabled) { - this.brushView.setBackground(null); - this.brushView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY)); - - this.textView.setBackground(background); - this.textView.setColorFilter(new PorterDuffColorFilter(foregroundSelectedTint, PorterDuff.Mode.MULTIPLY)); - - this.stickerView.setBackground(null); - this.stickerView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY)); - - this.separatorView.setVisibility(View.VISIBLE); - this.undoView.setVisibility(View.GONE); - this.deleteView.setVisibility(View.VISIBLE); - - this.selected = Selected.TEXT; - } else { - this.textView.setBackground(null); - this.textView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY)); - - this.separatorView.setVisibility(View.GONE); - this.deleteView.setVisibility(View.GONE); - - this.selected = Selected.NONE; - } - } - - public void setStickerSelected(boolean enabled) { - if (enabled) { - this.brushView.setBackground(null); - this.brushView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY)); - - this.textView.setBackground(null); - this.textView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY)); - - this.separatorView.setVisibility(View.VISIBLE); - this.undoView.setVisibility(View.GONE); - this.deleteView.setVisibility(View.VISIBLE); - - this.selected = Selected.STICKER; - } else { - this.separatorView.setVisibility(View.GONE); - this.deleteView.setVisibility(View.GONE); - - this.selected = Selected.NONE; - } - } - - public void setNoneSelected() { - setBrushSelected(false); - setStickerSelected(false); - setTextSelected(false); - - this.selected = Selected.NONE; - } - - public interface ScribbleToolbarListener { - public void onBrushSelected(boolean enabled); - public void onPaintUndo(); - public void onTextSelected(boolean enabled); - public void onStickerSelected(boolean enabled); - public void onDeleteSelected(); - public void onSave(); - } -} diff --git a/src/org/thoughtcrime/securesms/scribbles/viewmodel/TextLayer.java b/src/org/thoughtcrime/securesms/scribbles/viewmodel/TextLayer.java index c367510827..efe95c4379 100644 --- a/src/org/thoughtcrime/securesms/scribbles/viewmodel/TextLayer.java +++ b/src/org/thoughtcrime/securesms/scribbles/viewmodel/TextLayer.java @@ -88,7 +88,7 @@ public class TextLayer extends Layer { float FONT_SIZE_STEP = 0.008F; - float INITIAL_FONT_SIZE = 0.075F; + float INITIAL_FONT_SIZE = 0.1F; int INITIAL_FONT_COLOR = 0xff000000; float INITIAL_SCALE = 0.8F; // set the same to avoid text scaling diff --git a/src/org/thoughtcrime/securesms/scribbles/widget/CanvasView.java b/src/org/thoughtcrime/securesms/scribbles/widget/CanvasView.java index fde325c36c..1f80c61300 100644 --- a/src/org/thoughtcrime/securesms/scribbles/widget/CanvasView.java +++ b/src/org/thoughtcrime/securesms/scribbles/widget/CanvasView.java @@ -20,15 +20,16 @@ import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; -import android.graphics.Typeface; +import android.support.annotation.NonNull; import android.util.AttributeSet; -import android.util.Log; import android.view.MotionEvent; import android.view.View; import java.io.ByteArrayOutputStream; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; /** * This class defines fields and methods for drawing. @@ -37,6 +38,8 @@ public class CanvasView extends View { private static final String TAG = CanvasView.class.getSimpleName(); + public static final int DEFAULT_STROKE_WIDTH = 15; + // Enumeration for Mode public enum Mode { DRAW, @@ -78,7 +81,7 @@ public class CanvasView extends View { private Paint.Style paintStyle = Paint.Style.STROKE; private int paintStrokeColor = Color.BLACK; private int paintFillColor = Color.BLACK; - private float paintStrokeWidth = 15F; + private float paintStrokeWidth = DEFAULT_STROKE_WIDTH; private int opacity = 255; private float blur = 0F; private Paint.Cap lineCap = Paint.Cap.ROUND; @@ -143,7 +146,7 @@ public class CanvasView extends View { paint.setStyle(this.paintStyle); paint.setStrokeWidth(this.paintStrokeWidth); paint.setStrokeCap(this.lineCap); - paint.setStrokeJoin(Paint.Join.MITER); // fixed + paint.setStrokeJoin(Paint.Join.ROUND); // fixed if (this.mode == Mode.ERASER) { // Eraser @@ -275,7 +278,9 @@ public class CanvasView extends View { switch (this.drawer) { case PEN : - path.lineTo(x, y); + for (int i = 0; i < event.getHistorySize(); i++) { + path.lineTo(event.getHistoricalX(i), event.getHistoricalY(i)); + } break; case LINE : path.reset(); @@ -770,4 +775,14 @@ public class CanvasView extends View { return this.getBitmapAsByteArray(CompressFormat.PNG, 100); } + public @NonNull Set getUniqueColors() { + Set colors = new LinkedHashSet<>(); + + for (int i = 1; i < paintLists.size() && i < historyPointer; i++) { + int color = paintLists.get(i).getColor(); + colors.add(Color.rgb(Color.red(color), Color.green(color), Color.blue(color))); + } + + return colors; + } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/scribbles/widget/ColorPaletteAdapter.java b/src/org/thoughtcrime/securesms/scribbles/widget/ColorPaletteAdapter.java new file mode 100644 index 0000000000..401a847f0f --- /dev/null +++ b/src/org/thoughtcrime/securesms/scribbles/widget/ColorPaletteAdapter.java @@ -0,0 +1,73 @@ +package org.thoughtcrime.securesms.scribbles.widget; + +import android.graphics.PorterDuff; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import org.thoughtcrime.securesms.R; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class ColorPaletteAdapter extends RecyclerView.Adapter { + + private final List colors = new ArrayList<>(); + + private EventListener eventListener; + + @Override + public ColorViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ColorViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_color, parent, false)); + } + + @Override + public void onBindViewHolder(ColorViewHolder holder, int position) { + holder.bind(colors.get(position), eventListener); + } + + @Override + public int getItemCount() { + return colors.size(); + } + + public void setColors(@NonNull Collection colors) { + this.colors.clear(); + this.colors.addAll(colors); + + notifyDataSetChanged(); + } + + public void setEventListener(@Nullable EventListener eventListener) { + this.eventListener = eventListener; + + notifyDataSetChanged(); + } + + public interface EventListener { + void onColorSelected(int color); + } + + static class ColorViewHolder extends RecyclerView.ViewHolder { + + ImageView foreground; + + ColorViewHolder(View itemView) { + super(itemView); + foreground = itemView.findViewById(R.id.palette_item_foreground); + } + + void bind(int color, @Nullable EventListener eventListener) { + foreground.setColorFilter(color, PorterDuff.Mode.SRC_IN); + + if (eventListener != null) { + itemView.setOnClickListener(v -> eventListener.onColorSelected(color)); + } + } + } +} diff --git a/src/org/thoughtcrime/securesms/scribbles/widget/MotionView.java b/src/org/thoughtcrime/securesms/scribbles/widget/MotionView.java index 670b861df7..28420bc937 100644 --- a/src/org/thoughtcrime/securesms/scribbles/widget/MotionView.java +++ b/src/org/thoughtcrime/securesms/scribbles/widget/MotionView.java @@ -36,6 +36,7 @@ import android.support.v4.view.ViewCompat; import android.text.Editable; import android.text.InputType; import android.text.Selection; +import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; @@ -56,7 +57,9 @@ import org.thoughtcrime.securesms.scribbles.widget.entity.MotionEntity; import org.thoughtcrime.securesms.scribbles.widget.entity.TextEntity; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; public class MotionView extends FrameLayout implements TextWatcher { @@ -182,6 +185,18 @@ public class MotionView extends FrameLayout implements TextWatcher { } } + public @NonNull Set getUniqueColors() { + Set colors = new LinkedHashSet<>(); + + for (MotionEntity entity : entities) { + if (entity instanceof TextEntity) { + colors.add(((TextEntity) entity).getLayer().getFont().getColor()); + } + } + + return colors; + } + private void initEntityBorder(@NonNull MotionEntity entity ) { // init stroke int strokeSize = getResources().getDimensionPixelSize(R.dimen.scribble_stroke_size); @@ -274,14 +289,17 @@ public class MotionView extends FrameLayout implements TextWatcher { } private void selectEntity(@Nullable MotionEntity entity, boolean updateCallback) { - if (selectedEntity != null) { + if (selectedEntity != null && entity != selectedEntity) { selectedEntity.setIsSelected(false); if (selectedEntity instanceof TextEntity) { - editText.clearComposingText(); - editText.clearFocus(); - - InputMethodManager imm = (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (TextUtils.isEmpty(((TextEntity) selectedEntity).getLayer().getText())) { + deletedSelectedEntity(); + } else { + editText.clearComposingText(); + editText.clearFocus(); + } + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); } @@ -413,6 +431,12 @@ public class MotionView extends FrameLayout implements TextWatcher { updateSelectionOnTap(e); return true; } + + @Override + public boolean onDown(MotionEvent e) { + updateSelectionOnTap(e); + return false; + } } private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { diff --git a/src/org/thoughtcrime/securesms/scribbles/widget/ScribbleView.java b/src/org/thoughtcrime/securesms/scribbles/widget/ScribbleView.java index d078de4b2b..d0e04376d5 100644 --- a/src/org/thoughtcrime/securesms/scribbles/widget/ScribbleView.java +++ b/src/org/thoughtcrime/securesms/scribbles/widget/ScribbleView.java @@ -21,18 +21,22 @@ import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.AsyncTask; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; -import android.util.Log; +import android.view.MotionEvent; +import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.Target; +import com.bumptech.glide.request.transition.Transition; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; @@ -43,12 +47,15 @@ import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.thoughtcrime.securesms.util.concurrent.SettableFuture; -import java.util.concurrent.ExecutionException; +import java.util.LinkedHashSet; +import java.util.Set; public class ScribbleView extends FrameLayout { private static final String TAG = ScribbleView.class.getSimpleName(); + public static final int DEFAULT_BRUSH_WIDTH = CanvasView.DEFAULT_STROKE_WIDTH; + private ImageView imageView; private MotionView motionView; private CanvasView canvasView; @@ -77,7 +84,7 @@ public class ScribbleView extends FrameLayout { } public void setImage(@NonNull GlideRequests glideRequests, @NonNull Uri uri) { - this.imageUri = uri; + this.imageUri = uri; glideRequests.load(new DecryptableUri(uri)) .diskCacheStrategy(DiskCacheStrategy.NONE) @@ -85,7 +92,6 @@ public class ScribbleView extends FrameLayout { .into(imageView); } - @SuppressLint("StaticFieldLeak") public @NonNull ListenableFuture getRenderedImage(@NonNull GlideRequests glideRequests) { final SettableFuture future = new SettableFuture<>(); final Context context = getContext(); @@ -96,43 +102,33 @@ public class ScribbleView extends FrameLayout { return future; } - new AsyncTask() { - @Override - protected @Nullable Bitmap doInBackground(Void... params) { - try { - int width = Target.SIZE_ORIGINAL; - int height = Target.SIZE_ORIGINAL; + int width = Target.SIZE_ORIGINAL; + int height = Target.SIZE_ORIGINAL; - if (isLowMemory) { - width = 768; - height = 768; - } + if (isLowMemory) { + width = 768; + height = 768; + } - return glideRequests.asBitmap() - .load(new DecryptableUri(imageUri)) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(true) - .into(width, height) - .get(); - } catch (InterruptedException | ExecutionException e) { - Log.w(TAG, e); - return null; - } - } + glideRequests.asBitmap() + .load(new DecryptableUri(imageUri)) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + .override(width, height) + .into(new SimpleTarget() { + @Override + public void onResourceReady(@NonNull Bitmap bitmap, @Nullable Transition transition) { + Canvas canvas = new Canvas(bitmap); + motionView.render(canvas); + canvasView.render(canvas); + future.set(bitmap); + } - @Override - protected void onPostExecute(@Nullable Bitmap bitmap) { - if (bitmap == null) { - future.set(null); - return; - } - - Canvas canvas = new Canvas(bitmap); - motionView.render(canvas); - canvasView.render(canvas); - future.set(bitmap); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + @Override + public void onLoadFailed(@Nullable Drawable errorDrawable) { + future.set(null); + } + }); return future; } @@ -149,6 +145,18 @@ public class ScribbleView extends FrameLayout { this.motionView.setMotionViewCallback(callback); } + @SuppressLint("ClickableViewAccessibility") + public void setDrawingChangedListener(@Nullable DrawingChangedListener listener) { + this.canvasView.setOnTouchListener((v, event) -> { + if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { + if (listener != null) { + listener.onDrawingChanged(); + } + } + return false; + }); + } + public void setDrawingMode(boolean enabled) { this.canvasView.setActive(enabled); if (enabled) this.motionView.unselectEntity(); @@ -157,6 +165,11 @@ public class ScribbleView extends FrameLayout { public void setDrawingBrushColor(int color) { this.canvasView.setPaintFillColor(color); this.canvasView.setPaintStrokeColor(color); + this.canvasView.setOpacity(Color.alpha(color)); + } + + public void setDrawingBrushWidth(int width) { + this.canvasView.setPaintStrokeWidth(width); } public void addEntityAndPosition(MotionEntity entity) { @@ -183,6 +196,15 @@ public class ScribbleView extends FrameLayout { this.motionView.startEditing(entity); } + public @NonNull Set getUniqueColors() { + Set colors = new LinkedHashSet<>(); + + colors.addAll(motionView.getUniqueColors()); + colors.addAll(canvasView.getUniqueColors()); + + return colors; + } + @Override public void onMeasure(int width, int height) { super.onMeasure(width, height); @@ -196,4 +218,7 @@ public class ScribbleView extends FrameLayout { MeasureSpec.makeMeasureSpec(imageView.getMeasuredHeight(), MeasureSpec.EXACTLY)); } + public interface DrawingChangedListener { + void onDrawingChanged(); + } } diff --git a/src/org/thoughtcrime/securesms/scribbles/widget/VerticalSlideColorPicker.java b/src/org/thoughtcrime/securesms/scribbles/widget/VerticalSlideColorPicker.java index ab6f0d5da5..497a830b1c 100644 --- a/src/org/thoughtcrime/securesms/scribbles/widget/VerticalSlideColorPicker.java +++ b/src/org/thoughtcrime/securesms/scribbles/widget/VerticalSlideColorPicker.java @@ -43,8 +43,12 @@ import org.thoughtcrime.securesms.R; public class VerticalSlideColorPicker extends View { + private static final float INDICATOR_TO_BAR_WIDTH_RATIO = 0.8f; + private Paint paint; private Paint strokePaint; + private Paint indicatorStrokePaint; + private Paint indicatorFillPaint; private Path path; private Bitmap bitmap; private Canvas bitmapCanvas; @@ -59,8 +63,12 @@ public class VerticalSlideColorPicker extends View { private int borderColor; private float borderWidth; + private float indicatorRadius; private int[] colors; + private int touchY; + private int activeColor; + public VerticalSlideColorPicker(Context context) { super(context); init(); @@ -74,9 +82,9 @@ public class VerticalSlideColorPicker extends View { try { int colorsResourceId = a.getResourceId(R.styleable.VerticalSlideColorPicker_pickerColors, R.array.scribble_colors); - colors = a.getResources().getIntArray(colorsResourceId); - borderColor = a.getColor(R.styleable.VerticalSlideColorPicker_pickerBorderColor, Color.WHITE); - borderWidth = a.getDimension(R.styleable.VerticalSlideColorPicker_pickerBorderWidth, 10f); + colors = a.getResources().getIntArray(colorsResourceId); + borderColor = a.getColor(R.styleable.VerticalSlideColorPicker_pickerBorderColor, Color.WHITE); + borderWidth = a.getDimension(R.styleable.VerticalSlideColorPicker_pickerBorderWidth, 10f); } finally { a.recycle(); @@ -110,6 +118,13 @@ public class VerticalSlideColorPicker extends View { strokePaint.setColor(borderColor); strokePaint.setAntiAlias(true); strokePaint.setStrokeWidth(borderWidth); + + indicatorStrokePaint = new Paint(strokePaint); + indicatorStrokePaint.setStrokeWidth(borderWidth / 2); + + indicatorFillPaint = new Paint(); + indicatorFillPaint.setStyle(Paint.Style.FILL); + indicatorFillPaint.setAntiAlias(true); } @Override @@ -126,20 +141,27 @@ public class VerticalSlideColorPicker extends View { bitmapCanvas.drawPath(path, paint); canvas.drawBitmap(bitmap, 0, 0, null); + + touchY = Math.max((int) colorPickerBody.top, touchY); + + indicatorFillPaint.setColor(activeColor); + canvas.drawCircle(centerX, touchY, indicatorRadius, indicatorFillPaint); + canvas.drawCircle(centerX, touchY, indicatorRadius, indicatorStrokePaint); } @Override public boolean onTouchEvent(MotionEvent event) { + touchY = (int) Math.min(event.getY(), colorPickerBody.bottom); + touchY = (int) Math.max(colorPickerBody.top, touchY); - float yPos = Math.min(event.getY(), colorPickerBody.bottom); - yPos = Math.max(colorPickerBody.top, yPos); - - int selectedColor = bitmap.getPixel(viewWidth/2, (int) yPos); + activeColor = bitmap.getPixel(viewWidth/2, touchY); if (onColorChangeListener != null) { - onColorChangeListener.onColorChange(selectedColor); + onColorChangeListener.onColorChange(activeColor); } + invalidate(); + return true; } @@ -147,13 +169,16 @@ public class VerticalSlideColorPicker extends View { protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); - viewWidth = w; + viewWidth = w; viewHeight = h; - centerX = viewWidth / 2; - colorPickerRadius = (viewWidth / 2) - borderWidth; + int barWidth = (int) (viewWidth * INDICATOR_TO_BAR_WIDTH_RATIO); - colorPickerBody = new RectF(centerX - colorPickerRadius, borderWidth + colorPickerRadius, centerX + colorPickerRadius, viewHeight - (borderWidth + colorPickerRadius)); + centerX = viewWidth / 2; + indicatorRadius = (viewWidth / 2) - borderWidth; + colorPickerRadius = (barWidth / 2) - borderWidth; + + colorPickerBody = new RectF(centerX - colorPickerRadius, borderWidth + colorPickerRadius, centerX + colorPickerRadius, viewHeight - (borderWidth + colorPickerRadius)); LinearGradient gradient = new LinearGradient(0, colorPickerBody.top, 0, colorPickerBody.bottom, colors, null, Shader.TileMode.CLAMP); paint.setShader(gradient); @@ -164,8 +189,6 @@ public class VerticalSlideColorPicker extends View { bitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888); bitmapCanvas = new Canvas(bitmap); - - resetToDefault(); } public void setBorderColor(int borderColor) { @@ -183,20 +206,29 @@ public class VerticalSlideColorPicker extends View { invalidate(); } - public void resetToDefault() { + public void setActiveColor(int color) { + activeColor = color; + + if (colorPickerBody != null) { + touchY = (int) colorPickerBody.top; + } + if (onColorChangeListener != null) { - onColorChangeListener.onColorChange(Color.RED); + onColorChangeListener.onColorChange(color); } invalidate(); } + public int getActiveColor() { + return activeColor; + } + public void setOnColorChangeListener(OnColorChangeListener onColorChangeListener) { this.onColorChangeListener = onColorChangeListener; } public interface OnColorChangeListener { - void onColorChange(int selectedColor); } } \ No newline at end of file