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

PDF import + multiple barcode support #1795

Merged
merged 5 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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