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"/>
-
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 94d916534..84a57d55f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -135,6 +135,7 @@
Leave without saving
Are you sure you want to leave this screen? Changed made will not be saved.
Manually enter card ID
+ Select image from gallery
Groups: %s
Expires: %s
Expired: %s
@@ -150,6 +151,9 @@
Move the barcode to the top of the screen
Center the barcode on the screen
+ No barcode was found
+ Error reading image
+
Balance
Currency
Points