Compare commits

..

44 Commits
v0.2 ... v0.5

Author SHA1 Message Date
Branden Archer
14972c9cc9 Merge pull request #40 from brarcher/pre-v0.5
Update for release v0.5
2016-05-16 21:15:18 -04:00
Branden Archer
213d1f64a6 Update for release v0.5 2016-05-16 21:06:29 -04:00
Branden Archer
f4f22055fc Merge pull request #38 from brarcher/import-export
Add support for backing up cards to CSV file on external storage
2016-05-16 21:00:38 -04:00
Branden Archer
b99704a3d8 Add an About page
This about page layout is borrowed from K-9 Mail:
https://github.com/k9mail

Italian translations provided by Airon90.
Dutch translations provided by PanderMusubi.
2016-05-16 20:51:59 -04:00
Branden Archer
715d726ea3 Update Gradle to 2.10 2016-05-16 20:51:59 -04:00
Branden Archer
8b887a2ee9 Update Gradle Android plugin to 2.0.0 2016-05-16 20:51:59 -04:00
Branden Archer
0248df532a Use gradle wrapper script on Travis-CI 2016-05-16 20:51:59 -04:00
Branden Archer
8c83b85cea Request external storage permission if necessary
Starting on Android 6.0 permissions may be requested at
runtime. To allow import/exporting on 6.0+, request permission
to external storage.
2016-05-16 20:51:59 -04:00
Branden Archer
b8f3d891ea Allow user to import/export cards to/from CSV on external storage
Italian translations provided by Airon90.
Dutch translations provided by PanderMusubi
2016-05-16 20:51:59 -04:00
Branden Archer
20338eb09b Add task for importing/exporting 2016-05-16 20:51:59 -04:00
Branden Archer
ce272fe7f1 Add ability to import/export cards to/from CSV 2016-05-16 20:51:59 -04:00
Branden Archer
fb34fb7451 Add call for writing card entry to specific id
This will be necessary when importing card entries later
2016-05-16 20:51:59 -04:00
Branden Archer
7b2d022e92 Merge pull request #35 from brarcher/gradle-wrappers
Add wrapper scripts and instructions for building
2016-04-22 09:27:49 -04:00
Branden Archer
283858a6e3 Add wrapper scripts and instructions for building 2016-04-22 08:58:38 -04:00
Branden Archer
d46b3c0810 Merge pull request #31 from brarcher/note-translation
Update Italian translation for note
2016-04-12 09:04:54 -04:00
Branden Archer
a2ff1a9e05 Update Italian translation for note
Airon90 mentioned that although "Nota" is Italian for "Note", it would be better to use the plural
form "Note" here.
2016-04-12 08:58:05 -04:00
Branden Archer
b2e333c379 Merge pull request #30 from brarcher/pre-v0.4
Update for release v0.4
2016-04-09 22:26:30 -04:00
Branden Archer
2b058ee766 Update for release v0.4 2016-04-09 22:21:23 -04:00
Branden Archer
fc3302219e Merge pull request #29 from brarcher/note-field
Add a note field
2016-04-09 22:18:35 -04:00
Branden Archer
e0a77e9628 display store and note on card summary view
Dutch translation provided by PanderMusubi

Related issues:
https://github.com/brarcher/loyalty-card-locker/issues/23
https://github.com/brarcher/loyalty-card-locker/issues/24
2016-04-09 22:11:20 -04:00
Branden Archer
f6f749de1c Use multi exception catch for idential exceptions 2016-03-20 18:05:51 -04:00
Branden Archer
61dec10e74 Do not overwrite store field when capturing barcode 2016-03-20 18:05:51 -04:00
Branden Archer
9306e1a3d9 Add ability to store a note with loyalty card 2016-03-20 18:05:51 -04:00
Branden Archer
62e4fa402c split parts of test into separate methods
in the future additional tests will be added, and this will
allow for more reuse
2016-03-20 18:05:51 -04:00
Branden Archer
d12edd06a4 Merge pull request #28 from brarcher/dutch
Add Dutch translations
2016-03-16 23:02:28 -04:00
Branden Archer
4a8bc4744d Add Dutch translations
These are provided by PanderMusubi

https://github.com/brarcher/loyalty-card-locker/issues/27
2016-03-16 22:54:31 -04:00
Branden Archer
34faf18deb Merge pull request #26 from brarcher/edit-name
Edit name
2016-03-12 21:26:18 -05:00
Branden Archer
8c371751a7 Allow store name to be editable after creation 2016-03-11 21:53:38 -05:00
Branden Archer
c21c81f2c7 Correct names of unit tests
Some of the tests mentioned "Gift" cards, but should mention
"Loyalty" cards instead. Additionally, made one unit test's
intention more clear.
2016-03-11 21:53:38 -05:00
Branden Archer
77afaf1a3d Merge pull request #22 from brarcher/findbugs
enable findbugs in travis-ci build
2016-02-20 16:29:24 -05:00
Branden Archer
eb61f81b52 cat unit test failure report if failures found 2016-02-20 13:47:30 -05:00
Branden Archer
0289720c09 enable findbugs during travis-ci build 2016-02-20 13:46:53 -05:00
Branden Archer
5f2e3a5e1d Change inner class to be static 2016-02-20 13:31:34 -05:00
Branden Archer
5bc992f7f8 Merge pull request #15 from brarcher/readme
mention barcode types in README.md
2016-02-11 06:32:17 -05:00
Branden Archer
a2ab434d01 Merge branch 'master' into readme 2016-02-11 01:36:03 -05:00
Branden Archer
9593b94a2b Merge pull request #16 from brarcher/pre-v0.3
update version for next release
2016-02-11 01:35:56 -05:00
Branden Archer
075a440e4f update version for next release 2016-02-11 01:30:12 -05:00
Branden Archer
c6967f6a3f mention barcode types in README.md 2016-02-11 01:29:30 -05:00
Branden Archer
a8ca5470c7 Merge pull request #14 from brarcher/update-zxing-android-embedded
update zxing-android-embedded to latest release
2016-02-11 01:22:41 -05:00
Branden Archer
4d277eff51 update zxing-android-embedded to latest release
there was an issue with requesting camera permission
for Android 6 which was resolved in release 3.2.0.
2016-02-11 01:00:02 -05:00
Branden Archer
c51531e367 Merge pull request #13 from brarcher/all-barcodes
All barcodes
2016-02-10 21:49:21 -05:00
Branden Archer
5e57d66daa Do not bring up keyboard in view activity
Typically one will open the view activity to bring up a
barcode, and the keyboard is not expected. Disable the
keyboard until an editable field is selected.
2016-02-10 21:42:57 -05:00
Branden Archer
73aff4aeb8 Support all barcodes zxing can generate 2016-02-10 21:42:57 -05:00
Branden Archer
e6241b5a47 Update to using latest zxing library release, 3.2.1 2016-02-10 21:42:57 -05:00
44 changed files with 1800 additions and 96 deletions

View File

@@ -19,7 +19,9 @@ android:
# if you need to run emulator(s) during your tests
- sys-img-x86-android-17
script: gradle build lint test
script: ./gradlew build lint findbugs test
after_failure:
- cat app/build/outputs/lint-results.xml
- cat app/build/reports/findbugs/findbugs.html
- cat app/build/reports/tests/debug/index.html

View File

