diff --git a/CHANGELOG.md b/CHANGELOG.md index a83b45718..b4b817d78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Changes: - Support importing [Fidme](https://play.google.com/store/apps/details?id=fr.snapp.fidme) exports +- Allow importing a card from a picture stored in the user's Android gallery - Fix multiline note cutoff - Change "Thank you" text on privacy dialog to "Accept" because Huawei is overly pedantic diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java index 7bf92e5c4..095b859d4 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java @@ -6,6 +6,7 @@ import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Color; import android.net.Uri; import android.os.Build; @@ -76,6 +77,8 @@ public class LoyaltyCardEditActivity extends AppCompatActivity View barcodeImageLayout; View barcodeCaptureLayout; + Button captureButton; + Button importImageButton; Button enterButton; int loyaltyCardId; @@ -634,6 +637,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity } } + class ColorSelectListener implements View.OnClickListener { final int defaultColor; @@ -840,7 +844,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity { super.onActivityResult(requestCode, resultCode, intent); - BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent); + BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this); barcodeType = barcodeValues.format(); cardId = barcodeValues.content(); diff --git a/app/src/main/java/protect/card_locker/MainActivity.java b/app/src/main/java/protect/card_locker/MainActivity.java index 1440a3fb8..2f5535903 100644 --- a/app/src/main/java/protect/card_locker/MainActivity.java +++ b/app/src/main/java/protect/card_locker/MainActivity.java @@ -188,7 +188,7 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O return; } - BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent); + BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this); if(!barcodeValues.isEmpty()) { Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class); diff --git a/app/src/main/java/protect/card_locker/ScanActivity.java b/app/src/main/java/protect/card_locker/ScanActivity.java index d1e9fd6ba..fd3ebbeba 100644 --- a/app/src/main/java/protect/card_locker/ScanActivity.java +++ b/app/src/main/java/protect/card_locker/ScanActivity.java @@ -132,7 +132,7 @@ public class ScanActivity extends AppCompatActivity { { super.onActivityResult(requestCode, resultCode, intent); - BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent); + BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this); if (!barcodeValues.isEmpty()) { Intent manualResult = new Intent(); @@ -154,4 +154,10 @@ public class ScanActivity extends AppCompatActivity { } startActivityForResult(i, Utils.SELECT_BARCODE_REQUEST); } + + public void addFromImage(View view) { + Intent photoPickerIntent = new Intent(Intent.ACTION_PICK); + photoPickerIntent.setType("image/*"); + startActivityForResult(photoPickerIntent, Utils.BARCODE_IMPORT_FROM_IMAGE_FILE); + } } diff --git a/app/src/main/java/protect/card_locker/Utils.java b/app/src/main/java/protect/card_locker/Utils.java index 287816965..1c69d55ae 100644 --- a/app/src/main/java/protect/card_locker/Utils.java +++ b/app/src/main/java/protect/card_locker/Utils.java @@ -3,21 +3,31 @@ package protect.card_locker; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; import android.graphics.Color; +import android.provider.MediaStore; import android.util.Log; +import android.widget.Toast; +import java.io.IOException; import java.math.BigDecimal; import java.text.NumberFormat; -import java.text.ParseException; import java.util.Calendar; import java.util.Currency; import java.util.Date; import java.util.GregorianCalendar; -import java.util.List; -import java.util.Locale; import androidx.core.graphics.ColorUtils; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.LuminanceSource; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.NotFoundException; +import com.google.zxing.RGBLuminanceSource; +import com.google.zxing.Result; +import com.google.zxing.common.HybridBinarizer; + public class Utils { private static final String TAG = "Catima"; @@ -25,6 +35,7 @@ public class Utils { public static final int MAIN_REQUEST = 1; public static final int SELECT_BARCODE_REQUEST = 2; public static final int BARCODE_SCAN = 3; + public static final int BARCODE_IMPORT_FROM_IMAGE_FILE = 4; static final double LUMINANCE_MIDPOINT = 0.5; @@ -48,28 +59,75 @@ public class Utils { return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT; } - static public BarcodeValues parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent) { - String contents = null; - String format = null; + static public BarcodeValues parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) { + String contents; + String format; - if (resultCode == Activity.RESULT_OK) - { + if (resultCode != Activity.RESULT_OK) { + return new BarcodeValues(null, null); + } + + if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) { + Log.i(TAG, "Received image file with possible barcode"); + + Bitmap bitmap; + try { + bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), intent.getData()); + } catch (IOException e) { + Log.e(TAG, "Error getting data from image file"); + e.printStackTrace(); + Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show(); + return new BarcodeValues(null, null); + } + + BarcodeValues barcodeFromBitmap = getBarcodeFromBitmap(bitmap); + + if (barcodeFromBitmap.isEmpty()) { + Log.i(TAG, "No barcode found in image file"); + Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show(); + } + + Log.i(TAG, "Read barcode id: " + barcodeFromBitmap.content()); + Log.i(TAG, "Read format: " + barcodeFromBitmap.format()); + + return barcodeFromBitmap; + } + + if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) { if (requestCode == Utils.BARCODE_SCAN) { Log.i(TAG, "Received barcode information from camera"); } else if (requestCode == Utils.SELECT_BARCODE_REQUEST) { Log.i(TAG, "Received barcode information from typing it"); - } else { - return new BarcodeValues(null, null); } contents = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_CONTENTS); format = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_FORMAT); + + Log.i(TAG, "Read barcode id: " + contents); + Log.i(TAG, "Read format: " + format); + + return new BarcodeValues(format, contents); } - Log.i(TAG, "Read barcode id: " + contents); - Log.i(TAG, "Read format: " + format); + throw new UnsupportedOperationException("Unknown request code for parseSetBarcodeActivityResult"); + } - return new BarcodeValues(format, contents); + static public BarcodeValues getBarcodeFromBitmap(Bitmap bitmap) { + // In order to decode it, the Bitmap must first be converted into a pixel array... + int[] intArray = new int[bitmap.getWidth() * bitmap.getHeight()]; + bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); + + // ...and then turned into a binary bitmap from its luminance + LuminanceSource source = new RGBLuminanceSource(bitmap.getWidth(), bitmap.getHeight(), intArray); + BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); + + try { + Result barcodeResult = new MultiFormatReader().decode(binaryBitmap); + + return new BarcodeValues(barcodeResult.getBarcodeFormat().name(), barcodeResult.getText()); + } catch (NotFoundException e) { + return new BarcodeValues(null, null); + } } static public Boolean hasExpired(Date expiryDate) { diff --git a/app/src/main/res/layout/custom_barcode_scanner.xml b/app/src/main/res/layout/custom_barcode_scanner.xml index 038cb949d..e6ccacfd4 100644 --- a/app/src/main/res/layout/custom_barcode_scanner.xml +++ b/app/src/main/res/layout/custom_barcode_scanner.xml @@ -19,11 +19,25 @@ app:zxing_viewfinder_laser="@color/zxing_custom_viewfinder_laser" app:zxing_viewfinder_mask="@color/zxing_custom_viewfinder_mask"/> -