From f6fee780eec1552f16f99ff04459d832795a54da Mon Sep 17 00:00:00 2001 From: Katharine Date: Sat, 30 Oct 2021 00:03:42 +0800 Subject: [PATCH 01/10] cropper poc --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 5 + .../card_locker/LoyaltyCardEditActivity.java | 159 ++++++++++++------ 3 files changed, 113 insertions(+), 52 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1ff625b18..5a14cdd49 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -85,6 +85,7 @@ dependencies { implementation 'androidx.exifinterface:exifinterface:1.3.3' implementation 'androidx.preference:preference:1.1.1' implementation 'com.google.android.material:material:1.4.0' + implementation 'com.github.yalantis:ucrop:2.2.6' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' // Splash Screen diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1e81afb71..5a5217267 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -115,6 +115,11 @@ + + mCropperLauncher; + int mRequestedImage; + UCrop.Options mCropperOptions; + // FIX ME: for until everything is switched to activity launcher callbacks + private boolean skipOnce = false; + final private TaskHandler mTasks = new TaskHandler(); private static LoyaltyCard updateTempState(LoyaltyCard loyaltyCard, LoyaltyCardField fieldName, Object value) { @@ -188,6 +199,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { tabs = findViewById(R.id.tabs); savedInstanceState.putInt(STATE_TAB_INDEX, tabs.getSelectedTabPosition()); savedInstanceState.putParcelable(STATE_TEMP_CARD, tempLoyaltyCard); + savedInstanceState.putInt("skipOnce", skipOnce? 1: 0); } @Override @@ -509,6 +521,56 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { }); tabs.selectTab(tabs.getTabAt(0)); + + mCropperLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), + new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult result) { + Intent intent = result.getData(); + if (result.getResultCode() == Activity.RESULT_OK) { + Uri debugUri = UCrop.getOutput(intent); + String cropOutputPath = debugUri.getPath(); + Log.d("cropper", "cropper thinks it has produced image at " + cropOutputPath + " " + debugUri.toString()); + Bitmap bitmap = BitmapFactory.decodeFile(cropOutputPath); + + + if (bitmap != null) { + bitmap = Utils.resizeBitmap(bitmap); + try { + bitmap = Utils.rotateBitmap(bitmap, new ExifInterface(cropOutputPath)); + } catch (IOException e) { + e.printStackTrace(); + } + + if (mRequestedImage == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || mRequestedImage == Utils.CARD_IMAGE_FROM_FILE_FRONT) { + setCardImage(cardImageFront, bitmap); + } else { + setCardImage(cardImageBack, bitmap); + } + + hasChanged = true; + } else { + Toast.makeText(LoyaltyCardEditActivity.this, R.string.errorReadingImage, Toast.LENGTH_LONG).show(); + } + }else if(result.getResultCode() == UCrop.RESULT_ERROR){ + Log.e("cropper error", UCrop.getError(intent).toString()); + } + } + }); + + if (savedInstanceState != null) { + skipOnce = savedInstanceState.getInt("skipOnce") == 1 ? true : false; + } + mCropperOptions = new UCrop.Options(); + mCropperOptions.setCompressionFormat(Bitmap.CompressFormat.JPEG); + setCropperTheme(); + } + + private void setCropperTheme(){ + mCropperOptions.setToolbarColor(getResources().getColor(R.color.colorPrimary)); + mCropperOptions.setStatusBarColor(getResources().getColor(R.color.colorPrimary)); + mCropperOptions.setToolbarWidgetColor(Color.WHITE); + mCropperOptions.setActiveControlsWidgetColor(getResources().getColor(R.color.colorPrimary)); } @Override @@ -786,15 +848,21 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { confirmExitDialog.show(); } + private File createTempFile(String prefix, String suffix) throws IOException{ + String imageFileName = prefix + new Date().getTime(); + File file = File.createTempFile( + imageFileName, + suffix, + getExternalFilesDir(Environment.DIRECTORY_PICTURES) + ); + return file; + } + private void takePhotoForCard(int type) throws IOException { Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + File image = createTempFile("CATIMA_", ".jpg"); String imageFileName = "CATIMA_" + new Date().getTime(); - File image = File.createTempFile( - imageFileName, - ".jpg", - getExternalFilesDir(Environment.DIRECTORY_PICTURES) - ); tempCameraPicturePath = image.getAbsolutePath(); @@ -1035,58 +1103,45 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { return super.onOptionsItemSelected(item); } + public boolean startCropper(String sourceImagePath){ + return startCropperUri(Uri.parse("file://" + sourceImagePath)); + } + public boolean startCropperUri(Uri sourceUri){ + Log.d("cropper", "launching cropper with image " + sourceUri.getPath()); + File cropOutput; + try { + cropOutput = createTempFile("UCROP_", ".jpg"); + }catch(java.io.IOException e){ + Log.e("image cropping", "failed creating temporarily file"); + return false; + } + Uri destUri = Uri.parse("file://" + cropOutput.getAbsolutePath()); + Log.d("cropper", "asking cropper to output to " + destUri.toString()); + skipOnce = true; + mCropperLauncher.launch( + UCrop.of( + sourceUri, + destUri + ).withOptions(mCropperOptions).getIntent(this) + ); + return true; + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); - + if(skipOnce){ + skipOnce = false; + return; + } if (resultCode == RESULT_OK) { if (requestCode == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || requestCode == Utils.CARD_IMAGE_FROM_CAMERA_BACK) { - Bitmap bitmap = BitmapFactory.decodeFile(tempCameraPicturePath); - - if (bitmap != null) { - bitmap = Utils.resizeBitmap(bitmap); - try { - bitmap = Utils.rotateBitmap(bitmap, new ExifInterface(tempCameraPicturePath)); - } catch (IOException e) { - e.printStackTrace(); - } - - if (requestCode == Utils.CARD_IMAGE_FROM_CAMERA_FRONT) { - setCardImage(cardImageFront, bitmap); - } else { - setCardImage(cardImageBack, bitmap); - } - - hasChanged = true; - } else { - Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show(); - } + mRequestedImage = requestCode; + startCropper(tempCameraPicturePath); } else if (requestCode == Utils.CARD_IMAGE_FROM_FILE_FRONT || requestCode == Utils.CARD_IMAGE_FROM_FILE_BACK) { - Bitmap bitmap = null; - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - ImageDecoder.Source image_source = ImageDecoder.createSource(getContentResolver(), intent.getData()); - bitmap = ImageDecoder.decodeBitmap(image_source, (decoder, info, source) -> decoder.setMutableRequired(true)); - } else { - bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), intent.getData()); - } - } catch (IOException e) { - Log.e(TAG, "Error getting data from image file"); - e.printStackTrace(); - } - - if (bitmap != null) { - bitmap = Utils.resizeBitmap(bitmap); - if (requestCode == Utils.CARD_IMAGE_FROM_FILE_FRONT) { - setCardImage(cardImageFront, bitmap); - } else { - setCardImage(cardImageBack, bitmap); - } - - hasChanged = true; - } else { - Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show(); - } + mRequestedImage = requestCode; + Uri uri = intent.getData(); + startCropperUri(uri); } else { BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this); From 3e5b018b55658be6f7171ae9060bfd3b03c9dd5d Mon Sep 17 00:00:00 2001 From: Katharine Date: Sat, 30 Oct 2021 08:15:03 +0800 Subject: [PATCH 02/10] fxing spotbug raised issues --- .../card_locker/LoyaltyCardEditActivity.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java index 96acd4eb5..4ef86acb8 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java @@ -529,8 +529,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { Intent intent = result.getData(); if (result.getResultCode() == Activity.RESULT_OK) { Uri debugUri = UCrop.getOutput(intent); + if (debugUri == null){ + throw new RuntimeException("ucrop returned success but not destination uri!"); + } String cropOutputPath = debugUri.getPath(); - Log.d("cropper", "cropper thinks it has produced image at " + cropOutputPath + " " + debugUri.toString()); + Log.d("cropper", "cropper has produced image at " + debugUri.toString()); Bitmap bitmap = BitmapFactory.decodeFile(cropOutputPath); @@ -553,7 +556,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { Toast.makeText(LoyaltyCardEditActivity.this, R.string.errorReadingImage, Toast.LENGTH_LONG).show(); } }else if(result.getResultCode() == UCrop.RESULT_ERROR){ - Log.e("cropper error", UCrop.getError(intent).toString()); + Throwable e = UCrop.getError(intent); + if (e == null){ + throw new RuntimeException("ucrop returned error state but not and error!"); + } + Log.e("cropper error", e.toString()); } } }); @@ -562,10 +569,15 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { skipOnce = savedInstanceState.getInt("skipOnce") == 1 ? true : false; } mCropperOptions = new UCrop.Options(); - mCropperOptions.setCompressionFormat(Bitmap.CompressFormat.JPEG); + setCropperOptions(); setCropperTheme(); } + private void setCropperOptions(){ + mCropperOptions.setCompressionFormat(Bitmap.CompressFormat.JPEG); + mCropperOptions.setFreeStyleCropEnabled(false); + } + private void setCropperTheme(){ mCropperOptions.setToolbarColor(getResources().getColor(R.color.colorPrimary)); mCropperOptions.setStatusBarColor(getResources().getColor(R.color.colorPrimary)); @@ -862,7 +874,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File image = createTempFile("CATIMA_", ".jpg"); - String imageFileName = "CATIMA_" + new Date().getTime(); tempCameraPicturePath = image.getAbsolutePath(); From 9cc66c5d6746459c29a09401842ba3c94295d7c7 Mon Sep 17 00:00:00 2001 From: Katharine Date: Sat, 30 Oct 2021 18:38:12 +0800 Subject: [PATCH 03/10] minor touch ups from android studio suggestions --- .../card_locker/LoyaltyCardEditActivity.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java index 4ef86acb8..d8bf503bb 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java @@ -527,6 +527,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { @Override public void onActivityResult(ActivityResult result) { Intent intent = result.getData(); + if (intent == null){ + throw(new RuntimeException("ucrop returned a null intent")); + } if (result.getResultCode() == Activity.RESULT_OK) { Uri debugUri = UCrop.getOutput(intent); if (debugUri == null){ @@ -566,7 +569,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { }); if (savedInstanceState != null) { - skipOnce = savedInstanceState.getInt("skipOnce") == 1 ? true : false; + skipOnce = savedInstanceState.getInt("skipOnce") == 1; } mCropperOptions = new UCrop.Options(); setCropperOptions(); @@ -862,12 +865,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { private File createTempFile(String prefix, String suffix) throws IOException{ String imageFileName = prefix + new Date().getTime(); - File file = File.createTempFile( + return File.createTempFile( imageFileName, suffix, getExternalFilesDir(Environment.DIRECTORY_PICTURES) ); - return file; } private void takePhotoForCard(int type) throws IOException { @@ -1114,17 +1116,17 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { return super.onOptionsItemSelected(item); } - public boolean startCropper(String sourceImagePath){ - return startCropperUri(Uri.parse("file://" + sourceImagePath)); + public void startCropper(String sourceImagePath){ + startCropperUri(Uri.parse("file://" + sourceImagePath)); } - public boolean startCropperUri(Uri sourceUri){ + public void startCropperUri(Uri sourceUri){ Log.d("cropper", "launching cropper with image " + sourceUri.getPath()); File cropOutput; try { cropOutput = createTempFile("UCROP_", ".jpg"); }catch(java.io.IOException e){ Log.e("image cropping", "failed creating temporarily file"); - return false; + return; } Uri destUri = Uri.parse("file://" + cropOutput.getAbsolutePath()); Log.d("cropper", "asking cropper to output to " + destUri.toString()); @@ -1135,7 +1137,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { destUri ).withOptions(mCropperOptions).getIntent(this) ); - return true; + return; } @Override From a744c19cce5f4041295ee5b884094df8e7049446 Mon Sep 17 00:00:00 2001 From: Katharine Date: Sat, 30 Oct 2021 18:53:19 +0800 Subject: [PATCH 04/10] handle null intent from ucrop for when user just exits without finish cropping --- .../java/protect/card_locker/LoyaltyCardEditActivity.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java index d8bf503bb..d43b448f4 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java @@ -528,7 +528,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { public void onActivityResult(ActivityResult result) { Intent intent = result.getData(); if (intent == null){ - throw(new RuntimeException("ucrop returned a null intent")); + Log.d("cropper", "ucrop returned a null intent"); + return; } if (result.getResultCode() == Activity.RESULT_OK) { Uri debugUri = UCrop.getOutput(intent); @@ -578,7 +579,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { private void setCropperOptions(){ mCropperOptions.setCompressionFormat(Bitmap.CompressFormat.JPEG); - mCropperOptions.setFreeStyleCropEnabled(false); + mCropperOptions.setFreeStyleCropEnabled(true); } private void setCropperTheme(){ From 3ee533b8159b4fa84c9cdb6a6a30e55ee39de180 Mon Sep 17 00:00:00 2001 From: Katharine Date: Mon, 1 Nov 2021 14:46:04 +0800 Subject: [PATCH 05/10] migrate to registerForActivityResult callbacks --- .../card_locker/LoyaltyCardEditActivity.java | 168 +++++++++--------- 1 file changed, 86 insertions(+), 82 deletions(-) diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java index d43b448f4..3f2c82d6d 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java @@ -146,11 +146,15 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { LoyaltyCard tempLoyaltyCard; + ActivityResultLauncher mPhotoTakerLauncher; + ActivityResultLauncher mPhotoPickerLauncher; + ActivityResultLauncher mCardIdAndBardCodeEditorLauncher; + ActivityResultLauncher mCropperLauncher; int mRequestedImage; UCrop.Options mCropperOptions; - // FIX ME: for until everything is switched to activity launcher callbacks - private boolean skipOnce = false; + + final private TaskHandler mTasks = new TaskHandler(); @@ -199,7 +203,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { tabs = findViewById(R.id.tabs); savedInstanceState.putInt(STATE_TAB_INDEX, tabs.getSelectedTabPosition()); savedInstanceState.putParcelable(STATE_TEMP_CARD, tempLoyaltyCard); - savedInstanceState.putInt("skipOnce", skipOnce? 1: 0); } @Override @@ -522,56 +525,84 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { tabs.selectTab(tabs.getTabAt(0)); - mCropperLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - new ActivityResultCallback() { - @Override - public void onActivityResult(ActivityResult result) { - Intent intent = result.getData(); - if (intent == null){ - Log.d("cropper", "ucrop returned a null intent"); - return; - } - if (result.getResultCode() == Activity.RESULT_OK) { - Uri debugUri = UCrop.getOutput(intent); - if (debugUri == null){ - throw new RuntimeException("ucrop returned success but not destination uri!"); - } - String cropOutputPath = debugUri.getPath(); - Log.d("cropper", "cropper has produced image at " + debugUri.toString()); - Bitmap bitmap = BitmapFactory.decodeFile(cropOutputPath); + mPhotoTakerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + if (result.getResultCode() == RESULT_OK) { + startCropper(tempCameraPicturePath); + } + }); - if (bitmap != null) { - bitmap = Utils.resizeBitmap(bitmap); - try { - bitmap = Utils.rotateBitmap(bitmap, new ExifInterface(cropOutputPath)); - } catch (IOException e) { - e.printStackTrace(); - } - - if (mRequestedImage == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || mRequestedImage == Utils.CARD_IMAGE_FROM_FILE_FRONT) { - setCardImage(cardImageFront, bitmap); - } else { - setCardImage(cardImageBack, bitmap); - } - - hasChanged = true; - } else { - Toast.makeText(LoyaltyCardEditActivity.this, R.string.errorReadingImage, Toast.LENGTH_LONG).show(); - } - }else if(result.getResultCode() == UCrop.RESULT_ERROR){ - Throwable e = UCrop.getError(intent); - if (e == null){ - throw new RuntimeException("ucrop returned error state but not and error!"); - } - Log.e("cropper error", e.toString()); - } + mPhotoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + if (result.getResultCode() == RESULT_OK) { + Intent intent = result.getData(); + if (intent == null){ + Log.d("photo picker", "photo picker returned without an intent"); + return; } - }); + Uri uri = intent.getData(); + startCropperUri(uri); + } + + }); + + mCardIdAndBardCodeEditorLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + if (result.getResultCode() == RESULT_OK) { + Intent intent = result.getData(); + if (intent == null){ + Log.d("barcode card id editor", "barcode and card id editor picker returned without an intent"); + return; + } + BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, getApplicationContext()); + + cardId = barcodeValues.content(); + barcodeType = barcodeValues.format(); + barcodeId = ""; + } + }); + + mCropperLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + Intent intent = result.getData(); + if (intent == null){ + Log.d("cropper", "ucrop returned a null intent"); + return; + } + if (result.getResultCode() == Activity.RESULT_OK) { + Uri debugUri = UCrop.getOutput(intent); + if (debugUri == null){ + throw new RuntimeException("ucrop returned success but not destination uri!"); + } + String cropOutputPath = debugUri.getPath(); + Log.d("cropper", "cropper has produced image at " + debugUri.toString()); + Bitmap bitmap = BitmapFactory.decodeFile(cropOutputPath); + + + if (bitmap != null) { + bitmap = Utils.resizeBitmap(bitmap); + try { + bitmap = Utils.rotateBitmap(bitmap, new ExifInterface(cropOutputPath)); + } catch (IOException e) { + e.printStackTrace(); + } + + if (mRequestedImage == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || mRequestedImage == Utils.CARD_IMAGE_FROM_FILE_FRONT) { + setCardImage(cardImageFront, bitmap); + } else { + setCardImage(cardImageBack, bitmap); + } + + hasChanged = true; + } else { + Toast.makeText(LoyaltyCardEditActivity.this, R.string.errorReadingImage, Toast.LENGTH_LONG).show(); + } + }else if(result.getResultCode() == UCrop.RESULT_ERROR){ + Throwable e = UCrop.getError(intent); + if (e == null){ + throw new RuntimeException("ucrop returned error state but not and error!"); + } + Log.e("cropper error", e.toString()); + } + }); - if (savedInstanceState != null) { - skipOnce = savedInstanceState.getInt("skipOnce") == 1; - } mCropperOptions = new UCrop.Options(); setCropperOptions(); setCropperTheme(); @@ -579,7 +610,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { private void setCropperOptions(){ mCropperOptions.setCompressionFormat(Bitmap.CompressFormat.JPEG); - mCropperOptions.setFreeStyleCropEnabled(true); + mCropperOptions.setFreeStyleCropEnabled(false); } private void setCropperTheme(){ @@ -882,8 +913,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { Uri photoURI = FileProvider.getUriForFile(LoyaltyCardEditActivity.this, BuildConfig.APPLICATION_ID, image); i.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); - - startActivityForResult(i, type); + mRequestedImage = type; + mPhotoTakerLauncher.launch(i); } class EditCardIdAndBarcode implements View.OnClickListener { @@ -893,7 +924,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { final Bundle b = new Bundle(); b.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, cardIdFieldView.getText().toString()); i.putExtras(b); - startActivityForResult(i, Utils.BARCODE_SCAN); + mCardIdAndBardCodeEditorLauncher.launch(i); } } @@ -922,7 +953,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { cardOptions.put(getString(R.string.addFromImage), () -> { Intent i = new Intent(Intent.ACTION_PICK); i.setType("image/*"); - startActivityForResult(i, v.getId() == ID_IMAGE_FRONT ? Utils.CARD_IMAGE_FROM_FILE_FRONT : Utils.CARD_IMAGE_FROM_FILE_BACK); + mRequestedImage = v.getId() == ID_IMAGE_FRONT ? Utils.CARD_IMAGE_FROM_FILE_FRONT : Utils.CARD_IMAGE_FROM_FILE_BACK; + mPhotoPickerLauncher.launch(i); return null; }); @@ -1131,7 +1163,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { } Uri destUri = Uri.parse("file://" + cropOutput.getAbsolutePath()); Log.d("cropper", "asking cropper to output to " + destUri.toString()); - skipOnce = true; mCropperLauncher.launch( UCrop.of( sourceUri, @@ -1141,33 +1172,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { return; } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - super.onActivityResult(requestCode, resultCode, intent); - if(skipOnce){ - skipOnce = false; - return; - } - if (resultCode == RESULT_OK) { - if (requestCode == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || requestCode == Utils.CARD_IMAGE_FROM_CAMERA_BACK) { - mRequestedImage = requestCode; - startCropper(tempCameraPicturePath); - } else if (requestCode == Utils.CARD_IMAGE_FROM_FILE_FRONT || requestCode == Utils.CARD_IMAGE_FROM_FILE_BACK) { - mRequestedImage = requestCode; - Uri uri = intent.getData(); - startCropperUri(uri); - } else { - BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this); - - cardId = barcodeValues.content(); - barcodeType = barcodeValues.format(); - barcodeId = ""; - } - } - - onResume(); - } - private void showBarcode() { barcodeImageLayout.setVisibility(View.VISIBLE); } From 4536453fdf323f619c7259f0d63a2c53a6ed2f4c Mon Sep 17 00:00:00 2001 From: Katharine Date: Mon, 1 Nov 2021 19:46:07 +0800 Subject: [PATCH 06/10] debugging test case breakage --- .../card_locker/LoyaltyCardEditActivity.java | 17 ++++++++++++----- .../LoyaltyCardViewActivityTest.java | 10 ++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java index 3f2c82d6d..48157722f 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java @@ -64,11 +64,10 @@ import java.util.Locale; import java.util.NoSuchElementException; import java.util.concurrent.Callable; -import androidx.activity.result.ActivityResult; -import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; @@ -148,7 +147,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { ActivityResultLauncher mPhotoTakerLauncher; ActivityResultLauncher mPhotoPickerLauncher; - ActivityResultLauncher mCardIdAndBardCodeEditorLauncher; + ActivityResultLauncher mCardIdAndBarCodeEditorLauncher; ActivityResultLauncher mCropperLauncher; int mRequestedImage; @@ -545,7 +544,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { }); - mCardIdAndBardCodeEditorLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + mCardIdAndBarCodeEditorLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + Log.d("requestCode", "result arriving at mCardIdAndBarCodeEditorLauncher"); if (result.getResultCode() == RESULT_OK) { Intent intent = result.getData(); if (intent == null){ @@ -924,7 +924,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { final Bundle b = new Bundle(); b.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, cardIdFieldView.getText().toString()); i.putExtras(b); - mCardIdAndBardCodeEditorLauncher.launch(i); + mCardIdAndBarCodeEditorLauncher.launch(i); } } @@ -1271,4 +1271,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { throw new UnsupportedOperationException(); } } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + System.out.println("requestCode: " + requestCode); + Log.d("requestCode", "requestCode: " + requestCode); + } } diff --git a/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java b/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java index a7371bdd2..01a37a8ea 100644 --- a/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java +++ b/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java @@ -263,11 +263,15 @@ public class LoyaltyCardViewActivityTest assertNotNull(intent); assertEquals(intent.getComponent().getClassName(), ScanActivity.class.getCanonicalName()); + // this confuses the test suit, a activity result with request type 2 gets sent to the activity being tested + /* Activity newActivity = Robolectric.buildActivity(ScanActivity.class, intent).create().get(); final Button manualButton = newActivity.findViewById(R.id.add_manually); manualButton.performClick(); + */ + intentForResult = shadowOf(activity).peekNextStartedActivityForResult(); assertNotNull(intentForResult); @@ -516,6 +520,7 @@ public class LoyaltyCardViewActivityTest // Complete barcode capture successfully captureBarcodeWithResult(activity, true); + activityController.resume(); checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, context.getString(R.string.sameAsCardId), BARCODE_TYPE.prettyName(), null, null); @@ -540,6 +545,7 @@ public class LoyaltyCardViewActivityTest // Complete barcode capture in failure captureBarcodeWithResult(activity, false); + activityController.resume(); shadowOf(getMainLooper()).idle(); @@ -561,6 +567,7 @@ public class LoyaltyCardViewActivityTest // Complete barcode capture successfully captureBarcodeWithResult(activity, true); + activityController.resume(); checkAllFields(activity, ViewMode.ADD_CARD, "", "", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, context.getString(R.string.sameAsCardId), BARCODE_TYPE.prettyName(), null, null); @@ -660,6 +667,7 @@ public class LoyaltyCardViewActivityTest // Complete barcode capture successfully captureBarcodeWithResult(activity, true); + activityController.resume(); checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, context.getString(R.string.sameAsCardId), BARCODE_TYPE.prettyName(), null, null); @@ -684,6 +692,7 @@ public class LoyaltyCardViewActivityTest // Complete barcode capture successfully captureBarcodeWithResult(activity, true); + activityController.resume(); checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, context.getString(R.string.sameAsCardId), BARCODE_TYPE.prettyName(), null, null); @@ -1136,6 +1145,7 @@ public class LoyaltyCardViewActivityTest // Complete empty barcode selection successfully selectBarcodeWithResult(activity, BARCODE_DATA, "", true); + activityController.resume(); // Check if the barcode type is NO_BARCODE as expected checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", context.getString(R.string.never), "0", context.getString(R.string.points), BARCODE_DATA, context.getString(R.string.sameAsCardId), context.getString(R.string.noBarcode), null, null); From f475844bd0b1024a465e6a0f6719b70c034ea349 Mon Sep 17 00:00:00 2001 From: Katharine Date: Tue, 2 Nov 2021 08:43:14 +0800 Subject: [PATCH 07/10] wrapping up test case debugging --- .../protect/card_locker/LoyaltyCardEditActivity.java | 9 --------- .../protect/card_locker/LoyaltyCardViewActivityTest.java | 9 --------- 2 files changed, 18 deletions(-) diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java index 48157722f..b49c0698a 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java @@ -67,7 +67,6 @@ import java.util.concurrent.Callable; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; @@ -545,7 +544,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { }); mCardIdAndBarCodeEditorLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { - Log.d("requestCode", "result arriving at mCardIdAndBarCodeEditorLauncher"); if (result.getResultCode() == RESULT_OK) { Intent intent = result.getData(); if (intent == null){ @@ -1271,11 +1269,4 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { throw new UnsupportedOperationException(); } } - - @Override - protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - super.onActivityResult(requestCode, resultCode, data); - System.out.println("requestCode: " + requestCode); - Log.d("requestCode", "requestCode: " + requestCode); - } } diff --git a/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java b/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java index 01a37a8ea..d305b908a 100644 --- a/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java +++ b/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java @@ -263,15 +263,6 @@ public class LoyaltyCardViewActivityTest assertNotNull(intent); assertEquals(intent.getComponent().getClassName(), ScanActivity.class.getCanonicalName()); - // this confuses the test suit, a activity result with request type 2 gets sent to the activity being tested - /* - Activity newActivity = Robolectric.buildActivity(ScanActivity.class, intent).create().get(); - - final Button manualButton = newActivity.findViewById(R.id.add_manually); - manualButton.performClick(); - - */ - intentForResult = shadowOf(activity).peekNextStartedActivityForResult(); assertNotNull(intentForResult); From 57cfac317266277a8be55aab8d29c8b8b1d83ccb Mon Sep 17 00:00:00 2001 From: Katharine Date: Tue, 2 Nov 2021 10:01:34 +0800 Subject: [PATCH 08/10] save state for cropper --- .../card_locker/LoyaltyCardEditActivity.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java index b49c0698a..e864f7492 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java @@ -44,6 +44,8 @@ import com.google.android.material.tabs.TabLayout; import com.jaredrummler.android.colorpicker.ColorPickerDialog; import com.jaredrummler.android.colorpicker.ColorPickerDialogListener; import com.yalantis.ucrop.UCrop; +import com.yalantis.ucrop.UCropActivity; +import com.yalantis.ucrop.model.AspectRatio; import java.io.File; import java.io.FileNotFoundException; @@ -81,6 +83,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { private final String STATE_TAB_INDEX = "savedTab"; private final String STATE_TEMP_CARD = "tempLoyaltyCard"; + private final String STATE_REQUESTED_IMAGE = "requestedImage"; + private final String STATE_TEMP_CAMERA_PICTURE_PATH = "tempCameraPicturePath"; private static final int ID_IMAGE_FRONT = 0; private static final int ID_IMAGE_BACK = 1; @@ -201,6 +205,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { tabs = findViewById(R.id.tabs); savedInstanceState.putInt(STATE_TAB_INDEX, tabs.getSelectedTabPosition()); savedInstanceState.putParcelable(STATE_TEMP_CARD, tempLoyaltyCard); + savedInstanceState.putInt(STATE_REQUESTED_IMAGE, mRequestedImage); + savedInstanceState.putString(STATE_TEMP_CAMERA_PICTURE_PATH, tempCameraPicturePath); } @Override @@ -209,6 +215,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { super.onRestoreInstanceState(savedInstanceState); tabs = findViewById(R.id.tabs); tabs.selectTab(tabs.getTabAt(savedInstanceState.getInt(STATE_TAB_INDEX))); + mRequestedImage = savedInstanceState.getInt(STATE_REQUESTED_IMAGE); + tempCameraPicturePath = savedInstanceState.getString(STATE_TEMP_CAMERA_PICTURE_PATH); } @Override @@ -608,12 +616,19 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { private void setCropperOptions(){ mCropperOptions.setCompressionFormat(Bitmap.CompressFormat.JPEG); - mCropperOptions.setFreeStyleCropEnabled(false); + mCropperOptions.setFreeStyleCropEnabled(true); + mCropperOptions.setHideBottomControls(false); + // needed when bottom controls are hidden + //mCropperOptions.setAllowedGestures(UCropActivity.ALL, UCropActivity.ALL, UCropActivity.ALL); + mCropperOptions.setAspectRatioOptions(0, + new AspectRatio(null, 1, 1), + new AspectRatio(getResources().getString(R.string.card),(float)85.6,(float)53.98 ) + ); } private void setCropperTheme(){ mCropperOptions.setToolbarColor(getResources().getColor(R.color.colorPrimary)); - mCropperOptions.setStatusBarColor(getResources().getColor(R.color.colorPrimary)); + mCropperOptions.setStatusBarColor(getResources().getColor(R.color.colorPrimaryDark)); mCropperOptions.setToolbarWidgetColor(Color.WHITE); mCropperOptions.setActiveControlsWidgetColor(getResources().getColor(R.color.colorPrimary)); } @@ -1161,6 +1176,13 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { } Uri destUri = Uri.parse("file://" + cropOutput.getAbsolutePath()); Log.d("cropper", "asking cropper to output to " + destUri.toString()); + + if(mRequestedImage == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || mRequestedImage == Utils.CARD_IMAGE_FROM_FILE_FRONT){ + mCropperOptions.setToolbarTitle(getResources().getString(R.string.setFrontImage)); + }else{ + mCropperOptions.setToolbarTitle(getResources().getString(R.string.setBackImage)); + } + mCropperLauncher.launch( UCrop.of( sourceUri, From 8940a8ea77f2bd25ee59512d94fd37fd66c01396 Mon Sep 17 00:00:00 2001 From: Katharine Date: Thu, 4 Nov 2021 02:41:23 +0800 Subject: [PATCH 09/10] card edit activity: revised instance saving, revised temp image handling --- app/src/main/AndroidManifest.xml | 1 - .../card_locker/LoyaltyCardEditActivity.java | 172 +++++++++++------- .../main/java/protect/card_locker/Utils.java | 36 +++- app/src/main/res/xml/file_provider_paths.xml | 2 +- 4 files changed, 136 insertions(+), 75 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5a5217267..ed11d0e1b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -117,7 +117,6 @@ currencies = new HashMap<>(); - String tempCameraPicturePath; - LoyaltyCard tempLoyaltyCard; - ActivityResultLauncher mPhotoTakerLauncher; + ActivityResultLauncher mPhotoTakerLauncher; ActivityResultLauncher mPhotoPickerLauncher; ActivityResultLauncher mCardIdAndBarCodeEditorLauncher; ActivityResultLauncher mCropperLauncher; - int mRequestedImage; + int mRequestedImage = 0; + int mCropperFinishedType = 0; UCrop.Options mCropperOptions; - + boolean mFrontImageUnsaved = false; + boolean mBackImageUnsaved = false; final private TaskHandler mTasks = new TaskHandler(); @@ -199,6 +206,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { + ", updateLoyaltyCard=" + updateLoyaltyCard); } + + @Override public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); @@ -206,7 +215,20 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { savedInstanceState.putInt(STATE_TAB_INDEX, tabs.getSelectedTabPosition()); savedInstanceState.putParcelable(STATE_TEMP_CARD, tempLoyaltyCard); savedInstanceState.putInt(STATE_REQUESTED_IMAGE, mRequestedImage); - savedInstanceState.putString(STATE_TEMP_CAMERA_PICTURE_PATH, tempCameraPicturePath); + Object cardImageFrontObj = cardImageFront.getTag(); + if (mFrontImageUnsaved && (cardImageFrontObj instanceof Bitmap) && Utils.saveTempImage(this, (Bitmap)cardImageFrontObj, TEMP_UNSAVED_FRONT_IMAGE_NAME, TEMP_UNSAVED_IMAGE_FORMAT) != null){ + savedInstanceState.putInt(STATE_FRONT_IMAGE_UNSAVED, 1); + }else{ + savedInstanceState.putInt(STATE_FRONT_IMAGE_UNSAVED, 0); + } + Object cardImageBackObj = cardImageBack.getTag(); + if (mBackImageUnsaved && (cardImageBackObj instanceof Bitmap) && Utils.saveTempImage(this, (Bitmap)cardImageBackObj, TEMP_UNSAVED_BACK_IMAGE_NAME, TEMP_UNSAVED_IMAGE_FORMAT) != null){ + savedInstanceState.putInt(STATE_BACK_IMAGE_UNSAVED, 1); + }else{ + savedInstanceState.putInt(STATE_BACK_IMAGE_UNSAVED, 0); + } + savedInstanceState.putInt(STATE_UPDATE_LOYALTY_CARD, updateLoyaltyCard ? 1 : 0); + savedInstanceState.putInt(STATE_HAS_CHANGED, hasChanged? 1:0); } @Override @@ -216,7 +238,10 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { tabs = findViewById(R.id.tabs); tabs.selectTab(tabs.getTabAt(savedInstanceState.getInt(STATE_TAB_INDEX))); mRequestedImage = savedInstanceState.getInt(STATE_REQUESTED_IMAGE); - tempCameraPicturePath = savedInstanceState.getString(STATE_TEMP_CAMERA_PICTURE_PATH); + mFrontImageUnsaved = savedInstanceState.getInt(STATE_FRONT_IMAGE_UNSAVED) == 1; + mBackImageUnsaved = savedInstanceState.getInt(STATE_BACK_IMAGE_UNSAVED) == 1; + updateLoyaltyCard = savedInstanceState.getInt(STATE_UPDATE_LOYALTY_CARD) == 1; + hasChanged = savedInstanceState.getInt(STATE_HAS_CHANGED) == 1; } @Override @@ -438,7 +463,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { if (!lastValue.toString().equals(getString(R.string.setBarcodeId))) { barcodeIdField.setText(lastValue); } - ; AlertDialog.Builder builder = new AlertDialog.Builder(LoyaltyCardEditActivity.this); builder.setTitle(R.string.setBarcodeId); @@ -531,13 +555,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { tabs.selectTab(tabs.getTabAt(0)); - mPhotoTakerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { - if (result.getResultCode() == RESULT_OK) { - startCropper(tempCameraPicturePath); - } + mPhotoTakerLauncher = registerForActivityResult(new ActivityResultContracts.TakePicture(), result->{ + if (result) { + startCropper(getCacheDir() + "/" + TEMP_CAMERA_IMAGE_NAME); + } }); + // android 11: wanted to swap it to ActivityResultContracts.GetContent but then it shows a file browsers that shows image mime types, offering gallery in the file browser mPhotoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK) { Intent intent = result.getData(); @@ -548,7 +573,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { Uri uri = intent.getData(); startCropperUri(uri); } - }); mCardIdAndBarCodeEditorLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { @@ -577,25 +601,20 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { if (debugUri == null){ throw new RuntimeException("ucrop returned success but not destination uri!"); } - String cropOutputPath = debugUri.getPath(); - Log.d("cropper", "cropper has produced image at " + debugUri.toString()); - Bitmap bitmap = BitmapFactory.decodeFile(cropOutputPath); - + Log.d("cropper", "ucrop produced image at " + debugUri); + Bitmap bitmap = BitmapFactory.decodeFile(getCacheDir() + "/" + TEMP_CROP_IMAGE_NAME); if (bitmap != null) { bitmap = Utils.resizeBitmap(bitmap); - try { - bitmap = Utils.rotateBitmap(bitmap, new ExifInterface(cropOutputPath)); - } catch (IOException e) { - e.printStackTrace(); - } - - if (mRequestedImage == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || mRequestedImage == Utils.CARD_IMAGE_FROM_FILE_FRONT) { + if (requestedFrontImage()) { + mFrontImageUnsaved = true; setCardImage(cardImageFront, bitmap); } else { + mBackImageUnsaved = true; setCardImage(cardImageBack, bitmap); } - + Log.d("cropper", "mRequestedImage: " + mRequestedImage); + mCropperFinishedType = mRequestedImage; hasChanged = true; } else { Toast.makeText(LoyaltyCardEditActivity.this, R.string.errorReadingImage, Toast.LENGTH_LONG).show(); @@ -615,7 +634,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { } private void setCropperOptions(){ - mCropperOptions.setCompressionFormat(Bitmap.CompressFormat.JPEG); + mCropperOptions.setCompressionFormat(TEMP_CROP_IMAGE_FORMAT); mCropperOptions.setFreeStyleCropEnabled(true); mCropperOptions.setHideBottomControls(false); // needed when bottom controls are hidden @@ -641,6 +660,18 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { extractIntentFields(intent); } + private boolean requestedFrontImage(){ + return mRequestedImage == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || mRequestedImage == Utils.CARD_IMAGE_FROM_FILE_FRONT; + } + + private boolean croppedFrontImage(){ + return mCropperFinishedType == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || mCropperFinishedType == Utils.CARD_IMAGE_FROM_FILE_FRONT; + } + + private boolean croppedBackImage(){ + return mCropperFinishedType == Utils.CARD_IMAGE_FROM_CAMERA_BACK || mCropperFinishedType == Utils.CARD_IMAGE_FROM_FILE_BACK; + } + @SuppressLint("DefaultLocale") @Override public void onResume() { @@ -650,6 +681,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { onResuming = true; + if (tempLoyaltyCard == null) { if (updateLoyaltyCard) { tempLoyaltyCard = db.getLoyaltyCard(loyaltyCardId); @@ -659,9 +691,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { finish(); return; } - setTitle(R.string.editCardTitle); - setCardImage(cardImageFront, Utils.retrieveCardImage(this, tempLoyaltyCard.id, true)); - setCardImage(cardImageBack, Utils.retrieveCardImage(this, tempLoyaltyCard.id, false)); } else if (importLoyaltyCardUri != null) { try { tempLoyaltyCard = importUriHelper.parse(importLoyaltyCardUri); @@ -670,14 +699,37 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { finish(); return; } - setTitle(R.string.addCardTitle); } else { // New card, use default values tempLoyaltyCard = new LoyaltyCard(-1, "", "", null, new BigDecimal("0"), null, "", null, null, null, 0, Utils.getUnixTime(),100); - setTitle(R.string.addCardTitle); + } } + if(!initDone) { + if (updateLoyaltyCard) { + setTitle(R.string.editCardTitle); + if (!mFrontImageUnsaved && !croppedFrontImage()) { + setCardImage(cardImageFront, Utils.retrieveCardImage(this, tempLoyaltyCard.id, true)); + } + if (!mBackImageUnsaved && !croppedBackImage()) { + setCardImage(cardImageBack, Utils.retrieveCardImage(this, tempLoyaltyCard.id, false)); + } + }else{ + setTitle(R.string.addCardTitle); + } + if(mFrontImageUnsaved && !croppedFrontImage()){ + setCardImage(cardImageFront, Utils.loadTempImage(this, TEMP_UNSAVED_FRONT_IMAGE_NAME)); + } + if(mBackImageUnsaved && !croppedBackImage()){ + setCardImage(cardImageBack, Utils.loadTempImage(this, TEMP_UNSAVED_BACK_IMAGE_NAME)); + } + } + + mCropperFinishedType = 0; + + boolean hadChanges = hasChanged; + storeFieldEdit.setText(tempLoyaltyCard.store); noteFieldEdit.setText(tempLoyaltyCard.note); formatExpiryField(this, expiryField, tempLoyaltyCard.expiry); @@ -767,8 +819,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { // Initialization has finished if (!initDone) { - hasChanged = false; initDone = true; + hasChanged = hadChanges; } generateOrHideBarcode(); @@ -908,26 +960,13 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { confirmExitDialog.show(); } - private File createTempFile(String prefix, String suffix) throws IOException{ - String imageFileName = prefix + new Date().getTime(); - return File.createTempFile( - imageFileName, - suffix, - getExternalFilesDir(Environment.DIRECTORY_PICTURES) - ); - } + private void takePhotoForCard(int type) throws IOException { - Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - - File image = createTempFile("CATIMA_", ".jpg"); - - tempCameraPicturePath = image.getAbsolutePath(); - - Uri photoURI = FileProvider.getUriForFile(LoyaltyCardEditActivity.this, BuildConfig.APPLICATION_ID, image); - i.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); + Uri photoURI = FileProvider.getUriForFile(LoyaltyCardEditActivity.this, BuildConfig.APPLICATION_ID, Utils.createTempFile(this, TEMP_CAMERA_IMAGE_NAME)); mRequestedImage = type; - mPhotoTakerLauncher.launch(i); + + mPhotoTakerLauncher.launch(photoURI); } class EditCardIdAndBarcode implements View.OnClickListener { @@ -1168,12 +1207,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { public void startCropperUri(Uri sourceUri){ Log.d("cropper", "launching cropper with image " + sourceUri.getPath()); File cropOutput; - try { - cropOutput = createTempFile("UCROP_", ".jpg"); - }catch(java.io.IOException e){ - Log.e("image cropping", "failed creating temporarily file"); - return; - } + cropOutput = Utils.createTempFile(this, TEMP_CROP_IMAGE_NAME); Uri destUri = Uri.parse("file://" + cropOutput.getAbsolutePath()); Log.d("cropper", "asking cropper to output to " + destUri.toString()); diff --git a/app/src/main/java/protect/card_locker/Utils.java b/app/src/main/java/protect/card_locker/Utils.java index 0ab199492..eb889b479 100644 --- a/app/src/main/java/protect/card_locker/Utils.java +++ b/app/src/main/java/protect/card_locker/Utils.java @@ -25,6 +25,7 @@ import com.google.zxing.Result; import com.google.zxing.common.HybridBinarizer; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -410,10 +411,37 @@ public class Utils { Configuration config = inputContext.getResources().getConfiguration(); int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK; return (currentNightMode == Configuration.UI_MODE_NIGHT_YES); - }else if (nightModeSetting == AppCompatDelegate.MODE_NIGHT_YES){ - return true; - }else{ - return false; + }else { + return nightModeSetting == AppCompatDelegate.MODE_NIGHT_YES; } } + + public static File createTempFile(Context context, String name){ + return new File(context.getCacheDir() + "/" + name); + } + + public static String saveTempImage(Context context, Bitmap in, String name, Bitmap.CompressFormat format){ + File image = createTempFile(context, name); + try (FileOutputStream out = new FileOutputStream(image)){ + in.compress(format, 100, out); + return image.getAbsolutePath(); + }catch(IOException e){ + Log.d("store temp image", "failed writing temp file for temporary image, name: " + name); + return null; + } + } + + public static Bitmap loadImage(String path){ + try{ + return BitmapFactory.decodeStream(new FileInputStream(path)); + }catch(IOException e){ + Log.d("load image", "failed loading image from " + path); + return null; + } + } + + public static Bitmap loadTempImage(Context context, String name){ + return loadImage(context.getCacheDir() + "/" + name); + } + } diff --git a/app/src/main/res/xml/file_provider_paths.xml b/app/src/main/res/xml/file_provider_paths.xml index 8bacf6557..3cd946b00 100644 --- a/app/src/main/res/xml/file_provider_paths.xml +++ b/app/src/main/res/xml/file_provider_paths.xml @@ -1,3 +1,3 @@ - + \ No newline at end of file From dd981d72d7bb6eebe367287cfb9e049d143e56f3 Mon Sep 17 00:00:00 2001 From: Katharine Date: Sat, 6 Nov 2021 12:12:03 +0800 Subject: [PATCH 10/10] do not attempt image load if removed, typo fixes, do not cast literals, offer full image on cropper start --- .../card_locker/LoyaltyCardEditActivity.java | 75 +++++++++++++++---- 1 file changed, 62 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java index 6c382f0df..8ed8c37ea 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java @@ -41,6 +41,7 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; import androidx.core.content.FileProvider; +import androidx.exifinterface.media.ExifInterface; import androidx.fragment.app.DialogFragment; import com.google.android.material.chip.Chip; @@ -84,6 +85,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { private final String STATE_BACK_IMAGE_UNSAVED = "backImageUnsaved"; private final String STATE_UPDATE_LOYALTY_CARD = "updateLoyaltyCard"; private final String STATE_HAS_CHANGED = "hasChange"; + private final String STATE_FRONT_IMAGE_REMOVED = "frontImageRemoved"; + private final String STATE_BACK_IMAGE_REMOVED = "backImageRemoved"; private final String TEMP_CAMERA_IMAGE_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_camera_image.jpg"; private final String TEMP_CROP_IMAGE_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_crop_image.png"; @@ -165,6 +168,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { boolean mFrontImageUnsaved = false; boolean mBackImageUnsaved = false; + boolean mFrontImageRemoved = false; + boolean mBackImageRemoved = false; + final private TaskHandler mTasks = new TaskHandler(); private static LoyaltyCard updateTempState(LoyaltyCard loyaltyCard, LoyaltyCardField fieldName, Object value) { @@ -228,7 +234,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { savedInstanceState.putInt(STATE_BACK_IMAGE_UNSAVED, 0); } savedInstanceState.putInt(STATE_UPDATE_LOYALTY_CARD, updateLoyaltyCard ? 1 : 0); - savedInstanceState.putInt(STATE_HAS_CHANGED, hasChanged? 1:0); + savedInstanceState.putInt(STATE_HAS_CHANGED, hasChanged ? 1 : 0); + savedInstanceState.putInt(STATE_FRONT_IMAGE_REMOVED, mFrontImageRemoved ? 1 : 0); + savedInstanceState.putInt(STATE_BACK_IMAGE_REMOVED, mBackImageRemoved ? 1 : 0); } @Override @@ -242,6 +250,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { mBackImageUnsaved = savedInstanceState.getInt(STATE_BACK_IMAGE_UNSAVED) == 1; updateLoyaltyCard = savedInstanceState.getInt(STATE_UPDATE_LOYALTY_CARD) == 1; hasChanged = savedInstanceState.getInt(STATE_HAS_CHANGED) == 1; + mFrontImageRemoved = savedInstanceState.getInt(STATE_FRONT_IMAGE_REMOVED) == 1; + mBackImageRemoved = savedInstanceState.getInt(STATE_BACK_IMAGE_REMOVED) == 1; } @Override @@ -622,26 +632,32 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { }else if(result.getResultCode() == UCrop.RESULT_ERROR){ Throwable e = UCrop.getError(intent); if (e == null){ - throw new RuntimeException("ucrop returned error state but not and error!"); + throw new RuntimeException("ucrop returned error state but not an error!"); } Log.e("cropper error", e.toString()); } }); mCropperOptions = new UCrop.Options(); - setCropperOptions(); setCropperTheme(); } - private void setCropperOptions(){ + // ucrop 2.2.6 initial aspect ratio is glitched when 0x0 is used as the initial ratio option + // https://github.com/Yalantis/uCrop/blob/281c8e6438d81f464d836fc6b500517144af264a/ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java#L264 + // so source width height has to be provided for now, depending on whether future versions of ucrop will support 0x0 as the default option + private void setCropperOptions(float sourceWidth, float sourceHeight){ mCropperOptions.setCompressionFormat(TEMP_CROP_IMAGE_FORMAT); mCropperOptions.setFreeStyleCropEnabled(true); mCropperOptions.setHideBottomControls(false); - // needed when bottom controls are hidden - //mCropperOptions.setAllowedGestures(UCropActivity.ALL, UCropActivity.ALL, UCropActivity.ALL); - mCropperOptions.setAspectRatioOptions(0, + // default aspect ratio workaround + int selectedByDefault = 1; + if (sourceWidth == 0f && sourceHeight == 0f){ + selectedByDefault = 0; + } + mCropperOptions.setAspectRatioOptions(selectedByDefault, new AspectRatio(null, 1, 1), - new AspectRatio(getResources().getString(R.string.card),(float)85.6,(float)53.98 ) + new AspectRatio(getResources().getString(R.string.ucrop_label_original).toUpperCase(), sourceWidth, sourceHeight), + new AspectRatio(getResources().getString(R.string.card),85.6f,53.98f ) ); } @@ -709,10 +725,10 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { if(!initDone) { if (updateLoyaltyCard) { setTitle(R.string.editCardTitle); - if (!mFrontImageUnsaved && !croppedFrontImage()) { + if (!mFrontImageUnsaved && !croppedFrontImage() && !mFrontImageRemoved) { setCardImage(cardImageFront, Utils.retrieveCardImage(this, tempLoyaltyCard.id, true)); } - if (!mBackImageUnsaved && !croppedBackImage()) { + if (!mBackImageUnsaved && !croppedBackImage() && !mBackImageRemoved) { setCardImage(cardImageBack, Utils.retrieveCardImage(this, tempLoyaltyCard.id, false)); } }else{ @@ -989,6 +1005,13 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { if (targetView.getTag() != null) { cardOptions.put(getString(R.string.removeImage), () -> { setCardImage(targetView, null); + if (targetView == cardImageFront){ + mFrontImageRemoved = true; + mFrontImageUnsaved = false; + }else{ + mBackImageRemoved = true; + mBackImageUnsaved = false; + } return null; }); } @@ -1206,8 +1229,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { } public void startCropperUri(Uri sourceUri){ Log.d("cropper", "launching cropper with image " + sourceUri.getPath()); - File cropOutput; - cropOutput = Utils.createTempFile(this, TEMP_CROP_IMAGE_NAME); + File cropOutput = Utils.createTempFile(this, TEMP_CROP_IMAGE_NAME); Uri destUri = Uri.parse("file://" + cropOutput.getAbsolutePath()); Log.d("cropper", "asking cropper to output to " + destUri.toString()); @@ -1217,11 +1239,38 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { mCropperOptions.setToolbarTitle(getResources().getString(R.string.setBackImage)); } + // sniff the input image for width and height to work around a ucrop bug + Bitmap image = null; + try { + image = BitmapFactory.decodeStream(getContentResolver().openInputStream(sourceUri)); + }catch(FileNotFoundException e){ + e.printStackTrace(); + Log.d("cropper", "failed opening bitmap for initial width and height for ucrop " + sourceUri.toString()); + } + if (image == null){ + Log.d("cropper", "failed loading bitmap for initial width and height for ucrop " + sourceUri.toString()); + setCropperOptions(0f, 0f); + }else{ + try { + Bitmap imageRotated = Utils.rotateBitmap(image, new ExifInterface(getContentResolver().openInputStream(sourceUri))); + setCropperOptions(imageRotated.getWidth(), imageRotated.getHeight()); + }catch(FileNotFoundException e){ + e.printStackTrace(); + Log.d("cropper", "failed opening image for exif reading before setting initial width and height for ucrop"); + setCropperOptions(image.getWidth(), image.getHeight()); + }catch(IOException e){ + e.printStackTrace(); + Log.d("cropper", "exif reading failed before setting initial width and height for ucrop"); + setCropperOptions(image.getWidth(), image.getHeight()); + } + } + mCropperLauncher.launch( UCrop.of( sourceUri, destUri - ).withOptions(mCropperOptions).getIntent(this) + ).withOptions(mCropperOptions) + .getIntent(this) ); return; }