@@ -4,11 +4,38 @@
[![F-Droid](https://upload.wikimedia.org/wikipedia/commons/thumb/0/0d/Get_it_on_F-Droid.svg/160px-Get_it_on_F-Droid.svg.png)](https://f-droid.org/repository/browse/?fdid=protect.card_locker "Loyalty Card Locker on F-Droid")
Stores all of your store loyalty cards on your phone, removing the need to carry them around.
Stores all of your store loyalty cards on your phone, removing the need to carry them around. Currently the following barcode types are supported:
- AZTEC
- CODABAR
- CODE_39
- CODE_128
- DATA_MATRIX
- EAN_8
- EAN_13
- ITF
- PDF_417
- QR_CODE
- UPC_A
If there is any interest in improving this project, kindly submit a pull request with
proposed changes.
# Building
To build, use the gradle wrapper scripts provided in the top level directory of the project. The following will
compile the application and run all unit tests:
GNU/Linux, OSX, UNIX:
```
./gradlew build
```
Windows:
```
./gradlew.bat build
```
# Thanks
App icon originals by [Freepik](https://www.flaticon.com) and distributed under the [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/) license,

View File

@@ -1,4 +1,10 @@
apply plugin: 'com.android.application'
apply plugin: 'findbugs'
findbugs {
sourceSets = []
ignoreFailures = false
}
android {
compileSdkVersion 23
@@ -8,8 +14,8 @@ android {
applicationId "protect.card_locker"
minSdkVersion 17
targetSdkVersion 23
versionCode 2
versionName "0.2"
versionCode 5
versionName "0.5"
}
buildTypes {
release {
@@ -31,8 +37,28 @@ dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
compile 'com.journeyapps:zxing-android-embedded:3.0.1@aar'
compile 'com.google.zxing:core:3.2.0'
compile 'com.journeyapps:zxing-android-embedded:3.2.0@aar'
compile 'com.google.zxing:core:3.2.1'
compile 'org.apache.commons:commons-csv:1.2'
testCompile 'junit:junit:4.12'
testCompile "org.robolectric:robolectric:3.0"
}
task findbugs(type: FindBugs, dependsOn: assembleDebug) {
description 'Run findbugs'
group 'verification'
classes = fileTree('build/intermediates/classes/debug/')
source = fileTree('src/main/java')
classpath = files()
effort = 'max'
excludeFilter = file("./config/findbugs/exclude.xml")
reports {
xml.enabled = false
html.enabled = true
}
}

View File

@@ -0,0 +1,10 @@
<FindBugsFilter>
<Match>
<Class name="~.*R\$.*"/>
</Match>
<Match>
<Class name="~.*Manifest\$.*"/>
</Match>
</FindBugsFilter>

View File

@@ -5,7 +5,8 @@
<uses-permission
android:name="android.permission.CAMERA"/>
<uses-permission
android:maxSdkVersion="18"
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature
@@ -35,7 +36,14 @@
android:name=".LoyaltyCardViewActivity"
android:theme="@style/AppTheme.NoActionBar"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateHidden"
android:parentActivityName="protect.card_locker.MainActivity"/>
<activity
android:name=".ImportExportActivity"
android:label="@string/importExport"
android:configChanges="orientation|screenSize"
android:theme="@style/AppTheme.NoActionBar"
android:parentActivityName=".MainActivity"/>
</application>
</manifest>

View File

@@ -0,0 +1,50 @@
package protect.card_locker;
import android.database.Cursor;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import java.io.IOException;
import java.io.OutputStreamWriter;
/**
* Class for exporting the database into CSV (Comma Separate Values)
* format.
*/
public class CsvDatabaseExporter implements DatabaseExporter
{
public void exportData(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException
{
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
// Print the header
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
DBHelper.LoyaltyCardDbIds.STORE,
DBHelper.LoyaltyCardDbIds.NOTE,
DBHelper.LoyaltyCardDbIds.CARD_ID,
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
Cursor cursor = db.getLoyaltyCardCursor();
while(cursor.moveToNext())
{
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
printer.printRecord(card.id,
card.store,
card.note,
card.cardId,
card.barcodeType);
if(Thread.currentThread().isInterrupted())
{
throw new InterruptedException();
}
}
cursor.close();
printer.close();
}
}

View File

@@ -0,0 +1,135 @@
package protect.card_locker;
import android.database.sqlite.SQLiteDatabase;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* Class for importing a database from CSV (Comma Separate Values)
* formatted data.
*
* 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 void importData(DBHelper db, InputStreamReader input) throws IOException, FormatException, InterruptedException
{
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.withHeader());
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
try
{
for (CSVRecord record : parser)
{
importLoyaltyCard(database, db, record);
if(Thread.currentThread().isInterrupted())
{
throw new InterruptedException();
}
}
parser.close();
database.setTransactionSuccessful();
}
catch(IllegalArgumentException e)
{
throw new FormatException("Issue parsing CSV data", e);
}
finally
{
database.endTransaction();
database.close();
}
}
/**
* 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
* "key" as the key. If no such key exists, defaultValue is returned
* if it is not null. Otherwise, a FormatException is thrown.
*/
private String extractString(String key, CSVRecord record, String defaultValue)
throws FormatException
{
String toReturn = defaultValue;
if(record.isMapped(key))
{
toReturn = record.get(key);
}
else
{
if(defaultValue == null)
{
throw new FormatException("Field not used but expected: " + key);
}
}
return toReturn;
}
/**
* Extract an integer 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, or the data is not a valid
* int, a FormatException is thrown.
*/
private int extractInt(String key, CSVRecord record)
throws FormatException
{
if(record.isMapped(key) == false)
{
throw new FormatException("Field not used but expected: " + key);
}
try
{
return Integer.parseInt(record.get(key));
}
catch(NumberFormatException e)
{
throw new FormatException("Failed to parse field: " + key, e);
}
}
/**
* Import a single loyalty card into the database using the given
* session.
*/
private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record)
throws IOException, FormatException
{
int id = extractInt(DBHelper.LoyaltyCardDbIds.ID, record);
String store = extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
if(store.isEmpty())
{
throw new FormatException("No store listed, but is required");
}
String note = extractString(DBHelper.LoyaltyCardDbIds.NOTE, record, "");
String cardId = extractString(DBHelper.LoyaltyCardDbIds.CARD_ID, record, "");
if(cardId.isEmpty())
{
throw new FormatException("No card ID listed, but is required");
}
String barcodeType = extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, "");
if(barcodeType.isEmpty())
{
throw new FormatException("No barcode type listed, but is required");
}
helper.insertLoyaltyCard(database, id, store, note, cardId, barcodeType);
}
}

View File

@@ -10,13 +10,15 @@ import android.database.sqlite.SQLiteOpenHelper;
public class DBHelper extends SQLiteOpenHelper
{
public static final String DATABASE_NAME = "LoyaltyCards.db";
public static final int DATABASE_VERSION = 1;
public static final int ORIGINAL_DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 2;
class LoyaltyCardDbIds
static class LoyaltyCardDbIds
{
public static final String TABLE = "cards";
public static final String ID = "_id";
public static final String STORE = "store";
public static final String NOTE = "note";
public static final String CARD_ID = "cardid";
public static final String BARCODE_TYPE = "barcodetype";
}
@@ -33,6 +35,7 @@ public class DBHelper extends SQLiteOpenHelper
db.execSQL("create table " + LoyaltyCardDbIds.TABLE + "(" +
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
LoyaltyCardDbIds.STORE + " TEXT not null," +
LoyaltyCardDbIds.NOTE + " TEXT not null," +
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT not null)");
}
@@ -40,16 +43,35 @@ public class DBHelper extends SQLiteOpenHelper
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
// Do not support versioning yet
db.execSQL("DROP TABLE IF EXISTS " + LoyaltyCardDbIds.TABLE);
onCreate(db);
// Upgrade from version 1 to version 2
if(oldVersion < 2 && newVersion >= 2)
{
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.NOTE + " TEXT not null default ''");
}
}
public boolean insertLoyaltyCard(final String store, final String cardId, final String barcodeType)
public boolean insertLoyaltyCard(final String store, final String note, final String cardId,
final String barcodeType)
{
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
return (newId != -1);
}
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id,
final String store, final String note, final String cardId,
final String barcodeType)
{
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.ID, id);
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
@@ -57,12 +79,13 @@ public class DBHelper extends SQLiteOpenHelper
}
public boolean updateLoyaltyCard(final int id, final String store, final String cardId,
final String barcodeType)
public boolean updateLoyaltyCard(final int id, final String store, final String note,
final String cardId, final String barcodeType)
{
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,

View File

@@ -0,0 +1,8 @@
package protect.card_locker;
public enum DataFormat
{
CSV,
;
}

View File

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

View File

@@ -0,0 +1,19 @@
package protect.card_locker;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* Interface for a class which can import the contents of a stream
* into the database.
*/
public interface DatabaseImporter
{
/**
* Import data from the input stream in a given format into
* the database.
* @throws IOException
* @throws FormatException
*/
void importData(DBHelper db, InputStreamReader input) throws IOException, FormatException, InterruptedException;
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker;
/**
* Exception thrown when something unexpected is
* encountered with the format of data being
* imported or exported.
*/
public class FormatException extends Exception
{
public FormatException(String message)
{
super(message);
}
public FormatException(String message, Exception rootCause)
{
super(message, rootCause);
}
}

View File

@@ -0,0 +1,131 @@
package protect.card_locker;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class ImportExportActivity extends AppCompatActivity
{
private static final int PERMISSIONS_EXTERNAL_STORAGE_IMPORT = 1;
private static final int PERMISSIONS_EXTERNAL_STORAGE_EXPORT = 2;
ImportExportTask importExporter;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.import_export_activity);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
actionBar.setDisplayHomeAsUpEnabled(true);
}
Button importButton = (Button)findViewById(R.id.importButton);
importButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
{
startImport();
}
else
{
ActivityCompat.requestPermissions(ImportExportActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
PERMISSIONS_EXTERNAL_STORAGE_IMPORT);
}
}
});
Button exportButton = (Button)findViewById(R.id.exportButton);
exportButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
{
startExport();
}
else
{
ActivityCompat.requestPermissions(ImportExportActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSIONS_EXTERNAL_STORAGE_EXPORT);
}
}
});
}
private void startImport()
{
importExporter = new ImportExportTask(ImportExportActivity.this,
true, DataFormat.CSV);
importExporter.execute();
}
private void startExport()
{
importExporter = new ImportExportTask(ImportExportActivity.this,
false, DataFormat.CSV);
importExporter.execute();
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
{
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE_IMPORT ||
requestCode == PERMISSIONS_EXTERNAL_STORAGE_EXPORT)
{
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
// permission was granted.
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE_IMPORT)
{
startImport();
}
else
{
startExport();
}
}
else
{
// External storage permission rejected, inform user that
// import/export is prevented
Toast.makeText(getApplicationContext(), R.string.noExternalStoragePermissionError,
Toast.LENGTH_LONG).show();
}
}
}
@Override
protected void onDestroy()
{
if(importExporter != null && importExporter.getStatus() != AsyncTask.Status.RUNNING)
{
importExporter.cancel(true);
}
super.onDestroy();
}
}

