Compare commits

...

91 Commits
v0.2 ... v0.8

Author SHA1 Message Date
Branden Archer
151b81daa7 Merge pull request #69 from brarcher/pre-v0.8
Update for release v0.8
2016-11-22 22:21:35 -05:00
Branden Archer
c090af1c7d Update for release v0.8 2016-11-22 22:13:29 -05:00
Branden Archer
54a0f89821 Merge pull request #66 from lgasp/patch-1
Small update to italian translation
2016-11-06 15:07:58 -05:00
lgasp
f01058d5b8 Update strings.xml 2016-11-05 11:19:48 +01:00
lgasp
e01c77681b proposing less confusing trnalsation fo r button
"Scan code" is translated as "salva tessera" that corresponds to English "save card",  that is similar to the button " save" on the same screen.
"Scansione codice"is a more literal, and hopefully more clear, translational
2016-11-04 19:48:54 +01:00
Branden Archer
c49ecdca56 Merge pull request #57 from pbeckmann/master
Add a german translation.
2016-11-02 14:37:18 -04:00
Peer Beckmann
b7672e5247 Merge branch 'master' into master 2016-11-01 09:50:18 +01:00
Branden Archer
bebab039d7 Merge pull request #60 from brarcher/screenshots
Simplify screenshots in README.md
2016-09-12 00:10:06 -04:00
Branden Archer
588a573878 Simplify screenshots in README.md 2016-09-11 23:56:32 -04:00
Branden Archer
a9bbf8fb65 Merge pull request #59 from brarcher/screenshots
Add screenshots to README.md
2016-08-28 15:44:22 -04:00
Branden Archer
175135d695 Add screenshots to README.md
Requested in:
https://github.com/brarcher/loyalty-card-locker/issues/56
2016-08-28 15:30:23 -04:00
Branden Archer
9f08d07a6a Merge pull request #58 from Clonewayx/patch-1
Czech translation
2016-08-27 13:49:07 -04:00
Clonewayx
07f50b1544 Update and rename strings.xml to strings.xml
remove untranslatable line
2016-08-27 18:04:00 +02:00
Clonewayx
584064a4a2 Czech translation 2016-08-27 06:58:12 +02:00
Peer Beckmann
484f357a82 Add a german translation. 2016-08-25 19:57:55 +02:00
Branden Archer
e32b40a024 Merge pull request #55 from brarcher/delete-confirmation
Delete confirmation
2016-07-22 08:04:15 -04:00
Branden Archer
010b88d477 Attempt to force travis builds on non-container infrastructure
There is an issue with running the travis builds. The root
cause is unknown. Attempting to use the non-container
version of the infrastructure to see if the issue persists.
2016-07-22 07:56:23 -04:00
Branden Archer
fe0bb7b870 Add a confirmation before deleting card
https://github.com/brarcher/loyalty-card-locker/issues/53
2016-07-22 07:56:22 -04:00
Branden Archer
925f780599 Merge pull request #54 from brarcher/screen-brightness
Adjust brightness to its maximum when displaying a card
2016-07-20 00:04:42 -04:00
Branden Archer
9492d206d9 Adjust brightness to its maximum when displaying a card
Some users have mentioned that a barcode scanner has the best
success when the screen is at its brightest. To enable the best
results, the screen brightness will now be adjusted to its
maximum when displaying a loyalty card.

https://github.com/brarcher/loyalty-card-locker/issues/21
2016-07-19 23:42:36 -04:00
Branden Archer
a32415b4d2 Reduce the number of cards tested in import/export tests
There is no need to have so many cards tested in the test, just
a few will do to prove the point.
2016-07-19 23:41:31 -04:00
Branden Archer
5965b82251 Merge pull request #51 from brarcher/pre-v0.7
Update for release v0.7
2016-07-14 16:00:10 -04:00
Branden Archer
a5650e6382 Update for release v0.7 2016-07-14 15:37:35 -04:00
Branden Archer
6befc99ccc Merge pull request #50 from brarcher/id-to-clipboard
Copy card id to clipboard
2016-07-14 14:56:20 -04:00
Branden Archer
0e124b2550 Allow long-click of card to copy id to clipboard
https://github.com/brarcher/loyalty-card-locker/issues/49
2016-07-14 13:38:42 -04:00
Branden Archer
06a7db0e6d Update Android gradle plugin to 2.1.2 2016-07-14 13:38:42 -04:00
Branden Archer
f2f56b06dd Merge pull request #47 from brarcher/back-button
Let pushing back button in menu end import/export activity
2016-05-26 23:12:22 -04:00
Branden Archer
518f339c7a Let pushing back button in menu end import/export activity 2016-05-24 07:55:04 -04:00
Branden Archer
bbe6ec3741 Merge pull request #46 from brarcher/pre-v0.6
Update for release v0.6
2016-05-23 09:08:06 -04:00
Branden Archer
18310fdd25 Update for release v0.6 2016-05-23 08:52:27 -04:00
Branden Archer
19bd64c48c Merge pull request #44 from brarcher/manual-barcode-entry
Allow manual barcode entry
2016-05-23 08:51:28 -04:00
Branden Archer
c86819fc74 Allow user to enter a barcode manually
If a user is unable to scan a barcode, this commit allows a
user to enter is manually.

