Skip to content

Commit

Permalink
Merge pull request #1795 from CatimaLoyalty/pdfImport
Browse files Browse the repository at this point in the history
PDF import + multiple barcode support
  • Loading branch information
TheLastProject authored Mar 26, 2024
2 parents 1e0dc4f + 8519e12 commit f848710
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 111 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## Unreleased - 134

- Support for scanning PDF files for barcodes
- Support for image files with multiple barcodes

## v2.28.0 - 133 (2024-03-08)

- Target Android 14
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
<data android:mimeType="application/pdf" />
</intent-filter>
</activity>
<activity
Expand Down
11 changes: 7 additions & 4 deletions app/src/main/java/protect/card_locker/BarcodeValues.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
public class BarcodeValues {
private final String mFormat;
private final String mContent;
private String mNote;

public BarcodeValues(String format, String content) {
mFormat = format;
mContent = content;
}

public void setNote(String note) {
mNote = note;
}

public String format() {
return mFormat;
}
Expand All @@ -17,7 +22,5 @@ public String content() {
return mContent;
}

public boolean isEmpty() {
return mFormat == null && mContent == null;
}
}
public String note() { return mNote; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package protect.card_locker;

public interface BarcodeValuesListDisambiguatorCallback {
void onUserChoseBarcode(BarcodeValues barcodeValues);
void onUserDismissedSelector();
}
19 changes: 15 additions & 4 deletions app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -646,11 +646,22 @@ public void onTabReselected(TabLayout.Tab tab) {
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 = "";
List<BarcodeValues> barcodeValuesList = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, getApplicationContext());

Utils.makeUserChooseBarcodeFromList(this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
@Override
public void onUserChoseBarcode(BarcodeValues barcodeValues) {
cardId = barcodeValues.content();
barcodeType = barcodeValues.format();
barcodeId = "";
}

@Override
public void onUserDismissedSelector() {

}
});
}
});

Expand Down
83 changes: 38 additions & 45 deletions app/src/main/java/protect/card_locker/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
import android.content.SharedPreferences;
import android.database.CursorIndexOutOfBoundsException;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
Expand All @@ -33,7 +31,6 @@
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -195,10 +192,12 @@ public void onDestroyActionMode(ActionMode inputMode) {

@Override
protected void onCreate(Bundle inputSavedInstanceState) {
extractIntentFields(getIntent());
SplashScreen.installSplashScreen(this);
super.onCreate(inputSavedInstanceState);

// We should extract the share intent after we called the super.onCreate as it may need to spawn a dialog window and the app needs to be initialized to not crash
extractIntentFields(getIntent());

binding = MainActivityBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
Expand Down Expand Up @@ -288,11 +287,11 @@ public void onClick(DialogInterface dialog, int whichButton) {
}

Intent intent = result.getData();
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, this);
List<BarcodeValues> barcodeValuesList = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, this);

Bundle inputBundle = intent.getExtras();
String group = inputBundle != null ? inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
processBarcodeValues(barcodeValues, group);
processBarcodeValuesList(barcodeValuesList, group, false);
});

mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
Expand Down Expand Up @@ -447,63 +446,57 @@ private void updateLoyaltyCardList(boolean updateCount) {
}
}