View File

@@ -0,0 +1,146 @@
package protect.card_locker;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
class ImportExportTask extends AsyncTask<Void, Void, Void>
{
private static final String TAG = "BudgetWatch";
private static final String TARGET_FILE = "LoyaltyCardLocker.csv";
private Activity activity;
private boolean doImport;
private DataFormat format;
private ProgressDialog progress;
public ImportExportTask(Activity activity, boolean doImport, DataFormat format)
{
super();
this.activity = activity;
this.doImport = doImport;
this.format = format;
}
private void toastWithArg(int stringId, String argument)
{
final String template = activity.getResources().getString(stringId);
final String message = String.format(template, argument);
activity.runOnUiThread(new Runnable()
{
@Override
public void run()
{
Toast.makeText(activity, message, Toast.LENGTH_LONG).show();
}
});
}
private void performImport(File importFile, DBHelper db)
{
if(importFile.exists() == false)
{
toastWithArg(R.string.fileMissing, importFile.getAbsolutePath());
return;
}
boolean result = false;
try
{
FileInputStream fileReader = new FileInputStream(importFile);
InputStreamReader reader = new InputStreamReader(fileReader, Charset.forName("UTF-8"));
result = MultiFormatImporter.importData(db, reader, format);
reader.close();
}
catch(IOException e)
{
Log.e(TAG, "Unable to import file", e);
}
int messageId = result ? R.string.importedFrom : R.string.importFailed;
toastWithArg(messageId, importFile.getAbsolutePath());
}
private void performExport(File exportFile, DBHelper db)
{
boolean result = false;
try
{
FileOutputStream fileWriter = new FileOutputStream(exportFile);
OutputStreamWriter writer = new OutputStreamWriter(fileWriter, Charset.forName("UTF-8"));
result = MultiFormatExporter.exportData(db, writer, format);
writer.close();
}
catch (IOException e)
{
Log.e(TAG, "Unable to export file", e);
}
int messageId = result ? R.string.exportedTo : R.string.exportFailed;
toastWithArg(messageId, exportFile.getAbsolutePath());
}
protected void onPreExecute()
{
progress = new ProgressDialog(activity);
progress.setTitle(doImport ? R.string.importing : R.string.exporting);
progress.setOnDismissListener(new DialogInterface.OnDismissListener()
{
@Override
public void onDismiss(DialogInterface dialog)
{
ImportExportTask.this.cancel(true);
}
});
progress.show();
}
protected Void doInBackground(Void... nothing)
{
final File sdcardDir = Environment.getExternalStorageDirectory();
final File importExportFile = new File(sdcardDir, TARGET_FILE);
final DBHelper db = new DBHelper(activity);
if(doImport)
{
performImport(importExportFile, db);
}
else
{
performExport(importExportFile, db);
}
return null;
}
protected void onPostExecute(Void result)
{
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
}
protected void onCancelled()
{
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled");
}
}

View File

@@ -6,13 +6,15 @@ public class LoyaltyCard
{
public final int id;
public final String store;
public final String note;
public final String cardId;
public final String barcodeType;
public LoyaltyCard(final int id, final String store, final String cardId, final String barcodeType)
public LoyaltyCard(final int id, final String store, final String note, final String cardId, final String barcodeType)
{
this.id = id;
this.store = store;
this.note = note;
this.cardId = cardId;
this.barcodeType = barcodeType;
}
@@ -21,9 +23,10 @@ public class LoyaltyCard
{
int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
String store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID));
String barcodeType = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE));
return new LoyaltyCard(id, store, cardId, barcodeType);
return new LoyaltyCard(id, store, note, cardId, barcodeType);
}
}

View File

@@ -36,7 +36,14 @@ class LoyaltyCardCursorAdapter extends CursorAdapter
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(cursor);
// Populate fields with extracted properties
storeField.setText(loyaltyCard.store);
String storeAndNote = loyaltyCard.store;
if(loyaltyCard.note.isEmpty() == false)
{
String storeNameAndNoteFormat = view.getResources().getString(R.string.storeNameAndNoteFormat);
storeAndNote = String.format(storeNameAndNoteFormat, loyaltyCard.store, loyaltyCard.note);
}
storeField.setText(storeAndNote);
String cardIdFormat = view.getResources().getString(R.string.cardIdFormat);
String cardIdLabel = view.getResources().getString(R.string.cardId);

View File