If the user selects to Enter Card instead of Capture Card,
the user may enter the card's id. As it may not be known which barcode
format the user expects, and the user may not know what barcode
type is what, all barcode types are generated from the user
input. Those that are valid are displayed to the user. The user
may then select the barcode image which matches what the user wants.

Italian translations provided by Michael Moroni (Airon90)
Dutch translations provided by PanderMusubi
2016-05-23 08:43:52 -04:00
Branden Archer
8edc9ce5fd Set barcode image visibility upon completion
If the barcode generation succeeds set it as visible, otherwise
make it gone.
2016-05-21 22:58:08 -04:00
Branden Archer
a3a5a3a8db Remove GlobalLayoutListener when no longer needed 2016-05-21 22:58:08 -04:00
Branden Archer
793247a48c Remove unused context 2016-05-21 22:58:08 -04:00
Branden Archer
2f86de4c1b Protect against unexpected failures when encoding barcodes
It was observed that some barcode encoders will fail if the
data passed to them is not valid for the format. For example,
the ITF encoder will throw an ArrayIndexOutOfBoundsException
on the input "this is a test".
2016-05-21 22:58:08 -04:00
Branden Archer
0aa1804258 Allow home button to back out to previous activity 2016-05-21 22:58:08 -04:00
Branden Archer
1c8ef34b8a Remove unneeded parentActivtyName attributes 2016-05-21 22:58:08 -04:00
Branden Archer
3fd45af7d9 Add dependency on Guava 2016-05-21 22:58:08 -04:00
Branden Archer
227af54de7 Merge pull request #43 from brarcher/datamatrix-scaling
Datamatrix scaling
2016-05-21 18:46:57 -04:00
Branden Archer
b89c5eb91c Change padding of card list on main screen
The entire space for the ListView was not being used, which
appeared odd.
2016-05-21 18:32:29 -04:00
Branden Archer
cecec15762 Scale barcodes to ImageView's size without filtering
It turns out that the library used to create datamatrix barcodes
returns the smallest image necessary to contain the barcode. That
is, the size passed into the barcode writer. If the ImageView
scales the tiny image itself into the full size it will use
bi-linear filtering, which results in a blurry barcode.

To avoid this, if scaling is needed do so without using filtering.
2016-05-21 18:32:29 -04:00
Branden Archer
dc4a41088c Generate barcode after ImageView size is known
The size of the ImageView may not yet be known when the
barcode generation is needed. If this is the case, wait
until the final layout is complete then start the
barcode generation.
2016-05-21 18:32:29 -04:00
Branden Archer
b91d4c934a Generate barcode in an AsyncTask
This change moves the generation of the barcode into its
own async task. In addition, the size of the ImageView is
used to determine the barcode size to use.

There will be cases when the size of the ImageView will not
be known when the barcode generation starts. This will be resolved
in a future commit.
2016-05-21 18:32:29 -04:00
Branden Archer
b8d4eaacad Merge pull request #42 from brarcher/min-sdk-11
Reduce min SDK version to 11
2016-05-17 18:23:11 -04:00
Branden Archer
0a7d5d89cf Reduce min SDK version to 11
The selection of SDK 17 was arbitrarily based on the version
available on my device at the time. As no APIs are being used
at that level, a lower SDK version can be targeted.

According to the current distribution of Android device versions,
99.9% of devices are at SDK 11+. Changing to this for the min SDK
for now.
2016-05-17 14:30:40 -04:00
Branden Archer
7bbcc52ba6 Remove usage of emulator in Travis-CI
The emulator was never used, no reason to download it on every build
2016-05-16 23:26:31 -04:00
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
51 changed files with 2669 additions and 172 deletions

View File

