Adding and viewing front/back images (#215)

* Adding and viewing front/back images

* Fix import and export

* Fix unit tests

* Smaller preview pictures but clickable to make big

* Implement removing image

* Add card photo direct from camera

* Read Exif rotation info from picture taken

* Fix bad copy-paste

* Refactor to use local file system

* Delete card images when deleting card

* Prepare for image-based unit tests in ViewActivityTest
This commit is contained in:
Sylvia van Os
2021-06-28 20:40:52 +02:00
committed by GitHub
parent b913fad847
commit 8d48da431e
22 changed files with 921 additions and 188 deletions

View File

@@ -6,9 +6,11 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import com.google.zxing.BarcodeFormat;
import java.io.FileNotFoundException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Currency;
@@ -52,9 +54,13 @@ public class DBHelper extends SQLiteOpenHelper
public static final String groupID = "groupId";
}
private Context mContext;
public DBHelper(Context context)
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
}
@Override
@@ -78,7 +84,7 @@ public class DBHelper extends SQLiteOpenHelper
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
LoyaltyCardDbIds.BARCODE_ID + " TEXT," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0' )");
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0')");
// create associative table for cards in groups
db.execSQL("create table " + LoyaltyCardDbIdsGroups.TABLE + "(" +
@@ -426,7 +432,7 @@ public class DBHelper extends SQLiteOpenHelper
}
}
public boolean deleteLoyaltyCard (final int id)
public boolean deleteLoyaltyCard(final int id)
{
SQLiteDatabase db = getWritableDatabase();
// Delete card
@@ -439,6 +445,14 @@ public class DBHelper extends SQLiteOpenHelper
LoyaltyCardDbIdsGroups.cardID + " = ? ",
new String[]{String.format("%d", id)});
// Also wipe card images associated with this card
try {
Utils.saveCardImage(mContext, null, id, true);
Utils.saveCardImage(mContext, null, id, false);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return (rowsDeleted == 1);
}

View File