@@ -23,11 +23,32 @@ import com.google.zxing.common.BitMatrix;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
public class LoyaltyCardViewActivity extends AppCompatActivity
{
private static final String TAG = "CardLocker";
// These are all the barcode types that the zxing library
// is able to generate a barcode for, and thus should be
// the only barcodes which we should attempt to scan.
Collection<String> supportedBarcodeTypes = Collections.unmodifiableList(Arrays.asList(
BarcodeFormat.AZTEC.name(),
BarcodeFormat.CODE_39.name(),
BarcodeFormat.CODE_128.name(),
BarcodeFormat.CODABAR.name(),
BarcodeFormat.DATA_MATRIX.name(),
BarcodeFormat.EAN_8.name(),
BarcodeFormat.EAN_13.name(),
BarcodeFormat.ITF.name(),
BarcodeFormat.PDF_417.name(),
BarcodeFormat.QR_CODE.name(),
BarcodeFormat.UPC_A.name()
));
@Override
protected void onCreate(Bundle savedInstanceState)
{
@@ -56,6 +77,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
Log.i(TAG, "To view card: " + loyaltyCardId);
final EditText storeField = (EditText) findViewById(R.id.storeName);
final EditText noteField = (EditText) findViewById(R.id.note);
final EditText cardIdField = (EditText) findViewById(R.id.cardId);
final EditText barcodeTypeField = (EditText) findViewById(R.id.barcodeType);
final ImageView barcodeImage = (ImageView) findViewById(R.id.barcode);
@@ -74,7 +96,15 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
{
final LoyaltyCard loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
storeField.setText(loyaltyCard.store);
if(storeField.getText().length() == 0)
{
storeField.setText(loyaltyCard.store);
}
if(noteField.getText().length() == 0)
{
noteField.setText(loyaltyCard.note);
}
if(cardIdField.getText().length() == 0)
{
@@ -86,7 +116,11 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
barcodeTypeField.setText(loyaltyCard.barcodeType);
}
storeField.setEnabled(false);
if(viewLoyaltyCard)
{
storeField.setEnabled(false);
noteField.setEnabled(false);
}
if(updateLoyaltyCard)
{
@@ -126,11 +160,14 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
throw new IllegalArgumentException("Unrecognized barcode format: " + formatString);
}
int generateWidth = 100;
int generateHeight = 100;
String cardIdString = cardIdField.getText().toString();
Log.i(TAG, "Card: " + cardIdString);
result = writer.encode(cardIdString, format, 1500, 400, null);
result = writer.encode(cardIdString, format, generateWidth, generateHeight, null);
final int WHITE = 0xFFFFFFFF;
final int BLACK = 0xFF000000;
@@ -154,11 +191,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
barcodeIdLayout.setVisibility(View.VISIBLE);
barcodeImageLayout.setVisibility(View.VISIBLE);
}
catch (WriterException e)
{
Log.e(TAG, "Failed to generate barcode", e);
}
catch(IllegalArgumentException e)
catch (WriterException | IllegalArgumentException e)
{
Log.e(TAG, "Failed to generate barcode", e);
}
@@ -170,7 +203,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
public void onClick(View v)
{
IntentIntegrator integrator = new IntentIntegrator(LoyaltyCardViewActivity.this);
integrator.setDesiredBarcodeFormats(IntentIntegrator.ONE_D_CODE_TYPES);
integrator.setDesiredBarcodeFormats(supportedBarcodeTypes);
String prompt = getResources().getString(R.string.scanCardBarcode);
integrator.setPrompt(prompt);
@@ -186,6 +219,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
public void onClick(final View v)
{
String store = storeField.getText().toString();
String note = noteField.getText().toString();
String cardId = cardIdField.getText().toString();
String barcodeType = barcodeTypeField.getText().toString();
@@ -203,12 +237,12 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
if(updateLoyaltyCard)
{
db.updateLoyaltyCard(loyaltyCardId, store, cardId, barcodeType);
db.updateLoyaltyCard(loyaltyCardId, store, note, cardId, barcodeType);
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
}
else
{
db.insertLoyaltyCard(store, cardId, barcodeType);
db.insertLoyaltyCard(store, note, cardId, barcodeType);
}
finish();

View File

@@ -1,19 +1,29 @@
package protect.card_locker;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.Calendar;
public class MainActivity extends AppCompatActivity
{
private static final String TAG = "LoyaltyCardLocker";
@Override
protected void onCreate(Bundle savedInstanceState)
{
@@ -92,6 +102,88 @@ public class MainActivity extends AppCompatActivity
return true;
}
if(id == R.id.action_import_export)
{
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
startActivity(i);
return true;
}
if(id == R.id.action_about)
{
displayAboutDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
private void displayAboutDialog()
{
final String[][] USED_LIBRARIES = new String[][]
{
new String[] {"Commons CSV", "https://commons.apache.org/proper/commons-csv/"},
new String[] {"ZXing", "https://github.com/zxing/zxing"},
new String[] {"ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded"},
};
StringBuilder libs = new StringBuilder().append("<ul>");
for (String[] library : USED_LIBRARIES)
{
libs.append("<li><a href=\"").append(library[1]).append("\">").append(library[0]).append("</a></li>");
}
libs.append("</ul>");
String appName = getString(R.string.app_name);
int year = Calendar.getInstance().get(Calendar.YEAR);
String version = "?";
try
{
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
version = pi.versionName;
}
catch (PackageManager.NameNotFoundException e)
{
Log.w(TAG, "Package name not found", e);
}
WebView wv = new WebView(this);
String html =
"<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />" +
"<img src=\"file:///android_res/mipmap/ic_launcher.png\" alt=\"" + appName + "\"/>" +
"<h1>" +
String.format(getString(R.string.about_title_fmt),
"<a href=\"" + getString(R.string.app_webpage_url)) + "\">" +
appName +
"</a>" +
"</h1><p>" +
appName +
" " +
String.format(getString(R.string.debug_version_fmt), version) +
"</p><p>" +
String.format(getString(R.string.app_revision_fmt),
"<a href=\"" + getString(R.string.app_revision_url) + "\">" +
getString(R.string.app_revision_url) +
"</a>") +
"</p><hr/><p>" +
String.format(getString(R.string.app_copyright_fmt), year) +
"</p><hr/><p>" +
getString(R.string.app_license) +
"</p><hr/><p>" +
String.format(getString(R.string.app_libraries), appName, libs.toString());
wv.loadDataWithBaseURL("file:///android_res/drawable/", html, "text/html", "utf-8", null);
new AlertDialog.Builder(this)
.setView(wv)
.setCancelable(true)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
})
.show();
}
}

View File

@@ -0,0 +1,57 @@
package protect.card_locker;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class MultiFormatExporter
{
private static final String TAG = "LoyaltyCardLocker";
/**
* Attempts to export data to the output stream in the
* given format, if possible.
*
* The output stream is closed on success.
*
* @return true if the database was successfully exported,
* 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)
{
DatabaseExporter exporter = null;
switch(format)
{
case CSV:
exporter = new CsvDatabaseExporter();
break;
}
if(exporter != null)
{
try
{
exporter.exportData(db, output);
return true;
}
catch(IOException e)
{
Log.e(TAG, "Failed to export data", e);
}
catch(InterruptedException e)
{
Log.e(TAG, "Failed to export data", e);
}
return false;
}
else
{
Log.e(TAG, "Unsupported data format exported: " + format.name());
return false;
}
}
}

View File

@@ -0,0 +1,62 @@
package protect.card_locker;
import android.util.Log;
import java.io.IOException;
import java.io.InputStreamReader;
public class MultiFormatImporter
{
private static final String TAG = "LoyaltyCardLocker";
/**
* Attempts to import data from the input stream of the
* given format into the database.
*
* The input stream is not closed, and doing so is the
* responsibility of the caller.
*
* @return true if the database was successfully imported,
* false otherwise. If false, no data was written to
* the database.
*/
public static boolean importData(DBHelper db, InputStreamReader input, DataFormat format)
{
DatabaseImporter importer = null;
switch(format)
{
case CSV:
importer = new CsvDatabaseImporter();
break;
}
if(importer != null)
{
try
{
importer.importData(db, input);
return true;
}
catch(IOException e)
{
Log.e(TAG, "Failed to input data", e);
}
catch(FormatException e)
{
Log.e(TAG, "Failed to input data", e);
}
catch(InterruptedException e)
{
Log.e(TAG, "Failed to input data", e);
}
return false;
}
else
{
Log.e(TAG, "Unsupported data format imported: " + format.name());
return false;
}
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:textSize="20sp"
android:layout_gravity="center"
android:text="@string/importExportHelp"/>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/importName"
android:layout_weight="1.0"
android:id="@+id/importButton"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/exportName"
android:layout_weight="1.0"
android:id="@+id/exportButton"/>
</LinearLayout>
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>

View File

@@ -13,7 +13,8 @@
android:id="@+id/store"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"/>
android:layout_weight="1.0"
android:maxLines="1"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"

View File

@@ -52,6 +52,28 @@
android:inputType="text"/>
</LinearLayout>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:textSize="16.0sp"
android:textStyle="bold"
android:layout_gravity="center_vertical"
android:paddingStart="20.0dip"
android:paddingEnd="20.0dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:labelFor="@+id/note"
android:text="@string/note" />
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText android:id="@+id/note"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="text"/>
</LinearLayout>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dip"
android:layout_width="fill_parent"

View File

@@ -7,4 +7,13 @@
android:icon="@drawable/ic_add_white_24dp"
android:title="@string/action_add"
app:showAsAction="always"/>
<item
android:id="@+id/action_import_export"
android:icon="@drawable/ic_import_export_white_24dp"
android:title="@string/importExport"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_about"
android:title="@string/about"
app:showAsAction="never"/>
</menu>

View File

@@ -1,4 +1,6 @@
<resources>
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Carte fedeltà</string>
<string name="action_add">Aggiungi</string>
@@ -25,4 +27,26 @@
<string name="noCardIdError">Nessun codice carta inserito</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="note">Note</string>
<string name="importExport">Importa/Esporta</string>
<string name="importName">Importa</string>
<string name="exportName">Esporta</string>
<string name="importExportHelp">I dati sono stati importati in o esportati dal file LoyaltyCardLocker.csv sulla memoria esterna</string>
<string name="importedFrom">Importato da: %1$s</string>
<string name="exportedTo">Esportato in: %1$s</string>
<string name="fileMissing">File mancante: %1$s</string>
<string name="importFailed">Impossibile importare: %1$s</string>
<string name="exportFailed">Impossibile esportare: %1$s</string>
<string name="importing">Importazione in corso&#8230;</string>
<string name="exporting">Esportazione in corso&#8230;</string>
<string name="noExternalStoragePermissionError">Impossibile importare o esportare i dati senza il permesso per l\'uso della memoria esterna.</string>
<string name="about">Informazioni</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_license">Pubblicato sotto licenza GPLv3.</string>
<string name="about_title_fmt">Informazioni su <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versione: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Informazione sulla revisione: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> usa le seguenti librerie di terze parti: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="ok">Ok</string>
</resources>

View File

@@ -0,0 +1,52 @@
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Klantenkaartenkluis</string>
<string name="action_add">Voeg toe</string>
<string name="noGiftCards">Er zijn momenteel geen klantenkaarten toegevoegd. Klik de knop met "+" (plus) om te beginnen.\n\nKlantenkaartenkluis beheert klantenkaarten op een smartphone of tablet, zodat ze altijd binnen handbereik zijn.</string>
<string name="storeName">Winkel</string>
<string name="cardId">Kaart-ID</string>
<string name="barcodeType">Barcodetype</string>
<string name="cancel">Annuleer</string>
<string name="save">Sla op</string>
<string name="capture">Scan kaart</string>
<string name="edit">Bewerk</string>
<string name="delete">Verwijder</string>
<string name="editCardTitle">Bewerk klantenkaart</string>
<string name="addCardTitle">Voeg klantenkaart toe</string>
<string name="viewCardTitle">Bekijk klantenkaart</string>
<string name="scanCardBarcode">Scan barcode klantenkaart</string>
<string name="barcodeImageDescription">Afbeelding barcode klantenkaart</string>
<string name="noStoreError">Geen winkel toegevoegd</string>
<string name="noCardIdError">Geen kaart-ID toegevoegd</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="note">Notitie</string>
<string name="importExport">Importeer/Exporteer</string>
<string name="importName">Importeer</string>
<string name="exportName">Exporteer</string>
<string name="importExportHelp">Data is geïmporteerd van of geëxporteerd naar LoyaltyCardLocker.csv op externe opslag</string>
<string name="importedFrom">Geïmporteerd van: %1$s</string>
<string name="exportedTo">Geëxporteerd naar: %1$s</string>
<string name="fileMissing">Bestand niet gevonden: %1$s</string>
<string name="importFailed">Importeren mislukte: %1$s</string>
<string name="exportFailed">Exporteren mislukte: %1$s</string>
<string name="importing">Importerende&#8230;</string>
<string name="exporting">Exporterende&#8230;</string>
<string name="noExternalStoragePermissionError">Niet mogelijk te importeren of exporteren zonder rechten op externe opslag</string>
<string name="about">Over</string>
<string name="app_copyright_fmt">Auteursrecht 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_license">Gelicenseerd met GPLv3.</string>
<string name="about_title_fmt">Over <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versie: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Revisieïnformatie: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> gebruikt de volgende bibliotheken van derden: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="ok">Oké</string>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_revision_url" translatable="false">https://github.com/brarcher/loyalty-card-locker/releases</string>
<string name="app_webpage_url" translatable="false">https://github.com/brarcher/loyalty-card-locker</string>
</resources>

View File

@@ -6,5 +6,5 @@
<dimen name="no_data_textSize">16sp</dimen>
<dimen name="no_data_padding">22dp</dimen>
<dimen name="barcode_disp_height">50dp</dimen>
<dimen name="barcode_disp_height">200dp</dimen>
</resources>

View File

@@ -1,10 +1,13 @@
<resources>
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Loyalty Card Locker</string>
<string name="action_add">Add</string>
<string name="noGiftCards">You don\'t have any loyalty cards at the moment. Click the "+" (plus) button up top to get started.\n\nLoyalty Card Locker lets you carry your loyalty cards on your phone, so they are always within reach.</string>
<string name="storeName">Store</string>
<string name="note">Note</string>
<string name="cardId">Card ID</string>
<string name="barcodeType">Barcode Type</string>
@@ -13,6 +16,7 @@
<string name="capture">Capture Card</string>
<string name="edit">Edit</string>
<string name="delete">Delete</string>
<string name="ok">OK</string>
<string name="editCardTitle">Edit Loyalty Card</string>
<string name="addCardTitle">Add Loyalty Card</string>
@@ -25,4 +29,26 @@
<string name="noCardIdError">No Card ID entered</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="storeNameAndNoteFormat" translatable="false">%1$s - %2$s</string>
<string name="importExport">Import/Export</string>
<string name="importName">Import</string>
<string name="exportName">Export</string>
<string name="importExportHelp">Data is imported to/exported from LoyaltyCardLocker.csv on external storage</string>
<string name="importedFrom">Imported from: %1$s</string>
<string name="exportedTo">Exported to: %1$s</string>
<string name="fileMissing">File missing: %1$s</string>
<string name="importFailed">Failed to import: %1$s</string>
<string name="exportFailed">Failed to export: %1$s</string>
<string name="importing">Importing&#8230;</string>
<string name="exporting">Exporting&#8230;</string>
<string name="noExternalStoragePermissionError">Unable to import or export cards without the external storage permission</string>
<string name="about">About</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_license">Licensed under the GPLv3.</string>
<string name="about_title_fmt">About <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Version: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Revision Information: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> uses the following third-party libraries: <xliff:g id="app_libraries_list">%s</xliff:g></string>
</resources>

View File

@@ -1,7 +1,9 @@
package protect.card_locker;
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.google.zxing.BarcodeFormat;
@@ -34,13 +36,14 @@ public class DatabaseTest
public void addRemoveOneGiftCard()
{
assertEquals(0, db.getLoyaltyCardCount());
boolean result = db.insertLoyaltyCard("store", "cardId", BarcodeFormat.UPC_A.toString());
boolean result = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString());
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard loyaltyCard = db.getLoyaltyCard(1);
assertNotNull(loyaltyCard);
assertEquals("store", loyaltyCard.store);
assertEquals("note", loyaltyCard.note);
assertEquals("cardId", loyaltyCard.cardId);
assertEquals(BarcodeFormat.UPC_A.toString(), loyaltyCard.barcodeType);
@@ -53,17 +56,18 @@ public class DatabaseTest
@Test
public void updateGiftCard()
{
boolean result = db.insertLoyaltyCard("store", "cardId", BarcodeFormat.UPC_A.toString());
boolean result = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString());
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
result = db.updateLoyaltyCard(1, "store1", "cardId1", BarcodeFormat.AZTEC.toString());
result = db.updateLoyaltyCard(1, "store1", "note1", "cardId1", BarcodeFormat.AZTEC.toString());
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard loyaltyCard = db.getLoyaltyCard(1);
assertNotNull(loyaltyCard);
assertEquals("store1", loyaltyCard.store);
assertEquals("note1", loyaltyCard.note);
assertEquals("cardId1", loyaltyCard.cardId);
assertEquals(BarcodeFormat.AZTEC.toString(), loyaltyCard.barcodeType);
}
@@ -73,7 +77,8 @@ public class DatabaseTest
{
assertEquals(0, db.getLoyaltyCardCount());
boolean result = db.updateLoyaltyCard(1, "store1", "cardId1", BarcodeFormat.UPC_A.toString());
boolean result = db.updateLoyaltyCard(1, "store1", "note1", "cardId1",
BarcodeFormat.UPC_A.toString());
assertEquals(false, result);
assertEquals(0, db.getLoyaltyCardCount());
}
@@ -81,13 +86,14 @@ public class DatabaseTest
@Test
public void emptyGiftCardValues()
{
boolean result = db.insertLoyaltyCard("", "", "");
boolean result = db.insertLoyaltyCard("", "", "", "");
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard loyaltyCard = db.getLoyaltyCard(1);
assertNotNull(loyaltyCard);
assertEquals("", loyaltyCard.store);
assertEquals("", loyaltyCard.note);
assertEquals("", loyaltyCard.cardId);
assertEquals("", loyaltyCard.barcodeType);
}
@@ -101,7 +107,8 @@ public class DatabaseTest
// that they are sorted
for(int index = CARDS_TO_ADD-1; index >= 0; index--)
{
boolean result = db.insertLoyaltyCard("store" + index, "cardId" + index, BarcodeFormat.UPC_A.toString());
boolean result = db.insertLoyaltyCard("store" + index, "note" + index, "cardId" + index,
BarcodeFormat.UPC_A.toString());
assertTrue(result);
}
@@ -117,6 +124,7 @@ public class DatabaseTest
for(int index = 0; index < CARDS_TO_ADD; index++)
{
assertEquals("store"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE)));
assertEquals("note"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE)));
assertEquals("cardId"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID)));
assertEquals(BarcodeFormat.UPC_A.toString(), cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE)));
@@ -125,4 +133,54 @@ public class DatabaseTest
assertTrue(cursor.isAfterLast());
}
private void setupDatabaseVersion1(SQLiteDatabase database)
{
// Delete the tables as they exist now
database.execSQL("drop table " + DBHelper.LoyaltyCardDbIds.TABLE);
// Create the table as it existed in revision 1
database.execSQL("create table " + DBHelper.LoyaltyCardDbIds.TABLE + "(" +
DBHelper.LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
DBHelper.LoyaltyCardDbIds.STORE + " TEXT not null," +
DBHelper.LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE + " TEXT not null)");
}
private int insertCardVersion1(SQLiteDatabase database,
final String store, final String cardId,
final String barcodeType)
{
ContentValues contentValues = new ContentValues();
contentValues.put(DBHelper.LoyaltyCardDbIds.STORE, store);
contentValues.put(DBHelper.LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
final long newId = database.insert(DBHelper.LoyaltyCardDbIds.TABLE, null, contentValues);
assertTrue(newId != -1);
return (int)newId;
}
@Test
public void databaseUpgradeFromVersion1()
{
SQLiteDatabase database = db.getWritableDatabase();
// Setup the database as it appeared in revision 1
setupDatabaseVersion1(database);
// Insert a budget and transaction
int newCardId = insertCardVersion1(database, "store", "cardId", BarcodeFormat.UPC_A.toString());
// Upgrade database
db.onUpgrade(database, DBHelper.ORIGINAL_DATABASE_VERSION, DBHelper.DATABASE_VERSION);
// Determine that the entries are queryable and the fields are correct
LoyaltyCard card = db.getLoyaltyCard(newCardId);
assertEquals("store", card.store);
assertEquals("cardId", card.cardId);
assertEquals(BarcodeFormat.UPC_A.toString(), card.barcodeType);
assertEquals("", card.note);
database.close();
}
}