@@ -1,4 +1,5 @@
language: android
sudo: true
android:
components:
# Uncomment the lines below if you want to
@@ -15,11 +16,9 @@ android:
# Additional components
- extra
# Specify at least one system image,
# 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,46 @@
[![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.
# Screenshots
[<img src="https://cloud.githubusercontent.com/assets/5264535/18036233/32fae9a6-6d33-11e6-81e4-55ba60e83d9b.png" width=250>](https://cloud.githubusercontent.com/assets/5264535/18036233/32fae9a6-6d33-11e6-81e4-55ba60e83d9b.png)
[<img src="https://cloud.githubusercontent.com/assets/5264535/18036246/7ee5c7f0-6d33-11e6-90cf-d2b3ca5a94c7.png" width=250>](https://cloud.githubusercontent.com/assets/5264535/18036246/7ee5c7f0-6d33-11e6-90cf-d2b3ca5a94c7.png)
[<img src="https://cloud.githubusercontent.com/assets/5264535/18036258/bb19562e-6d33-11e6-856e-740e8785ad71.png" width=250>](https://cloud.githubusercontent.com/assets/5264535/18036258/bb19562e-6d33-11e6-856e-740e8785ad71.png)
[<img src="https://cloud.githubusercontent.com/assets/5264535/18036269/0202baf8-6d34-11e6-9c17-449d5b348738.png" width=250>](https://cloud.githubusercontent.com/assets/5264535/18036269/0202baf8-6d34-11e6-9c17-449d5b348738.png)
# 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
@@ -6,10 +12,10 @@ android {
defaultConfig {
applicationId "protect.card_locker"
minSdkVersion 17
minSdkVersion 11
targetSdkVersion 23
versionCode 2
versionName "0.2"
versionCode 8
versionName "0.8"
}
buildTypes {
release {
@@ -31,8 +37,29 @@ 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'
compile group: 'com.google.guava', name: 'guava', version: '18.0'
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,18 @@
android:name=".LoyaltyCardViewActivity"
android:theme="@style/AppTheme.NoActionBar"
android:configChanges="orientation|screenSize"
android:parentActivityName="protect.card_locker.MainActivity"/>
android:windowSoftInputMode="stateHidden"/>
<activity
android:name=".BarcodeSelectorActivity"
android:label="@string/selectBarcodeTitle"
android:theme="@style/AppTheme.NoActionBar"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateHidden"/>
<activity
android:name=".ImportExportActivity"
android:label="@string/importExport"
android:configChanges="orientation|screenSize"
android:theme="@style/AppTheme.NoActionBar"/>
</application>
</manifest>

View File

@@ -0,0 +1,131 @@
package protect.card_locker;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.Log;
import android.view.View;
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 java.lang.ref.WeakReference;
/**
* This task will generate a barcode and load it into an ImageView.
* Only a weak reference of the ImageView is kept, so this class will not
* prevent the ImageView from being garbage collected.
*/
class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
{
private static final String TAG = "LoyaltyCardLocker";
private final WeakReference<ImageView> imageViewReference;
private final String cardId;
private final BarcodeFormat format;
private final int imageHeight;
private final int imageWidth;
public BarcodeImageWriterTask(ImageView imageView, String cardIdString,
BarcodeFormat barcodeFormat)
{
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<>(imageView);
cardId = cardIdString;
format = barcodeFormat;
imageHeight = imageView.getHeight();
imageWidth = imageView.getWidth();
}
public Bitmap doInBackground(Void... params)
{
MultiFormatWriter writer = new MultiFormatWriter();
BitMatrix bitMatrix;
try
{
try
{
bitMatrix = writer.encode(cardId, format, imageWidth, imageHeight, null);
}
catch(Exception e)
{
// Cast a wider net here and catch any exception, as there are some
// cases where an encoder may fail if the data is invalid for the
// barcode type. If this happens, we want to fail gracefully.
throw new WriterException(e);
}
final int WHITE = 0xFFFFFFFF;
final int BLACK = 0xFF000000;
int bitMatrixWidth = bitMatrix.getWidth();
int bitMatrixHeight = bitMatrix.getHeight();
int[] pixels = new int[bitMatrixWidth * bitMatrixHeight];
for (int y = 0; y < bitMatrixHeight; y++)
{
int offset = y * bitMatrixWidth;
for (int x = 0; x < bitMatrixWidth; x++)
{
int color = bitMatrix.get(x, y) ? BLACK : WHITE;
pixels[offset + x] = color;
}
}
Bitmap bitmap = Bitmap.createBitmap(bitMatrixWidth, bitMatrixHeight,
Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, bitMatrixWidth, 0, 0, bitMatrixWidth, bitMatrixHeight);
// Determine if the image needs to be scaled.
// This is necessary because the datamatrix barcode generator
// ignores the requested size and returns the smallest image necessary
// to represent the barcode. If we let the ImageView scale the image
// it will use bi-linear filtering, which results in a blurry barcode.
// To avoid this, if scaling is needed do so without filtering.
int heightScale = imageHeight / bitMatrixHeight;
int widthScale = imageWidth / bitMatrixHeight;
int scalingFactor = Math.min(heightScale, widthScale);
if(scalingFactor > 1)
{
bitmap = Bitmap.createScaledBitmap(bitmap, bitMatrixWidth * scalingFactor, bitMatrixHeight * scalingFactor, false);
}
return bitmap;
}
catch (WriterException e)
{
Log.e(TAG, "Failed to generate barcode of type " + format + ": " + cardId, e);
}
return null;
}
protected void onPostExecute(Bitmap result)
{
Log.i(TAG, "Finished generating barcode image of type " + format + ": " + cardId);
ImageView imageView = imageViewReference.get();
if(imageView == null)
{
// The ImageView no longer exists, nothing to do
return;
}
imageView.setImageBitmap(result);
if(result != null)
{
Log.i(TAG, "Displaying barcode");
imageView.setVisibility(View.VISIBLE);
}
else
{
Log.i(TAG, "Barcode generation failed, removing image from display");
imageView.setVisibility(View.GONE);
}
}
}

View File

@@ -0,0 +1,202 @@
package protect.card_locker;
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.EditText;
import android.widget.ImageView;
import com.google.common.collect.ImmutableMap;
import com.google.zxing.BarcodeFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Map;
/**
* This activity is callable and will allow a user to enter
* barcode data and generate all barcodes possible for
* the data. The user may then select any barcode, where its
* data and type will be returned to the caller.
*/
public class BarcodeSelectorActivity extends AppCompatActivity
{
private static final String TAG = "LoyaltyCardLocker";
// Result this activity will return
public static final String BARCODE_CONTENTS = "contents";
public static final String BARCODE_FORMAT = "format";
// 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.
public static final Collection<String> SUPPORTED_BARCODE_TYPES = 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()
));
private Map<String, Integer> barcodeViewMap;
private LinkedList<AsyncTask> barcodeGeneratorTasks = new LinkedList<>();
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.barcode_selector_activity);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
actionBar.setDisplayHomeAsUpEnabled(true);
}
barcodeViewMap = ImmutableMap.<String, Integer>builder()
.put(BarcodeFormat.AZTEC.name(), R.id.aztecBarcode)
.put(BarcodeFormat.CODE_39.name(), R.id.code39Barcode)
.put(BarcodeFormat.CODE_128.name(), R.id.code128Barcode)
.put(BarcodeFormat.CODABAR.name(), R.id.codabarBarcode)
.put(BarcodeFormat.DATA_MATRIX.name(), R.id.datamatrixBarcode)
.put(BarcodeFormat.EAN_8.name(), R.id.ean8Barcode)
.put(BarcodeFormat.EAN_13.name(), R.id.ean13Barcode)
.put(BarcodeFormat.ITF.name(), R.id.itfBarcode)
.put(BarcodeFormat.PDF_417.name(), R.id.pdf417Barcode)
.put(BarcodeFormat.QR_CODE.name(), R.id.qrcodeBarcode)
.put(BarcodeFormat.UPC_A.name(), R.id.upcaBarcode)
.build();
EditText cardId = (EditText) findViewById(R.id.cardId);
cardId.addTextChangedListener(new TextWatcher()
{
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after)
{
// Noting to do
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count)
{
Log.d(TAG, "Entered text: " + s);
// Stop any async tasks which may not have been started yet
for(AsyncTask task : barcodeGeneratorTasks)
{
task.cancel(false);
}
barcodeGeneratorTasks.clear();
// Update barcodes
for(String key : barcodeViewMap.keySet())
{
ImageView image = (ImageView)findViewById(barcodeViewMap.get(key));
createBarcodeOption(image, key, s.toString());
}
}
@Override
public void afterTextChanged(Editable s)
{
// Noting to do
}
});
}
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId)
{
final BarcodeFormat format = BarcodeFormat.valueOf(formatType);
if(format == null)
{
Log.w(TAG, "Unsupported barcode format: " + formatType);
return;
}
image.setImageBitmap(null);
image.setVisibility(View.GONE);
image.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
Log.d(TAG, "Selected barcode type " + formatType);
Intent result = new Intent();
result.putExtra(BARCODE_FORMAT, formatType);
result.putExtra(BARCODE_CONTENTS, cardId);
BarcodeSelectorActivity.this.setResult(RESULT_OK, result);
finish();
}
});
if(image.getHeight() == 0)
{
// The size of the ImageView is not yet available as it has not
// yet been drawn. Wait for it to be drawn so the size is available.
image.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener()
{
@Override
public void onGlobalLayout()
{
Log.d(TAG, "Global layout finished, type: + " + formatType + ", width: " + image.getWidth());
if (Build.VERSION.SDK_INT < 16)
{
image.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
else
{
image.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
Log.d(TAG, "Generating barcode for type " + formatType);
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format);
barcodeGeneratorTasks.add(task);
task.execute();
}
});
}
else
{
Log.d(TAG, "Generating barcode for type " + formatType);
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format);
barcodeGeneratorTasks.add(task);
task.execute();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
if (item.getItemId() == android.R.id.home)
{
setResult(Activity.RESULT_CANCELED);
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

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,146 @@
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.MenuItem;
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();
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
int id = item.getItemId();
if(id == android.R.id.home)
{
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

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

@@ -1,25 +1,28 @@
package protect.card_locker;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.ActionBar;
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.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
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;
@@ -28,6 +31,8 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
{
private static final String TAG = "CardLocker";
private static final int SELECT_BARCODE_REQUEST = 1;
@Override
protected void onCreate(Bundle savedInstanceState)
{
@@ -55,7 +60,22 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
Log.i(TAG, "To view card: " + loyaltyCardId);
if(viewLoyaltyCard)
{
// The brightness value is on a scale from [0, ..., 1], where
// '1' is the brightest. We attempt to maximize the brightness
// to help barcode readers scan the barcode.
Window window = getWindow();
if(window != null)
{
WindowManager.LayoutParams attributes = window.getAttributes();
attributes.screenBrightness = 1F;
window.setAttributes(attributes);
}
}
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);
@@ -65,6 +85,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
final View barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
final Button captureButton = (Button) findViewById(R.id.captureButton);
final Button enterButton = (Button) findViewById(R.id.enterButton);
final Button saveButton = (Button) findViewById(R.id.saveButton);
final Button cancelButton = (Button) findViewById(R.id.cancelButton);
@@ -74,7 +95,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 +115,11 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
barcodeTypeField.setText(loyaltyCard.barcodeType);
}
storeField.setEnabled(false);
if(viewLoyaltyCard)
{
storeField.setEnabled(false);
noteField.setEnabled(false);
}
if(updateLoyaltyCard)
{
@@ -115,53 +148,43 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
if(cardIdField.getText().length() > 0 && barcodeTypeField.getText().length() > 0)
{
MultiFormatWriter writer = new MultiFormatWriter();
BitMatrix result;
try
String formatString = barcodeTypeField.getText().toString();
final BarcodeFormat format = BarcodeFormat.valueOf(formatString);
final String cardIdString = cardIdField.getText().toString();
if(barcodeImage.getHeight() == 0)
{
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++)
Log.d(TAG, "ImageView size is not known known at start, waiting for load");
// The size of the ImageView is not yet available as it has not
// yet been drawn. Wait for it to be drawn so the size is available.
barcodeImage.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener()
{
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);
@Override
public void onGlobalLayout()
{
if (Build.VERSION.SDK_INT < 16)
{
barcodeImage.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
else
{
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
barcodeIdLayout.setVisibility(View.VISIBLE);
barcodeImageLayout.setVisibility(View.VISIBLE);
Log.d(TAG, "ImageView size now known");
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
}
});
}
catch (WriterException e)
else
{
Log.e(TAG, "Failed to generate barcode", e);
}
catch(IllegalArgumentException e)
{
Log.e(TAG, "Failed to generate barcode", e);
Log.d(TAG, "ImageView size known known, creating barcode");
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
}
barcodeIdLayout.setVisibility(View.VISIBLE);
barcodeImageLayout.setVisibility(View.VISIBLE);
}
View.OnClickListener captureCallback = new View.OnClickListener()
@@ -170,7 +193,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(BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES);
String prompt = getResources().getString(R.string.scanCardBarcode);
integrator.setPrompt(prompt);
@@ -180,12 +203,23 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
captureButton.setOnClickListener(captureCallback);
enterButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
startActivityForResult(i, SELECT_BARCODE_REQUEST);
}
});
saveButton.setOnClickListener(new View.OnClickListener()
{
@Override
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();
@@ -254,12 +288,38 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
switch(id)
{
case R.id.action_delete:
Log.e(TAG, "Deleting card: " + loyaltyCardId);
DBHelper db = new DBHelper(this);
db.deleteLoyaltyCard(loyaltyCardId);
case android.R.id.home:
finish();
break;
case R.id.action_delete:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.deleteTitle);
builder.setMessage(R.string.deleteConfirmation);
builder.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
Log.e(TAG, "Deleting card: " + loyaltyCardId);
DBHelper db = new DBHelper(LoyaltyCardViewActivity.this);
db.deleteLoyaltyCard(loyaltyCardId);
finish();
dialog.dismiss();
}
});
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
AlertDialog dialog = builder.create();
dialog.show();
return true;
case R.id.action_edit:
Intent intent = new Intent(getApplicationContext(), LoyaltyCardViewActivity.class);
@@ -278,24 +338,37 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent)
{
String contents = null;
String format = null;
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);
Log.i(TAG, "Received barcode information from capture");
contents = result.getContents();
format = result.getFormatName();
}
final EditText cardIdField = (EditText) findViewById(R.id.cardId);
cardIdField.setText(contents);
final EditText barcodeTypeField = (EditText) findViewById(R.id.barcodeType);
barcodeTypeField.setText(format);
onResume();
}
if(requestCode == SELECT_BARCODE_REQUEST && resultCode == Activity.RESULT_OK)
{
Log.i(TAG, "Received barcode information from capture");
contents = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_CONTENTS);
format = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_FORMAT);
}
if(contents != null && contents.isEmpty() == false &&
format != null && format.isEmpty() == false)
{
Log.i(TAG, "Read barcode id: " + 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();
}
}
}
}

