diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..c6cbe562a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 000000000..b9acfb4b5
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,38 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.2"
+
+ defaultConfig {
+ applicationId "protect.card_locker"
+ minSdkVersion 17
+ targetSdkVersion 23
+ versionCode 1
+ versionName "0.1"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ lintOptions {
+ disable "GoogleAppIndexingWarning"
+ disable "ButtonStyle"
+ disable "AlwaysShowAction"
+ }
+
+ // This is for Robolectric support for SDK 23
+ useLibrary 'org.apache.http.legacy'
+}
+
+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'
+ testCompile 'junit:junit:4.12'
+ testCompile "org.robolectric:robolectric:3.0"
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 000000000..147200682
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/brarcher/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/app/src/androidTest/java/protect/card_locker/ApplicationTest.java b/app/src/androidTest/java/protect/card_locker/ApplicationTest.java
new file mode 100644
index 000000000..802030b5b
--- /dev/null
+++ b/app/src/androidTest/java/protect/card_locker/ApplicationTest.java
@@ -0,0 +1,15 @@
+package protect.card_locker;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase
+{
+ public ApplicationTest()
+ {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..52c803ddc
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/protect/card_locker/DBHelper.java b/app/src/main/java/protect/card_locker/DBHelper.java
new file mode 100644
index 000000000..6d2890b4f
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/DBHelper.java
@@ -0,0 +1,128 @@
+package protect.card_locker;
+
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+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;
+
+ class LoyaltyCardDbIds
+ {
+ public static final String TABLE = "cards";
+ public static final String ID = "_id";
+ public static final String STORE = "store";
+ public static final String CARD_ID = "cardid";
+ public static final String BARCODE_TYPE = "barcodetype";
+ }
+
+ public DBHelper(Context context)
+ {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db)
+ {
+ // create table for gift cards
+ db.execSQL("create table " + LoyaltyCardDbIds.TABLE + "(" +
+ LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
+ LoyaltyCardDbIds.STORE + " TEXT not null," +
+ LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
+ LoyaltyCardDbIds.BARCODE_TYPE + " TEXT not null)");
+ }
+
+ @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);
+ }
+
+ public boolean insertLoyaltyCard(final String store, final String cardId, final String barcodeType)
+ {
+ SQLiteDatabase db = getWritableDatabase();
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(LoyaltyCardDbIds.STORE, store);
+ 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 updateLoyaltyCard(final int id, final String store, final String cardId,
+ final String barcodeType)
+ {
+ SQLiteDatabase db = getWritableDatabase();
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(LoyaltyCardDbIds.STORE, store);
+ contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
+ contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
+ int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,
+ LoyaltyCardDbIds.ID + "=?",
+ new String[]{Integer.toString(id)});
+ return (rowsUpdated == 1);
+ }
+
+ public LoyaltyCard getLoyaltyCard(final int id)
+ {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor data = db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
+ " where " + LoyaltyCardDbIds.ID + "=?", new String[]{String.format("%d", id)});
+
+ LoyaltyCard card = null;
+
+ if(data.getCount() == 1)
+ {
+ data.moveToFirst();
+ card = LoyaltyCard.toLoyaltyCard(data);
+ }
+
+ data.close();
+
+ return card;
+ }
+
+ public boolean deleteLoyaltyCard (final int id)
+ {
+ SQLiteDatabase db = getWritableDatabase();
+ int rowsDeleted = db.delete(LoyaltyCardDbIds.TABLE,
+ LoyaltyCardDbIds.ID + " = ? ",
+ new String[]{String.format("%d", id)});
+ return (rowsDeleted == 1);
+ }
+
+ public Cursor getLoyaltyCardCursor()
+ {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor res = db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
+ " ORDER BY " + LoyaltyCardDbIds.STORE, null);
+ return res;
+ }
+
+ public int getLoyaltyCardCount()
+ {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbIds.TABLE, null);
+
+ int numItems = 0;
+
+ if(data.getCount() == 1)
+ {
+ data.moveToFirst();
+ numItems = data.getInt(0);
+ }
+
+ data.close();
+
+ return numItems;
+ }
+}
+
diff --git a/app/src/main/java/protect/card_locker/LoyaltyCard.java b/app/src/main/java/protect/card_locker/LoyaltyCard.java
new file mode 100644
index 000000000..504692fac
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/LoyaltyCard.java
@@ -0,0 +1,29 @@
+package protect.card_locker;
+
+import android.database.Cursor;
+
+public class LoyaltyCard
+{
+ public final int id;
+ public final String store;
+ public final String cardId;
+ public final String barcodeType;
+
+ public LoyaltyCard(final int id, final String store, final String cardId, final String barcodeType)
+ {
+ this.id = id;
+ this.store = store;
+ this.cardId = cardId;
+ this.barcodeType = barcodeType;
+ }
+
+ public static LoyaltyCard toLoyaltyCard(Cursor cursor)
+ {
+ int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
+ String store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
+ 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);
+ }
+}
diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java b/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java
new file mode 100644
index 000000000..4bec48ca0
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/LoyaltyCardCursorAdapter.java
@@ -0,0 +1,46 @@
+package protect.card_locker;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.TextView;
+
+class LoyaltyCardCursorAdapter extends CursorAdapter
+{
+ public LoyaltyCardCursorAdapter(Context context, Cursor cursor)
+ {
+ super(context, cursor, 0);
+ }
+
+ // The newView method is used to inflate a new view and return it,
+ // you don't bind any data to the view at this point.
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent)
+ {
+ return LayoutInflater.from(context).inflate(R.layout.loyalty_card_layout, parent, false);
+ }
+
+ // The bindView method is used to bind all data to a given view
+ // such as setting the text on a TextView.
+ @Override
+ public void bindView(View view, Context context, Cursor cursor)
+ {
+ // Find fields to populate in inflated template
+ TextView storeField = (TextView) view.findViewById(R.id.store);
+ TextView cardIdField = (TextView) view.findViewById(R.id.cardId);
+
+ // Extract properties from cursor
+ LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(cursor);
+
+ // Populate fields with extracted properties
+ storeField.setText(loyaltyCard.store);
+
+ String cardIdFormat = view.getResources().getString(R.string.cardIdFormat);
+ String cardIdLabel = view.getResources().getString(R.string.cardId);
+ String cardIdText = String.format(cardIdFormat, cardIdLabel, loyaltyCard.cardId);
+ cardIdField.setText(cardIdText);
+ }
+}
diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java
new file mode 100644
index 000000000..c607c0dd6
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java
@@ -0,0 +1,301 @@
+package protect.card_locker;
+
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.design.widget.Snackbar;
+import android.support.v7.app.ActionBar;
+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.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.integration.android.IntentIntegrator;
+import com.google.zxing.integration.android.IntentResult;
+
+
+public class LoyaltyCardViewActivity extends AppCompatActivity
+{
+ private static final String TAG = "CardLocker";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.loyalty_card_view_activity);
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ ActionBar actionBar = getSupportActionBar();
+ if(actionBar != null)
+ {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ @Override
+ public void onResume()
+ {
+ super.onResume();
+
+ final Bundle b = getIntent().getExtras();
+ final int loyaltyCardId = b != null ? b.getInt("id") : 0;
+ final boolean updateLoyaltyCard = b != null && b.getBoolean("update", false);
+ final boolean viewLoyaltyCard = b != null && b.getBoolean("view", false);
+
+ Log.i(TAG, "To view card: " + loyaltyCardId);
+
+ final EditText storeField = (EditText) findViewById(R.id.storeName);
+ final EditText cardIdField = (EditText) findViewById(R.id.cardId);
+ final EditText barcodeTypeField = (EditText) findViewById(R.id.barcodeType);
+ final ImageView barcodeImage = (ImageView) findViewById(R.id.barcode);
+ final View barcodeIdLayout = findViewById(R.id.barcodeIdLayout);
+ final View barcodeTypeLayout = findViewById(R.id.barcodeTypeLayout);
+ final View barcodeImageLayout = findViewById(R.id.barcodeLayout);
+ final View barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
+
+ final Button captureButton = (Button) findViewById(R.id.captureButton);
+ final Button saveButton = (Button) findViewById(R.id.saveButton);
+ final Button cancelButton = (Button) findViewById(R.id.cancelButton);
+
+ final DBHelper db = new DBHelper(this);
+
+ if(updateLoyaltyCard || viewLoyaltyCard)
+ {
+ final LoyaltyCard loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
+
+ storeField.setText(loyaltyCard.store);
+
+ if(cardIdField.getText().length() == 0)
+ {
+ cardIdField.setText(loyaltyCard.cardId);
+ }
+
+ if(barcodeTypeField.getText().length() == 0)
+ {
+ barcodeTypeField.setText(loyaltyCard.barcodeType);
+ }
+
+ storeField.setEnabled(false);
+
+ if(updateLoyaltyCard)
+ {
+ setTitle(R.string.editCardTitle);
+ }
+ else
+ {
+ barcodeCaptureLayout.setVisibility(View.GONE);
+ captureButton.setVisibility(View.GONE);
+ saveButton.setVisibility(View.GONE);
+ cancelButton.setVisibility(View.GONE);
+ setTitle(R.string.viewCardTitle);
+ }
+ }
+ else
+ {
+ setTitle(R.string.addCardTitle);
+ }
+
+ if(cardIdField.getText().length() == 0)
+ {
+ barcodeIdLayout.setVisibility(View.GONE);
+ }
+
+ barcodeTypeLayout.setVisibility(View.GONE);
+
+ if(cardIdField.getText().length() > 0 && barcodeTypeField.getText().length() > 0)
+ {
+ MultiFormatWriter writer = new MultiFormatWriter();
+ BitMatrix result;
+ try
+ {
+ String formatString = barcodeTypeField.getText().toString();
+ BarcodeFormat format = BarcodeFormat.valueOf(formatString);
+ if(format == null)
+ {
+ throw new IllegalArgumentException("Unrecognized barcode format: " + formatString);
+ }
+
+ String cardIdString = cardIdField.getText().toString();
+
+ Log.i(TAG, "Card: " + cardIdString);
+
+ result = writer.encode(cardIdString, format, 1500, 400, null);
+
+ final int WHITE = 0xFFFFFFFF;
+ final int BLACK = 0xFF000000;
+
+ int width = result.getWidth();
+ int height = result.getHeight();
+ int[] pixels = new int[width * height];
+ for (int y = 0; y < height; y++)
+ {
+ int offset = y * width;
+ for (int x = 0; x < width; x++)
+ {
+ pixels[offset + x] = result.get(x, y) ? BLACK : WHITE;
+ }
+ }
+ Bitmap bitmap = Bitmap.createBitmap(width, height,
+ Bitmap.Config.ARGB_8888);
+ bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
+ barcodeImage.setImageBitmap(bitmap);
+
+ barcodeIdLayout.setVisibility(View.VISIBLE);
+ barcodeImageLayout.setVisibility(View.VISIBLE);
+ }
+ catch (WriterException e)
+ {
+ Log.e(TAG, "Failed to generate barcode", e);
+ }
+ catch(IllegalArgumentException e)
+ {
+ Log.e(TAG, "Failed to generate barcode", e);
+ }
+ }
+
+ View.OnClickListener captureCallback = new View.OnClickListener()
+ {
+ @Override
+ public void onClick(View v)
+ {
+ IntentIntegrator integrator = new IntentIntegrator(LoyaltyCardViewActivity.this);
+ integrator.setDesiredBarcodeFormats(IntentIntegrator.PRODUCT_CODE_TYPES);
+
+ String prompt = getResources().getString(R.string.scanCardBarcode);
+ integrator.setPrompt(prompt);
+ integrator.initiateScan();
+ }
+ };
+
+ captureButton.setOnClickListener(captureCallback);
+
+ saveButton.setOnClickListener(new View.OnClickListener()
+ {
+ @Override
+ public void onClick(final View v)
+ {
+ String store = storeField.getText().toString();
+ String cardId = cardIdField.getText().toString();
+ String barcodeType = barcodeTypeField.getText().toString();
+
+ if(store.isEmpty())
+ {
+ Snackbar.make(v, R.string.noStoreError, Snackbar.LENGTH_LONG).show();
+ return;
+ }
+
+ if(cardId.isEmpty() || barcodeType.isEmpty())
+ {
+ Snackbar.make(v, R.string.noCardIdError, Snackbar.LENGTH_LONG).show();
+ return;
+ }
+
+ if(updateLoyaltyCard)
+ {
+ db.updateLoyaltyCard(loyaltyCardId, store, cardId, barcodeType);
+ Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
+ }
+ else
+ {
+ db.insertLoyaltyCard(store, cardId, barcodeType);
+ }
+
+ finish();
+ }
+ });
+
+ cancelButton.setOnClickListener(new View.OnClickListener()
+ {
+ @Override
+ public void onClick(View v)
+ {
+ finish();
+ }
+ });
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu)
+ {
+ final Bundle b = getIntent().getExtras();
+ final boolean updateLoyaltyCard = b != null && b.getBoolean("update", false);
+ final boolean viewLoyaltyCard = b != null && b.getBoolean("view", false);
+
+ if(viewLoyaltyCard)
+ {
+ getMenuInflater().inflate(R.menu.card_edit_menu, menu);
+ }
+ else if(updateLoyaltyCard)
+ {
+ getMenuInflater().inflate(R.menu.card_delete_menu, menu);
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item)
+ {
+ int id = item.getItemId();
+
+ final Bundle b = getIntent().getExtras();
+ final int loyaltyCardId = b != null ? b.getInt("id") : 0;
+
+ switch(id)
+ {
+ case R.id.action_delete:
+ Log.e(TAG, "Deleting card: " + loyaltyCardId);
+
+ DBHelper db = new DBHelper(this);
+ db.deleteLoyaltyCard(loyaltyCardId);
+ finish();
+ return true;
+ case R.id.action_edit:
+ Intent intent = new Intent(getApplicationContext(), LoyaltyCardViewActivity.class);
+ Bundle bundle = new Bundle();
+ bundle.putInt("id", loyaltyCardId);
+ bundle.putBoolean("update", true);
+ intent.putExtras(bundle);
+ startActivity(intent);
+ finish();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent)
+ {
+ IntentResult result =
+ IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
+ if (result != null)
+ {
+ String contents = result.getContents();
+ String format = result.getFormatName();
+ if(contents != null && contents.isEmpty() == false &&
+ format != null && format.isEmpty() == false)
+ {
+ Log.i(TAG, "Read Contents from scan: " + contents);
+ Log.i(TAG, "Read Format: " + format);
+
+ final EditText cardIdField = (EditText) findViewById(R.id.cardId);
+ cardIdField.setText(contents);
+ final EditText barcodeTypeField = (EditText) findViewById(R.id.barcodeType);
+ barcodeTypeField.setText(format);
+ onResume();
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/protect/card_locker/MainActivity.java b/app/src/main/java/protect/card_locker/MainActivity.java
new file mode 100644
index 000000000..b7819033e
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/MainActivity.java
@@ -0,0 +1,97 @@
+package protect.card_locker;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class MainActivity extends AppCompatActivity
+{
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_activity);
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ updateLoyaltyCardList();
+ }
+
+ @Override
+ protected void onResume()
+ {
+ super.onResume();
+
+ updateLoyaltyCardList();
+ }
+
+ private void updateLoyaltyCardList()
+ {
+ final ListView cardList = (ListView) findViewById(R.id.list);
+ final TextView helpText = (TextView) findViewById(R.id.helpText);
+ final DBHelper db = new DBHelper(this);
+
+ if(db.getLoyaltyCardCount() > 0)
+ {
+ cardList.setVisibility(View.VISIBLE);
+ helpText.setVisibility(View.GONE);
+ }
+ else
+ {
+ cardList.setVisibility(View.GONE);
+ helpText.setVisibility(View.VISIBLE);
+ }
+
+ Cursor cardCursor = db.getLoyaltyCardCursor();
+
+ final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
+ cardList.setAdapter(adapter);
+
+ cardList.setOnItemClickListener(new AdapterView.OnItemClickListener()
+ {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id)
+ {
+ Cursor selected = (Cursor) parent.getItemAtPosition(position);
+ LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
+
+ Intent i = new Intent(view.getContext(), LoyaltyCardViewActivity.class);
+ final Bundle b = new Bundle();
+ b.putInt("id", loyaltyCard.id);
+ b.putBoolean("view", true);
+ i.putExtras(b);
+ startActivity(i);
+ }
+ });
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu)
+ {
+ getMenuInflater().inflate(R.menu.main_menu, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item)
+ {
+ int id = item.getItemId();
+
+ if (id == R.id.action_add)
+ {
+ Intent i = new Intent(getApplicationContext(), LoyaltyCardViewActivity.class);
+ startActivity(i);
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png
new file mode 100644
index 000000000..694179bd4
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_delete_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_delete_white_24dp.png
new file mode 100644
index 000000000..4a9f76947
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_delete_white_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_mode_edit_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_mode_edit_white_24dp.png
new file mode 100644
index 000000000..595ff10ac
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_mode_edit_white_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png
new file mode 100644
index 000000000..3856041d7
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_delete_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_delete_white_24dp.png
new file mode 100644
index 000000000..e2f5f3555
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_delete_white_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_mode_edit_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_mode_edit_white_24dp.png
new file mode 100644
index 000000000..12b09f1d9
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_mode_edit_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png
new file mode 100644
index 000000000..67bb598e5
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_delete_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_delete_white_24dp.png
new file mode 100644
index 000000000..388b5b060
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_delete_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_mode_edit_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_mode_edit_white_24dp.png
new file mode 100644
index 000000000..5a06bff5a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_mode_edit_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png
new file mode 100644
index 000000000..0fdced8fc
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_delete_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_delete_white_24dp.png
new file mode 100644
index 000000000..3fcdfdb55
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_delete_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_mode_edit_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_mode_edit_white_24dp.png
new file mode 100644
index 000000000..02e19d045
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_mode_edit_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png
new file mode 100644
index 000000000..d64c22e9e
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_delete_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_delete_white_24dp.png
new file mode 100644
index 000000000..8d322aa9b
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_delete_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_mode_edit_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_mode_edit_white_24dp.png
new file mode 100644
index 000000000..d6668a051
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_mode_edit_white_24dp.png differ
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
new file mode 100644
index 000000000..fab4bdcdd
--- /dev/null
+++ b/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/loyalty_card_layout.xml b/app/src/main/res/layout/loyalty_card_layout.xml
new file mode 100644
index 000000000..6679ab575
--- /dev/null
+++ b/app/src/main/res/layout/loyalty_card_layout.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/loyalty_card_view_activity.xml b/app/src/main/res/layout/loyalty_card_view_activity.xml
new file mode 100644
index 000000000..5e53fbb1b
--- /dev/null
+++ b/app/src/main/res/layout/loyalty_card_view_activity.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml
new file mode 100644
index 000000000..b4f2b8006
--- /dev/null
+++ b/app/src/main/res/layout/main_activity.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/card_delete_menu.xml b/app/src/main/res/menu/card_delete_menu.xml
new file mode 100644
index 000000000..77de03d76
--- /dev/null
+++ b/app/src/main/res/menu/card_delete_menu.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/card_edit_menu.xml b/app/src/main/res/menu/card_edit_menu.xml
new file mode 100644
index 000000000..657d80dca
--- /dev/null
+++ b/app/src/main/res/menu/card_edit_menu.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml
new file mode 100644
index 000000000..406ff41a0
--- /dev/null
+++ b/app/src/main/res/menu/main_menu.xml
@@ -0,0 +1,10 @@
+
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100755
index 000000000..11a8da376
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100755
index 000000000..f6334a352
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100755
index 000000000..d3170b42a
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100755
index 000000000..f2aba1073
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100755
index 000000000..57d780b5c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..251fb9fb7
--- /dev/null
+++ b/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,9 @@
+>
+
+
+
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 000000000..63fc81644
--- /dev/null
+++ b/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000..c0f22a62d
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+
+ #4CAF50
+ #388E3C
+ #607D8B
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..d420afd39
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,10 @@
+
+
+ 16dp
+ 16dp
+
+ 16sp
+ 22dp
+
+ 50dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..0176a6c15
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,28 @@
+
+ Loyalty Card Locker
+ Add
+
+ 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.
+
+ Store
+ Card ID
+ Barcode Type
+
+ Cancel
+ Save
+ Capture Card
+ Edit
+ Delete
+
+ Edit Loyalty Card
+ Add Loyalty Card
+ View Loyalty Card
+ Scan Card\'s Barcode
+
+ Image of card\'s barcode
+
+ No Store entered
+ No Card ID entered
+
+ %1$s: %2$s
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000..ad615a084
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/test/java/protect/card_locker/DatabaseTest.java b/app/src/test/java/protect/card_locker/DatabaseTest.java
new file mode 100644
index 000000000..0de2deb75
--- /dev/null
+++ b/app/src/test/java/protect/card_locker/DatabaseTest.java
@@ -0,0 +1,128 @@
+package protect.card_locker;
+
+import android.app.Activity;
+import android.database.Cursor;
+
+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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(RobolectricGradleTestRunner.class)
+@Config(constants = BuildConfig.class, sdk = 17)
+public class DatabaseTest
+{
+ private DBHelper db;
+
+ @Before
+ public void setUp()
+ {
+ Activity activity = Robolectric.setupActivity(MainActivity.class);
+ db = new DBHelper(activity);
+ }
+
+ @Test
+ public void addRemoveOneGiftCard()
+ {
+ assertEquals(0, db.getLoyaltyCardCount());
+ boolean result = db.insertLoyaltyCard("store", "cardId", BarcodeFormat.UPC_A.toString());
+ assertTrue(result);
+ assertEquals(1, db.getLoyaltyCardCount());
+
+ LoyaltyCard loyaltyCard = db.getLoyaltyCard(1);
+ assertNotNull(loyaltyCard);
+ assertEquals("store", loyaltyCard.store);
+ assertEquals("cardId", loyaltyCard.cardId);
+ assertEquals(BarcodeFormat.UPC_A.toString(), loyaltyCard.barcodeType);
+
+ result = db.deleteLoyaltyCard(1);
+ assertTrue(result);
+ assertEquals(0, db.getLoyaltyCardCount());
+ assertNull(db.getLoyaltyCard(1));
+ }
+
+ @Test
+ public void updateGiftCard()
+ {
+ boolean result = db.insertLoyaltyCard("store", "cardId", BarcodeFormat.UPC_A.toString());
+ assertTrue(result);
+ assertEquals(1, db.getLoyaltyCardCount());
+
+ result = db.updateLoyaltyCard(1, "store1", "cardId1", BarcodeFormat.AZTEC.toString());
+ assertTrue(result);
+ assertEquals(1, db.getLoyaltyCardCount());
+
+ LoyaltyCard loyaltyCard = db.getLoyaltyCard(1);
+ assertNotNull(loyaltyCard);
+ assertEquals("store1", loyaltyCard.store);
+ assertEquals("cardId1", loyaltyCard.cardId);
+ assertEquals(BarcodeFormat.AZTEC.toString(), loyaltyCard.barcodeType);
+ }
+
+ @Test
+ public void updateMissingGiftCard()
+ {
+ assertEquals(0, db.getLoyaltyCardCount());
+
+ boolean result = db.updateLoyaltyCard(1, "store1", "cardId1", BarcodeFormat.UPC_A.toString());
+ assertEquals(false, result);
+ assertEquals(0, db.getLoyaltyCardCount());
+ }
+
+ @Test
+ public void emptyGiftCardValues()
+ {
+ boolean result = db.insertLoyaltyCard("", "", "");
+ assertTrue(result);
+ assertEquals(1, db.getLoyaltyCardCount());
+
+ LoyaltyCard loyaltyCard = db.getLoyaltyCard(1);
+ assertNotNull(loyaltyCard);
+ assertEquals("", loyaltyCard.store);
+ assertEquals("", loyaltyCard.cardId);
+ assertEquals("", loyaltyCard.barcodeType);
+ }
+
+ @Test
+ public void giftCardsViaCursor()
+ {
+ final int CARDS_TO_ADD = 10;
+
+ // Add the gift cards in reverse order, to ensure
+ // 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());
+ assertTrue(result);
+ }
+
+ assertEquals(CARDS_TO_ADD, db.getLoyaltyCardCount());
+
+ Cursor cursor = db.getLoyaltyCardCursor();
+ assertNotNull(cursor);
+
+ assertEquals(CARDS_TO_ADD, cursor.getCount());
+
+ cursor.moveToFirst();
+
+ for(int index = 0; index < CARDS_TO_ADD; index++)
+ {
+ assertEquals("store"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE)));
+ assertEquals("cardId"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID)));
+ assertEquals(BarcodeFormat.UPC_A.toString(), cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE)));
+
+ cursor.moveToNext();
+ }
+
+ assertTrue(cursor.isAfterLast());
+ }
+}
diff --git a/app/src/test/java/protect/card_locker/LoyaltyCardCursorAdapterTest.java b/app/src/test/java/protect/card_locker/LoyaltyCardCursorAdapterTest.java
new file mode 100644
index 000000000..0c6ccc14d
--- /dev/null
+++ b/app/src/test/java/protect/card_locker/LoyaltyCardCursorAdapterTest.java
@@ -0,0 +1,51 @@
+package protect.card_locker;
+
+import android.app.Activity;
+import android.database.Cursor;
+import android.view.View;
+import android.widget.TextView;
+
+import com.google.zxing.BarcodeFormat;
+
+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;
+
+@RunWith(RobolectricGradleTestRunner.class)
+@Config(constants = BuildConfig.class, sdk = 17)
+public class LoyaltyCardCursorAdapterTest
+{
+ @Test
+ public void TestCursorAdapter()
+ {
+ 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();
+
+ 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);
+
+ assertEquals(card.store, storeField.getText().toString());
+
+ final TextView cardIdField = (TextView) view.findViewById(R.id.cardId);
+ 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());
+ }
+}
diff --git a/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java b/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java
new file mode 100644
index 000000000..909ffc525
--- /dev/null
+++ b/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java
@@ -0,0 +1,372 @@
+package protect.card_locker;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.client.android.Intents;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.res.builder.RobolectricPackageManager;
+import org.robolectric.shadows.ShadowActivity;
+import org.robolectric.util.ActivityController;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.robolectric.Shadows.shadowOf;
+
+@RunWith(RobolectricGradleTestRunner.class)
+@Config(constants = BuildConfig.class, sdk = 17)
+public class LoyaltyCardViewActivityTest
+{
+ private final String BARCODE_DATA = "428311627547";
+ private final String BARCODE_TYPE = BarcodeFormat.UPC_A.name();
+
+ private final String EAN_BARCODE_DATA = "4763705295336";
+ private final String EAN_BARCODE_TYPE = BarcodeFormat.EAN_13.name();
+
+ /**
+ * Register a handler in the package manager for a image capture intent
+ */
+ private void registerMediaStoreIntentHandler()
+ {
+ // Add something that will 'handle' the media capture intent
+ RobolectricPackageManager packageManager = (RobolectricPackageManager) shadowOf(
+ RuntimeEnvironment.application).getPackageManager();
+
+ ResolveInfo info = new ResolveInfo();
+ info.isDefault = true;
+
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = "does.not.matter";
+ info.activityInfo = new ActivityInfo();
+ info.activityInfo.applicationInfo = applicationInfo;
+ info.activityInfo.name = "DoesNotMatter";
+
+ Intent intent = new Intent(Intents.Scan.ACTION);
+
+ packageManager.addResolveInfoForIntent(intent, info);
+ }
+
+ /**
+ * Save a loyalty card and check that the database contains the
+ * expected values
+ */
+ private void saveLoyaltyCardWithArguments(final Activity activity,
+ final String store, final String cardId,
+ final String barcodeType,
+ boolean creatingNewCard)
+ {
+ DBHelper db = new DBHelper(activity);
+ if(creatingNewCard)
+ {
+ assertEquals(0, db.getLoyaltyCardCount());
+ }
+ else
+ {
+ assertEquals(1, db.getLoyaltyCardCount());
+ }
+
+ final EditText storeField = (EditText) activity.findViewById(R.id.storeName);
+ 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);
+ cardIdField.setText(cardId);
+ barcodeTypeField.setText(barcodeType);
+
+ assertEquals(false, activity.isFinishing());
+ saveButton.performClick();
+ assertEquals(true, activity.isFinishing());
+
+ assertEquals(1, db.getLoyaltyCardCount());
+
+ LoyaltyCard card = db.getLoyaltyCard(1);
+ assertEquals(store, card.store);
+ assertEquals(cardId, card.cardId);
+ assertEquals(barcodeType, card.barcodeType);
+ }
+
+ /**
+ * Initiate and complete a barcode capture, either in success
+ * or in failure
+ */
+ private void captureBarcodeWithResult(final Activity activity, final int buttonId, final boolean success) throws IOException
+ {
+ // Start image capture
+ final Button captureButton = (Button) activity.findViewById(buttonId);
+ captureButton.performClick();
+
+ ShadowActivity.IntentForResult intentForResult = shadowOf(activity).peekNextStartedActivityForResult();
+ assertNotNull(intentForResult);
+
+ Intent intent = intentForResult.intent;
+ assertNotNull(intent);
+
+ String action = intent.getAction();
+ assertNotNull(action);
+ assertEquals(Intents.Scan.ACTION, action);
+
+ Bundle bundle = intent.getExtras();
+ assertNotNull(bundle);
+
+ Intent resultIntent = new Intent(intent);
+ Bundle resultBuddle = new Bundle();
+ resultBuddle.putString(Intents.Scan.RESULT, BARCODE_DATA);
+ resultBuddle.putString(Intents.Scan.RESULT_FORMAT, BARCODE_TYPE);
+ resultIntent.putExtras(resultBuddle);
+
+ // Respond to image capture, success
+ shadowOf(activity).receiveResult(
+ intent,
+ success ? Activity.RESULT_OK : Activity.RESULT_CANCELED,
+ resultIntent);
+ }
+
+ private void checkFieldProperties(final Activity activity, final int id, final int visibility,
+ final String contents)
+ {
+ final View view = activity.findViewById(id);
+ assertNotNull(view);
+ assertEquals(visibility, view.getVisibility());
+ if(contents != null)
+ {
+ TextView textView = (TextView)view;
+ assertEquals(contents, textView.getText().toString());
+ }
+ }
+
+ private void checkAllFields(final Activity activity, final String store, final String cardId,
+ final String barcodeType)
+ {
+ int cardIdVisibility = cardId.isEmpty() ? View.GONE : View.VISIBLE;
+
+ 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.barcodeIdLayout, cardIdVisibility, null);
+ checkFieldProperties(activity, R.id.barcodeLayout, cardIdVisibility, null);
+ checkFieldProperties(activity, R.id.barcodeTypeLayout, View.GONE, null);
+ }
+
+ @Test
+ public void startWithoutParametersCheckFieldsAvailable()
+ {
+ ActivityController activityController = Robolectric.buildActivity(LoyaltyCardViewActivity.class).create();
+ activityController.start();
+ activityController.visible();
+ activityController.resume();
+
+ Activity activity = (Activity)activityController.get();
+
+ checkAllFields(activity, "", "", "");
+ }
+
+ @Test
+ public void startWithoutParametersCannotCreateGiftCard()
+ {
+ ActivityController activityController = Robolectric.buildActivity(LoyaltyCardViewActivity.class).create();
+ activityController.start();
+ activityController.visible();
+ activityController.resume();
+
+ Activity activity = (Activity)activityController.get();
+ DBHelper db = new DBHelper(activity);
+ assertEquals(0, db.getLoyaltyCardCount());
+
+ final EditText storeField = (EditText) activity.findViewById(R.id.storeName);
+ final EditText cardIdField = (EditText) activity.findViewById(R.id.cardId);
+
+ final Button saveButton = (Button) activity.findViewById(R.id.saveButton);
+
+ saveButton.performClick();
+ assertEquals(0, db.getLoyaltyCardCount());
+
+ storeField.setText("store");
+ saveButton.performClick();
+ assertEquals(0, db.getLoyaltyCardCount());
+
+ cardIdField.setText("cardId");
+ saveButton.performClick();
+ assertEquals(0, db.getLoyaltyCardCount());
+ }
+
+ @Test
+ public void startWithoutParametersCancel()
+ {
+ ActivityController activityController = Robolectric.buildActivity(LoyaltyCardViewActivity.class).create();
+ activityController.start();
+ activityController.visible();
+ activityController.resume();
+
+ Activity activity = (Activity)activityController.get();
+
+ final Button cancelButton = (Button) activity.findViewById(R.id.cancelButton);
+
+ assertEquals(false, activity.isFinishing());
+ cancelButton.performClick();
+ assertEquals(true, activity.isFinishing());
+ }
+
+ @Test
+ public void startWithoutParametersCaptureBarcodeCreateGiftCard() throws IOException
+ {
+ registerMediaStoreIntentHandler();
+
+ ActivityController activityController = Robolectric.buildActivity(LoyaltyCardViewActivity.class).create();
+ activityController.start();
+ activityController.visible();
+ activityController.resume();
+
+ Activity activity = (Activity)activityController.get();
+
+ checkAllFields(activity, "", "", "");
+
+ // Complete barcode capture successfully
+ captureBarcodeWithResult(activity, R.id.captureButton, true);
+
+ checkAllFields(activity, "", BARCODE_DATA, BARCODE_TYPE);
+
+ // Save and check the gift card
+ saveLoyaltyCardWithArguments(activity, "store", BARCODE_DATA, BARCODE_TYPE, true);
+ }
+
+ @Test
+ public void startWithoutParametersCaptureBarcodeFailure() throws IOException
+ {
+ ActivityController activityController = Robolectric.buildActivity(LoyaltyCardViewActivity.class).create();
+ activityController.start();
+ activityController.visible();
+ activityController.resume();
+
+ Activity activity = (Activity)activityController.get();
+
+ checkAllFields(activity, "", "", "");
+
+ // Complete barcode capture in failure
+ captureBarcodeWithResult(activity, R.id.captureButton, false);
+
+ checkAllFields(activity, "", "", "");
+ }
+
+ @Test
+ public void startWithoutParametersCaptureBarcodeCancel() throws IOException
+ {
+ ActivityController activityController = Robolectric.buildActivity(LoyaltyCardViewActivity.class).create();
+ activityController.start();
+ activityController.visible();
+ activityController.resume();
+
+ Activity activity = (Activity)activityController.get();
+
+ checkAllFields(activity, "", "", "");
+
+ // Complete barcode capture successfully
+ captureBarcodeWithResult(activity, R.id.captureButton, true);
+
+ checkAllFields(activity, "", BARCODE_DATA, BARCODE_TYPE);
+
+ // Cancel the gift card creation
+ final Button cancelButton = (Button) activity.findViewById(R.id.cancelButton);
+ assertEquals(false, activity.isFinishing());
+ cancelButton.performClick();
+ assertEquals(true, activity.isFinishing());
+ }
+
+ private ActivityController createActivityWithLoyaltyCard()
+ {
+ Intent intent = new Intent();
+ final Bundle bundle = new Bundle();
+ bundle.putInt("id", 1);
+ bundle.putBoolean("update", true);
+ intent.putExtras(bundle);
+
+ return Robolectric.buildActivity(LoyaltyCardViewActivity.class).withIntent(intent).create();
+ }
+
+ @Test
+ public void startWithGiftCardCheckDisplay() throws IOException
+ {
+ ActivityController activityController = createActivityWithLoyaltyCard();
+ Activity activity = (Activity)activityController.get();
+ DBHelper db = new DBHelper(activity);
+
+ db.insertLoyaltyCard("store", BARCODE_DATA, BARCODE_TYPE);
+
+ activityController.start();
+ activityController.visible();
+ activityController.resume();
+
+ checkAllFields(activity, "store", BARCODE_DATA, BARCODE_TYPE);
+ }
+
+ @Test
+ public void startWithLoyaltyCardWithBarcodeUpdateBarcode() throws IOException
+ {
+ ActivityController activityController = createActivityWithLoyaltyCard();
+ Activity activity = (Activity)activityController.get();
+ DBHelper db = new DBHelper(activity);
+
+ db.insertLoyaltyCard("store", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
+
+ activityController.start();
+ activityController.visible();
+ activityController.resume();
+
+ checkAllFields(activity, "store", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
+
+ // Complete barcode capture successfully
+ captureBarcodeWithResult(activity, R.id.captureButton, true);
+
+ checkAllFields(activity, "store", BARCODE_DATA, BARCODE_TYPE);
+ }
+
+ @Test
+ public void startWithGiftCardWithReceiptUpdateReceiptCancel() throws IOException
+ {
+ ActivityController activityController = createActivityWithLoyaltyCard();
+ Activity activity = (Activity)activityController.get();
+ DBHelper db = new DBHelper(activity);
+
+ db.insertLoyaltyCard("store", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
+
+ activityController.start();
+ activityController.visible();
+ activityController.resume();
+
+ checkAllFields(activity, "store", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
+
+ // Complete barcode capture successfully
+ captureBarcodeWithResult(activity, R.id.captureButton, true);
+
+ checkAllFields(activity, "store", BARCODE_DATA, BARCODE_TYPE);
+
+ // Cancel the gift card creation
+ final Button cancelButton = (Button) activity.findViewById(R.id.cancelButton);
+ assertEquals(false, activity.isFinishing());
+ cancelButton.performClick();
+ assertEquals(true, activity.isFinishing());
+ }
+}
diff --git a/app/src/test/java/protect/card_locker/MainActivityTest.java b/app/src/test/java/protect/card_locker/MainActivityTest.java
new file mode 100644
index 000000000..185142455
--- /dev/null
+++ b/app/src/test/java/protect/card_locker/MainActivityTest.java
@@ -0,0 +1,101 @@
+package protect.card_locker;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.database.Cursor;
+import android.view.Menu;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.google.zxing.BarcodeFormat;
+
+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;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.robolectric.Shadows.shadowOf;
+
+@RunWith(RobolectricGradleTestRunner.class)
+@Config(constants = BuildConfig.class, sdk = 17)
+public class MainActivityTest
+{
+ @Test
+ public void initiallyNoLoyaltyCards() throws Exception
+ {
+ Activity activity = Robolectric.setupActivity(MainActivity.class);
+ assertTrue(activity != null);
+
+ TextView helpText = (TextView)activity.findViewById(R.id.helpText);
+ assertEquals(View.VISIBLE, helpText.getVisibility());
+
+ ListView list = (ListView)activity.findViewById(R.id.list);
+ assertEquals(View.GONE, list.getVisibility());
+ }
+
+ @Test
+ public void onCreateShouldInflateLayout() throws Exception
+ {
+ final MainActivity activity = Robolectric.setupActivity(MainActivity.class);
+
+ final Menu menu = shadowOf(activity).getOptionsMenu();
+ assertTrue(menu != null);
+
+ // The settings and add button should be present
+ assertEquals(menu.size(), 1);
+
+ assertEquals("Add", menu.findItem(R.id.action_add).getTitle().toString());
+ }
+
+ @Test
+ public void clickAddLaunchesLoyaltyCardViewActivity()
+ {
+ final MainActivity activity = Robolectric.setupActivity(MainActivity.class);
+
+ shadowOf(activity).clickMenuItem(R.id.action_add);
+
+ Intent intent = shadowOf(activity).peekNextStartedActivityForResult().intent;
+
+ assertEquals(new ComponentName(activity, LoyaltyCardViewActivity.class), intent.getComponent());
+ assertNull(intent.getExtras());
+ }
+
+ @Test
+ public void addOneLoyaltyCard()
+ {
+ ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create();
+
+ Activity mainActivity = (Activity)activityController.get();
+ activityController.start();
+ activityController.resume();
+
+ TextView helpText = (TextView)mainActivity.findViewById(R.id.helpText);
+ ListView list = (ListView)mainActivity.findViewById(R.id.list);
+
+ assertEquals(0, list.getCount());
+
+ DBHelper db = new DBHelper(mainActivity);
+ db.insertLoyaltyCard("store", "cardId", BarcodeFormat.UPC_A.toString());
+
+ assertEquals(View.VISIBLE, helpText.getVisibility());
+ assertEquals(View.GONE, list.getVisibility());
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(1, list.getAdapter().getCount());
+ Cursor cursor = (Cursor)list.getAdapter().getItem(0);
+ assertNotNull(cursor);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..e0b366a78
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,23 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.5.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 000000000..1d3591c8a
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 000000000..e7b4def49
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app'