View File

@@ -0,0 +1,242 @@
package protect.card_locker;
import android.app.Activity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.google.zxing.BarcodeFormat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Calendar;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 17)
public class ImportExportTest
{
private Activity activity;
private DBHelper db;
private long nowMs;
private long lastYearMs;
private final int MONTHS_PER_YEAR = 12;
private final String BARCODE_DATA = "428311627547";
private final String BARCODE_TYPE = BarcodeFormat.UPC_A.name();
@Before
public void setUp()
{
activity = Robolectric.setupActivity(MainActivity.class);
db = new DBHelper(activity);
nowMs = System.currentTimeMillis();
Calendar lastYear = Calendar.getInstance();
lastYear.set(Calendar.YEAR, lastYear.get(Calendar.YEAR)-1);
lastYearMs = lastYear.getTimeInMillis();
}
/**
* Add the given number of cards, each with
* an index in the store name.
* @param cardsToAdd
*/
private void addLoyaltyCards(int cardsToAdd)
{
// Add in reverse order to test sorting
for(int index = cardsToAdd; index > 0; index--)
{
String storeName = String.format("store, \"%4d", index);
String note = String.format("note, \"%4d", index);
boolean result = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE);
assertTrue(result);
}
assertEquals(cardsToAdd, db.getLoyaltyCardCount());
}
/**
* Check that all of the cards follow the pattern
* specified in addLoyaltyCards(), and are in sequential order
* where the smallest card's index is 1
*/
private void checkLoyaltyCards()
{
Cursor cursor = db.getLoyaltyCardCursor();
int index = 1;
while(cursor.moveToNext())
{
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
String expectedStore = String.format("store, \"%4d", index);
String expectedNote = String.format("note, \"%4d", index);
assertEquals(expectedStore, card.store);
assertEquals(expectedNote, card.note);
assertEquals(BARCODE_DATA, card.cardId);
assertEquals(BARCODE_TYPE, card.barcodeType);
index++;
}
cursor.close();
}
/**
* Delete the contents of the database
*/
private void clearDatabase()
{
SQLiteDatabase database = db.getWritableDatabase();
database.execSQL("delete from " + DBHelper.LoyaltyCardDbIds.TABLE);
database.close();
assertEquals(0, db.getLoyaltyCardCount());
}
@Test
public void multipleCardsExportImport() throws IOException
{
final int NUM_CARDS = 1000;
for(DataFormat format : DataFormat.values())
{
addLoyaltyCards(NUM_CARDS);
ByteArrayOutputStream outData = new ByteArrayOutputStream();
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
boolean result = MultiFormatExporter.exportData(db, outStream, format);
assertTrue(result);
outStream.close();
clearDatabase();
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
InputStreamReader inStream = new InputStreamReader(inData);
// Import the CSV data
result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV);
assertTrue(result);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
checkLoyaltyCards();
// Clear the database for the next format under test
clearDatabase();
}
}
@Test
public void importExistingCardsNotReplace() throws IOException
{
final int NUM_CARDS = 1000;
for(DataFormat format : DataFormat.values())
{
addLoyaltyCards(NUM_CARDS);
ByteArrayOutputStream outData = new ByteArrayOutputStream();
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export into CSV data
boolean result = MultiFormatExporter.exportData(db, outStream, format);
assertTrue(result);
outStream.close();
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
InputStreamReader inStream = new InputStreamReader(inData);
// Import the CSV data on top of the existing database
result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV);
assertTrue(result);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
checkLoyaltyCards();
// Clear the database for the next format under test
clearDatabase();
}
}
@Test
public void corruptedImportNothingSaved() throws IOException
{
final int NUM_CARDS = 1000;
for(DataFormat format : DataFormat.values())
{
addLoyaltyCards(NUM_CARDS);
ByteArrayOutputStream outData = new ByteArrayOutputStream();
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
boolean result = MultiFormatExporter.exportData(db, outStream, format);
assertTrue(result);
clearDatabase();
String corruptEntry = "ThisStringIsLikelyNotPartOfAnyFormat";
ByteArrayInputStream inData = new ByteArrayInputStream((outData.toString() + corruptEntry).getBytes());
InputStreamReader inStream = new InputStreamReader(inData);
// Attempt to import the CSV data
result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV);
assertEquals(false, result);
assertEquals(0, db.getLoyaltyCardCount());
}
}
@Test
public void useImportExportTask()
{
final int NUM_CARDS = 10;
for(DataFormat format : DataFormat.values())
{
addLoyaltyCards(NUM_CARDS);
// Export to whatever the default location is
ImportExportTask task = new ImportExportTask(activity, false, format);
task.execute();
// Actually run the task to completion
Robolectric.flushBackgroundThreadScheduler();
clearDatabase();
// Import everything back from the default location
task = new ImportExportTask(activity, true, format);
task.execute();
// Actually run the task to completion
Robolectric.flushBackgroundThreadScheduler();
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
checkLoyaltyCards();
// Clear the database for the next format under test
clearDatabase();
}
}
}