@@ -2,6 +2,7 @@ package protect.card_locker;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.util.Log;
@@ -56,26 +57,26 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
this.listener = listener;
}
private boolean performImport(InputStream stream, DBHelper db)
private boolean performImport(Context context, InputStream stream, DBHelper db)
{
boolean result = false;
result = MultiFormatImporter.importData(db, stream, format);
result = MultiFormatImporter.importData(context, db, stream, format);
Log.i(TAG, "Import result: " + result);
return result;
}
private boolean performExport(OutputStream stream, DBHelper db)
private boolean performExport(Context context, OutputStream stream, DBHelper db)
{
boolean result = false;
try
{
OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8"));
result = MultiFormatExporter.exportData(db, writer, format);
result = MultiFormatExporter.exportData(context, db, writer, format);
writer.close();
}
catch (IOException e)
@@ -112,11 +113,11 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
if(doImport)
{
result = performImport(inputStream, db);
result = performImport(activity.getApplicationContext(), inputStream, db);
}
else
{
result = performExport(outputStream, db);
result = performExport(activity.getApplicationContext(), outputStream, db);
}
return result;

View File

@@ -2,6 +2,7 @@ package protect.card_locker;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import com.google.zxing.BarcodeFormat;
@@ -21,7 +22,6 @@ public class ImportURIHelper {
private static final String CARD_ID = DBHelper.LoyaltyCardDbIds.CARD_ID;
private static final String BARCODE_ID = DBHelper.LoyaltyCardDbIds.BARCODE_ID;
private static final String BARCODE_TYPE = DBHelper.LoyaltyCardDbIds.BARCODE_TYPE;
private static final String HEADER_COLOR = DBHelper.LoyaltyCardDbIds.HEADER_COLOR;
private final Context context;
@@ -125,7 +125,8 @@ public class ImportURIHelper {
if(loyaltyCard.headerColor != null) {
uriBuilder.appendQueryParameter(HEADER_COLOR, loyaltyCard.headerColor.toString());
}
//StarStatus will not be exported
// Star status will not be exported
// Front and back pictures are often too big to fit into a message in base64 nicely, not sharing either...
return uriBuilder.build();
}

View File

@@ -1,6 +1,10 @@
package protect.card_locker;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.sql.Blob;
import com.google.zxing.BarcodeFormat;

View File

@@ -1,16 +1,23 @@
package protect.card_locker;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.LocaleList;
import android.provider.MediaStore;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
@@ -38,6 +45,9 @@ import com.google.zxing.BarcodeFormat;
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.math.BigDecimal;
import java.text.DateFormat;
@@ -47,21 +57,33 @@ import java.util.Collections;
import java.util.Currency;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.FileProvider;
import androidx.fragment.app.DialogFragment;
public class LoyaltyCardEditActivity extends AppCompatActivity
{
private static final String TAG = "Catima";
private final String STATE_TAB_INDEX = "savedTab";
private static final int ID_IMAGE_FRONT = 0;
private static final int ID_IMAGE_BACK = 1;
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_FRONT = 100;
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_BACK = 101;
TabLayout tabs;
ImageView thumbnail;
@@ -77,6 +99,13 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
ImageView barcodeImage;
View barcodeImageLayout;
View barcodeCaptureLayout;
View cardImageFrontHolder;
View cardImageBackHolder;
ImageView cardImageFront;
ImageView cardImageBack;
Bitmap frontImageBitmap;
Bitmap backImageBitmap;
Button enterButton;
@@ -102,6 +131,8 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
HashMap<String, Currency> currencies = new HashMap<>();
String tempCameraPicturePath;
private void extractIntentFields(Intent intent)
{
final Bundle b = intent.getExtras();
@@ -169,6 +200,12 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
barcodeImage = findViewById(R.id.barcode);
barcodeImageLayout = findViewById(R.id.barcodeLayout);
barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
cardImageFrontHolder = findViewById(R.id.frontImageHolder);
cardImageBackHolder = findViewById(R.id.backImageHolder);
cardImageFrontHolder.setId(ID_IMAGE_FRONT);
cardImageBackHolder.setId(ID_IMAGE_BACK);
cardImageFront = findViewById(R.id.frontImage);
cardImageBack = findViewById(R.id.backImage);
enterButton = findViewById(R.id.enterButton);
@@ -489,6 +526,8 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
barcodeIdField.setTag(null);
barcodeIdField.setText("");
barcodeTypeField.setText("");
cardImageFront.setTag(null);
cardImageBack.setTag(null);
}
@SuppressLint("DefaultLocale")
@@ -563,6 +602,16 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
}
}
if(cardImageFront.getTag() == null)
{
setCardImage(cardImageFront, Utils.retrieveCardImage(this, loyaltyCard.id, true));
}
if(cardImageBack.getTag() == null)
{
setCardImage(cardImageBack, Utils.retrieveCardImage(this, loyaltyCard.id, false));
}
setTitle(R.string.editCardTitle);
}
else if(importLoyaltyCardUri != null)
@@ -601,6 +650,8 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
balanceCurrencyField.setTag(null);
formatBalanceCurrencyField(null);
hideBarcode();
setCardImage(cardImageFront, null);
setCardImage(cardImageBack, null);
}
if(groupsChips.getChildCount() == 0)
@@ -679,12 +730,25 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
enterButton.setOnClickListener(new EditCardIdAndBarcode());
barcodeImage.setOnClickListener(new EditCardIdAndBarcode());
cardImageFrontHolder.setOnClickListener(new ChooseCardImage());
cardImageBackHolder.setOnClickListener(new ChooseCardImage());
FloatingActionButton saveButton = findViewById(R.id.fabSave);
saveButton.setOnClickListener(v -> doSave());
generateIcon(storeFieldEdit.getText().toString());
}
private void setCardImage(ImageView imageView, Bitmap bitmap) {
imageView.setTag(bitmap);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.ic_camera_white);
}
}
private void formatExpiryField(Date expiry) {
if (expiry == null) {
expiryField.setText(getString(R.string.never));
@@ -706,6 +770,23 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
askBeforeQuitIfChanged();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
try {
if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_FRONT) {
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_FRONT);
} else {
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_BACK);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void askBarcodeChange(Runnable callback) {
if (tempStoredOldBarcodeValue.equals(cardIdFieldView.getText().toString())) {
// They are the same, don't ask
@@ -782,12 +863,29 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
confirmExitDialog.show();
}
private void takePhotoForCard(int type) throws IOException {
Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
String imageFileName = "CATIMA_" + new Date().getTime();
File image = File.createTempFile(
imageFileName,
".jpg",
getExternalFilesDir(Environment.DIRECTORY_PICTURES)
);
tempCameraPicturePath = image.getAbsolutePath();
Uri photoURI = FileProvider.getUriForFile(LoyaltyCardEditActivity.this, BuildConfig.APPLICATION_ID, image);
i.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(i, type);
}
class EditCardIdAndBarcode implements View.OnClickListener
{
@Override
public void onClick(View v)
{
Intent i = new Intent(getApplicationContext(), ScanActivity.class);
final Bundle b = new Bundle();
b.putString("cardId", cardIdFieldView.getText().toString());
@@ -796,6 +894,56 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
}
}
class ChooseCardImage implements View.OnClickListener
{
@Override
public void onClick(View v) throws NoSuchElementException
{
ImageView targetView = v.getId() == ID_IMAGE_FRONT ? cardImageFront : cardImageBack;
LinkedHashMap<String, Callable<Void>> cardOptions = new LinkedHashMap<>();
if (targetView.getTag() != null) {
cardOptions.put(getString(R.string.removeImage), () -> {
setCardImage(targetView, null);
return null;
});
}
cardOptions.put(getString(R.string.takePhoto), () -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.CAMERA}, v.getId() == ID_IMAGE_FRONT ? PERMISSION_REQUEST_CAMERA_IMAGE_FRONT : PERMISSION_REQUEST_CAMERA_IMAGE_BACK);
} else {
takePhotoForCard(v.getId() == ID_IMAGE_FRONT ? Utils.CARD_IMAGE_FROM_CAMERA_FRONT : Utils.CARD_IMAGE_FROM_CAMERA_BACK);
}
return null;
});
cardOptions.put(getString(R.string.chooseImageFromGallery), () -> {
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);
return null;
});
new AlertDialog.Builder(LoyaltyCardEditActivity.this)
.setTitle(v.getId() == ID_IMAGE_FRONT ? getString(R.string.setFrontImage) : getString(R.string.setBackImage))
.setItems(cardOptions.keySet().toArray(new CharSequence[cardOptions.size()]), (dialog, which) -> {
Iterator<Callable<Void>> callables = cardOptions.values().iterator();
Callable<Void> callable = callables.next();
for (int i = 0; i < which; i++) {
callable = callables.next();
}
try {
callable.call();
} catch (Exception e) {
e.printStackTrace();
}
})
.show();
}
}
class ColorSelectListener implements View.OnClickListener
{
@@ -878,8 +1026,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
}
}
private void doSave()
{
private void doSave() {
if (tempStoredOldBarcodeValue != null) {
askBarcodeChange(this::doSave);
return;
@@ -922,11 +1069,23 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
if(updateLoyaltyCard)
{ //update of "starStatus" not necessary, since it cannot be changed in this activity (only in ViewActivity)
db.updateLoyaltyCard(loyaltyCardId, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headingColorValue);
try {
Utils.saveCardImage(this, (Bitmap) cardImageFront.getTag(), loyaltyCardId, true);
Utils.saveCardImage(this, (Bitmap) cardImageBack.getTag(), loyaltyCardId, false);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
}
else
{
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headingColorValue, 0);
try {
Utils.saveCardImage(this, (Bitmap) cardImageFront.getTag(), loyaltyCardId, true);
Utils.saveCardImage(this, (Bitmap) cardImageBack.getTag(), loyaltyCardId, false);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
db.setLoyaltyCardGroups(loyaltyCardId, selectedGroups);
@@ -1002,12 +1161,57 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
{
super.onActivityResult(requestCode, resultCode, intent);
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
if (requestCode == Utils.CARD_IMAGE_FROM_CAMERA_FRONT || requestCode == Utils.CARD_IMAGE_FROM_CAMERA_BACK) {
if (resultCode == RESULT_OK) {
Bitmap bitmap = BitmapFactory.decodeFile(tempCameraPicturePath);
if (resultCode == RESULT_OK) {
cardId = barcodeValues.content();
barcodeType = barcodeValues.format();
barcodeId = "";
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 if (requestCode == Utils.CARD_IMAGE_FROM_FILE_FRONT || requestCode == Utils.CARD_IMAGE_FROM_FILE_BACK) {
if (resultCode == RESULT_OK) {
Bitmap bitmap = null;
try {
bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), intent.getData());
} catch (IOException e) {
Log.e(TAG, "Error getting data from image file");
e.printStackTrace();
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
}
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 {
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
if (resultCode == RESULT_OK) {
cardId = barcodeValues.content();
barcodeType = barcodeValues.format();
barcodeId = "";
}
}
onResume();
@@ -1081,19 +1285,29 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
View cardPart = findViewById(R.id.cardPart);
View barcodePart = findViewById(R.id.barcodePart);
View picturesPart = findViewById(R.id.picturesPart);
if (getString(R.string.card).equals(part)) {
cardPart.setVisibility(View.VISIBLE);
barcodePart.setVisibility(View.GONE);
picturesPart.setVisibility(View.GONE);
// Explicitly hide barcode (fixes blurriness on redraw)
hideBarcode();
} else if (getString(R.string.barcode).equals(part)) {
cardPart.setVisibility(View.GONE);
barcodePart.setVisibility(View.VISIBLE);
picturesPart.setVisibility(View.GONE);
// Redraw barcode due to size change (Visibility.GONE sets it to 0)
generateOrHideBarcode();
} else if (getString(R.string.photos).equals(part)) {
cardPart.setVisibility(View.GONE);
barcodePart.setVisibility(View.GONE);
picturesPart.setVisibility(View.VISIBLE);
// Explicitly hide barcode (fixes blurriness on redraw)
hideBarcode();
} else {
throw new UnsupportedOperationException();
}

View File

@@ -3,6 +3,7 @@ package protect.card_locker;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -18,6 +19,7 @@ import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
@@ -49,7 +51,12 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
TextView cardIdFieldView;
BottomSheetBehavior behavior;
View bottomSheet;
View bottomSheetContentWrapper;
ImageView bottomSheetButton;
View frontImageView;
ImageView frontImage;
View backImageView;
ImageView backImage;
TextView noteView;
TextView groupsView;
TextView balanceView;
@@ -76,9 +83,19 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
Guideline centerGuideline;
SeekBar barcodeScaler;
Bitmap frontImageBitmap;
Bitmap backImageBitmap;
boolean starred;
boolean backgroundNeedsDarkIcons;
boolean barcodeIsFullscreen = false;
FullscreenType fullscreenType = FullscreenType.NONE;
enum FullscreenType {
NONE,
BARCODE,
IMAGE_FRONT,
IMAGE_BACK
}
private void extractIntentFields(Intent intent)
{
@@ -120,7 +137,12 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
cardIdFieldView = findViewById(R.id.cardIdView);
bottomSheet = findViewById(R.id.bottom_sheet);
bottomSheetContentWrapper = findViewById(R.id.bottomSheetContentWrapper);
bottomSheetButton = findViewById(R.id.bottomSheetButton);
frontImageView = findViewById(R.id.frontImageView);
frontImage = findViewById(R.id.frontImage);
backImageView = findViewById(R.id.backImageView);
backImage = findViewById(R.id.backImage);
noteView = findViewById(R.id.noteView);
groupsView = findViewById(R.id.groupsView);
balanceView = findViewById(R.id.balanceView);
@@ -144,7 +166,9 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
float scale = (float) progress / (float) barcodeScaler.getMax();
Log.d(TAG, "Scaling to " + scale);
redrawBarcodeAfterResize();
if (fullscreenType == FullscreenType.BARCODE) {
redrawBarcodeAfterResize();
}
centerGuideline.setGuidelinePercent(0.5f * scale);
}
@@ -162,18 +186,29 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
rotationEnabled = true;
// Allow making barcode fullscreen on tap
maximizeButton.setOnClickListener(v -> setFullscreen(true));
maximizeButton.setOnClickListener(v -> setFullscreen(FullscreenType.BARCODE));
barcodeImage.setOnClickListener(view -> {
if (barcodeIsFullscreen)
{
setFullscreen(false);
}
else
{
setFullscreen(true);
if (fullscreenType != FullscreenType.NONE) {
setFullscreen(FullscreenType.NONE);
} else {
setFullscreen(FullscreenType.BARCODE);
}
});
minimizeButton.setOnClickListener(v -> setFullscreen(false));
frontImageView.setOnClickListener(view -> {
if (fullscreenType != FullscreenType.IMAGE_FRONT) {
setFullscreen(FullscreenType.IMAGE_FRONT);
} else {
setFullscreen(FullscreenType.NONE);
}
});
backImageView.setOnClickListener(view -> {
if (fullscreenType != FullscreenType.IMAGE_BACK) {
setFullscreen(FullscreenType.IMAGE_BACK);
} else {
setFullscreen(FullscreenType.NONE);
}
});
minimizeButton.setOnClickListener(v -> setFullscreen(FullscreenType.NONE));
editButton = findViewById(R.id.fabEdit);
editButton.setOnClickListener(v -> {
@@ -197,7 +232,12 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
editButton.hide();
} else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_up_24);
editButton.show();
if (fullscreenType == FullscreenType.NONE) {
editButton.show();
}
// Scroll bottomsheet content back to top
bottomSheetContentWrapper.setScrollY(0);
}
}
@@ -212,6 +252,25 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
});
// Fix bottom sheet content sizing
ViewTreeObserver viewTreeObserver = bottomSheet.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
bottomSheet.getViewTreeObserver().removeOnGlobalLayoutListener(this);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
int height = displayMetrics.heightPixels;
int maxHeight = height - appBarLayout.getHeight() - bottomSheetButton.getHeight();
Log.d(TAG, "Button sheet should be " + maxHeight + " pixels high");
bottomSheetContentWrapper.setLayoutParams(
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
maxHeight
)
);
}
});
}
@Override
@@ -275,6 +334,22 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
settings.getFontSizeMin(settings.getLargeFont()), settings.getFontSizeMax(settings.getLargeFont()),
1, TypedValue.COMPLEX_UNIT_SP);
frontImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, true);
if (frontImageBitmap != null) {
frontImageView.setVisibility(View.VISIBLE);
frontImage.setImageBitmap(frontImageBitmap);
} else {
frontImageView.setVisibility(View.GONE);
}
backImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, false);
if (backImageBitmap != null) {
backImageView.setVisibility(View.VISIBLE);
backImage.setImageBitmap(backImageBitmap);
} else {
backImageView.setVisibility(View.GONE);
}
if(loyaltyCard.note.length() > 0)
{
noteView.setVisibility(View.VISIBLE);
@@ -331,7 +406,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
}
expiryView.setTag(loyaltyCard.expiry);
if (!barcodeIsFullscreen) {
if (fullscreenType != FullscreenType.NONE) {
makeBottomSheetVisibleIfUseful();
}
@@ -394,7 +469,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
if(format != null && isBarcodeSupported)
{
if (!barcodeIsFullscreen) {
if (fullscreenType == FullscreenType.NONE) {
maximizeButton.setVisibility(View.VISIBLE);
}
barcodeImage.setVisibility(View.VISIBLE);
@@ -419,7 +494,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
}
// Force redraw fullscreen state
setFullscreen(barcodeIsFullscreen);
setFullscreen(fullscreenType);
}
else
{
@@ -430,9 +505,9 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
@Override
public void onBackPressed() {
if (barcodeIsFullscreen)
if (fullscreenType != FullscreenType.NONE)
{
setFullscreen(false);
setFullscreen(FullscreenType.NONE);
return;
}
@@ -566,7 +641,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
private void makeBottomSheetVisibleIfUseful()
{
if (noteView.getVisibility() == View.VISIBLE || groupsView.getVisibility() == View.VISIBLE || balanceView.getVisibility() == View.VISIBLE || expiryView.getVisibility() == View.VISIBLE) {
if (frontImageView.getVisibility() == View.VISIBLE || backImageView.getVisibility() == View.VISIBLE || noteView.getVisibility() == View.VISIBLE || groupsView.getVisibility() == View.VISIBLE || balanceView.getVisibility() == View.VISIBLE || expiryView.getVisibility() == View.VISIBLE) {
bottomSheet.setVisibility(View.VISIBLE);
}
else
@@ -604,14 +679,21 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
* The purpose of this function is to make sure the barcode can be scanned from the phone
* by machines which offer no space to insert the complete device.
*/
private void setFullscreen(boolean enable)
private void setFullscreen(FullscreenType fullscreenType)
{
ActionBar actionBar = getSupportActionBar();
if(enable)
if (fullscreenType != FullscreenType.NONE)
{
Log.d(TAG, "Move into of fullscreen");
// Prepare redraw after size change
redrawBarcodeAfterResize();
if (fullscreenType == FullscreenType.IMAGE_FRONT) {
barcodeImage.setImageBitmap(frontImageBitmap);
} else if (fullscreenType == FullscreenType.IMAGE_BACK) {
barcodeImage.setImageBitmap(backImageBitmap);
} else {
// Prepare redraw after size change
redrawBarcodeAfterResize();
}
// Hide maximize and show minimize button and scaler
maximizeButton.setVisibility(View.GONE);
@@ -646,11 +728,8 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_FULLSCREEN
);
// Set current state
barcodeIsFullscreen = true;
}
else if(!enable)
else
{
Log.d(TAG, "Move out of fullscreen");
@@ -690,9 +769,8 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
);
// Set current state
barcodeIsFullscreen = false;
}
this.fullscreenType = fullscreenType;
}
}

View File

@@ -4,8 +4,12 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.provider.MediaStore;
import android.util.Base64;
import android.util.Log;
import android.widget.Toast;
@@ -17,6 +21,10 @@ import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.NumberFormat;
@@ -35,9 +43,15 @@ public class Utils {
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;
public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 5;
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 6;
public static final int CARD_IMAGE_FROM_FILE_FRONT = 7;
public static final int CARD_IMAGE_FROM_FILE_BACK = 8;
static final double LUMINANCE_MIDPOINT = 0.5;
static final int BITMAP_SIZE_BIG = 512;
static public LetterBitmap generateIcon(Context context, String store, Integer backgroundColor) {
return generateIcon(context, store, backgroundColor, false);
}
@@ -208,4 +222,127 @@ public class Utils {
// Parse as BigDecimal
return new BigDecimal(value);
}
static public byte[] bitmapToByteArray(Bitmap bitmap) {
if (bitmap == null) {
return null;
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
return bos.toByteArray();
}
static public Bitmap byteArrayToBitmap(byte[] byteArray) {
if (byteArray == null) {
return null;
}
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
}
static public String bitmapToBase64(Bitmap bitmap) {
if (bitmap == null) {
return null;
}
return Base64.encodeToString(bitmapToByteArray(bitmap), Base64.URL_SAFE);
}
static public Bitmap base64ToBitmap(String base64) {
if (base64 == null) {
return null;
}
return byteArrayToBitmap(Base64.decode(base64, Base64.URL_SAFE));
}
static public Bitmap resizeBitmap(Bitmap bitmap) {
if (bitmap == null) {
return null;
}
Integer maxSize = BITMAP_SIZE_BIG;
Integer width = bitmap.getWidth();
Integer height = bitmap.getHeight();
if (height > width) {
Integer scale = height / maxSize;
height = maxSize;
width = width / scale;
} else if (width > height) {
Integer scale = width / maxSize;
width = maxSize;
height = height / scale;
} else {
height = maxSize;
width = maxSize;
}
return Bitmap.createScaledBitmap(bitmap, width, height, true);
}
static public Bitmap rotateBitmap(Bitmap bitmap, ExifInterface exifInterface) {
switch (exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
case ExifInterface.ORIENTATION_ROTATE_90:
return rotateBitmap(bitmap, 90f);
case ExifInterface.ORIENTATION_ROTATE_180:
return rotateBitmap(bitmap, 180f);
case ExifInterface.ORIENTATION_ROTATE_270:
return rotateBitmap(bitmap, 270f);
default:
return bitmap;
}
}
static public Bitmap rotateBitmap(Bitmap bitmap, float rotation) {
if (rotation == 0) {
return bitmap;
}
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
static private String getCardImageFileName(int loyaltyCardId, boolean front) {
StringBuilder cardImageFileNameBuilder = new StringBuilder();
cardImageFileNameBuilder.append("card_");
cardImageFileNameBuilder.append(loyaltyCardId);
cardImageFileNameBuilder.append("_");
if (front) {
cardImageFileNameBuilder.append("front");
} else {
cardImageFileNameBuilder.append("back");
}
cardImageFileNameBuilder.append(".png");
return cardImageFileNameBuilder.toString();
}
static public void saveCardImage(Context context, Bitmap bitmap, int loyaltyCardId, boolean front) throws FileNotFoundException {
String fileName = getCardImageFileName(loyaltyCardId, front);
if (bitmap == null) {
context.deleteFile(fileName);
return;
}
FileOutputStream out = context.openFileOutput(getCardImageFileName(loyaltyCardId, front), Context.MODE_PRIVATE);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
}
static public Bitmap retrieveCardImage(Context context, int loyaltyCardId, boolean front) {
FileInputStream in;
try {
in = context.openFileInput(getCardImageFileName(loyaltyCardId, front));
} catch (FileNotFoundException e) {
return null;
}
return BitmapFactory.decodeStream(in);
}
}

View File

@@ -1,10 +1,38 @@
package protect.card_locker.importexport;
import android.graphics.Bitmap;
import org.apache.commons.csv.CSVRecord;
import protect.card_locker.FormatException;
import protect.card_locker.Utils;
public class CSVHelpers {
static String IMAGE_FRONT = "frontimage";
static String IMAGE_BACK = "backimage";
/**
* Extract an image from the items array. The index into the array
* is determined by looking up the index in the fields map using the
* "key" as the key. If no such key exists, defaultValue is returned
* if it is not null. Otherwise, a FormatException is thrown.
*/
static Bitmap extractImage(String key, CSVRecord record)
{
if(record.isMapped(key))
{
String value = record.get(key);
if (value.isEmpty()) {
return null;
}
return Utils.base64ToBitmap(value);
}
return null;
}
/**
* Extract a string from the items array. The index into the array
* is determined by looking up the index in the fields map using the

View File

@@ -1,5 +1,6 @@
package protect.card_locker.importexport;
import android.content.Context;
import android.database.Cursor;
import org.apache.commons.csv.CSVFormat;
@@ -11,14 +12,15 @@ import java.io.OutputStreamWriter;
import protect.card_locker.DBHelper;
import protect.card_locker.Group;
import protect.card_locker.LoyaltyCard;
import protect.card_locker.Utils;
/**
* Class for exporting the database into CSV (Comma Separate Values)
* format.
*/
public class CsvDatabaseExporter implements DatabaseExporter
public class CsvExporter implements Exporter
{
public void exportData(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException
public void exportData(Context context, DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException
{
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
@@ -57,10 +59,12 @@ public class CsvDatabaseExporter implements DatabaseExporter
DBHelper.LoyaltyCardDbIds.BALANCE,
DBHelper.LoyaltyCardDbIds.BALANCE_TYPE,
DBHelper.LoyaltyCardDbIds.CARD_ID,
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
DBHelper.LoyaltyCardDbIds.BARCODE_ID,
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
DBHelper.LoyaltyCardDbIds.STAR_STATUS);
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
DBHelper.LoyaltyCardDbIds.STAR_STATUS,
CSVHelpers.IMAGE_FRONT,
CSVHelpers.IMAGE_BACK);
Cursor cardCursor = db.getLoyaltyCardCursor();
@@ -75,10 +79,12 @@ public class CsvDatabaseExporter implements DatabaseExporter
card.balance,
card.balanceType,
card.cardId,
card.headerColor,
card.barcodeId,
card.barcodeType,
card.starStatus);
card.headerColor,
card.starStatus,
Utils.bitmapToBase64(Utils.retrieveCardImage(context, card.id, true)),
Utils.bitmapToBase64(Utils.retrieveCardImage(context, card.id, false)));
if(Thread.currentThread().isInterrupted())
{

View File

@@ -1,6 +1,10 @@
package protect.card_locker.importexport;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
import com.google.zxing.BarcodeFormat;
@@ -22,6 +26,7 @@ import java.util.List;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.Group;
import protect.card_locker.Utils;
/**
* Class for importing a database from CSV (Comma Separate Values)
@@ -30,9 +35,9 @@ import protect.card_locker.Group;
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
public class CsvDatabaseImporter implements DatabaseImporter
public class CsvImporter implements Importer
{
public void importData(DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException
public void importData(Context context, DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException
{
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
@@ -50,10 +55,10 @@ public class CsvDatabaseImporter implements DatabaseImporter
switch (version) {
case 1:
parseV1(db, bufferedReader);
parseV1(context, db, bufferedReader);
break;
case 2:
parseV2(db, bufferedReader);
parseV2(context, db, bufferedReader);
break;
default:
throw new FormatException(String.format("No code to parse version %s", version));
@@ -62,7 +67,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
bufferedReader.close();
}
public void parseV1(DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
public void parseV1(Context context, DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
{
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.withHeader());
@@ -73,7 +78,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
{
for (CSVRecord record : parser)
{
importLoyaltyCard(database, db, record);
importLoyaltyCard(context, database, db, record);
if(Thread.currentThread().isInterrupted())
{
@@ -95,7 +100,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
}
}
public void parseV2(DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
public void parseV2(Context context, DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
{
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
@@ -116,7 +121,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
parseV2Groups(db, database, stringPart);
break;
case 2:
parseV2Cards(db, database, stringPart);
parseV2Cards(context, db, database, stringPart);
break;
case 3:
parseV2CardGroups(db, database, stringPart);
@@ -164,14 +169,14 @@ public class CsvDatabaseImporter implements DatabaseImporter
}
}
public void parseV2Cards(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
public void parseV2Cards(Context context, DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
{
// Parse cards
final CSVParser cardParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
try {
for (CSVRecord record : cardParser) {
importLoyaltyCard(database, db, record);
importLoyaltyCard(context, database, db, record);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
@@ -208,7 +213,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
* Import a single loyalty card into the database using the given
* session.
*/
private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record)
private void importLoyaltyCard(Context context, SQLiteDatabase database, DBHelper helper, CSVRecord record)
throws IOException, FormatException
{
int id = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
@@ -270,11 +275,15 @@ public class CsvDatabaseImporter implements DatabaseImporter
try {
starStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record, false);
} catch (FormatException _e ) {
// This field did not exist in versions 0.28 and before
// This field did not exist in versions 0.278 and before
// We catch this exception so we can still import old backups
}
if (starStatus != 1) starStatus = 0;
helper.insertLoyaltyCard(database, id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus);
Utils.saveCardImage(context, CSVHelpers.extractImage(CSVHelpers.IMAGE_FRONT, record), id, true);
Utils.saveCardImage(context, CSVHelpers.extractImage(CSVHelpers.IMAGE_BACK, record), id, false);
}
/**

View File

@@ -1,5 +1,7 @@
package protect.card_locker.importexport;
import android.content.Context;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -9,11 +11,11 @@ import protect.card_locker.DBHelper;
* Interface for a class which can export the contents of the database
* in a given format.
*/
public interface DatabaseExporter
public interface Exporter
{
/**
* Export the database to the output stream in a given format.
* @throws IOException
*/
void exportData(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException;
void exportData(Context context, DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException;
}

View File

@@ -1,5 +1,6 @@
package protect.card_locker.importexport;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import com.google.zxing.BarcodeFormat;
@@ -28,9 +29,9 @@ import protect.card_locker.FormatException;
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
public class FidmeImporter implements DatabaseImporter
public class FidmeImporter implements Importer
{
public void importData(DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException {
public void importData(Context context, DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException {
// We actually retrieve a .zip file
ZipInputStream zipInputStream = new ZipInputStream(input);
@@ -130,6 +131,8 @@ public class FidmeImporter implements DatabaseImporter
// No favourite data in the export either
int starStatus = 0;
// TODO: Front and back image
helper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, starStatus);
}
}

View File

@@ -1,5 +1,7 @@
package protect.card_locker.importexport;
import android.content.Context;
import org.json.JSONException;
import java.io.IOException;
@@ -13,7 +15,7 @@ import protect.card_locker.FormatException;
* Interface for a class which can import the contents of a stream
* into the database.
*/
public interface DatabaseImporter
public interface Importer
{
/**
* Import data from the input stream in a given format into
@@ -21,5 +23,5 @@ public interface DatabaseImporter
* @throws IOException
* @throws FormatException
*/
void importData(DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
void importData(Context context, DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
}

View File

@@ -1,5 +1,6 @@
package protect.card_locker.importexport;
import android.content.Context;
import android.util.Log;
import java.io.IOException;
@@ -22,14 +23,14 @@ public class MultiFormatExporter
* false otherwise. If false, partial data may have been
* written to the output stream, and it should be discarded.
*/
public static boolean exportData(DBHelper db, OutputStreamWriter output, DataFormat format)
public static boolean exportData(Context context, DBHelper db, OutputStreamWriter output, DataFormat format)
{
DatabaseExporter exporter = null;
Exporter exporter = null;
switch(format)
{
case Catima:
exporter = new CsvDatabaseExporter();
exporter = new CsvExporter();
break;
default:
Log.e(TAG, "Failed to export data, unknown format " + format.name());
@@ -40,7 +41,7 @@ public class MultiFormatExporter
{
try
{
exporter.exportData(db, output);
exporter.exportData(context, db, output);
return true;
}
catch(IOException e)

View File

@@ -1,5 +1,6 @@
package protect.card_locker.importexport;
import android.content.Context;
import android.util.Log;
import org.json.JSONException;
@@ -27,14 +28,14 @@ public class MultiFormatImporter
* false otherwise. If false, no data was written to
* the database.
*/
public static boolean importData(DBHelper db, InputStream input, DataFormat format)
public static boolean importData(Context context, DBHelper db, InputStream input, DataFormat format)
{
DatabaseImporter importer = null;
Importer importer = null;
switch(format)
{
case Catima:
importer = new CsvDatabaseImporter();
importer = new CsvImporter();
break;
case Fidme:
importer = new FidmeImporter();
@@ -48,7 +49,7 @@ public class MultiFormatImporter
{
try
{
importer.importData(db, input);
importer.importData(context, db, input);
return true;
}
catch(IOException | FormatException | InterruptedException | JSONException | ParseException e)

View File

@@ -1,6 +1,7 @@
package protect.card_locker.importexport;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color;
@@ -32,9 +33,9 @@ import protect.card_locker.FormatException;
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
public class VoucherVaultImporter implements DatabaseImporter
public class VoucherVaultImporter implements Importer
{
public void importData(DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException {
public void importData(Context context, DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
@@ -122,7 +123,7 @@ public class VoucherVaultImporter implements DatabaseImporter
headerColor = Color.YELLOW;
break;
default:
throw new FormatException("Unknown colour type foun: " + colorFromJSON);
throw new FormatException("Unknown colour type found: " + colorFromJSON);
}
db.insertLoyaltyCard(store, "", expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0);