View File

@@ -1,19 +1,34 @@
package protect.card_locker;
import android.content.ClipData;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ClipboardManager;
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.ContextMenu;
import android.view.Menu;
import android.view.MenuInflater;
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 android.widget.Toast;
import java.util.Calendar;
public class MainActivity extends AppCompatActivity
{
private static final String TAG = "LoyaltyCardLocker";
@Override
protected void onCreate(Bundle savedInstanceState)
{
@@ -55,6 +70,8 @@ public class MainActivity extends AppCompatActivity
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
cardList.setAdapter(adapter);
registerForContextMenu(cardList);
cardList.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
@Override
@@ -73,6 +90,39 @@ public class MainActivity extends AppCompatActivity
});
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, v, menuInfo);
if (v.getId()==R.id.list)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.card_longclick_menu, menu);
}
}
@Override
public boolean onContextItemSelected(MenuItem item)
{
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
ListView listView = (ListView) findViewById(R.id.list);
Cursor cardCursor = (Cursor)listView.getItemAtPosition(info.position);
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
if(card != null && item.getItemId() == R.id.action_clipboard)
{
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(card.store, card.cardId);
clipboard.setPrimaryClip(clip);
Toast.makeText(this, R.string.copy_to_clipboard_toast, Toast.LENGTH_LONG).show();
return true;
}
return super.onContextItemSelected(item);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
@@ -92,6 +142,89 @@ 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[] {"Guava", "https://github.com/google/guava"},
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,205 @@
<?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>
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/scrollView">
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout android:orientation="horizontal"
android:padding="10.0dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:textSize="20sp"
android:layout_gravity="center_vertical"
android:paddingStart="20.0dip"
android:paddingEnd="20.0dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enterBarcodeInstructions" />
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/barcodeIdLayout">
<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/cardId"
android:text="@string/cardId" />
<EditText android:id="@+id/cardId"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="text"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/barcodesLayout"/>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/aztecBarcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/code39Barcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/code128Barcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/codabarBarcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/datamatrixBarcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/ean8Barcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/ean13Barcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/itfBarcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/pdf417Barcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/qrcodeBarcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/upcaBarcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
</android.support.design.widget.CoordinatorLayout>

View File

@@ -5,10 +5,6 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="protect.card_locker.MainActivity"
tools:showIn="@layout/main_activity">

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

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:orientation="vertical"
android:padding="5.0dip"
android:padding="10.0dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout android:orientation="horizontal"
android:padding="5.0dip"
android:padding="5.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:baselineAligned="true">
@@ -13,11 +13,12 @@
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"
android:padding="5.0dip"
android:padding="5.0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:baselineAligned="true">

View File

@@ -1,11 +1,9 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="protect.budgetwatch.AccountsActivity">
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
@@ -52,6 +50,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"
@@ -116,6 +136,11 @@
android:layout_height="wrap_content"
android:text="@string/capture"
android:layout_weight="1.0"/>
<Button android:id="@+id/enterButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/enterCard"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dip"

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_clipboard"
android:title="@string/copy_to_clipboard"
app:showAsAction="always"/>
</menu>

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

@@ -0,0 +1,64 @@
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Loyalty Card Locker</string>
<string name="action_add">Přidat</string>
<string name="noGiftCards">ZAtím némáte žádné věrnostní karty. Klikněte na tlačítko "+" (plus) nahoře a začněte.\n\nLoyalty Card Locker umožňuje nosit své věrnostní karty v telefonu, takže jsou vždy na dosah.</string>
<string name="storeName">Obchod</string>
<string name="note">Poznámka</string>
<string name="cardId">ID karty</string>
<string name="barcodeType">Typ čárového kódu</string>
<string name="cancel">Zrušit</string>
<string name="save">Uložit</string>
<string name="capture">Naskenovat kartu</string>
<string name="enterCard">Vložit vlastnoručně</string>
<string name="edit">Editovat</string>
<string name="delete">Smazat</string>
<string name="confirm">Potvrdit</string>
<string name="deleteTitle">Odstzranit věrnostní kartu</string>
<string name="deleteConfirmation">Opravdu chcete smazat tuto věrnostní kartu?</string>
<string name="ok">Ano</string>
<string name="copy_to_clipboard">Kopírovat ID do schránky</string>
<string name="editCardTitle">Editovat věrnostní kartu</string>
<string name="addCardTitle">Přidat věrnostní kartu</string>
<string name="viewCardTitle">Zobrazit věrnostní kartu</string>
<string name="scanCardBarcode">Oskenujte kód karty</string>
<string name="barcodeImageDescription">Obrázek kódu karty</string>
<string name="noStoreError">Nebyl zadán Obchod</string>
<string name="noCardIdError">Nebylo zadáno ID karty</string>
<string name="cardIdFormat">%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 jsou importována do/z LoyaltyCardLocker.csv na externí úložišti.</string>
<string name="importedFrom">Importováno z: %1$s</string>
<string name="exportedTo">Exportováno do: %1$s</string>
<string name="fileMissing">Doubor chybí: %1$s</string>
<string name="importFailed">Import selhal: %1$s</string>
<string name="exportFailed">Export selhal: %1$s</string>
<string name="importing">Importuji&#8230;</string>
<string name="exporting">Exportuji&#8230;</string>
<string name="noExternalStoragePermissionError">Nelze importovat nebo exportovat karty bez přístupu k externímu uložišti</string>
<string name="about">O aplikaci</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">O aplikaci <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Verze: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Revizní informace: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> používá tyto knihovny třetích stran: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Vyberte čárový kód</string>
<string name="enterBarcodeInstructions">Zadejte hodnotu čárového kódu a potm vyberte kód, který představuje čárový kód, který je na kartě.</string>
<string name="copy_to_clipboard_toast">ID karty zkopírováno do schránky</string>
</resources>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Loyalty Card Locker</string>
<string name="about">Über</string>
<string name="about_title_fmt">Über <xliff:g id="app_name">%s</xliff:g></string>
<string name="action_add">Neu</string>
<string name="addCardTitle">Neue Kundenkarte</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_license">Lizensiert unter der GPLv3.</string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> benutzt die folgenden Fremdbibliotheken: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="viewCardTitle">Kundenkarte anzeigen</string>
<string name="ok">Ok</string>
<string name="note">Notiz</string>
<string name="save">Speichern</string>
<string name="scanCardBarcode">Barcode scannen</string>
<string name="selectBarcodeTitle">Barcode auswählen</string>
<string name="storeName">Firma</string>
<string name="noStoreError">Keine Firma angegeben</string>
<string name="exportName">Exportieren</string>
<string name="exportedTo">Exportiert nach: %1$s</string>
<string name="fileMissing">Datei fehlt: %1$s</string>
<string name="importExport">Import/Export</string>
<string name="importFailed">Import fehlgeschlagen: %1$s</string>
<string name="importName">Import</string>
<string name="importedFrom">Importiert von: %1$s</string>
<string name="noCardIdError">Keine Kartennummer angegeben</string>
<string name="noExternalStoragePermissionError">Ohne die Berechtigung für den externen Speicher kann kein Import oder Export erfolgen.</string>
<string name="noGiftCards">Du hast noch keine Kundenkarte angelegt. Über den "+" Button oben rechts, kannst du welche anlegen.\n\nDiese App ermöglicht es dir, deine Kundenkarten immer mit dir zu führen.</string>
<string name="cancel">Abrechen</string>
<string name="capture">Karte aufnehmen</string>
<string name="cardId">Kartennummer</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="confirm">Bestätigen</string>
<string name="copy_to_clipboard">Kopiere die Nummer in die Zwischenablage</string>
<string name="delete">Löschen</string>
<string name="deleteConfirmation">Bitte bestätige, dass du die Karte löschen möchtest.</string>
<string name="deleteTitle">Lösche die Kundenkarte</string>
<string name="edit">Bearbeiten</string>
<string name="editCardTitle">Kundenkarte bearbeiten</string>
<string name="enterCard">Karte einfügen</string>
<string name="exportFailed">Export fehlgeschlagen: %1$s</string>
<string name="barcodeType">Barcodeart</string>
<string name="barcodeImageDescription">Bild des Barcodes</string>
<string name="copy_to_clipboard_toast">Nummer in die Zwischenablage kopiert</string>
<string name="debug_version_fmt">Version: <xliff:g id="version">%s</xliff:g></string>
<string name="enterBarcodeInstructions">Füge die Kundennummer ein, anschließend wähle die korrekte Barcodeart aus.</string>
<string name="app_revision_fmt">Versions Information: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="importExportHelp">Daten werden aus LoyalityCardLocker.csv aus dem externen Speicher im-/exportiert.</string>
<string name="importing">Importiere…</string>
<string name="exporting">Exportiere…</string>
</resources>

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>
@@ -10,7 +12,7 @@
<string name="cancel">Annulla</string>
<string name="save">Salva</string>
<string name="capture">Salva tessera</string>
<string name="capture">Scansione carta</string>
<string name="edit">Modifica</string>
<string name="delete">Elimina</string>
@@ -25,4 +27,34 @@
<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>
<string name="enterCard">Inserisci carta</string>
<string name="selectBarcodeTitle">Seleziona codice a barre</string>
<string name="enterBarcodeInstructions">Digita il valore del codice a barre, quindi seleziona l\'immagine che rappresenta il codice a barre che vuoi usare.</string>
<string name="copy_to_clipboard">Copia ID negli appunti</string>
<string name="copy_to_clipboard_toast">ID della carta copiato negli appunti</string>
<string name="confirm">Conferma</string>
<string name="deleteTitle">Rimuovi carta fedeltà</string>
<string name="deleteConfirmation">Conferma che vuoi eliminare questa carta.</string>
</resources>

View File

@@ -0,0 +1,60 @@
<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>
<string name="enterCard">Voer kaart in</string>
<string name="selectBarcodeTitle">Selecteer barcode</string>
<string name="enterBarcodeInstructions">Voer de waarde van de barcode in en kies daarna de afbeelding die de barcode die je wil gebruiken representeert</string>
<string name="copy_to_clipboard">Kopieer het ID naar het klembord</string>
<string name="copy_to_clipboard_toast">Het ID is naar het klembord gekopieerd</string>
<string name="confirm">Bevestig</string>
<string name="deleteConfirmation">Bevestig deze kaart te verwijderen.</string>
<string name="deleteTitle">Verwijder kaart</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,18 +1,27 @@
<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>
<string name="cancel">Cancel</string>
<string name="save">Save</string>
<string name="capture">Capture Card</string>
<string name="enterCard">Enter Card</string>
<string name="edit">Edit</string>
<string name="delete">Delete</string>
<string name="confirm">Confirm</string>
<string name="deleteTitle">Remove Loyalty Card</string>
<string name="deleteConfirmation">Please confirm that you want to delete this card.</string>
<string name="ok">OK</string>
<string name="copy_to_clipboard">Copy ID to clipboard</string>
<string name="editCardTitle">Edit Loyalty Card</string>
<string name="addCardTitle">Add Loyalty Card</string>
@@ -25,4 +34,32 @@
<string name="noCardIdError">No Card ID entered</string>
<string name="cardIdFormat">%1$s: %2$s</string>
</resources>
<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>
<string name="selectBarcodeTitle">Select Barcode</string>
<string name="enterBarcodeInstructions">Enter the barcode value then select the image which represents the barcode you want to use</string>
<string name="copy_to_clipboard_toast">Card ID copied to clipboard</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 = 10;
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 = 10;
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 = 10;
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.1.2'
// 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