View File

@@ -7,12 +7,12 @@ import android.widget.TextView;
import com.google.zxing.BarcodeFormat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
import static org.junit.Assert.assertEquals;
@@ -20,32 +20,72 @@ import static org.junit.Assert.assertEquals;
@Config(constants = BuildConfig.class, sdk = 17)
public class LoyaltyCardCursorAdapterTest
{
@Test
public void TestCursorAdapter()
private Activity activity;
private DBHelper db;
@Before
public void setUp()
{
ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create();
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "cardId", BarcodeFormat.UPC_A.toString());
LoyaltyCard card = db.getLoyaltyCard(1);
Cursor cursor = db.getLoyaltyCardCursor();
cursor.moveToFirst();
activity = Robolectric.setupActivity(MainActivity.class);
db = new DBHelper(activity);
}
private View createView(Cursor cursor)
{
LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(activity.getApplicationContext(), cursor);
View view = adapter.newView(activity.getApplicationContext(), cursor, null);
adapter.bindView(view, activity.getApplicationContext(), cursor);
final TextView storeField = (TextView) view.findViewById(R.id.store);
return view;
}
assertEquals(card.store, storeField.getText().toString());
private void checkView(final View view, final String store, final String cardId)
{
final TextView storeField = (TextView) view.findViewById(R.id.store);
assertEquals(store, storeField.getText().toString());
final TextView cardIdField = (TextView) view.findViewById(R.id.cardId);
assertEquals(cardId, cardIdField.getText().toString());
}
@Test
public void TestCursorAdapterEmptyNote()
{
db.insertLoyaltyCard("store", "", "cardId", BarcodeFormat.UPC_A.toString());
LoyaltyCard card = db.getLoyaltyCard(1);
Cursor cursor = db.getLoyaltyCardCursor();
cursor.moveToFirst();
View view = createView(cursor);
final String cardIdLabel = activity.getResources().getString(R.string.cardId);
final String cardIdFormat = activity.getResources().getString(R.string.cardIdFormat);
String cardIdText = String.format(cardIdFormat, cardIdLabel, "cardId");
assertEquals(cardIdText, cardIdField.getText().toString());
String cardIdText = String.format(cardIdFormat, cardIdLabel, card.cardId);
checkView(view, card.store, cardIdText);
}
@Test
public void TestCursorAdapterWithNote()
{
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString());
LoyaltyCard card = db.getLoyaltyCard(1);
Cursor cursor = db.getLoyaltyCardCursor();
cursor.moveToFirst();
View view = createView(cursor);
final String storeNameAndNoteFormat = activity.getResources().getString(R.string.storeNameAndNoteFormat);
String storeAndNoteText = String.format(storeNameAndNoteFormat, card.store, card.note);
final String cardIdLabel = activity.getResources().getString(R.string.cardId);
final String cardIdFormat = activity.getResources().getString(R.string.cardIdFormat);
String cardIdText = String.format(cardIdFormat, cardIdLabel, card.cardId);
checkView(view, storeAndNoteText, cardIdText);
}
}

