Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Crop functionality #6543

Merged
merged 24 commits into from
Jun 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3498209
NF: remove API15 remnants now that minSdkVersion is 16
mikehardy Jun 23, 2020
22521e3
NF: extract saveAndExit from MultimediaEditFieldActivity
mikehardy Jun 23, 2020
409ee80
NF: path variables, extract createNewFile/handleTakePictureResult
mikehardy Jun 23, 2020
44426e5
Extract file path / URI conversions, make work across all APIs
mikehardy Jun 23, 2020
bc4d457
Add ability to crop image on user request and camera result return
mikehardy Jun 23, 2020
b2dd84d
Prompt user to crop on image edit return if images is large
mikehardy Jun 24, 2020
e11ce6a
Image crop feedback: JPG compress, "No" in dialogs, add comments
mikehardy Jun 26, 2020
1f9c811
Only consider RESULT_OK as a positive image edit result
mikehardy Jun 27, 2020
a0791bf
Add Activity restart support to multimedia edit, implement for Image
mikehardy Jun 27, 2020
2d847f1
Refactor crop dialog so one definition is reusable
mikehardy Jun 27, 2020
3586444
NF: add annotations from super to subclass compat copy methods
mikehardy Jun 27, 2020
1978f47
Improve messaging on unexpected crop results
mikehardy Jun 28, 2020
7500e85
Improve image editor file handling
mikehardy Jun 28, 2020
ba7feb4
Improve error-handling in image edit on activity starts
mikehardy Jun 28, 2020
8756fd4
Image edit can internalize ephemeral files now
mikehardy Jun 28, 2020
b146e9e
Fix incorrect change to URI generation on API<N
mikehardy Jun 28, 2020
c1c87a6
Use FileProvider-correct test for image test
mikehardy Jun 28, 2020
408192f
Fully restore previous image on unsuccessful image edit
mikehardy Jun 28, 2020
f253f18
NF: image editor re-categorize logging, clean imports, remove @Suppress
mikehardy Jun 28, 2020
30fa233
Image controller should treat all errors >= 1 as non-success
mikehardy Jun 28, 2020
2e1f5ea
URI path/name resolution typed more strongly
mikehardy Jun 28, 2020
0281280
NF: extract image revert handling (save, revert, delete) methods
mikehardy Jun 28, 2020
a4746f0
Image picker should be restricted to images only
mikehardy Jun 28, 2020
2da041f
Image rotate and compress detects bad images, callers handle it
mikehardy Jun 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.ActivityCompat;
import android.view.Menu;
Expand All @@ -39,6 +40,7 @@
import com.ichi2.anki.multimediacard.fields.AudioClipField;
import com.ichi2.anki.multimediacard.fields.AudioRecordingField;
import com.ichi2.anki.multimediacard.fields.BasicControllerFactory;
import com.ichi2.anki.multimediacard.fields.BasicImageFieldController;
import com.ichi2.anki.multimediacard.fields.EFieldType;
import com.ichi2.anki.multimediacard.fields.IControllerFactory;
import com.ichi2.anki.multimediacard.fields.IField;
Expand All @@ -48,6 +50,7 @@
import com.ichi2.utils.Permissions;

import java.io.File;
import java.text.DecimalFormat;

import timber.log.Timber;

Expand All @@ -65,6 +68,8 @@ public class MultimediaEditFieldActivity extends AnkiActivity
private static final int REQUEST_AUDIO_PERMISSION = 0;
private static final int REQUEST_CAMERA_PERMISSION = 1;

public static final int sImageLimit = 1024 * 1024; // 1MB in bytes

private IField mField;
private IMultimediaEditableNote mNote;
private int mFieldIndex;
Expand All @@ -80,9 +85,13 @@ public class MultimediaEditFieldActivity extends AnkiActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Bundle controllerBundle = null;
if (savedInstanceState != null) {
Timber.i("onCreate - saved bundle exists");
boolean b = savedInstanceState.getBoolean(BUNDLE_KEY_SHUT_OFF, false);
if (b) {
controllerBundle = savedInstanceState.getBundle("controllerBundle");
if (controllerBundle == null && b) {
Timber.i("onCreate - saved bundle has BUNDLE_KEY_SHUT_OFF and no controller bundle, terminating");
finishCancel();
return;
}
Expand All @@ -105,7 +114,7 @@ protected void onCreate(Bundle savedInstanceState) {
return;
}

recreateEditingUi(ChangeUIRequest.init(mField));
recreateEditingUi(ChangeUIRequest.init(mField), controllerBundle);
}

@VisibleForTesting
Expand Down Expand Up @@ -142,14 +151,19 @@ private boolean performPermissionRequest(IField field) {
}

/** Sets various properties required for IFieldController to be in a valid state */
private void setupUIController(IFieldController fieldController) {
private void setupUIController(IFieldController fieldController, @Nullable Bundle savedInstanceState) {
fieldController.setField(mField);
fieldController.setFieldIndex(mFieldIndex);
fieldController.setNote(mNote);
fieldController.setEditingActivity(this);
fieldController.loadInstanceState(savedInstanceState);
}

private void recreateEditingUi(ChangeUIRequest newUI) {
this.recreateEditingUi(newUI, null);
}

private void recreateEditingUi(ChangeUIRequest newUI, @Nullable Bundle savedInstanceState) {
Timber.d("recreateEditingUi()");

//Permissions are checked async, save our current state to allow continuation
Expand All @@ -167,7 +181,7 @@ private void recreateEditingUi(ChangeUIRequest newUI) {
IFieldController fieldController = controllerFactory.createControllerForField(newUI.getField());

if (fieldController == null) {
Timber.d("Field controller creation failed");
Timber.w("Field controller creation failed");
UIRecreationHandler.onControllerCreationFailed(newUI, this);
return;
}
Expand All @@ -177,7 +191,7 @@ private void recreateEditingUi(ChangeUIRequest newUI) {
mFieldController = fieldController;
mField = newUI.getField();

setupUIController(mFieldController);
setupUIController(mFieldController, savedInstanceState);

LinearLayout linearLayout = findViewById(R.id.LinearLayoutInScrollViewFieldEdit);

Expand Down Expand Up @@ -237,11 +251,8 @@ public boolean onOptionsItemSelected(MenuItem item) {


protected void done() {

mFieldController.onDone();

Intent resultData = new Intent();

boolean bChangeToText = false;

if (mField.getType() == EFieldType.IMAGE) {
Expand All @@ -253,6 +264,12 @@ protected void done() {
File f = new File(mField.getImagePath());
if (!f.exists()) {
bChangeToText = true;
} else {
long length = f.length();
if (length > sImageLimit) {
showLargeFileCropDialog((float) (1.0 * length / sImageLimit));
return;
}
}
}
} else if (mField.getType() == EFieldType.AUDIO_RECORDING) {
Expand All @@ -271,13 +288,7 @@ protected void done() {
if (bChangeToText) {
mField = new TextField();
}

resultData.putExtra(EXTRA_RESULT_FIELD, mField);
resultData.putExtra(EXTRA_RESULT_FIELD_INDEX, mFieldIndex);

setResult(RESULT_OK, resultData);

finishWithoutAnimation();
saveAndExit();
}


Expand Down Expand Up @@ -379,22 +390,49 @@ public void handleFieldChanged(IField newField) {
recreateEditingUi(ChangeUIRequest.fieldChange(newField));
}

public void showLargeFileCropDialog(float length) {
BasicImageFieldController imageFieldController = (BasicImageFieldController) mFieldController;
DecimalFormat decimalFormat = new DecimalFormat(".00");
String size = decimalFormat.format(length);
String content = getString(R.string.save_dialog_content, size);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe go with Formatter.formatShortFileSize.

You could bring the format string inside the resource.

imageFieldController.showCropDialog(content, (dialog, which) -> saveAndExit());
}


private void saveAndExit() {
Intent resultData = new Intent();
resultData.putExtra(EXTRA_RESULT_FIELD, mField);
resultData.putExtra(EXTRA_RESULT_FIELD_INDEX, mFieldIndex);
setResult(RESULT_OK, resultData);
finishWithoutAnimation();
}

@Override
protected void onDestroy() {
super.onDestroy();

if (mFieldController != null) {
mFieldController.onDestroy();
}

super.onDestroy();
}


@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Timber.d("onSaveInstanceState - saving state");

// This is used to tell the whole activity to shut down if it is restored from Activity restart.
// Why? I am not really sure. Perhaps to avoid terrible bugs due to not implementing things correctly?
mikehardy marked this conversation as resolved.
Show resolved Hide resolved
outState.putBoolean(BUNDLE_KEY_SHUT_OFF, true);

// We will give field controllers a chance to save / restore across restarts though.
// If this bundle is not null, on restore, we should continue across Activity restart.
if (mFieldController != null) {
Bundle controllerBundle = mFieldController.saveInstanceState();
if (controllerBundle != null) {
outState.putBundle("controllerBundle", mFieldController.saveInstanceState());
}
}
super.onSaveInstanceState(outState);
}

/** Intermediate class to hold state for the onRequestPermissionsResult callback */
Expand Down
Loading