private void processBarcodeValues(BarcodeValues barcodeValues, String group) {
if (barcodeValues.isEmpty()) {
private void processBarcodeValuesList(List<BarcodeValues> barcodeValuesList, String group, boolean closeAppOnNoBarcode) {
if (barcodeValuesList.isEmpty()) {
throw new IllegalArgumentException("barcodesValues may not be empty");
}

Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle newBundle = new Bundle();
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
if (group != null) {
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
}
newIntent.putExtras(newBundle);
startActivity(newIntent);
Utils.makeUserChooseBarcodeFromList(MainActivity.this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
@Override
public void onUserChoseBarcode(BarcodeValues barcodeValues) {
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle newBundle = new Bundle();
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
if (group != null) {
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
}
newIntent.putExtras(newBundle);
startActivity(newIntent);
}

@Override
public void onUserDismissedSelector() {
if (closeAppOnNoBarcode) {
finish();
}
}
});
}

private void onSharedIntent(Intent intent) {
String receivedAction = intent.getAction();
String receivedType = intent.getType();

// Check if an image was shared to us
// Check if an image or file was shared to us
if (Intent.ACTION_SEND.equals(receivedAction)) {
if (!receivedType.startsWith("image/")) {
Log.e(TAG, "Wrong mime-type");
return;
}

BarcodeValues barcodeValues;
Bitmap bitmap;
List<BarcodeValues> barcodeValuesList;

Uri data = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (data == null) {
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
finish();
return;
}

try {
bitmap = Utils.retrieveImageFromUri(this, data);
} catch (IOException e) {
Log.e(TAG, "Error getting data from image file");
e.printStackTrace();
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
finish();
if (receivedType.startsWith("image/")) {
barcodeValuesList = Utils.retrieveBarcodesFromImage(this, intent.getParcelableExtra(Intent.EXTRA_STREAM));
} else if (receivedType.equals("application/pdf")) {
barcodeValuesList = Utils.retrieveBarcodesFromPdf(this, intent.getParcelableExtra(Intent.EXTRA_STREAM));
} else {
Log.e(TAG, "Wrong mime-type");
return;
}

barcodeValues = Utils.getBarcodeFromBitmap(bitmap);

if (barcodeValues.isEmpty()) {
Log.i(TAG, "No barcode found in image file");
Toast.makeText(this, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
if (barcodeValuesList.isEmpty()) {
finish();
return;
}

processBarcodeValues(barcodeValues, null);
processBarcodeValuesList(barcodeValuesList, null, true);
}
}

Expand Down
49 changes: 37 additions & 12 deletions app/src/main/java/protect/card_locker/ScanActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
private static final int COMPAT_SCALE_FACTOR_DIP = 320;

private static final int PERMISSION_SCAN_ADD_FROM_IMAGE = 100;
private static final int PERMISSION_SCAN_ADD_FROM_PDF = 101;

private CaptureManager capture;
private DecoratedBarcodeView barcodeScannerView;
Expand All @@ -73,6 +74,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
private ActivityResultLauncher<Intent> manualAddLauncher;
// can't use the pre-made contract because that launches the file manager for image type instead of gallery
private ActivityResultLauncher<Intent> photoPickerLauncher;
private ActivityResultLauncher<Intent> pdfPickerLauncher;

static final String STATE_SCANNER_ACTIVE = "scannerActive";
private boolean mScannerActive = true;
Expand All @@ -99,6 +101,7 @@ protected void onCreate(Bundle savedInstanceState) {

manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
photoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_IMAGE_FILE, result.getResultCode(), result.getData()));
pdfPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_PDF_FILE, result.getResultCode(), result.getData()));
customBarcodeScannerBinding.fabOtherOptions.setOnClickListener(view -> {
setScannerActive(false);

Expand All @@ -108,7 +111,8 @@ protected void onCreate(Bundle savedInstanceState) {
new CharSequence[]{
getString(R.string.addWithoutBarcode),
getString(R.string.addManually),
getString(R.string.addFromImage)
getString(R.string.addFromImage),
getString(R.string.addFromPdfFile)
},
(dialogInterface, i) -> {
switch (i) {
Expand All @@ -121,6 +125,9 @@ protected void onCreate(Bundle savedInstanceState) {
case 2:
addFromImage();
break;
case 3:
addFromPdfFile();
break;
default:
throw new IllegalArgumentException("Unknown 'Add a card in a different way' dialog option");
}
Expand Down Expand Up @@ -268,14 +275,24 @@ private void returnResult(String barcodeContents, String barcodeFormat) {
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);

BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
List<BarcodeValues> barcodeValuesList = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);

if (barcodeValues.isEmpty()) {
if (barcodeValuesList.isEmpty()) {
setScannerActive(true);
return;
}

returnResult(barcodeValues.content(), barcodeValues.format());
Utils.makeUserChooseBarcodeFromList(this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
@Override
public void onUserChoseBarcode(BarcodeValues barcodeValues) {
returnResult(barcodeValues.content(), barcodeValues.format());
}

@Override
public void onUserDismissedSelector() {
setScannerActive(true);
}
});
}

private void addWithoutBarcode() {
Expand Down Expand Up @@ -364,19 +381,23 @@ public void addFromImage() {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE);
}

private void addFromImageAfterPermission() {
public void addFromPdfFile() {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PDF);
}

private void addFromImageOrFileAfterPermission(String mimeType, ActivityResultLauncher<Intent> launcher, int chooserText, int errorMessage) {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("image/*");
photoPickerIntent.setType(mimeType);
Intent contentIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentIntent.setType("image/*");
contentIntent.setType(mimeType);

Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(R.string.addFromImage));
Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(chooserText));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { contentIntent });
try {
photoPickerLauncher.launch(chooserIntent);
launcher.launch(chooserIntent);
} catch (ActivityNotFoundException e) {
setScannerActive(true);
Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
Toast.makeText(getApplicationContext(), errorMessage, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
Expand Down Expand Up @@ -424,9 +445,13 @@ public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[]

if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
showCameraPermissionMissingText(!granted);
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE || requestCode == PERMISSION_SCAN_ADD_FROM_PDF) {
if (granted) {
addFromImageAfterPermission();
if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
addFromImageOrFileAfterPermission("image/*", photoPickerLauncher, R.string.addFromImage, R.string.failedLaunchingPhotoPicker);
} else {
addFromImageOrFileAfterPermission("application/pdf", pdfPickerLauncher, R.string.addFromPdfFile, R.string.failedLaunchingFileManager);
}
} else {
setScannerActive(true);
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG).show();
Expand Down
Loading

0 comments on commit f848710

Please sign in to comment.