View File

@@ -68,7 +68,8 @@ public class LoyaltyCardViewActivityTest
* expected values
*/
private void saveLoyaltyCardWithArguments(final Activity activity,
final String store, final String cardId,
final String store, final String note,
final String cardId,
final String barcodeType,
boolean creatingNewCard)
{
@@ -83,12 +84,14 @@ public class LoyaltyCardViewActivityTest
}
final EditText storeField = (EditText) activity.findViewById(R.id.storeName);
final EditText noteField = (EditText) activity.findViewById(R.id.note);
final EditText cardIdField = (EditText) activity.findViewById(R.id.cardId);
final EditText barcodeTypeField = (EditText) activity.findViewById(R.id.barcodeType);
final Button saveButton = (Button) activity.findViewById(R.id.saveButton);
storeField.setText(store);
noteField.setText(note);
cardIdField.setText(cardId);
barcodeTypeField.setText(barcodeType);
@@ -100,6 +103,7 @@ public class LoyaltyCardViewActivityTest
LoyaltyCard card = db.getLoyaltyCard(1);
assertEquals(store, card.store);
assertEquals(note, card.note);
assertEquals(cardId, card.cardId);
assertEquals(barcodeType, card.barcodeType);
}
@@ -141,11 +145,12 @@ public class LoyaltyCardViewActivityTest
}
private void checkFieldProperties(final Activity activity, final int id, final int visibility,
final String contents)
boolean enabled, final String contents)
{
final View view = activity.findViewById(id);
assertNotNull(view);
assertEquals(visibility, view.getVisibility());
assertEquals(enabled, view.isEnabled());
if(contents != null)
{
TextView textView = (TextView)view;
@@ -153,22 +158,24 @@ public class LoyaltyCardViewActivityTest
}
}
private void checkAllFields(final Activity activity, final String store, final String cardId,
final String barcodeType)
private void checkAllFields(final Activity activity, boolean editMode, final String store,
final String note, final String cardId, final String barcodeType)
{
int cardIdVisibility = cardId.isEmpty() ? View.GONE : View.VISIBLE;
int captureVisibility = editMode ? View.VISIBLE : View.GONE;
checkFieldProperties(activity, R.id.storeName, View.VISIBLE, store);
checkFieldProperties(activity, R.id.cardId, View.VISIBLE, cardId);
checkFieldProperties(activity, R.id.barcodeType, View.VISIBLE, barcodeType);
checkFieldProperties(activity, R.id.captureButton, View.VISIBLE, null);
checkFieldProperties(activity, R.id.saveButton, View.VISIBLE, null);
checkFieldProperties(activity, R.id.cancelButton, View.VISIBLE, null);
checkFieldProperties(activity, R.id.barcode, View.VISIBLE, null);
checkFieldProperties(activity, R.id.storeName, View.VISIBLE, editMode, store);
checkFieldProperties(activity, R.id.note, View.VISIBLE, editMode, note);
checkFieldProperties(activity, R.id.cardId, View.VISIBLE, false, cardId);
checkFieldProperties(activity, R.id.barcodeType, View.VISIBLE, false, barcodeType);
checkFieldProperties(activity, R.id.captureButton, captureVisibility, true, null);
checkFieldProperties(activity, R.id.saveButton, captureVisibility, true, null);
checkFieldProperties(activity, R.id.cancelButton, captureVisibility, true, null);
checkFieldProperties(activity, R.id.barcode, View.VISIBLE, true, null);
checkFieldProperties(activity, R.id.barcodeIdLayout, cardIdVisibility, null);
checkFieldProperties(activity, R.id.barcodeLayout, cardIdVisibility, null);
checkFieldProperties(activity, R.id.barcodeTypeLayout, View.GONE, null);
checkFieldProperties(activity, R.id.barcodeIdLayout, cardIdVisibility, true, null);
checkFieldProperties(activity, R.id.barcodeLayout, cardIdVisibility, true, null);
checkFieldProperties(activity, R.id.barcodeTypeLayout, View.GONE, true, null);
}
@Test
@@ -181,7 +188,7 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
checkAllFields(activity, "", "", "");
checkAllFields(activity, true, "", "", "", "");
}
@Test
@@ -197,6 +204,7 @@ public class LoyaltyCardViewActivityTest
assertEquals(0, db.getLoyaltyCardCount());
final EditText storeField = (EditText) activity.findViewById(R.id.storeName);
final EditText noteField = (EditText) activity.findViewById(R.id.note);
final EditText cardIdField = (EditText) activity.findViewById(R.id.cardId);
final Button saveButton = (Button) activity.findViewById(R.id.saveButton);
@@ -208,6 +216,10 @@ public class LoyaltyCardViewActivityTest
saveButton.performClick();
assertEquals(0, db.getLoyaltyCardCount());
noteField.setText("note");
saveButton.performClick();
assertEquals(0, db.getLoyaltyCardCount());
cardIdField.setText("cardId");
saveButton.performClick();
assertEquals(0, db.getLoyaltyCardCount());
@@ -231,7 +243,7 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithoutParametersCaptureBarcodeCreateGiftCard() throws IOException
public void startWithoutParametersCaptureBarcodeCreateLoyaltyCard() throws IOException
{
registerMediaStoreIntentHandler();
@@ -242,15 +254,15 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
checkAllFields(activity, "", "", "");
checkAllFields(activity, true, "", "", "", "");
// Complete barcode capture successfully
captureBarcodeWithResult(activity, R.id.captureButton, true);
checkAllFields(activity, "", BARCODE_DATA, BARCODE_TYPE);
checkAllFields(activity, true, "", "", BARCODE_DATA, BARCODE_TYPE);
// Save and check the gift card
saveLoyaltyCardWithArguments(activity, "store", BARCODE_DATA, BARCODE_TYPE, true);
saveLoyaltyCardWithArguments(activity, "store", "note", BARCODE_DATA, BARCODE_TYPE, true);
}
@Test
@@ -263,12 +275,12 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
checkAllFields(activity, "", "", "");
checkAllFields(activity, true, "", "", "", "");
// Complete barcode capture in failure
captureBarcodeWithResult(activity, R.id.captureButton, false);
checkAllFields(activity, "", "", "");
checkAllFields(activity, true, "", "", "", "");
}
@Test
@@ -281,12 +293,12 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
checkAllFields(activity, "", "", "");
checkAllFields(activity, true, "", "", "", "");
// Complete barcode capture successfully
captureBarcodeWithResult(activity, R.id.captureButton, true);
checkAllFields(activity, "", BARCODE_DATA, BARCODE_TYPE);
checkAllFields(activity, true, "", "", BARCODE_DATA, BARCODE_TYPE);
// Cancel the gift card creation
final Button cancelButton = (Button) activity.findViewById(R.id.cancelButton);
@@ -295,73 +307,98 @@ public class LoyaltyCardViewActivityTest
assertEquals(true, activity.isFinishing());
}
private ActivityController createActivityWithLoyaltyCard()
private ActivityController createActivityWithLoyaltyCard(boolean editMode)
{
Intent intent = new Intent();
final Bundle bundle = new Bundle();
bundle.putInt("id", 1);
bundle.putBoolean("update", true);
if(editMode)
{
bundle.putBoolean("update", true);
}
else
{
bundle.putBoolean("view", true);
}
intent.putExtras(bundle);
return Robolectric.buildActivity(LoyaltyCardViewActivity.class).withIntent(intent).create();
}
@Test
public void startWithGiftCardCheckDisplay() throws IOException
public void startWithLoyaltyCardEditModeCheckDisplay() throws IOException
{
ActivityController activityController = createActivityWithLoyaltyCard();
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", BARCODE_DATA, BARCODE_TYPE);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE);
activityController.start();
activityController.visible();
activityController.resume();
checkAllFields(activity, "store", BARCODE_DATA, BARCODE_TYPE);
checkAllFields(activity, true, "store", "note", BARCODE_DATA, BARCODE_TYPE);
}
@Test
public void startWithLoyaltyCardViewModeCheckDisplay() throws IOException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE);
activityController.start();
activityController.visible();
activityController.resume();
checkAllFields(activity, false, "store", "note", BARCODE_DATA, BARCODE_TYPE);
}
@Test
public void startWithLoyaltyCardWithBarcodeUpdateBarcode() throws IOException
{
ActivityController activityController = createActivityWithLoyaltyCard();
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
db.insertLoyaltyCard("store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
activityController.start();
activityController.visible();
activityController.resume();
checkAllFields(activity, "store", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
checkAllFields(activity, true, "store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
// Complete barcode capture successfully
captureBarcodeWithResult(activity, R.id.captureButton, true);
checkAllFields(activity, "store", BARCODE_DATA, BARCODE_TYPE);
checkAllFields(activity, true, "store", "note", BARCODE_DATA, BARCODE_TYPE);
}
@Test
public void startWithGiftCardWithReceiptUpdateReceiptCancel() throws IOException
public void startWithLoyaltyCardWithReceiptUpdateReceiptCancel() throws IOException
{
ActivityController activityController = createActivityWithLoyaltyCard();
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
db.insertLoyaltyCard("store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
activityController.start();
activityController.visible();
activityController.resume();
checkAllFields(activity, "store", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
checkAllFields(activity, true, "store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
// Complete barcode capture successfully
captureBarcodeWithResult(activity, R.id.captureButton, true);
checkAllFields(activity, "store", BARCODE_DATA, BARCODE_TYPE);
checkAllFields(activity, true, "store", "note", BARCODE_DATA, BARCODE_TYPE);
// Cancel the gift card creation
final Button cancelButton = (Button) activity.findViewById(R.id.cancelButton);

View File

@@ -50,9 +50,11 @@ public class MainActivityTest
assertTrue(menu != null);
// The settings and add button should be present
assertEquals(menu.size(), 1);
assertEquals(menu.size(), 3);
assertEquals("Add", menu.findItem(R.id.action_add).getTitle().toString());
assertEquals("Import/Export", menu.findItem(R.id.action_import_export).getTitle().toString());
assertEquals("About", menu.findItem(R.id.action_about).getTitle().toString());
}
@Test
@@ -83,7 +85,7 @@ public class MainActivityTest
assertEquals(0, list.getCount());
DBHelper db = new DBHelper(mainActivity);
db.insertLoyaltyCard("store", "cardId", BarcodeFormat.UPC_A.toString());
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString());
assertEquals(View.VISIBLE, helpText.getVisibility());
assertEquals(View.GONE, list.getVisibility());

View File

@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
classpath 'com.android.tools.build:gradle:2.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file
View File

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Tue May 03 10:42:45 EDT 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip

160
gradlew vendored Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega