Compare commits

..

225 Commits
v0.7 ... v0.18

Author SHA1 Message Date
Branden Archer
f775f9224b Merge pull request #181 from brarcher/pre-v0.18
Update for v0.18
2018-01-19 23:36:49 -05:00
Branden Archer
17acb8370c Update for v0.18 2018-01-19 23:16:15 -05:00
Branden Archer
23766fe9f0 Merge pull request #180 from brarcher/import-from-filesystem
Import from filesystem
2018-01-19 23:09:28 -05:00
Branden Archer
f1b0a26591 Allow file paths when importing directly from filesystem
The import path directly from the file system may not
result in using a content:// or file:// Uri, but instead
a file system path. If this occurs, attempt to use a FileInputStream
to read the contents.
2018-01-19 22:57:25 -05:00
Branden Archer
f1b2c0d93d Report Uri after import
The filename from Uri code was removed in a previous commit.
To give some feedback about what was imported, the Uri itself
is reported.
2018-01-19 22:57:25 -05:00
Branden Archer
4034997d7f Merge pull request #179 from brarcher/import-crash
Do not bother to show file name after an import
2018-01-19 08:27:36 -05:00
Branden Archer
d3bbaf39f4 Do not bother to show file name after an import
It was observed by one user that the Uri to filename lookup
failed with an IllegalStateException. The code which
converts a Uri to a file path may not be that great,
especially now that the app is expected to not receive
file Uris. Instead of determining the best way to lookup
the file names, as it is not that important it will be
removed.
2018-01-19 00:12:36 -05:00
Branden Archer
ae7684921f Merge pull request #177 from brarcher/csv-crash
Fix crash on some corruption when importing CSV
2018-01-17 08:31:21 -05:00
Branden Archer
e73974536c Update how SDK 27 is acceptd on Travis-CI
There are issues with using the "andorid" tool to accept
some licenses on Travis-CI. The theory from Travis-CI is that
the android binary is out of date. An alternative is using
sdkmanager.
2018-01-17 08:04:26 -05:00
Branden Archer
43072e283f Fix crash on some corruption when importing CSV
Commons-CSV would throw a RuntimeException in some cases of
bad CSV input. This was later changed to throwing an
IllegalStateException. Updating to v1.5 to pick-up the change.
2018-01-16 22:04:36 -05:00
Branden Archer
a3a70d459b Merge pull request #176 from brarcher/readme
Update screenshots in README
2018-01-11 23:06:05 -05:00
Branden Archer
edfc91376f Update screenshots in README
The screenshots now use the images from the metadata
folder in the repo.
2018-01-11 22:35:58 -05:00
Branden Archer
5fb68055ea Merge pull request #175 from brarcher/changelog
Update CHANGELOG
2018-01-11 22:24:43 -05:00
Branden Archer
085a3f10e7 Update CHANGELOG 2018-01-11 22:11:53 -05:00
Branden Archer
4070a6b4e0 Merge pull request #174 from brarcher/transifex
Import translations from Transifex
2018-01-11 22:08:26 -05:00
Branden Archer
bc547a1d2b Import translations from Transifex 2018-01-11 22:01:49 -05:00
Branden Archer
4f9c75da9e Merge pull request #173 from brarcher/pre-v0.17
Update for v0.17
2018-01-11 21:48:52 -05:00
Branden Archer
fde679751a Update for v0.17 2018-01-11 21:36:52 -05:00
Branden Archer
d4720db2e7 Merge pull request #172 from brarcher/update-sdk-27
Update to Android SDK 27
2018-01-03 23:02:12 -05:00
Branden Archer
245935242f Update to Android SDK 27 2018-01-03 22:20:00 -05:00
Branden Archer
e217bd28fb Merge pull request #171 from brarcher/new-icon
New app icon and color scheme
2018-01-03 21:26:32 -05:00
Branden Archer
ad17bc6bf3 Update first start wizard images with new logo and colors
Note that the second slide has been changed. Originally there was
text over the image with an arrow. This text could not be translated
and was part of the instruction. The arrow is really not needed,
so it has been removed with the overlay text.

Also, the originals folder has been removed. The screenshots which
were used to create the wizard images are in the metadata folder.
2018-01-03 21:17:59 -05:00
Branden Archer
17ab1365bf Add metadata in fastlane format
This is for F-Droid to use these for the app's listing.
2018-01-03 21:17:58 -05:00
Branden Archer
7ebbe6413f Update app icons and colors with new design
This icon and feature graphic are contributed under
CC0 1.0 Universal license by Samy, @samymarboy.
2018-01-03 21:17:58 -05:00
Branden Archer
adbc6bf999 Merge pull request #170 from brarcher/file-picker
Remove file:// Uri from backup file picker
2017-12-31 16:24:18 -05:00
Branden Archer
52c41f4c49 Remove file:// Uri from backup file picker
On Android SDK 24+ exposing a file Uri causes a failure.
Picking a file from a file chooser using data of a file://
Uri also hits this. As this is not necessary, removing the
Uri.
2017-12-31 15:53:57 -05:00
Branden Archer
858e317d8f Merge pull request #169 from brarcher/android-studio-3
Update gradle, et al, for Android Studio 3
2017-12-25 21:53:02 -05:00
Branden Archer
898e822a28 Update gradle and gradle android plugin
This is to support Android Studio 3
2017-12-25 19:17:35 -05:00
Branden Archer
6f45f635aa Quote FindBugs dependsOn target
later versions of gradle depend on this
2017-12-25 19:17:04 -05:00
Branden Archer
7eeb87578f Merge pull request #164 from brarcher/pre-v0.16
Update for v0.16
2017-11-29 20:23:57 -05:00
Branden Archer
badba551d0 Update CHANGELOG 2017-11-29 20:18:12 -05:00
Branden Archer
49abf8a918 Update for v0.16 2017-11-29 20:15:10 -05:00
Branden Archer
5fba338223 Merge pull request #163 from brarcher/shortcut
Remove support for adding shortcuts from within the app
2017-11-29 20:12:25 -05:00
Branden Archer
388a0723dc Remove support for adding shortcuts from within the app
The preferred way to create a shortcut is through the launcher.
Although it is possible for an app to create shortcuts, there is
concern that allowing the app to do so may not be desirable
due to potentials for abuse.

As there is support now for adding shortcuts through the launcher,
this app do not need to also support creating them itself.
2017-11-29 19:03:34 -05:00
Branden Archer
e14da63d06 Merge pull request #161 from brarcher/add-shortcuts-from-launcher
Add shortcuts from launcher
2017-11-27 14:56:44 -05:00
Branden Archer
1673c0229a Update strings around adding shortcuts
Chrome uses the wording "Add to home screen" when adding
a shortcut.

   https://developer.chrome.com/multidevice/android/installtohomescreen

That sounds more direct that this app's wording. Updating to match.
2017-11-27 14:50:18 -05:00
Branden Archer
22afeddcbc Add activity for handling shortcut add requests 2017-11-26 20:50:15 -05:00
Branden Archer
7758c61079 Merge pull request #157 from brarcher/pre-v0.15
Pre v0.15
2017-11-25 14:48:27 -05:00
Branden Archer
47b30ca9fb Update for v0.15 2017-11-25 14:41:20 -05:00
Branden Archer
7b4d587b13 Update CHANGELOG for v0.15 2017-11-25 14:40:40 -05:00
Branden Archer
685ee018c6 Merge pull request #155 from brarcher/shortcuts
Add support for shortcuts
2017-11-25 14:40:07 -05:00
Branden Archer
e0915578ba Redraw card info when launched by a shortcut
When a shortcut launches the view activity, if the activity
already exists it will get onNewIntent() called. This needs to
clear out the view items so the next card will be redrawn.
2017-11-25 14:33:59 -05:00
Branden Archer
c733a6c3b9 Start shortcuts with singleTop, but not all launches
This will prevent shortcuts from creating many views, and causing
them to leak. However, do not use singleTop for all launches
of the view activity, else the edit mode cannot be entered.
2017-11-25 14:33:59 -05:00
Branden Archer
ec17255a43 Remove unneeded local variables
The global variables have the same information; no need to look
up the information again.
2017-11-25 14:33:59 -05:00
Branden Archer
1fc7baa5a0 Capture view items in onCreate
Some of these will need to be accessed prior to onResume()
2017-11-25 14:33:59 -05:00
Branden Archer
0df411ee96 Revert "Add widget for directly opening specific card"
This is largely unneeded, as shortcuts are directly supported
now, and the widget was a hacky replacement for shortcuts.

This reverts commit ebe6139a64.
2017-11-25 14:33:59 -05:00
Branden Archer
23178d9694 Optionally add shortcut when creating/editing a card
When a card is being added or created, a checkbox will now
be presented asking if a shortcut should be made. If selected,
when saving a shortcut will be added to the home screen. The
shortcut will directly launch the given card in view mode.
2017-11-25 14:33:59 -05:00
Branden Archer
94accc951d Let insertLoyaltyCard return the new ID
Card IDs for new cards will soon need to be known once they
are created. This change updates this call to return the
new id.
2017-11-25 14:33:59 -05:00
Branden Archer
0207e12aed Revert "Only let one card viewing activity exist at a time"
This prevented the editing of cards, as the same activity
was used for viewing and editing, but the viewing "instance"
was calling finish(). It is simpler to remove the singleTop
option.

This reverts commit 4e02252b75.
2017-11-25 14:33:59 -05:00
Branden Archer
c760465b3e Merge pull request #156 from brarcher/mimetype
Report accurate mime type when sending backup data
2017-11-25 14:33:44 -05:00
Branden Archer
f480bd0c7e Report the correct mime type for exported data
Technically text/plain is correct-ish, but text/csv is more correct.
2017-11-25 14:25:47 -05:00
Branden Archer
5599560258 Remove unneeded imports 2017-11-25 14:25:47 -05:00
Branden Archer
7b4c119d7d Merge pull request #153 from brarcher/content-imports
Support importing/exporting via content providers
2017-11-21 21:24:52 -05:00
Branden Archer
6144353079 List correct file name on imports 2017-11-21 21:15:42 -05:00
Branden Archer
03a5334961 Export backups using FileProvider
On Android 7+ providing another activity a file:// Uri is
discouraged. This changes instead uses a content provider
backed by a FileProvider to give backup data to other
activities.
2017-11-21 21:14:47 -05:00
Branden Archer
04e0a5716e Flatten error handling when importing from an activity 2017-11-21 21:12:21 -05:00
Branden Archer
91e3f9f785 Support importing content URIs and file URIs
When importing backed up settings other activities may provide
data via a content URI. This is especially likely on Android 7+,
where providing a file URI is flagged as a security issue.
To support such activities, this commit enables supporting
content URIs for importing settings.
2017-11-21 18:15:56 -05:00
Branden Archer
2ebc862e27 Remove file arg from TaskCompleteListener
Soon more than files will be imported, as content URIs will
also be supported. This then makes the File argument for
onTaskComplete() not always useful, as there may not
be a direct file used. To this end, removing the File
argument as the caller should know what was passed
to the ImportExport task anyway.
2017-11-21 16:41:25 -05:00
Branden Archer
5d122affce Merge pull request #150 from brarcher/proguard
do not obfuscate using proguard
2017-10-01 14:32:48 -04:00
Branden Archer
9e09b9052a do not obfuscate using proguard 2017-10-01 13:53:11 -04:00
Branden Archer
2753b8826f Merge pull request #149 from brarcher/singletop
Only let one card viewing activity exist at a time
2017-09-28 12:59:19 -04:00
Branden Archer
4e02252b75 Only let one card viewing activity exist at a time
Now that there are shortcuts which can launch card viewing activities
directly, if a user does not back out of the activity then there will
be a pile up of activities over time. To prevent this, only let
one such view activity exist at a time.
2017-09-28 11:31:00 -04:00
Branden Archer
647ce00e72 Merge pull request #148 from brarcher/text
Capitalize first letter in store and note fields
2017-09-28 10:03:30 -04:00
Branden Archer
86a5f2fb50 Capitalize first letter in store and note fields 2017-09-28 09:46:14 -04:00
Branden Archer
dca5129031 Merge pull request #147 from brarcher/proguard
Do not strip line number info with proguard
2017-09-27 12:14:48 -04:00
Branden Archer
e30eb00bf7 Do not strip line number info with proguard 2017-09-27 11:46:31 -04:00
Branden Archer
11e5cb8ec2 Merge pull request #146 from brarcher/pre-v0.14
Update for v0.14
2017-09-26 23:25:48 -04:00
Branden Archer
bdd5c8fbbb Update CHANGELOG 2017-09-26 23:19:06 -04:00
Branden Archer
cd79839748 Update for v0.14 2017-09-26 23:14:47 -04:00
Branden Archer
d6b47914c8 Merge pull request #145 from brarcher/app-shortcuts
Add card shortcuts for most recently used cards
2017-09-26 23:12:00 -04:00
Branden Archer
ce1acb83f0 Add card shortcuts for most recently used cards
This adds app shortcuts for the most recently used cards.
When a new card is accessed, it is added to the shortcuts list.
When the list exceeds its maximum size, the least recently
used shortcut is discarded.

Android limits the maximum number of shortcuts to 5, however
it recommends in its documentation to limit this to 4. This
commit limits this to 3, however, as that is aesthetically pleasing.
2017-09-26 22:52:32 -04:00
Branden Archer
bc360aa06c Merge pull request #142 from brarcher/widget
Add widget for directly opening specific card
2017-09-19 13:49:47 -04:00
Branden Archer
ebe6139a64 Add widget for directly opening specific card 2017-09-19 12:58:48 -04:00
Branden Archer
d354fd1877 Merge pull request #138 from brarcher/transifex
Remove missing translations stubbed to English
2017-08-10 21:49:46 -04:00
Branden Archer
00b612d6c7 Remove missing translations stubbed to English
By request, a project on Transifex was created to better
manage translations. To make things less confusing, instead
of using the MissingTranslations lint error, and thus needing
to add stub translations, the progress of translations is now
handled on Transifex.

transifex.com/na-243/loyalty-card-locker/
2017-08-10 21:25:13 -04:00
Branden Archer
a307511193 Merge pull request #137 from brarcher/readme
README: add translating info
2017-08-10 21:22:41 -04:00
Branden Archer
f7f358c5c3 README: add translating info 2017-08-10 21:15:10 -04:00
Branden Archer
f7b50a72a4 Merge pull request #135 from brarcher/changelog
Update CHANGELOG for v0.11.1, v0.12, v0.13
2017-07-26 18:18:36 -04:00
Branden Archer
68ce1fe9fd Update CHANGELOG for v0.11.1, v0.12, v0.13 2017-07-26 18:12:01 -04:00
Branden Archer
e822ab0b56 Merge pull request #133 from brarcher/pre-v0.13
Update for v0.13
2017-07-25 10:04:44 -04:00
Branden Archer
e33ab682a6 Update for v0.13 2017-07-25 09:58:19 -04:00
Branden Archer
0d50ad6d10 Merge pull request #132 from brarcher/check-for-card
Verify card exists before displaying it
2017-07-23 10:27:06 -04:00
Branden Archer
4143e5c286 Bail if loyalty card could not be found
This case was hit at least once by a user, though the scenario
is not known. If it is hit, post a message and bail gracefully,
until the reason can be determined.
2017-07-23 10:05:05 -04:00
Branden Archer
15425d51aa Add debugging when entering view activity 2017-07-22 23:52:21 -04:00
Branden Archer
3abcb32a75 Merge pull request #130 from brarcher/text-update
Change lock/unlock text to say block/unblock
2017-07-18 22:25:47 -04:00
Branden Archer
93124a88a5 Change lock/unlock text to say block/unblock 2017-07-18 22:18:52 -04:00
Branden Archer
29b00e3b59 Merge pull request #129 from airon90/patch-1
Update strings.xml
2017-07-18 22:06:08 -04:00
Michael Moroni
04174154d6 Update strings.xml
Instead of "Block screen" I translated "Block rotation" because I think it's more intuitive
2017-07-17 12:49:39 +02:00
Branden Archer
bf60976d10 Merge pull request #128 from brarcher/lock-orientation
Add option to lock screen orientation
2017-07-16 21:32:55 -04:00
Branden Archer
fb7e3e12f2 Add menu option to lock screen when viewing card
When passing a phone to a clerk to scan the barcode, if the
phone is rotated and the screen reloads it can be bothersome
or confusion. To avoid this situation, a new option is
added to lock the screen.

A menu icon is now added which defaults as unlocked. When
touched the app sets its orientation to the "natural" orientation
of the device. When touched again the sensor dictates the
orientation of the device.
2017-07-16 21:26:40 -04:00
Branden Archer
641d29a6f8 Remove usage of depreated location of ActivityController 2017-07-16 21:26:40 -04:00
Branden Archer
0f2ee296b4 Remove container class for intro slides
Even with passing a bundle with the layout ID sometimes
some users will encounter a situation where the _layout
variable is null. To avoid further issues, creating a
class for each intro slide, so the layout is encoded
into the class itself.
2017-07-16 21:26:40 -04:00
Branden Archer
ecd2ecd517 Merge pull request #127 from brarcher/pre-v0.12
Update for v0.12
2017-07-12 22:55:31 -04:00
Branden Archer
f3e7d8f5c1 Update for v0.12 2017-07-12 22:47:29 -04:00
Branden Archer
f4522fb17d Merge pull request #126 from brarcher/scale-barcode
Scale barcode height to match reduced width
2017-07-12 22:14:03 -04:00
Branden Archer
824a72b156 Scale height of barcode to match ratio when reducing width
When reducing the pixel size of the width, if the height is not
scaled to match then 1D barcodes end up being squished and are
too narrow to scan. To avoid this, scale the height to match.
Then, when the barcode is loaded into a Bitmap it will scale up
to the correct size.

It was found that on a Galaxy S4 a barcode width of 400 px started
to see some blurriness, but 500 px still looked sharp. Reducing
the maximum width to 500 px.
2017-07-11 23:16:26 -04:00
Branden Archer
116a44fd1c Let BarcodeImageWriterTask's constructor be package private 2017-07-11 23:13:46 -04:00
Branden Archer
b6190d3250 Merge pull request #125 from arno-github/patch-3
French update
2017-07-03 15:55:48 -04:00
arno-github
82c40408f7 French update 2017-07-03 21:10:21 +02:00
Branden Archer
c6c9fb763a Merge pull request #124 from arno-github/patch-2
Update french translation
2017-07-02 22:36:30 -04:00
arno-github
dda5dcc652 Update french translation
Cosmetic changes
2017-07-02 20:00:21 +02:00
Branden Archer
d039ad9fa4 Merge pull request #122 from Entze/master
add & refine german translation
2017-06-29 21:44:02 -04:00
Lukas Grassauer
5c70c98eb9 Merge branch 'master' of github.com:brarcher/loyalty-card-locker 2017-06-29 16:28:17 +02:00
Lukas Grassauer
2face21985 add missing and revise de translations 2017-06-29 16:23:10 +02:00
Branden Archer
b38ec7b753 Merge pull request #121 from brarcher/pre-v0.11.1
Update for v0.11.1
2017-06-29 10:13:07 -04:00
Branden Archer
32d5aec76b Update for v0.11.1 2017-06-28 22:49:44 -04:00
Branden Archer
bdbcaf4b1e Merge pull request #120 from brarcher/handleConfigChanges
handle orientation and resize config change for IntroActivity
2017-06-28 22:48:50 -04:00
Branden Archer
f54905f218 handle orientation and resize config change for IntroActivity
If orientation and resize are not handled if the screen is
rotate or resized the IntroSlide fragment loses its layout,
and when it tries inflates the layout it fails to do so.
2017-06-28 22:42:02 -04:00
Branden Archer
2d3bd4a375 Merge pull request #118 from brarcher/credit
Add credit for save icon & update copyright string format
2017-06-26 22:56:36 -04:00
Branden Archer
de4ab95437 Add credit for save icon in about dialog 2017-06-26 22:10:09 -04:00
Branden Archer
09fba71710 Fix string format for copyright year
The year is an integer, but the format was for a string.
2017-06-26 22:10:09 -04:00
Branden Archer
2b84762b99 Merge pull request #117 from brarcher/changelog
Update CHANGELOG for v0.11
2017-06-26 22:07:38 -04:00
Branden Archer
3a4850d5cf Merge branch 'master' into changelog 2017-06-26 21:59:46 -04:00
Branden Archer
5bc8a915d6 Update CHANGELOG for v0.11 2017-06-26 21:59:36 -04:00
Branden Archer
15cbd5b641 Merge pull request #116 from brarcher/pre-v0.11
Update for v0.11 release
2017-06-26 21:59:07 -04:00
Branden Archer
d980079d76 Update for v0.11 release 2017-06-26 21:44:16 -04:00
Branden Archer
e58cbd972e Merge pull request #112 from brarcher/readme
Add attribution for save icon in README
2017-06-24 21:38:15 -04:00
Branden Archer
1fbecd439b Add attribution for save icon in README 2017-06-22 22:16:42 -04:00
Branden Archer
707306ac81 Merge pull request #111 from brarcher/readme
Update README with new screenshots
2017-06-21 23:42:12 -04:00
Branden Archer
86152586cd Update README with new screenshots 2017-06-21 23:32:09 -04:00
Branden Archer
fa33e4873e Merge pull request #109 from Airon90/patch-1
Update strings.xml
2017-06-20 22:43:40 -04:00
Branden Archer
24e4ef2d9b Merge branch 'master' into patch-1 2017-06-20 22:35:04 -04:00
Branden Archer
eac4142392 Escape ' in strings.xml 2017-06-20 22:23:33 -04:00
Branden Archer
f38a499bd8 Merge pull request #110 from brarcher/french
Update French translations
2017-06-20 22:21:45 -04:00
Branden Archer
0b8f52b87e Update French translations
Provided by GitHub user arno-github.
2017-06-20 22:14:27 -04:00
Michael Moroni
c8255a2648 Update strings.xml
Updated Italian (it) translation
2017-06-20 19:41:13 +02:00
Branden Archer
f28326e363 Merge pull request #108 from brarcher/intro
First run intro wizard
2017-06-18 23:46:37 -04:00
Branden Archer
a9952d021b Update to Robolectric 3.3.2
This is necessary, as Robolectric 3.0 did not work
with the newer Android SDK. Further, Robolectric 3.3.2
did not like running tests at SDK 17, so this was
also bumped.
2017-06-18 23:39:02 -04:00
Branden Archer
9274339be8 Add intro wizard for application 2017-06-18 23:37:51 -04:00
Branden Archer
18caa01089 Move the ic_launcher.xcf file into a separate directory 2017-06-18 23:05:23 -04:00
Branden Archer
ac495e1bf2 Add missing card add menu
There was no save button when creating a new card.
2017-06-18 23:05:22 -04:00
Branden Archer
a8eecb8841 Merge pull request #107 from brarcher/layout-and-colors
Layout and colors
2017-06-18 22:02:20 -04:00
Branden Archer
abdde45669 Update card view layout
This layout uses a TableLayout and shows lines dividing the
different parts of an entry.
2017-06-18 20:47:37 -04:00
Branden Archer
13b2faab39 Use save button in action bar 2017-06-18 15:37:40 -04:00
Branden Archer
433d31c30f Add super call in onCreateOptionsMenu 2017-06-18 15:37:40 -04:00
Branden Archer
fa7dcd253a Change menus names to focus where they are, not what they contain 2017-06-18 15:37:40 -04:00
Branden Archer
ac057f50e2 Change color palette to better match icon
The green primary color was a bit of contrast with the icons grey.
2017-06-18 15:37:40 -04:00
Branden Archer
ef33c7a33f Merge pull request #106 from brarcher/french-strings
Updated French strings.xml
2017-06-18 14:36:38 -04:00
Branden Archer
6028f7d674 Updated French strings.xml
Contributed by arno-github
2017-06-18 14:30:55 -04:00
Branden Archer
da5f999eed Merge pull request #104 from brarcher/edit-card
Update "Enter card" button if card id exists
2017-06-11 15:13:38 -04:00
Branden Archer
0a9124c834 Update "Enter card" button if card id exists
If someone is editing an existing card, or is adding a card
and wants to change the value, update the name of the button
from "Enter card" to "Edit card", to give the impression that
a new card id need not by typed from scratch.
2017-06-11 15:06:10 -04:00
Branden Archer
c173012955 Merge pull request #103 from brarcher/limit-width
Limit width of barcodes
2017-06-11 15:05:27 -04:00
Branden Archer
64db9e593d Limit width of barcodes
To reduce the amount of memory used in generating barcodes, limit
the width of the barcodes to no more than 3x the height.
This should be plenty sufficient to generate a usable barcode.

On devices with a really long width generating a barcode of 200x2560
is not useful and can result in an OutOfMemoryError.
2017-06-11 14:56:17 -04:00
Branden Archer
acde4ef181 Update to zxing 3.3.0 and zxing-android 3.5.0 2017-06-11 14:55:39 -04:00
Branden Archer
807ea25ac9 Merge pull request #102 from brarcher/privacy-policy
Create privacy-policy.md
2017-06-10 15:40:50 -04:00
Branden Archer
ad4476fead Create privacy-policy.md
This is required by Google for apps in the play store
which use certain permissions, such as the camera.
2017-06-10 15:33:25 -04:00
Branden Archer
f2c0e3904f Merge pull request #98 from brarcher/travis
Travis-CI changes
2017-05-20 18:07:47 -04:00
Branden Archer
0eeadbf77f Build only the release variant
Building both debug and release was unnecessary, and only
increased the time to validate a pull request
2017-05-20 13:36:23 -04:00
Branden Archer
c0e95bd4da do not emit xml results
gradle will output the failures, so this is unnecessary
2017-05-20 13:35:57 -04:00
Branden Archer
b7e8b8d36e Merge pull request #96 from brarcher/about
About
2017-03-06 21:49:53 -05:00
Branden Archer
2bca8b738a Change about dialog info to use map instead of String[][] 2017-03-05 14:42:19 -05:00
Branden Archer
2b617ceca5 Remove mention of Freepik icons from README
The main application icon was replaced, and the icon from
Freepik is no longer used.
2017-03-05 14:42:19 -05:00
Branden Archer
29838f4f73 Merge pull request #95 from brarcher/readme
Add Google Play badge
2017-03-04 00:08:04 -05:00
Branden Archer
1403d4a98e Add Google Play badge
Adds a Google Play badge and updated the F-Droid badge to match
2017-03-03 23:56:43 -05:00
Branden Archer
c4b860bb3d Merge pull request #94 from brarcher/pre-populate-enter-card
Pre-populate Barcode Selector
2017-02-12 22:48:35 -05:00
Branden Archer
2768f4c590 Start with all barcode images visible
If the user enters the barcode selector view with a pre-existing
id, the barcodes are immediately drawn. However, if the ImageViews
were invisible their initial size is not known, and as such the
generated barcode images are fuzzy or not as expected. To avoid this,
all images are set to be visible. The expectation is that once the
barcodes are drawn for the first time if they are invalid the images
will be made invisible anyway.
2017-02-12 22:42:25 -05:00
Branden Archer
8660d4e218 Pre-populate card id in Barcode Selector
If a user is attempting to update a card id of an existing card,
before when the Enter Card button was selected the barcode selector
view would be empty. To be more useful, the view will now be
pre-populated with the existing card id.
2017-02-12 22:42:25 -05:00
Branden Archer
596d899449 Merge pull request #93 from brarcher/changelog
Update changelog for v0.10
2017-02-12 15:53:25 -05:00
Branden Archer
0db5a6f803 Update changelog for v0.10 2017-02-12 15:40:20 -05:00
Branden Archer
bbd3782bf2 Merge pull request #92 from brarcher/pre-v0.10
Update for v0.10 release
2017-02-12 15:35:18 -05:00
Branden Archer
b69f2d470b Update for v0.10 release 2017-02-12 15:25:49 -05:00
Branden Archer
ce4dc01c9d Merge pull request #91 from brarcher/view-layout
View layout
2017-02-11 22:43:58 -05:00
Branden Archer
5cea7eba6c Allow text to be selectable in view mode
When text is long clicked it is now selectable, so that one can
copy it to the clipboard.
2017-02-11 22:33:57 -05:00
Branden Archer
0f7d64bed6 Show card data in a TextView when viewing
Earlier when a card was viewed it was shown in an EditView which
was disabled. This was not optimal, as the resulting text was
dimmed. To help the text be easier to read, a TextView is now
used to display the data.

In addition, the layout of the card viewing screen has been adjusted
2017-02-11 22:33:57 -05:00
Branden Archer
330b6ecd54 Merge pull request #87 from brarcher/string-correction
String correction
2017-01-28 19:58:56 -05:00
Branden Archer
3f5bcbb941 Add log message when exporting data 2017-01-28 16:22:14 -05:00
Branden Archer
9c74b2a264 Correct typo in comment 2017-01-28 16:22:14 -05:00
Branden Archer
dfc804fbe5 correct "Use external application" title
The title mistakenly said filesystem.
2017-01-28 16:22:14 -05:00
Branden Archer
fa73a30bf4 Use string const for button
There is a string for the "Use external application" button,
but it was not used.
2017-01-28 16:22:14 -05:00
Branden Archer
449419b647 Merge pull request #85 from brarcher/no-download-extras
Remove downloading extras during travis build
2017-01-17 21:43:35 -05:00
Branden Archer
36916b622b Remove downloading extras during travis build
The application does not use google play and other extra libraries,
so downloading them during the build should be unnecessary.
2017-01-17 21:25:50 -05:00
Branden Archer
03f97374bd Merge pull request #84 from brarcher/export-filename
Change default import/export file name
2017-01-17 21:24:35 -05:00
Branden Archer
8650878d95 Change default import/export file name
The application is now "Loyalty Card Keychain", but "Locker"
still appears in the application's default import/export
file. Updating the file to match the new name of the application.
2017-01-17 21:06:07 -05:00
Branden Archer
defb8793f6 Merge pull request #83 from brarcher/changelog
Update CHANGELOG for v0.9
2017-01-17 20:58:12 -05:00
Branden Archer
c6b7fd6032 Update CHANGELOG for v0.9 2017-01-17 20:47:31 -05:00
Branden Archer
5960f03f9b Merge pull request #82 from brarcher/pre-v0.9
Update for release v0.9
2017-01-16 23:07:37 -05:00
Branden Archer
b1df0c0baf Update for release v0.9 2017-01-16 22:53:21 -05:00
Branden Archer
bfb131c811 Merge pull request #81 from brarcher/keychain
Update the name "Locker" to "Keychain"
2017-01-16 22:50:36 -05:00
Branden Archer
4580ee8017 Update the "Locker" name to "Keychain"
The icon on the application is no longer a locker, so the
Locker part of the name no longer makes sense. Changing it
to Keychain, as it may make more sense.
2017-01-16 22:44:11 -05:00
Branden Archer
d76a6d79d7 Merge pull request #76 from brarcher/select-import-file
Select import file, allow exported file to be sent
2017-01-16 22:30:54 -05:00
Branden Archer
b38e035fb6 Allow sending newly exported data
With this change once data is exported it can be sent to
another application.

Translation contributions:
Clonewayx: cs
PanderMusubi: nl
pbeckmann: de
Airon90: it
arno-github : fr

# Conflicts:
#	app/src/main/res/values-it/strings.xml
2017-01-16 22:23:08 -05:00
Branden Archer
eeb41376c5 Add ability to select file to import
To date the only way to import a file is to hope the exported file
is in the correct location. To remove this requirement, this change
checks if there are any activities available on the system which
can look in the file system. If there are, new options for
importing data will be available.

In addition, the import/export activity's layout was updated,
adding more explanation.

Translation contributions:
Clonewayx: cs
PanderMusubi: nl
pbeckmann: de
Airon90: it
arno-github : fr
2017-01-16 22:23:08 -05:00
Branden Archer
89f649e5ef Correct TAG name 2017-01-16 22:23:08 -05:00
Branden Archer
4bde8f49b1 update gradle and android plugin 2017-01-16 22:23:08 -05:00
Branden Archer
87c1bbdbf4 Merge pull request #80 from arno-github/arno-github-patch-4
New french strings update
2016-12-10 11:17:16 -05:00
arno-github
7029818789 Update strings.xml 2016-12-10 15:10:41 +01:00
arno-github
b64c29bf96 Update french strings 2016-12-10 14:30:13 +01:00
arno-github
24317f4d99 Update strings.xml 2016-12-10 14:26:20 +01:00
arno-github
b9d3699038 Update strings.xml 2016-12-10 14:21:19 +01:00
arno-github
ed3c9ceb50 Update strings.xml 2016-12-10 14:13:03 +01:00
arno-github
0a938628cb Update strings.xml 2016-12-10 11:53:44 +01:00
arno-github
5720da1581 Merge pull request #1 from arno-github/arno-github-patch-1
Create strings.xml
2016-12-10 11:52:03 +01:00
arno-github
5393903872 Create strings.xml 2016-12-10 11:50:20 +01:00
Branden Archer
8e0a4b978d Merge pull request #75 from betsythefc/master
Icon V2
2016-12-06 21:53:07 -05:00
betsythefc
df21c2577d Improved icon with keyring hole 2016-12-05 20:47:52 -08:00
betsythefc
a7d5b6fa0c New icon V2 2016-12-05 11:35:40 -08:00
betsythefc
b49501e66a Merge remote-tracking branch 'brarcher/master' 2016-12-05 11:30:54 -08:00
Branden Archer
65d0f57358 Merge pull request #62 from techwebpd/patch-1
Lithuanian strings.xml
2016-12-03 11:35:32 -05:00
Branden Archer
927ac61912 Merge branch 'master' into patch-1 2016-12-03 11:29:52 -05:00
Branden Archer
fd2b84f318 Remove string that did not need translating. 2016-12-03 11:29:44 -05:00
betsythefc
ed4afed538 New Icon 2016-12-02 11:19:18 -08:00
Branden Archer
007427023b Merge pull request #70 from brarcher/changelog
Create CHANGELOG.md
2016-11-22 22:44:13 -05:00
Branden Archer
b4ff68eddb Create CHANGELOG.md
Per-request from:
https://github.com/brarcher/loyalty-card-locker/issues/48
2016-11-22 22:35:49 -05:00
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
techwebpd
71777a74fd Lithuanian strings.xml
lithuanian translation
2016-10-28 16:28:19 +03: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
112 changed files with 2909 additions and 519 deletions

View File

@@ -1,23 +1,16 @@
language: android
android:
components:
# Uncomment the lines below if you want to
# use the latest revision of Android SDK Tools
- platform-tools
- tools
sudo: true
# The BuildTools version used by your project
- build-tools-23.0.2
install:
- echo y | android update sdk -u -a -t tools
- echo y | android update sdk -u -a -t platform-tools
- echo y | android update sdk -u -a -t build-tools-26.0.2
- yes | sdkmanager "platforms;android-27"
- echo y | android update sdk -u -a -t extra-google-m2repository
- echo y | android update sdk -u -a -t extra-android-m2repository
# The SDK version used to compile your project
- android-23
# Additional components
- extra
script: ./gradlew build lint findbugs test
script: ./gradlew assembleRelease testReleaseUnitTest lintRelease findbugs
after_failure:
- cat app/build/outputs/lint-results.xml
- cat app/build/reports/findbugs/findbugs.html
- cat app/build/reports/tests/debug/index.html

141
CHANGELOG.md Normal file
View File

@@ -0,0 +1,141 @@
## v0.17 (2018-01-11)
Changes:
- Fix issue on Android SDK 24+ where using the file chooser import option would cause a crash. (https://github.com/brarcher/loyalty-card-locker/pull/170)
- New icon and color scheme. (https://github.com/brarcher/loyalty-card-locker/pull/171)
## v0.16 (2017-11-29)
Changes:
- Add support for adding loyalty card shortcuts from the launcher/homescreen. (https://github.com/brarcher/loyalty-card-locker/pull/161)
- Remove support for adding loyalty card shortcuts from the app itself. This removes the need for the shortcut permission. (https://github.com/brarcher/loyalty-card-locker/pull/163)
## v0.15 (2017-11-25)
Changes:
- Add support for adding shortcuts to home screen when adding or editing a card. (https://github.com/brarcher/loyalty-card-locker/pull/155)
- Remove widget, as it was a poor substitute for shortcuts. (https://github.com/brarcher/loyalty-card-locker/pull/155)
- Fix exporting backups on Android 7+. (https://github.com/brarcher/loyalty-card-locker/pull/153)
- Report more accurate mime type when exporting backup data. (https://github.com/brarcher/loyalty-card-locker/pull/156)
- Fix bug where a card could not be edited. (https://github.com/brarcher/loyalty-card-locker/pull/155)
## v0.14 (2017-10-26)
Changes:
- Add support for app shortcuts (Android 7.1+), where the most recently used cards will appear as shortcuts. (https://github.com/brarcher/loyalty-card-locker/pull/145)
- Add a widget which works like a pinned app shortcut, to support devices which run below Android 7.1. (https://github.com/brarcher/loyalty-card-locker/pull/142)
## v0.13 (2017-07-25)
Changes:
- Add screen rotation lock menu option when displaying a card. If locked, the screen will transition to its "natural" orientation and further screen rotation will be blocked. (https://github.com/brarcher/loyalty-card-locker/pull/128)
- If a card is selected from the main screen but cannot be loaded, the application fails gracefully and posts a message. (https://github.com/brarcher/loyalty-card-locker/pull/132)
- Fix case where layout IDs for intro wizard could not be found. (https://github.com/brarcher/loyalty-card-locker/pull/128)
## v0.12 (2017-07-16)
Changes:
- A change in v0.11 reduced the memory usage of barcode drawing, but affected the barcode dimensions. This is now changed to maintain the barcode dimensions while reducing memory usage. (https://github.com/brarcher/loyalty-card-locker/pull/126)
- Update German and French translations. (https://github.com/brarcher/loyalty-card-locker/pull/122, https://github.com/brarcher/loyalty-card-locker/pull/124, https://github.com/brarcher/loyalty-card-locker/pull/125)
## v0.11.1 (2017-06-29)
Changes:
- Prevent a crash when rotation the screen in the first run intro wizard.
## v0.11 (2017-06-26)
Improvements:
- When editing a card ID, pre-populate the existing ID to start. (https://github.com/brarcher/loyalty-card-locker/pull/94)
- Limit the width of generated barcodes to reduce memory usage and out of memory errors. (https://github.com/brarcher/loyalty-card-locker/pull/103)
- When editing a card, change the "Enter Card" button to say "Edit Card" if a card ID already exists. (https://github.com/brarcher/loyalty-card-locker/pull/104)
- Change the color scheme to be softer and compatible with the app icon, and change the layout when viewing a card to be cleaner. (https://github.com/brarcher/loyalty-card-locker/pull/107)
- Add an intro wizard which launches on the app's first launch. (https://github.com/brarcher/loyalty-card-locker/pull/108)
## v0.10 (2017-02-12)
Improvements:
- Changed the default import/export filename. (https://github.com/brarcher/loyalty-card-locker/pull/84)
- Correct string on the import/export page. (https://github.com/brarcher/loyalty-card-locker/pull/87)
- Improve layout of card view page. The text should be easier to read, and is selectable with a long click. (https://github.com/brarcher/loyalty-card-locker/pull/91)
## v0.9 (2017-01-17)
The "Locker" part of the name was not intuitive. To help remedy this a new application icon was created by betsythefc which better represents the purpose of the application: to store loyalty cards which use barcodes. Along with this new icon the name of the application has been changed to "Loyalty Card Keychain".
Additional features/improvements:
- Importing/Exporting cards was changed to be more flexible. (https://github.com/brarcher/loyalty-card-locker/pull/76)
- Translations for Lithuanian added. (https://github.com/brarcher/loyalty-card-locker/pull/62)
- Translations for French added. (https://github.com/brarcher/loyalty-card-locker/pull/80)
## v0.8 (2016-11-22)
New features/improvements:
- Screen brightness increased to its maximum when displaying a card, to help barcode scanners successfully capture the barcode. (https://github.com/brarcher/loyalty-card-locker/pull/54)
- Add a delete confirmation when deleting a card. (https://github.com/brarcher/loyalty-card-locker/pull/55)
- Add translations for German (https://github.com/brarcher/loyalty-card-locker/pull/57) and Czech (https://github.com/brarcher/loyalty-card-locker/pull/58).
- Clarification change for Italian translation. (https://github.com/brarcher/loyalty-card-locker/pull/66)
## v0.7 (2016-07-14)
New features/improvements:
- Long-click of a card brings up option to copy card ID to the clipboard. (https://github.com/brarcher/loyalty-card-locker/issues/49)
Bug fixes:
- Back button on Input/Export view now works, moving user to main view
## v0.6 (2016-05-23)
New features/improvements:
- Allow user to enter barcode manually. If a user elects to enter a barcode manually, a list of all valid and supported barcode images is displayed. The user then may select the barcode image which matches what the user wants. https://github.com/brarcher/loyalty-card-locker/issues/33, https://github.com/brarcher/loyalty-card-locker/pull/44
Bug fixes:
- Resolve issue where some displayed barcodes were blurry. (https://github.com/brarcher/loyalty-card-locker/issues/37)
## v0.5 (2016-05-16)
New features/improvements:
- An about dialog can be opened from the main screen, which gives details about the application and project on GitHub (https://github.com/brarcher/loyalty-card-locker/issues/19)
- Allow loyalty card information to be imported from/exported to a CSV file in external storage (https://github.com/brarcher/loyalty-card-locker/issues/36 https://github.com/brarcher/loyalty-card-locker/issues/20)
## v0.4 (2016-04-09)
New features/improvements:
- Dutch translation
- Allow name field to be editable after adding loyalty card
- Add an optional note field
Bug fixes:
- Resolve all issues identified by FindBugs and require all FindBugs issues be resolved prior to pull request acceptance
## v0.3 (2016-02-11)
- Now officially supports the following list of 1D and 2D barcodes:
* AZTEC
* CODABAR
* CODE_39
* CODE_128
* DATA_MATRIX
* EAN_8
* EAN_13
* ITF
* PDF_417
* QR_CODE
* UPC_A
- Generated barcodes are larger, easier to scan from a scanning device
## v0.2 (2016-02-07)
- Italian translations
- Support for all 1D barcode types. (Originally only product 1D barcodes were supported)
- Add required camera permission, which was initially missing.
## v0.1 (2016-01-30)
- Ability to create/edit/delete loyalty cards
- Capture barcode of loyalty card using a camera
- Display captured barcode, for scanning at a store

View File

@@ -1,8 +1,10 @@
# Loyalty Card Locker
# Loyalty Card Keychain
[![Build Status](https://travis-ci.org/brarcher/loyalty-card-locker.svg?branch=master)](https://travis-ci.org/brarcher/loyalty-card-locker)
[![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")
<a href="https://f-droid.org/repository/browse/?fdid=protect.card_locker" target="_blank">
<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="90"/></a>
<a href="https://play.google.com/store/apps/details?id=protect.card_locker" target="_blank">
<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="90"/></a>
Stores all of your store loyalty cards on your phone, removing the need to carry them around. Currently the following barcode types are supported:
@@ -21,6 +23,16 @@ Stores all of your store loyalty cards on your phone, removing the need to carry
If there is any interest in improving this project, kindly submit a pull request with
proposed changes.
# Screenshots
[<img src="https://github.com/brarcher/loyalty-card-locker/raw/master/metadata/en-US/images/phoneScreenshots/screenshot-01.png" width=250>](https://github.com/brarcher/loyalty-card-locker/raw/master/metadata/en-US/images/phoneScreenshots/screenshot-01.png)
[<img src="https://github.com/brarcher/loyalty-card-locker/raw/master/metadata/en-US/images/phoneScreenshots/screenshot-03.png" width=250>](https://github.com/brarcher/loyalty-card-locker/raw/master/metadata/en-US/images/phoneScreenshots/screenshot-03.png)
[<img src="https://github.com/brarcher/loyalty-card-locker/raw/master/metadata/en-US/images/phoneScreenshots/screenshot-02.png" width=250>](https://github.com/brarcher/loyalty-card-locker/raw/master/metadata/en-US/images/phoneScreenshots/screenshot-02.png)
[<img src="https://github.com/brarcher/loyalty-card-locker/raw/master/metadata/en-US/images/phoneScreenshots/screenshot-04.png" width=250>](https://github.com/brarcher/loyalty-card-locker/raw/master/metadata/en-US/images/phoneScreenshots/screenshot-04.png)
[<img src="https://github.com/brarcher/loyalty-card-locker/raw/master/metadata/en-US/images/phoneScreenshots/screenshot-05.png" width=250>](https://github.com/brarcher/loyalty-card-locker/raw/master/metadata/en-US/images/phoneScreenshots/screenshot-05.png)
[<img src="https://github.com/brarcher/loyalty-card-locker/raw/master/metadata/en-US/images/phoneScreenshots/screenshot-06.png" width=250>](https://github.com/brarcher/loyalty-card-locker/raw/master/metadata/en-US/images/phoneScreenshots/screenshot-06.png)
# Building
To build, use the gradle wrapper scripts provided in the top level directory of the project. The following will
@@ -36,7 +48,11 @@ Windows:
./gradlew.bat build
```
# Translating
If you are interested in translating this application to another language, create a pull request with changes or find the project listing on [Transifex](https://www.transifex.com/na-243/loyalty-card-locker).
# 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,
and formatted using [Android Asset Studio](https://romannurik.github.io/AndroidAssetStudio/index.html).
This application uses the following image:
- [Save](https://thenounproject.com/term/save/716011) by [Bernar Novalyi](https://thenounproject.com/bernar.novalyi)

View File

@@ -7,15 +7,14 @@ findbugs {
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
compileSdkVersion 27
defaultConfig {
applicationId "protect.card_locker"
minSdkVersion 11
targetSdkVersion 23
versionCode 7
versionName "0.7"
minSdkVersion 17
targetSdkVersion 27
versionCode 19
versionName "0.18"
}
buildTypes {
release {
@@ -27,6 +26,15 @@ android {
disable "GoogleAppIndexingWarning"
disable "ButtonStyle"
disable "AlwaysShowAction"
disable "MissingTranslation"
}
// Starting with Android Studio 3 Robolectric is unable to find resources.
// The following allows it to find the resources.
testOptions {
unitTests {
includeAndroidResources = true
}
}
// This is for Robolectric support for SDK 23
@@ -35,17 +43,18 @@ android {
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.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'
compile 'com.android.support:appcompat-v7:27.0.2'
compile 'com.android.support:design:27.0.2'
compile 'com.journeyapps:zxing-android-embedded:3.5.0@aar'
compile 'com.google.zxing:core:3.3.0'
compile 'org.apache.commons:commons-csv:1.5'
compile group: 'com.google.guava', name: 'guava', version: '20.0'
compile 'com.github.apl-devs:appintro:v4.2.0'
testCompile 'junit:junit:4.12'
testCompile "org.robolectric:robolectric:3.0"
testCompile "org.robolectric:robolectric:3.3.2"
}
task findbugs(type: FindBugs, dependsOn: assembleDebug) {
task findbugs(type: FindBugs, dependsOn: 'assembleDebug') {
description 'Run findbugs'
group 'verification'

View File

@@ -15,3 +15,10 @@
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
-keepattributes SourceFile,LineNumberTable
# This keep the class and method names the same, for debugging stack traces
-dontobfuscate

View File

@@ -36,7 +36,8 @@
android:name=".LoyaltyCardViewActivity"
android:theme="@style/AppTheme.NoActionBar"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateHidden"/>
android:windowSoftInputMode="stateHidden"
android:exported="true"/>
<activity
android:name=".BarcodeSelectorActivity"
android:label="@string/selectBarcodeTitle"
@@ -48,6 +49,30 @@
android:label="@string/importExport"
android:configChanges="orientation|screenSize"
android:theme="@style/AppTheme.NoActionBar"/>
<activity
android:name=".intro.IntroActivity"
android:label=""
android:configChanges="orientation|screenSize"
android:theme="@style/AppTheme.NoActionBar"/>
<activity
android:name=".CardShortcutConfigure"
android:label="@string/cardShortcut"
android:configChanges="orientation|screenSize"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="${applicationId}">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths"/>
</provider>
</application>
</manifest>

View File

@@ -21,6 +21,7 @@ import java.lang.ref.WeakReference;
class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
{
private static final String TAG = "LoyaltyCardLocker";
private static final int MAX_WIDTH = 500;
private final WeakReference<ImageView> imageViewReference;
private final String cardId;
@@ -28,7 +29,7 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
private final int imageHeight;
private final int imageWidth;
public BarcodeImageWriterTask(ImageView imageView, String cardIdString,
BarcodeImageWriterTask(ImageView imageView, String cardIdString,
BarcodeFormat barcodeFormat)
{
// Use a WeakReference to ensure the ImageView can be garbage collected
@@ -36,8 +37,19 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
cardId = cardIdString;
format = barcodeFormat;
imageHeight = imageView.getHeight();
imageWidth = imageView.getWidth();
if(imageView.getWidth() < MAX_WIDTH)
{
imageHeight = imageView.getHeight();
imageWidth = imageView.getWidth();
}
else
{
// Scale down the image to reduce the memory needed to produce it
imageWidth = MAX_WIDTH;
double ratio = (double)MAX_WIDTH / (double)imageView.getWidth();
imageHeight = (int)(imageView.getHeight() * ratio);
}
}
public Bitmap doInBackground(Void... params)

View File

@@ -124,6 +124,14 @@ public class BarcodeSelectorActivity extends AppCompatActivity
// Noting to do
}
});
final Bundle b = getIntent().getExtras();
final String initialCardId = b != null ? b.getString("initialCardId") : null;
if(initialCardId != null)
{
cardId.setText(initialCardId);
}
}
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId)
@@ -136,7 +144,6 @@ public class BarcodeSelectorActivity extends AppCompatActivity
}
image.setImageBitmap(null);
image.setVisibility(View.GONE);
image.setOnClickListener(new View.OnClickListener()
{
@Override

View File

@@ -0,0 +1,83 @@
package protect.card_locker;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
/**
* The configuration screen for creating a shortcut.
*/
public class CardShortcutConfigure extends AppCompatActivity
{
static final String TAG = "LoyaltyCardLocker";
@Override
public void onCreate(Bundle bundle)
{
super.onCreate(bundle);
// Set the result to CANCELED. This will cause nothing to happen if the
// aback button is pressed.
setResult(RESULT_CANCELED);
setContentView(R.layout.main_activity);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setVisibility(View.GONE);
final DBHelper db = new DBHelper(this);
// If there are no cards, bail
if(db.getLoyaltyCardCount() == 0)
{
Toast.makeText(this, R.string.noCardsMessage, Toast.LENGTH_LONG).show();
finish();
}
final ListView cardList = (ListView) findViewById(R.id.list);
cardList.setVisibility(View.VISIBLE);
Cursor cardCursor = db.getLoyaltyCardCursor();
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
cardList.setAdapter(adapter);
cardList.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Cursor selected = (Cursor) parent.getItemAtPosition(position);
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
Log.d(TAG, "Creating shortcut for card " + loyaltyCard.store + "," + loyaltyCard.id);
Intent shortcutIntent = new Intent(CardShortcutConfigure.this, LoyaltyCardViewActivity.class);
shortcutIntent.setAction(Intent.ACTION_MAIN);
// Prevent instances of the view activity from piling up; if one exists let this
// one replace it.
shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
Bundle bundle = new Bundle();
bundle.putInt("id", loyaltyCard.id);
bundle.putBoolean("view", true);
shortcutIntent.putExtras(bundle);
Parcelable icon = Intent.ShortcutIconResource.fromContext(CardShortcutConfigure.this, R.mipmap.ic_launcher);
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, loyaltyCard.store);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon);
setResult(RESULT_OK, intent);
finish();
}
});
}
}

View File

@@ -40,7 +40,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
parser.close();
database.setTransactionSuccessful();
}
catch(IllegalArgumentException e)
catch(IllegalArgumentException|IllegalStateException e)
{
throw new FormatException("Issue parsing CSV data", e);
}

View File

@@ -51,7 +51,7 @@ public class DBHelper extends SQLiteOpenHelper
}
}
public boolean insertLoyaltyCard(final String store, final String note, final String cardId,
public long insertLoyaltyCard(final String store, final String note, final String cardId,
final String barcodeType)
{
SQLiteDatabase db = getWritableDatabase();
@@ -61,7 +61,7 @@ public class DBHelper extends SQLiteOpenHelper
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
return (newId != -1);
return newId;
}
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id,

View File

@@ -1,25 +1,48 @@
package protect.card_locker;
import android.Manifest;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.provider.OpenableColumns;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
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.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
public class ImportExportActivity extends AppCompatActivity
{
private static final int PERMISSIONS_EXTERNAL_STORAGE_IMPORT = 1;
private static final int PERMISSIONS_EXTERNAL_STORAGE_EXPORT = 2;
private static final String TAG = "LoyaltyCardLocker";
ImportExportTask importExporter;
private static final int PERMISSIONS_EXTERNAL_STORAGE = 1;
private static final int CHOOSE_EXPORT_FILE = 2;
private ImportExportTask importExporter;
private final File sdcardDir = Environment.getExternalStorageDirectory();
private final File exportFile = new File(sdcardDir, "LoyaltyCardKeychain.csv");
@Override
protected void onCreate(Bundle savedInstanceState)
@@ -34,25 +57,20 @@ public class ImportExportActivity extends AppCompatActivity
actionBar.setDisplayHomeAsUpEnabled(true);
}
Button importButton = (Button)findViewById(R.id.importButton);
importButton.setOnClickListener(new View.OnClickListener()
// If the application does not have permissions to external
// storage, ask for it now
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
{
@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);
}
}
});
ActivityCompat.requestPermissions(ImportExportActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSIONS_EXTERNAL_STORAGE);
}
Button exportButton = (Button)findViewById(R.id.exportButton);
exportButton.setOnClickListener(new View.OnClickListener()
@@ -60,56 +78,129 @@ public class ImportExportActivity extends AppCompatActivity
@Override
public void onClick(View v)
{
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
startExport();
}
});
// Check that there is an activity that can bring up a file chooser
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
Button importFilesystem = (Button) findViewById(R.id.importOptionFilesystemButton);
importFilesystem.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
chooseFileWithIntent(intentPickAction);
}
});
if(isCallable(getApplicationContext(), intentPickAction) == false)
{
findViewById(R.id.dividerImportFilesystem).setVisibility(View.GONE);
findViewById(R.id.importOptionFilesystemTitle).setVisibility(View.GONE);
findViewById(R.id.importOptionFilesystemExplanation).setVisibility(View.GONE);
importFilesystem.setVisibility(View.GONE);
}
// Check that there is an application that can find content
final Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT);
intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE);
intentGetContentAction.setType("*/*");
Button importApplication = (Button) findViewById(R.id.importOptionApplicationButton);
importApplication.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
chooseFileWithIntent(intentGetContentAction);
}
});
if(isCallable(getApplicationContext(), intentGetContentAction) == false)
{
findViewById(R.id.dividerImportApplication).setVisibility(View.GONE);
findViewById(R.id.importOptionApplicationTitle).setVisibility(View.GONE);
findViewById(R.id.importOptionApplicationExplanation).setVisibility(View.GONE);
importApplication.setVisibility(View.GONE);
}
// This option, to import from the fixed location, should always be present
Button importButton = (Button)findViewById(R.id.importOptionFixedButton);
importButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
Uri uri = Uri.fromFile(exportFile);
try
{
startExport();
FileInputStream stream = new FileInputStream(exportFile);
startImport(stream, uri);
}
else
catch(FileNotFoundException e)
{
ActivityCompat.requestPermissions(ImportExportActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSIONS_EXTERNAL_STORAGE_EXPORT);
Log.e(TAG, "Could not import file " + exportFile.getAbsolutePath(), e);
onImportComplete(false, uri);
}
}
});
}
private void startImport()
private void startImport(final InputStream target, final Uri targetUri)
{
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@Override
public void onTaskComplete(boolean success)
{
onImportComplete(success, targetUri);
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
true, DataFormat.CSV);
DataFormat.CSV, target, listener);
importExporter.execute();
}
private void startExport()
{
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@Override
public void onTaskComplete(boolean success)
{
onExportComplete(success, exportFile);
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
false, DataFormat.CSV);
DataFormat.CSV, exportFile, listener);
importExporter.execute();
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
{
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE_IMPORT ||
requestCode == PERMISSIONS_EXTERNAL_STORAGE_EXPORT)
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE)
{
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED)
boolean success = grantResults.length > 0;
for(int grant : grantResults)
{
// permission was granted.
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE_IMPORT)
if(grant != PackageManager.PERMISSION_GRANTED)
{
startImport();
}
else
{
startExport();
success = false;
}
}
else
if(success == false)
{
// External storage permission rejected, inform user that
// import/export is prevented
@@ -143,4 +234,170 @@ public class ImportExportActivity extends AppCompatActivity
return super.onOptionsItemSelected(item);
}
private void onImportComplete(boolean success, Uri path)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if(success)
{
builder.setTitle(R.string.importSuccessfulTitle);
}
else
{
builder.setTitle(R.string.importFailedTitle);
}
int messageId = success ? R.string.importedFrom : R.string.importFailed;
final String template = getResources().getString(messageId);
// Get the filename of the file being imported
String filename = path.toString();
final String message = String.format(template, filename);
builder.setMessage(message);
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
builder.create().show();
}
private void onExportComplete(boolean success, final File path)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if(success)
{
builder.setTitle(R.string.exportSuccessfulTitle);
}
else
{
builder.setTitle(R.string.exportFailedTitle);
}
int messageId = success ? R.string.exportedTo : R.string.exportFailed;
final String template = getResources().getString(messageId);
final String message = String.format(template, path.getAbsolutePath());
builder.setMessage(message);
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
if(success)
{
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
builder.setPositiveButton(sendLabel, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
Uri outputUri = FileProvider.getUriForFile(ImportExportActivity.this, BuildConfig.APPLICATION_ID, path);
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, outputUri);
sendIntent.setType("text/csv");
// set flag to give temporary permission to external app to use the FileProvider
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
sendLabel));
dialog.dismiss();
}
});
}
builder.create().show();
}
/**
* Determines if there is at least one activity that can perform the given intent
*/
private boolean isCallable(Context context, final Intent intent)
{
PackageManager manager = context.getPackageManager();
if(manager == null)
{
return false;
}
List<ResolveInfo> list = manager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for(ResolveInfo info : list)
{
if(info.activityInfo.exported)
{
// There is one activity which is available to be called
return true;
}
}
return false;
}
private void chooseFileWithIntent(Intent intent)
{
try
{
startActivityForResult(intent, CHOOSE_EXPORT_FILE);
}
catch (ActivityNotFoundException e)
{
Log.e(TAG, "No activity found to handle intent", e);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK || requestCode != CHOOSE_EXPORT_FILE)
{
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
return;
}
Uri uri = data.getData();
if(uri == null)
{
Log.e(TAG, "Activity returned a NULL URI");
return;
}
try
{
InputStream reader;
if(uri.getScheme() != null)
{
reader = getContentResolver().openInputStream(uri);
}
else
{
reader = new FileInputStream(new File(uri.toString()));
}
Log.e(TAG, "Starting file import with: " + uri.toString());
startImport(reader, uri);
}
catch(FileNotFoundException e)
{
Log.e(TAG, "Failed to import file: " + uri.toString(), e);
onImportComplete(false, uri);
}
}
}

View File

@@ -12,59 +12,59 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
class ImportExportTask extends AsyncTask<Void, Void, Void>
class ImportExportTask extends AsyncTask<Void, Void, Boolean>
{
private static final String TAG = "BudgetWatch";
private static final String TARGET_FILE = "LoyaltyCardLocker.csv";
private static final String TAG = "LoyaltyCardLocker";
private Activity activity;
private boolean doImport;
private DataFormat format;
private File target;
private InputStream inputStream;
private TaskCompleteListener listener;
private ProgressDialog progress;
public ImportExportTask(Activity activity, boolean doImport, DataFormat format)
/**
* Constructor which will setup a task for exporting to the given file
*/
ImportExportTask(Activity activity, DataFormat format, File target,
TaskCompleteListener listener)
{
super();
this.activity = activity;
this.doImport = doImport;
this.doImport = false;
this.format = format;
this.target = target;
this.listener = listener;
}
private void toastWithArg(int stringId, String argument)
/**
* Constructor which will setup a task for importing from the given InputStream.
*/
ImportExportTask(Activity activity, DataFormat format, InputStream input,
TaskCompleteListener listener)
{
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();
}
});
super();
this.activity = activity;
this.doImport = true;
this.format = format;
this.inputStream = input;
this.listener = listener;
}
private void performImport(File importFile, DBHelper db)
private boolean performImport(InputStream stream, 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"));
InputStreamReader reader = new InputStreamReader(stream, Charset.forName("UTF-8"));
result = MultiFormatImporter.importData(db, reader, format);
reader.close();
}
@@ -73,11 +73,12 @@ class ImportExportTask extends AsyncTask<Void, Void, Void>
Log.e(TAG, "Unable to import file", e);
}
int messageId = result ? R.string.importedFrom : R.string.importFailed;
toastWithArg(messageId, importFile.getAbsolutePath());
Log.i(TAG, "Import result: " + result);
return result;
}
private void performExport(File exportFile, DBHelper db)
private boolean performExport(File exportFile, DBHelper db)
{
boolean result = false;
@@ -93,8 +94,9 @@ class ImportExportTask extends AsyncTask<Void, Void, Void>
Log.e(TAG, "Unable to export file", e);
}
int messageId = result ? R.string.exportedTo : R.string.exportFailed;
toastWithArg(messageId, exportFile.getAbsolutePath());
Log.i(TAG, "Export of '" + exportFile.getAbsolutePath() + "' result: " + result);
return result;
}
protected void onPreExecute()
@@ -114,26 +116,27 @@ class ImportExportTask extends AsyncTask<Void, Void, Void>
progress.show();
}
protected Void doInBackground(Void... nothing)
protected Boolean doInBackground(Void... nothing)
{
final File sdcardDir = Environment.getExternalStorageDirectory();
final File importExportFile = new File(sdcardDir, TARGET_FILE);
final DBHelper db = new DBHelper(activity);
boolean result;
if(doImport)
{
performImport(importExportFile, db);
result = performImport(inputStream, db);
}
else
{
performExport(importExportFile, db);
result = performExport(target, db);
}
return null;
return result;
}
protected void onPostExecute(Void result)
protected void onPostExecute(Boolean result)
{
listener.onTaskComplete(result);
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
}
@@ -143,4 +146,9 @@ class ImportExportTask extends AsyncTask<Void, Void, Void>
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled");
}
interface TaskCompleteListener
{
void onTaskComplete(boolean success);
}
}

View File

@@ -2,11 +2,16 @@ package protect.card_locker;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
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;
@@ -14,9 +19,14 @@ 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.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.integration.android.IntentIntegrator;
@@ -29,6 +39,41 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
private static final int SELECT_BARCODE_REQUEST = 1;
EditText storeFieldEdit;
TextView storeFieldView;
EditText noteFieldEdit;
TextView noteFieldView;
TextView cardIdFieldView;
View cardIdDivider;
View cardIdTableRow;
TextView barcodeTypeField;
ImageView barcodeImage;
View barcodeImageLayout;
View barcodeCaptureLayout;
Button captureButton;
Button enterButton;
int loyaltyCardId;
boolean updateLoyaltyCard;
boolean viewLoyaltyCard;
boolean rotationEnabled;
DBHelper db;
private void extractIntentFields(Intent intent)
{
final Bundle b = intent.getExtras();
loyaltyCardId = b != null ? b.getInt("id") : 0;
updateLoyaltyCard = b != null && b.getBoolean("update", false);
viewLoyaltyCard = b != null && b.getBoolean("view", false);
Log.d(TAG, "View activity: id=" + loyaltyCardId
+ ", updateLoyaltyCard=" + Boolean.toString(updateLoyaltyCard)
+ ", viewLoyaltyCard=" + Boolean.toString(viewLoyaltyCard));
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
@@ -42,6 +87,38 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
{
actionBar.setDisplayHomeAsUpEnabled(true);
}
extractIntentFields(getIntent());
db = new DBHelper(this);
storeFieldEdit = (EditText) findViewById(R.id.storeNameEdit);
storeFieldView = (TextView) findViewById(R.id.storeNameView);
noteFieldEdit = (EditText) findViewById(R.id.noteEdit);
noteFieldView = (TextView) findViewById(R.id.noteView);
cardIdFieldView = (TextView) findViewById(R.id.cardIdView);
cardIdDivider = findViewById(R.id.cardIdDivider);
cardIdTableRow = findViewById(R.id.cardIdTableRow);
barcodeTypeField = (TextView) findViewById(R.id.barcodeType);
barcodeImage = (ImageView) findViewById(R.id.barcode);
barcodeImageLayout = findViewById(R.id.barcodeLayout);
barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
captureButton = (Button) findViewById(R.id.captureButton);
enterButton = (Button) findViewById(R.id.enterButton);
}
@Override
public void onNewIntent(Intent intent)
{
Log.i(TAG, "Received new intent");
extractIntentFields(intent);
// Reset these fields, so they are re-populated in onResume().
storeFieldEdit.setText("");
noteFieldEdit.setText("");
cardIdFieldView.setText("");
barcodeTypeField.setText("");
}
@Override
@@ -49,47 +126,48 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
{
super.onResume();
final Bundle b = getIntent().getExtras();
final int loyaltyCardId = b != null ? b.getInt("id") : 0;
final boolean updateLoyaltyCard = b != null && b.getBoolean("update", false);
final boolean viewLoyaltyCard = b != null && b.getBoolean("view", false);
Log.i(TAG, "To view card: " + loyaltyCardId);
final EditText storeField = (EditText) findViewById(R.id.storeName);
final EditText 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);
final View barcodeIdLayout = findViewById(R.id.barcodeIdLayout);
final View barcodeTypeLayout = findViewById(R.id.barcodeTypeLayout);
final View barcodeImageLayout = findViewById(R.id.barcodeLayout);
final View barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
final Button captureButton = (Button) findViewById(R.id.captureButton);
final Button enterButton = (Button) findViewById(R.id.enterButton);
final Button saveButton = (Button) findViewById(R.id.saveButton);
final Button cancelButton = (Button) findViewById(R.id.cancelButton);
final DBHelper db = new DBHelper(this);
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);
}
}
if(updateLoyaltyCard || viewLoyaltyCard)
{
final LoyaltyCard loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
if(storeField.getText().length() == 0)
if(loyaltyCard == null)
{
storeField.setText(loyaltyCard.store);
Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId);
Toast.makeText(this, R.string.noCardExistsError, Toast.LENGTH_LONG).show();
finish();
return;
}
if(noteField.getText().length() == 0)
if(storeFieldEdit.getText().length() == 0)
{
noteField.setText(loyaltyCard.note);
storeFieldEdit.setText(loyaltyCard.store);
storeFieldView.setText(loyaltyCard.store);
}
if(cardIdField.getText().length() == 0)
if(noteFieldEdit.getText().length() == 0)
{
cardIdField.setText(loyaltyCard.cardId);
noteFieldEdit.setText(loyaltyCard.note);
noteFieldView.setText(loyaltyCard.note);
}
if(cardIdFieldView.getText().length() == 0)
{
cardIdFieldView.setText(loyaltyCard.cardId);
}
if(barcodeTypeField.getText().length() == 0)
@@ -97,42 +175,36 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
barcodeTypeField.setText(loyaltyCard.barcodeType);
}
if(viewLoyaltyCard)
{
storeField.setEnabled(false);
noteField.setEnabled(false);
}
if(updateLoyaltyCard)
{
setTitle(R.string.editCardTitle);
storeFieldView.setVisibility(View.GONE);
noteFieldView.setVisibility(View.GONE);
}
else
{
barcodeCaptureLayout.setVisibility(View.GONE);
captureButton.setVisibility(View.GONE);
saveButton.setVisibility(View.GONE);
cancelButton.setVisibility(View.GONE);
setTitle(R.string.viewCardTitle);
storeFieldEdit.setVisibility(View.GONE);
noteFieldEdit.setVisibility(View.GONE);
}
}
else
{
setTitle(R.string.addCardTitle);
storeFieldView.setVisibility(View.GONE);
noteFieldView.setVisibility(View.GONE);
}
if(cardIdField.getText().length() == 0)
{
barcodeIdLayout.setVisibility(View.GONE);
}
barcodeTypeLayout.setVisibility(View.GONE);
if(cardIdField.getText().length() > 0 && barcodeTypeField.getText().length() > 0)
if(cardIdFieldView.getText().length() > 0 && barcodeTypeField.getText().length() > 0)
{
String formatString = barcodeTypeField.getText().toString();
final BarcodeFormat format = BarcodeFormat.valueOf(formatString);
final String cardIdString = cardIdField.getText().toString();
final String cardIdString = cardIdFieldView.getText().toString();
if(barcodeImage.getHeight() == 0)
{
@@ -165,7 +237,6 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
}
barcodeIdLayout.setVisibility(View.VISIBLE);
barcodeImageLayout.setVisibility(View.VISIBLE);
}
@@ -191,73 +262,84 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
public void onClick(View v)
{
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
String cardId = cardIdFieldView.getText().toString();
if(cardId.length() > 0)
{
final Bundle b = new Bundle();
b.putString("initialCardId", cardId);
i.putExtras(b);
}
startActivityForResult(i, SELECT_BARCODE_REQUEST);
}
});
saveButton.setOnClickListener(new View.OnClickListener()
if(cardIdFieldView.getText().length() > 0)
{
@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();
if(store.isEmpty())
{
Snackbar.make(v, R.string.noStoreError, Snackbar.LENGTH_LONG).show();
return;
}
if(cardId.isEmpty() || barcodeType.isEmpty())
{
Snackbar.make(v, R.string.noCardIdError, Snackbar.LENGTH_LONG).show();
return;
}
if(updateLoyaltyCard)
{
db.updateLoyaltyCard(loyaltyCardId, store, note, cardId, barcodeType);
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
}
else
{
db.insertLoyaltyCard(store, note, cardId, barcodeType);
}
finish();
}
});
cancelButton.setOnClickListener(new View.OnClickListener()
cardIdDivider.setVisibility(View.VISIBLE);
cardIdTableRow.setVisibility(View.VISIBLE);
enterButton.setText(R.string.editCard);
}
else
{
@Override
public void onClick(View v)
{
finish();
}
});
cardIdDivider.setVisibility(View.GONE);
cardIdTableRow.setVisibility(View.GONE);
enterButton.setText(R.string.enterCard);
}
}
private void doSave()
{
String store = storeFieldEdit.getText().toString();
String note = noteFieldEdit.getText().toString();
String cardId = cardIdFieldView.getText().toString();
String barcodeType = barcodeTypeField.getText().toString();
if(store.isEmpty())
{
Snackbar.make(storeFieldEdit, R.string.noStoreError, Snackbar.LENGTH_LONG).show();
return;
}
if(cardId.isEmpty() || barcodeType.isEmpty())
{
Snackbar.make(cardIdFieldView, R.string.noCardIdError, Snackbar.LENGTH_LONG).show();
return;
}
if(updateLoyaltyCard)
{
db.updateLoyaltyCard(loyaltyCardId, store, note, cardId, barcodeType);
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
}
else
{
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, cardId, barcodeType);
}
finish();
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
final Bundle b = getIntent().getExtras();
final boolean updateLoyaltyCard = b != null && b.getBoolean("update", false);
final boolean viewLoyaltyCard = b != null && b.getBoolean("view", false);
if(viewLoyaltyCard)
{
getMenuInflater().inflate(R.menu.card_edit_menu, menu);
getMenuInflater().inflate(R.menu.card_view_menu, menu);
}
else if(updateLoyaltyCard)
{
getMenuInflater().inflate(R.menu.card_delete_menu, menu);
getMenuInflater().inflate(R.menu.card_update_menu, menu);
}
else
{
getMenuInflater().inflate(R.menu.card_add_menu, menu);
}
return true;
rotationEnabled = true;
return super.onCreateOptionsMenu(menu);
}
@Override
@@ -265,9 +347,6 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
{
int id = item.getItemId();
final Bundle b = getIntent().getExtras();
final int loyaltyCardId = b != null ? b.getInt("id") : 0;
switch(id)
{
case android.R.id.home:
@@ -275,11 +354,36 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
break;
case R.id.action_delete:
Log.e(TAG, "Deleting card: " + loyaltyCardId);
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);
ShortcutHelper.removeShortcut(LoyaltyCardViewActivity.this, 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();
DBHelper db = new DBHelper(this);
db.deleteLoyaltyCard(loyaltyCardId);
finish();
return true;
case R.id.action_edit:
Intent intent = new Intent(getApplicationContext(), LoyaltyCardViewActivity.class);
@@ -290,6 +394,26 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
startActivity(intent);
finish();
return true;
case R.id.action_lock_unlock:
if(rotationEnabled)
{
item.setIcon(R.drawable.ic_lock_outline_white_24dp);
item.setTitle(R.string.unlockScreen);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
}
else
{
item.setIcon(R.drawable.ic_lock_open_white_24dp);
item.setTitle(R.string.lockScreen);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
rotationEnabled = !rotationEnabled;
return true;
case R.id.action_save:
doSave();
return true;
}
return super.onOptionsItemSelected(item);
@@ -324,9 +448,10 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
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);
TextView cardIdView = (TextView)findViewById(R.id.cardIdView);
cardIdView.setText(contents);
final TextView barcodeTypeField = (TextView) findViewById(R.id.barcodeType);
barcodeTypeField.setText(format);
onResume();
}

View File

@@ -4,6 +4,7 @@ import android.content.ClipData;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ClipboardManager;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
@@ -23,7 +24,12 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.common.collect.ImmutableMap;
import java.util.Calendar;
import java.util.Map;
import protect.card_locker.intro.IntroActivity;
public class MainActivity extends AppCompatActivity
{
@@ -38,6 +44,12 @@ public class MainActivity extends AppCompatActivity
setSupportActionBar(toolbar);
updateLoyaltyCardList();
SharedPreferences prefs = getSharedPreferences("protect.card_locker", MODE_PRIVATE);
if (prefs.getBoolean("firstrun", true)) {
startIntro();
prefs.edit().putBoolean("firstrun", false).commit();
}
}
@Override
@@ -81,10 +93,14 @@ public class MainActivity extends AppCompatActivity
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
Intent i = new Intent(view.getContext(), LoyaltyCardViewActivity.class);
i.setAction("");
final Bundle b = new Bundle();
b.putInt("id", loyaltyCard.id);
b.putBoolean("view", true);
i.putExtras(b);
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard, i);
startActivity(i);
}
});
@@ -149,6 +165,12 @@ public class MainActivity extends AppCompatActivity
return true;
}
if(id == R.id.action_intro)
{
startIntro();
return true;
}
if(id == R.id.action_about)
{
displayAboutDialog();
@@ -160,21 +182,34 @@ public class MainActivity extends AppCompatActivity
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"},
};
final Map<String, String> USED_LIBRARIES = ImmutableMap.of
(
"Commons CSV", "https://commons.apache.org/proper/commons-csv/",
"Guava", "https://github.com/google/guava",
"ZXing", "https://github.com/zxing/zxing",
"ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded",
"AppIntro", "https://github.com/apl-devs/AppIntro"
);
final Map<String, String> USED_ASSETS = ImmutableMap.of
(
"Save by Bernar Novalyi", "https://thenounproject.com/term/save/716011"
);
StringBuilder libs = new StringBuilder().append("<ul>");
for (String[] library : USED_LIBRARIES)
for (Map.Entry<String, String> entry : USED_LIBRARIES.entrySet())
{
libs.append("<li><a href=\"").append(library[1]).append("\">").append(library[0]).append("</a></li>");
libs.append("<li><a href=\"").append(entry.getValue()).append("\">").append(entry.getKey()).append("</a></li>");
}
libs.append("</ul>");
StringBuilder resources = new StringBuilder().append("<ul>");
for (Map.Entry<String, String> entry : USED_ASSETS.entrySet())
{
resources.append("<li><a href=\"").append(entry.getValue()).append("\">").append(entry.getKey()).append("</a></li>");
}
resources.append("</ul>");
String appName = getString(R.string.app_name);
int year = Calendar.getInstance().get(Calendar.YEAR);
@@ -212,7 +247,9 @@ public class MainActivity extends AppCompatActivity
"</p><hr/><p>" +
getString(R.string.app_license) +
"</p><hr/><p>" +
String.format(getString(R.string.app_libraries), appName, libs.toString());
String.format(getString(R.string.app_libraries), appName, libs.toString()) +
"</p><hr/><p>" +
String.format(getString(R.string.app_resources), appName, resources.toString());
wv.loadDataWithBaseURL("file:///android_res/drawable/", html, "text/html", "utf-8", null);
new AlertDialog.Builder(this)
@@ -227,4 +264,10 @@ public class MainActivity extends AppCompatActivity
})
.show();
}
private void startIntro()
{
Intent intent = new Intent(this, IntroActivity.class);
startActivity(intent);
}
}

View File

@@ -0,0 +1,153 @@
package protect.card_locker;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.os.Build;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
class ShortcutHelper
{
// Android documentation says that no more than 5 shortcuts
// are supported. However, that may be too many, as not all
// launcher will show all 5. Instead, the number is limited
// to 3 here, so that the most recent shortcut has a good
// chance of being shown.
private static final int MAX_SHORTCUTS = 3;
/**
* Add a card to the app shortcuts, and maintain a list of the most
* recently used cards. If there is already a shortcut for the card,
* the card is marked as the most recently used card. If adding this
* card exceeds the max number of shortcuts, then the least recently
* used card shortcut is discarded.
*/
@TargetApi(25)
static void updateShortcuts(Context context, LoyaltyCard card, Intent intent)
{
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1)
{
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
LinkedList<ShortcutInfo> list = new LinkedList<>(shortcutManager.getDynamicShortcuts());
String shortcutId = Integer.toString(card.id);
// Sort the shortcuts by rank, so working with the relative order will be easier.
// This sorts so that the lowest rank is first.
Collections.sort(list, new Comparator<ShortcutInfo>()
{
@Override
public int compare(ShortcutInfo o1, ShortcutInfo o2)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
{
return o1.getRank() - o2.getRank();
}
else
{
return 0;
}
}
});
Integer foundIndex = null;
for(int index = 0; index < list.size(); index++)
{
if(list.get(index).getId().equals(shortcutId))
{
// Found the item already
foundIndex = index;
break;
}
}
if(foundIndex != null)
{
// If the item is already found, then the list needs to be
// reordered, so that the selected item now has the lowest
// rank, thus letting it survive longer.
ShortcutInfo found = list.remove(foundIndex.intValue());
list.addFirst(found);
}
else
{
// The item is new to the list. First, we need to trim the list
// until it is able to accept a new item, then the item is
// inserted.
while(list.size() >= MAX_SHORTCUTS)
{
list.pollLast();
}
ShortcutInfo shortcut = new ShortcutInfo.Builder(context, Integer.toString(card.id))
.setShortLabel(card.store)
.setLongLabel(card.store)
.setIntent(intent)
.build();
list.addFirst(shortcut);
}
LinkedList<ShortcutInfo> finalList = new LinkedList<>();
// The ranks are now updated; the order in the list is the rank.
for(int index = 0; index < list.size(); index++)
{
ShortcutInfo prevShortcut = list.get(index);
Intent shortcutIntent = prevShortcut.getIntent();
// Prevent instances of the view activity from piling up; if one exists let this
// one replace it.
shortcutIntent.setFlags(shortcutIntent.getFlags() | Intent.FLAG_ACTIVITY_SINGLE_TOP);
ShortcutInfo updatedShortcut = new ShortcutInfo.Builder(context, prevShortcut.getId())
.setShortLabel(prevShortcut.getShortLabel())
.setLongLabel(prevShortcut.getLongLabel())
.setIntent(shortcutIntent)
.setIcon(Icon.createWithResource(context, R.drawable.circle))
.setRank(index)
.build();
finalList.addLast(updatedShortcut);
}
shortcutManager.setDynamicShortcuts(finalList);
}
}
/**
* Remove the given card id from the app shortcuts, if such a
* shortcut exists.
*/
@TargetApi(25)
static void removeShortcut(Context context, int cardId)
{
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1)
{
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
List<ShortcutInfo> list = shortcutManager.getDynamicShortcuts();
String shortcutId = Integer.toString(cardId);
for(int index = 0; index < list.size(); index++)
{
if(list.get(index).getId().equals(shortcutId))
{
list.remove(index);
break;
}
}
shortcutManager.setDynamicShortcuts(list);
}
}
}

View File

@@ -0,0 +1,33 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import com.github.paolorotolo.appintro.AppIntro;
public class IntroActivity extends AppIntro
{
@Override
public void init(Bundle savedInstanceState)
{
addSlide(new IntroSlide1());
addSlide(new IntroSlide2());
addSlide(new IntroSlide3());
addSlide(new IntroSlide4());
addSlide(new IntroSlide5());
addSlide(new IntroSlide6());
}
@Override
public void onSkipPressed(Fragment fragment) {
finish();
}
@Override
public void onDonePressed(Fragment fragment) {
finish();
}
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import protect.card_locker.R;
public class IntroSlide1 extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.intro1_layout, container, false);
return v;
}
}

View File

@@ -0,0 +1,20 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import protect.card_locker.R;
public class IntroSlide2 extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.intro2_layout, container, false);
return v;
}
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import protect.card_locker.R;
public class IntroSlide3 extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.intro3_layout, container, false);
return v;
}
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import protect.card_locker.R;
public class IntroSlide4 extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.intro4_layout, container, false);
return v;
}
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import protect.card_locker.R;
public class IntroSlide5 extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.intro5_layout, container, false);
return v;
}
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import protect.card_locker.R;
public class IntroSlide6 extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.intro6_layout, container, false);
return v;
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -19,36 +19,148 @@
</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"/>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout android:orientation="horizontal"
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingBottom="8dp">
<TextView
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>
android:textSize="@dimen/text_size_medium"
android:layout_gravity="center"
android:text="@string/importExportHelp"/>
</LinearLayout>
<View
android:id="@+id/dividerExport"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_margin="16dp"
android:background="?android:attr/listDivider"/>
<TextView
android:id="@+id/exportOptionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_large"
android:text="@string/exportName"/>
<TextView
android:id="@+id/exportOptionExplanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/text_size_medium"
android:text="@string/exportOptionExplanation"/>
<Button
android:id="@+id/exportButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:text="@string/exportName" />
<View
android:id="@+id/dividerImportFilesystem"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_margin="16dp"
android:background="?android:attr/listDivider"/>
<TextView
android:id="@+id/importOptionFilesystemTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_large"
android:text="@string/importOptionFilesystemTitle"/>
<TextView
android:id="@+id/importOptionFilesystemExplanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/text_size_medium"
android:text="@string/importOptionFilesystemExplanation"/>
<Button
android:id="@+id/importOptionFilesystemButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:text="@string/importOptionFilesystemButton" />
<View
android:id="@+id/dividerImportApplication"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_margin="16dp"
android:background="?android:attr/listDivider"/>
<TextView
android:id="@+id/importOptionApplicationTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_large"
android:text="@string/importOptionApplicationTitle"/>
<TextView
android:id="@+id/importOptionApplicationExplanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/text_size_medium"
android:text="@string/importOptionApplicationExplanation"/>
<Button
android:id="@+id/importOptionApplicationButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:text="@string/importOptionApplicationButton" />
<View
android:id="@+id/dividerImportFixed"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_margin="16dp"
android:background="?android:attr/listDivider"/>
<TextView
android:id="@+id/importOptionFixedTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_large"
android:text="@string/importOptionFixedTitle"/>
<TextView
android:id="@+id/importOptionFixedExplanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/text_size_medium"
android:text="@string/importOptionFixedExplanation"/>
<Button
android:id="@+id/importOptionFixedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:text="@string/importOptionFixedButton" />
</LinearLayout>
</ScrollView>
</android.support.design.widget.CoordinatorLayout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#222222"
android:layout_weight="10"
android:id="@+id/main">
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="center"
android:gravity="center"
android:paddingLeft="32dp"
android:layout_weight="3"
android:fontFamily="sans-serif-thin"
android:textColor="#ffffff"
android:paddingRight="32dp"
android:textSize="28sp"
android:text="@string/intro1Title"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:gravity="center"
android:layout_weight="5">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:src="@drawable/app_icon_intro"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="3"
android:layout_gravity="center"
android:gravity="center"
android:textColor="#ffffff"
android:paddingLeft="64dp"
android:paddingRight="64dp"
android:textSize="16sp"
android:text="@string/intro1Description"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="64dp" />
</LinearLayout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#222222"
android:layout_weight="10"
android:id="@+id/main">
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="center"
android:gravity="center"
android:paddingLeft="32dp"
android:layout_weight="3"
android:fontFamily="sans-serif-thin"
android:textColor="#ffffff"
android:paddingRight="32dp"
android:textSize="28sp"
android:text="@string/intro2Title"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:gravity="center"
android:layout_weight="5">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:src="@drawable/intro2_image"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="3"
android:layout_gravity="center"
android:gravity="center"
android:textColor="#ffffff"
android:paddingLeft="64dp"
android:paddingRight="64dp"
android:textSize="16sp"
android:text="@string/intro2Description"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="64dp" />
</LinearLayout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#222222"
android:layout_weight="10"
android:id="@+id/main">
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="center"
android:gravity="center"
android:paddingLeft="32dp"
android:layout_weight="3"
android:fontFamily="sans-serif-thin"
android:textColor="#ffffff"
android:paddingRight="32dp"
android:textSize="28sp"
android:text="@string/intro3Title"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:gravity="center"
android:layout_weight="5">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:src="@drawable/intro3_image"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="3"
android:layout_gravity="center"
android:gravity="center"
android:textColor="#ffffff"
android:paddingLeft="64dp"
android:paddingRight="64dp"
android:textSize="16sp"
android:text="@string/intro3Description"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="64dp" />
</LinearLayout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#222222"
android:layout_weight="10"
android:id="@+id/main">
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="center"
android:gravity="center"
android:paddingLeft="32dp"
android:layout_weight="3"
android:fontFamily="sans-serif-thin"
android:textColor="#ffffff"
android:paddingRight="32dp"
android:textSize="28sp"
android:text="@string/intro4Title"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:gravity="center"
android:layout_weight="5">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:src="@drawable/intro4_image"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="3"
android:layout_gravity="center"
android:gravity="center"
android:textColor="#ffffff"
android:paddingLeft="64dp"
android:paddingRight="64dp"
android:textSize="16sp"
android:text="@string/intro4Description"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="64dp" />
</LinearLayout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#222222"
android:layout_weight="10"
android:id="@+id/main">
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="center"
android:gravity="center"
android:paddingLeft="32dp"
android:layout_weight="3"
android:fontFamily="sans-serif-thin"
android:textColor="#ffffff"
android:paddingRight="32dp"
android:textSize="28sp"
android:text="@string/intro5Title"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:gravity="center"
android:layout_weight="5">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:src="@drawable/intro5_image"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="3"
android:layout_gravity="center"
android:gravity="center"
android:textColor="#ffffff"
android:paddingLeft="64dp"
android:paddingRight="64dp"
android:textSize="16sp"
android:text="@string/intro5Description"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="64dp" />
</LinearLayout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#222222"
android:layout_weight="10"
android:id="@+id/main">
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="center"
android:gravity="center"
android:paddingLeft="32dp"
android:layout_weight="3"
android:fontFamily="sans-serif-thin"
android:textColor="#ffffff"
android:paddingRight="32dp"
android:textSize="28sp"
android:text="@string/intro6Title"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:gravity="center"
android:layout_weight="5">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:src="@drawable/app_icon_intro"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="3"
android:layout_gravity="center"
android:gravity="center"
android:textColor="#ffffff"
android:paddingLeft="64dp"
android:paddingRight="64dp"
android:textSize="16sp"
android:text="@string/intro6Description"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="64dp" />
</LinearLayout>

View File

@@ -19,145 +19,232 @@
</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">
<ScrollView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@color/inputContrastBackground"
app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TableLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:shrinkColumns="1"
android:background="@color/inputContrastBackground">
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout android:orientation="horizontal"
android:padding="10.0dip"
<!-- Store Name -->
<View
android:layout_height="@dimen/inputBorderThickness"
android:layout_width="match_parent"
android:background="@color/inputBorder" />
<TableRow
android:background="@color/inputBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:gravity="start"
android:layout_height="match_parent"
android:layout_width="@dimen/inputBorderThickness"
android:background="@color/inputBorder" />
<RelativeLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingRight="@dimen/inputPadding"
android:paddingEnd="@dimen/inputPadding">
<TextView
android:id="@+id/storeNameField"
android:text="@string/storeName"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:textSize="@dimen/inputSize"
android:padding="@dimen/inputPadding"
android:layout_alignParentStart="true"/>
<EditText
android:id="@+id/storeNameEdit"
android:inputType="textCapWords"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="@dimen/inputPadding"
android:textSize="@dimen/inputSize"
android:layout_toEndOf="@id/storeNameField"/>
<TextView
android:id="@+id/storeNameView"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="@dimen/inputPadding"
android:textSize="@dimen/inputSize"
android:textIsSelectable="true"
android:layout_toEndOf="@id/storeNameField"/>
</RelativeLayout>
<View
android:gravity="end"
android:layout_height="match_parent"
android:layout_width="@dimen/inputBorderThickness"
android:background="@color/inputBorder" />
</TableRow>
<!-- Note -->
<View
android:layout_height="@dimen/inputBorderThickness"
android:layout_width="match_parent"
android:background="@color/inputBorder" />
<TableRow
android:background="@color/inputBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:gravity="start"
android:layout_height="match_parent"
android:layout_width="@dimen/inputBorderThickness"
android:background="@color/inputBorder" />
<RelativeLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingRight="@dimen/inputPadding"
android:paddingEnd="@dimen/inputPadding">
<TextView
android:id="@+id/noteField"
android:text="@string/note"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:textSize="@dimen/inputSize"
android:padding="@dimen/inputPadding"
android:layout_alignParentStart="true"/>
<EditText
android:id="@+id/noteEdit"
android:inputType="textCapSentences"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="@dimen/inputPadding"
android:textSize="@dimen/inputSize"
android:layout_toEndOf="@id/noteField"/>
<TextView
android:id="@+id/noteView"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="@dimen/inputPadding"
android:textSize="@dimen/inputSize"
android:textIsSelectable="true"
android:layout_toEndOf="@id/noteField"/>
</RelativeLayout>
<View
android:gravity="end"
android:layout_height="match_parent"
android:layout_width="@dimen/inputBorderThickness"
android:background="@color/inputBorder" />
</TableRow>
<!-- Card ID -->
<View
android:id="@+id/cardIdDivider"
android:layout_height="@dimen/inputBorderThickness"
android:layout_width="match_parent"
android:background="@color/inputBorder" />
<TableRow
android:id="@+id/cardIdTableRow"
android:background="@color/inputBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:gravity="start"
android:layout_height="match_parent"
android:layout_width="@dimen/inputBorderThickness"
android:background="@color/inputBorder" />
<RelativeLayout
android:orientation="horizontal"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingRight="@dimen/inputPadding"
android:paddingEnd="@dimen/inputPadding">
<TextView
android:id="@+id/cardIdField"
android:text="@string/cardId"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:textSize="@dimen/inputSize"
android:padding="@dimen/inputPadding"
android:layout_alignParentStart="true"/>
<TextView
android:id="@+id/cardIdView"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="@dimen/inputPadding"
android:textSize="@dimen/inputSize"
android:textIsSelectable="true"
android:layout_toEndOf="@id/cardIdField"/>
</RelativeLayout>
<View
android:gravity="end"
android:layout_height="match_parent"
android:layout_width="@dimen/inputBorderThickness"
android:background="@color/inputBorder" />
</TableRow>
<View
android:layout_height="@dimen/inputBorderThickness"
android:layout_width="match_parent"
android:background="@color/inputBorder" />
<TextView android:id="@+id/barcodeType"
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/storeName"
android:text="@string/storeName" />
android:visibility="gone"/>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:id="@+id/barcodeLayout">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/barcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText android:id="@+id/storeName"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="text"/>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/barcodeCaptureLayout">
<Button android:id="@+id/captureButton"
android:layout_width="0dp"
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>
</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"
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:enabled="false"
android:inputType="text"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/barcodeTypeLayout">
<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/barcodeType"
android:text="@string/barcodeType" />
<EditText android:id="@+id/barcodeType"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:enabled="false"
android:inputType="text"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:id="@+id/barcodeLayout">
<ImageView
android:layout_width="0dp"
android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal"
android:id="@+id/barcode"
android:contentDescription="@string/barcodeImageDescription"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:padding="10.0dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/barcodeCaptureLayout">
<Button android:id="@+id/captureButton"
android:layout_width="0dp"
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"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:baselineAligned="true">
<Button android:id="@+id/cancelButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/cancel"
android:layout_weight="1.0" />
<Button android:id="@+id/saveButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/save"
android:layout_weight="1.0" />
</LinearLayout>
</TableLayout>
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,10 @@
<?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_save"
android:icon="@drawable/save_24dp"
android:title="@string/save"
app:showAsAction="always"/>
</menu>

View File

@@ -2,9 +2,15 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_save"
android:icon="@drawable/save_24dp"
android:title="@string/save"
app:showAsAction="always"/>
<item
android:id="@+id/action_delete"
android:icon="@drawable/ic_delete_white_24dp"
android:title="@string/delete"
app:showAsAction="always"/>
app:showAsAction="never"/>
</menu>

View File

@@ -2,6 +2,11 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_lock_unlock"
android:icon="@drawable/ic_lock_open_white_24dp"
android:title="@string/lockScreen"
app:showAsAction="always"/>
<item
android:id="@+id/action_edit"
android:icon="@drawable/ic_mode_edit_white_24dp"

View File

@@ -12,6 +12,10 @@
android:icon="@drawable/ic_import_export_white_24dp"
android:title="@string/importExport"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_intro"
android:title="@string/startIntro"
app:showAsAction="never"/>
<item
android:id="@+id/action_about"
android:title="@string/about"

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1012 B

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 676 B

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,74 @@
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Loyalty Card Keychain</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="sendLabel">Odeslat&#8230;</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">Zálohování dat vám umožní přesunout vaše uložené karty na jiné zařízení.</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="importSuccessfulTitle">Import proběhl úspěšně</string>
<string name="importFailedTitle">Import selhal</string>
<string name="importFailed">Import selhal: %1$s</string>
<string name="exportSuccessfulTitle">Export proběhl úspěšně</string>
<string name="exportFailedTitle">Export selhal</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="exportOptionExplanation">Data jsou zapsána do kořenové složky externího uložiště.</string>
<string name="importOptionFilesystemTitle">Import ze souborového systému</string>
<string name="importOptionFilesystemExplanation">Vyberte konkrétní soubor v uložišti.</string>
<string name="importOptionFilesystemButton">Ze souborového systému</string>
<string name="importOptionApplicationTitle">Použít externí aplikaci</string>
<string name="importOptionApplicationExplanation">K otevření souboru použije externí aplikaci jako Dropbox, Google Drive, nebo vámi preferovaný prohlížeč souborů.</string>
<string name="importOptionApplicationButton">Použít externí aplikaci</string>
<string name="importOptionFixedTitle">Import z umístění exportu</string>
<string name="importOptionFixedExplanation">Import ze stejné složky souborového systému do níž se zapisuje při exportu.</string>
<string name="importOptionFixedButton">Použít složku exportu</string>
<string name="about">O aplikaci</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</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,89 @@
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Loyalty Card Keychain</string>
<string name="action_add">Neu</string>
<string name="noGiftCards">Sie haben noch keine Kundenkarte angelegt. Über den "+" Button oben rechts, können welche angelegt werden.\n\nDiese App ermöglicht es, Kundenkarten immer mit zu führen.</string>
<string name="storeName">Geschäft</string>
<string name="note">Notiz</string>
<string name="cardId">Kartennummer</string>
<string name="barcodeType">Barcodeart</string>
<string name="cancel">Abbrechen</string>
<string name="save">Speichern</string>
<string name="capture">Karte erfassen</string>
<string name="enterCard">Karte einfügen</string>
<string name="editCard">Karte bearbeiten</string>
<string name="edit">Bearbeiten</string>
<string name="delete">Löschen</string>
<string name="confirm">Bestätigen</string>
<string name="deleteConfirmation">Bitte bestätigen Sie, dass diese Karte gelöscht werden soll.</string>
<string name="ok">Ok</string>
<string name="copy_to_clipboard">Kopiere die Nummer in die Zwischenablage</string>
<string name="sendLabel">Senden&#8230;</string>
<string name="editCardTitle">Kundenkarte bearbeiten</string>
<string name="addCardTitle">Neue Kundenkarte</string>
<string name="viewCardTitle">Kundenkarte anzeigen</string>
<string name="scanCardBarcode">Barcode scannen</string>
<string name="barcodeImageDescription">Bild des Barcodes</string>
<string name="noStoreError">Kein Geschäft angegeben</string>
<string name="noCardIdError">Keine Kartennummer angegeben</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="importExport">Import/Export</string>
<string name="importName">Import</string>
<string name="exportName">Exportieren</string>
<string name="importExportHelp">Gesicherte Daten ermöglichen das Verschieben der Kundenkarten auf ein anderes Gerät.</string>
<string name="importedFrom">Importiert von: %1$s</string>
<string name="exportedTo">Exportiert nach: %1$s</string>
<string name="fileMissing">Datei fehlt: %1$s</string>
<string name="importSuccessfulTitle">Import erfolgreich</string>
<string name="importFailedTitle">Import fehlgeschlagen</string>
<string name="importFailed">Import fehlgeschlagen: %1$s</string>
<string name="exportSuccessfulTitle">Export erfolgreich</string>
<string name="exportFailedTitle">Export fehlgeschlagen</string>
<string name="exportFailed">Export fehlgeschlagen: %1$s</string>
<string name="importing">Importiere…</string>
<string name="exporting">Exportiere…</string>
<string name="noExternalStoragePermissionError">Ohne die Berechtigung für den externen Speicher kann kein Import oder Export erfolgen.</string>
<string name="exportOptionExplanation">Die Datei wird ins Rootverzeichnis des externen Speichers geschrieben.</string>
<string name="importOptionFilesystemTitle">Importiere vom Dateisystem</string>
<string name="importOptionFilesystemExplanation">Wähle eine Datei im Speicher aus.</string>
<string name="importOptionFilesystemButton">Vom Dateisystem</string>
<string name="importOptionApplicationTitle">Nutze eine externe App</string>
<string name="importOptionApplicationExplanation">Wählen Sie eine Datei aus einer App wie Dropbox, Google Drive, oder Ihrem bevorzugten Dateisystem aus.</string>
<string name="importOptionApplicationButton">Nutze eine externe App</string>
<string name="importOptionFixedTitle">Importiere vom Export-Pfad</string>
<string name="importOptionFixedExplanation">Importiere vom Export-Pfad.</string>
<string name="importOptionFixedButton">Verwende den Export-Pfad.</string>
<string name="about">Über</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Lizensiert unter der GPLv3.</string>
<string name="about_title_fmt">Über <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">Versions Information: <xliff:g id="app_revision_url">%s</xliff:g></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="app_resources"><xliff:g id="app_name">%s</xliff:g> verwendet folgenden Dritt-Ressourcen: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Barcode auswählen</string>
<string name="enterBarcodeInstructions">Fügen Sie die Kundennummer ein, anschließend wählen Sie die korrekte Barcodeart aus.</string>
<string name="copy_to_clipboard_toast">Nummer in die Zwischenablage kopiert</string>
<string name="startIntro">Starte Einführung</string>
<string name="intro1Title">Willkommen zu Loyalty Card Keychain\n</string>
<string name="intro1Description">Verwalten Sie Ihre Barcode-Kundenkarten auf Ihrem Smartphone!\n\n</string>
<string name="intro2Title">Karten hinzufügen\n</string>
<string name="intro2Description">Fügen Sie neue Karten hinzu indem Sie das "+" Symbol in der Liste berühren.\n\n</string>
<string name="intro3Title">Karten hinzufügen\n</string>
<string name="intro3Description">Um einen Barcode hinzuzufügen, verwenden Sie die Kamera oder geben Sie den Code manuell ein.\n\n</string>
<string name="intro4Title">Karte anzeigen\n</string>
<string name="intro4Description">Um eine Karte anzuzeigen, den entsprechenen Namen in der Hauptansicht anwählen.\n\n</string>
<string name="intro5Title">Backup\n</string>
<string name="intro5Description">Sie können selbstverständlich Backups anlegen. Um Karten zu exportieren oder importieren wählen Sie Import/Export im Menü auf dem Hauptbildschirm.\n\n</string>
<string name="intro6Title">Feedback\n</string>
<string name="intro6Description">Diese App enthält keine Werbung, und ist freie und quelloffene Software. Für Details berühren Sie Über im Menü auf der Hauptseite.\n\nHinterlassen Sie uns ein Feedback im App-Store (:</string>
</resources>

View File

@@ -0,0 +1,99 @@
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Loyalty Card Keychain</string>
<string name="action_add">Ajouter</string>
<string name="noGiftCards">Pas de carte de fidélité enregistrée. Cliquez sur le bouton "+" (plus) pour commencer.\n\nLoyalty Card Locker vous permet d\'enregistrer vos cartes de fidélité sur votre téléphone pour toujours les avoir à portée de main.</string>
<string name="storeName">Nom</string>
<string name="note">Note</string>
<string name="cardId">Numéro</string>
<string name="barcodeType">Type de code-barres</string>
<string name="cancel">Annuler</string>
<string name="save">Enregistrer</string>
<string name="capture">Flasher</string>
<string name="enterCard">Mode manuel</string>
<string name="editCard">Modifier</string>
<string name="edit">Modifier</string>
<string name="delete">Supprimer</string>
<string name="confirm">Confirmer</string>
<string name="lockScreen">Désactiver la rotation</string>
<string name="unlockScreen">Activer la rotation</string>
<string name="deleteTitle">Supprimer la carte de fidélité</string>
<string name="deleteConfirmation">Confirmez que vous souhaitez supprimer cette carte</string>
<string name="ok">OK</string>
<string name="copy_to_clipboard">Copier le numéro dans le presse-papier</string>
<string name="sendLabel">Envoyer&#8230;</string>
<string name="addedShortcut">Ajouter à l\'écran d\'accueil</string>
<string name="editCardTitle">Modifier la carte de fidélité</string>
<string name="addCardTitle">Ajouter une carte de fidélité</string>
<string name="viewCardTitle">Voir la carte de fidélité</string>
<string name="scanCardBarcode">Flasher le code-barres de la carte</string>
<string name="cardShortcut">Raccourci de carte</string>
<string name="noCardsMessage">Il n\'y a aucune carte. Ajoutez en une d\'abord.</string>
<string name="barcodeImageDescription">Image du code-barres de la carte</string>
<string name="noStoreError">Aucun nom n\'a été saisi</string>
<string name="noCardIdError">Aucun numéro n\'a été saisi</string>
<string name="noCardExistsError">N\'a pas pu retrouver la carte de fidélité</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="importExport">Importer/Exporter</string>
<string name="importName">Importer</string>
<string name="exportName">Exporter</string>
<string name="importExportHelp">Exporter vos données vous permet de récupérer vos cartes sur un autre appareil.</string>
<string name="importedFrom">Importé depuis : %1$s</string>
<string name="exportedTo">Exporté vers : %1$s</string>
<string name="fileMissing">Fichier manquant : %1$s</string>
<string name="importSuccessfulTitle">Importé avec succès</string>
<string name="importFailedTitle">Échec de l\'import</string>
<string name="importFailed">Échec de l\'import : %1$s</string>
<string name="exportSuccessfulTitle">Exporté avec succès</string>
<string name="exportFailedTitle">Échec de l\'export</string>
<string name="exportFailed">Échec de l\'export : %1$s</string>
<string name="importing">Import &#8230;</string>
<string name="exporting">Export &#8230;</string>
<string name="noExternalStoragePermissionError">Impossible d\'importer ou d\'exporter les données sans l\'autorisation d\'accès au stockage externe</string>
<string name="exportOptionExplanation">Les données sont sauvegardées à la racine du stockage externe.</string>
<string name="importOptionFilesystemTitle">Importer depuis le système de fichiers.</string>
<string name="importOptionFilesystemExplanation">Choisissez le fichier à importer.</string>
<string name="importOptionFilesystemButton">Système de fichiers</string>
<string name="importOptionApplicationTitle">Application externe</string>
<string name="importOptionApplicationExplanation">Utilisez une application externe comme Dropbox, Google Drive, ou votre gestionnaire de fichiers favori pour ouvrir un fichier.</string>
<string name="importOptionApplicationButton">Application externe</string>
<string name="importOptionFixedTitle">Importer depuis le même emplacement que pour l\'export</string>
<string name="importOptionFixedExplanation">Importe les données depuis le même emplacement que celui défini pour l\'export.</string>
<string name="importOptionFixedButton">Utiliser l\'emplacement de l\'export</string>
<string name="about">À propos</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Licence GPLv3.</string>
<string name="about_title_fmt">À propos de <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">Notes sur les versions : <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> utilise les bibliothèques-tierces suivantes : <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> utilise les ressources-tierces suivantes : <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Choisissez le code-barre</string>
<string name="enterBarcodeInstructions">Saisissez les chiffres du code-barres et sélectionnez l\'image qui le représente</string>
<string name="copy_to_clipboard_toast">Numéro de carte copié dans le presse-papier</string>
<string name="startIntro">Présentation</string>
<string name="intro1Title">Bienvenue dans\nLoyalty Card Keychain\n</string>
<string name="intro1Description">Gérez vos cartes de fidélité\nsur votre téléphone !\n\n</string>
<string name="intro2Title">Ajouter une carte\n</string>
<string name="intro2Description">Touchez le signe \'+\' en haut de l\'écran pour ajouter une carte.\n\n</string>
<string name="intro3Title">Ajouter une carte\n</string>
<string name="intro3Description">Pour enregistrer le code-barres, utilisez l\'appareil-photo ou entrez-le manuellement.\n\n</string>
<string name="intro4Title">Afficher une carte\n</string>
<string name="intro4Description">Pour afficher une carte, touchez le nom du magasin sur l\'écran principal\n\n</string>
<string name="intro5Title">Sauvegarde\n</string>
<string name="intro5Description">Les cartes peuvent être sauvegardées. Touchez \'Importer/Exporter\' sur l\'écran principal pour restaurer ou sauvegarder les cartes.\n\n</string>
<string name="intro6Title">Votre avis\n</string>
<string name="intro6Description">Cette application est gratuite, sans publicité, et son code est ouvert. Découvrez-en plus en touchant \'à propos\' sur l\'écran principal.\n\nLaissez un commentaire sur votre magasin d\'applications (:</string>
</resources>

View File

@@ -0,0 +1,14 @@
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_add">הוספה</string>
<string name="cardId">מזהה כרטיס</string>
<string name="barcodeType">סוג ברקוד</string>
<string name="cancel">ביטול</string>
<string name="save">שמור</string>
<string name="capture">צלם כרטיס</string>
<string name="enterCard">הכנס כרטיס</string>
<string name="editCard">עריכת כרטיס</string>
</resources>

View File

@@ -7,51 +7,91 @@
<string name="noGiftCards">Non hai ancora alcuna tessera fedeltà al momento. Premi sul bottone "+" (più) in alto per incominciare.\n\nL\'app ti permette di portare con te le tue tessere fedeltà, così da averle sempre a disposizione.</string>
<string name="storeName">Negozio</string>
<string name="note">Note</string>
<string name="cardId">Codice</string>
<string name="barcodeType">Tipo codice a barre</string>
<string name="cancel">Annulla</string>
<string name="save">Salva</string>
<string name="capture">Salva tessera</string>
<string name="capture">Scansione carta</string>
<string name="enterCard">Inserisci carta</string>
<string name="editCard">Modifica carta</string>
<string name="edit">Modifica</string>
<string name="delete">Elimina</string>
<string name="confirm">Conferma</string>
<string name="lockScreen">Blocca rotazione</string>
<string name="unlockScreen">Sblocca rotazione</string>
<string name="deleteTitle">Rimuovi carta fedeltà</string>
<string name="deleteConfirmation">Conferma che vuoi eliminare questa carta.</string>
<string name="ok">Ok</string>
<string name="copy_to_clipboard">Copia ID negli appunti</string>
<string name="sendLabel">Invia&#8230;</string>
<string name="addedShortcut">Aggiunto al launcher</string>
<string name="editCardTitle">Modifica carta</string>
<string name="addCardTitle">Aggiungi carta</string>
<string name="viewCardTitle">Mostra carta</string>
<string name="scanCardBarcode">Scansiona codice carta</string>
<string name="cardShortcut">Scorciatoia per la carta</string>
<string name="noCardsMessage">Non ci sono carte. Aggiungine prima una</string>
<string name="barcodeImageDescription">Immagine del codice a barre della carta</string>
<string name="noStoreError">Nessun negozio inserito</string>
<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="importExportHelp">Fare il backup dei dati ti permette di spostare le tue tessere da un dispositivo ad un altro.</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="importSuccessfulTitle">Importazione avvenuta con successo</string>
<string name="importFailedTitle">Importazione fallita</string>
<string name="importFailed">Impossibile importare: %1$s</string>
<string name="exportSuccessfulTitle">Esportazione avvenuta con successo</string>
<string name="exportFailedTitle">Esportazione fallita</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="exportOptionExplanation">I dati sono stati scritti nella cartella principale della memoria esterna.</string>
<string name="importOptionFilesystemTitle">Importa dal file system</string>
<string name="importOptionFilesystemExplanation">Scegli un file dal file system.</string>
<string name="importOptionFilesystemButton">Dal file system</string>
<string name="importOptionApplicationTitle">Usa un\'applicazione esterna</string>
<string name="importOptionApplicationExplanation">Usa un\'applicazione esterna come Dropbox, Google Drive o il tuo file manager preferito per aprire il file.</string>
<string name="importOptionApplicationButton">Usa un\'applicazione esterna</string>
<string name="importOptionFixedTitle">Importa da un altro posto</string>
<string name="importOptionFixedExplanation">Importa dallo stesso posto del file system dove si è esportato.</string>
<string name="importOptionFixedButton">Usa luogo dell\'esportazione</string>
<string name="about">Informazioni</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</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="app_resources"><xliff:g id="app_name">%s</xliff:g> usa le seguenti risorse di terze parti: <xliff:g id="app_resources_list">%s</xliff:g></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="startIntro">Incomincia introduzione</string>
<string name="intro1Title">Benvenuto in Carte fedeltà\n</string>
<string name="intro1Description">Gestisci le tue tessere direttamente dal telefono!\n\n</string>
<string name="intro2Title">Aggiungi carte\n</string>
<string name="intro2Description">Aggiungi una nuova carta premendo il + dall\'elenco delle carte.\n\n</string>
<string name="intro3Title">Aggiungi carte\n</string>
<string name="intro3Description">Per aggiungere il codice a barre, catturalo con la fotocamera o inseriscilo manualmente.\n\n</string>
<string name="intro4Title">Mostra carta\n</string>
<string name="intro4Description">Per mostrare una carta, premi sul nome del negozio dalla schermata principale\n\n</string>
<string name="intro5Title">Backup\n</string>
<string name="intro5Description">I dati delle tessere possono essere salvati. Per esportare o importare tessere premi Importa/Esporta nel menù nella schermata principale.\n\n</string>
<string name="intro6Title">Feedback\n</string>
<string name="intro6Description">Questa app è gratuita, priva di pubblicità e open source. Guarda i dettagli premendo su Informazioni nella schermata principale.\n\nPer favore, lascia un feedback nell\'app store! (:</string>
</resources>

View File

@@ -0,0 +1,57 @@
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Loyalty Card Keychain</string>
<string name="action_add">Pridėti</string>
<string name="noGiftCards">Šiuo metu neturite nė vienos įvestos lojalumo kortelės. Paspauskite "+" (pliuso) pliuso mygtuką, kad pradėtumėte.\n\nLoyalty Card Locker leidžia Jums visada nešiotis lojalumo kortelių informaciją savo telefone ar planšetėje, taip jos visada pasiekiamos.</string>
<string name="storeName">Parduotuvė</string>
<string name="note">Užrašas</string>
<string name="cardId">Kortelės ID</string>
<string name="barcodeType">Brūkšninio kodo tipas</string>
<string name="cancel">Atšaukti</string>
<string name="save">Išsaugoti</string>
<string name="capture">Nufotografuoti kortelę</string>
<string name="enterCard">Įvesti kortelę</string>
<string name="edit">Redaguoti</string>
<string name="delete">Ištrinti</string>
<string name="confirm">Patvirtinti</string>
<string name="deleteTitle">Panaikinti lojalumo kortelę</string>
<string name="deleteConfirmation">Prašome patvirtinti jog Jūs norite panaikinti šią lojalumo kortelę.</string>
<string name="ok">Gerai</string>
<string name="copy_to_clipboard">Nukopijuoti ID į iškarpinę</string>
<string name="editCardTitle">Redaguoti lojalumo kortelę</string>
<string name="addCardTitle">Pridėti lojalumo kortelę</string>
<string name="viewCardTitle">Paeržiūrėti lojalumo kortelę</string>
<string name="scanCardBarcode">Nuskanuokite kortelės brūkšninį kodą</string>
<string name="barcodeImageDescription">Kortelės brūkšninio kodo paveikslėlis</string>
<string name="noStoreError">Parduotuvė neįvesta</string>
<string name="noCardIdError">Neįvestas kortelės ID</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="importExport">Importuoti/Exportuoti</string>
<string name="importName">Importuoti</string>
<string name="exportName">Exportuoti</string>
<string name="importedFrom">Importuota iš: %1$s</string>
<string name="exportedTo">Eksportuota į: %1$s</string>
<string name="fileMissing">Failas nerastas: %1$s</string>
<string name="importFailed">Nepavyko importuoti: %1$s</string>
<string name="exportFailed">Nepavyko eksportuoti: %1$s</string>
<string name="importing">Importuoja&#8230;</string>
<string name="exporting">Eksportuoja&#8230;</string>
<string name="noExternalStoragePermissionError">Negalima importuoti/eksportuoti kortelių be išorinės atminties leidimo</string>
<string name="about">Apie</string>
<string name="app_copyright_fmt">Visos teisės saugomos 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Licenzijuota pagal GPLv3.</string>
<string name="about_title_fmt">Apie <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versija: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Revizijos informacija: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> naudoja šias trečiosios šalies bibliotekas: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Pasirinkite brūkšninį kodą</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">Kortelės ID nukopijuota į iškarpinę</string>
</resources>

View File

@@ -7,51 +7,93 @@
<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="note">Notitie</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="enterCard">Voer kaart in</string>
<string name="editCard">Bewerk kaart</string>
<string name="edit">Bewerk</string>
<string name="delete">Verwijder</string>
<string name="confirm">Bevestig</string>
<string name="lockScreen">Blokkeer rotatie</string>
<string name="unlockScreen">Deblokkeer rotatie</string>
<string name="deleteTitle">Verwijder kaart</string>
<string name="deleteConfirmation">Bevestig deze kaart te verwijderen.</string>
<string name="ok">Oké</string>
<string name="copy_to_clipboard">Kopieer het ID naar het klembord</string>
<string name="sendLabel">Verzend&#8230;</string>
<string name="addedShortcut">Toegevoegd aan hoofdscherm</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="cardShortcut">Kaartsnelkoppeling</string>
<string name="noCardsMessage">Er zijn geen kaarten, voeg er eerst een toe</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="noCardExistsError">Kon klantenkaart niet vinden</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="importExportHelp">Data die is geback-upt maakt het mogelijk om je klantenkaarten naar een ander apparaat te verplaatsen.</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="importSuccessfulTitle">Importeren succesvol</string>
<string name="importFailedTitle">Importeren mislukte</string>
<string name="importFailed">Importeren mislukte: %1$s</string>
<string name="exportSuccessfulTitle">Exporteren succesvol</string>
<string name="exportFailedTitle">Exporteren mislukt</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="exportOptionExplanation">Data is weggeschreven naar de hoogste map in externe opslag.</string>
<string name="importOptionFilesystemTitle">Importeer van filesysteem</string>
<string name="importOptionFilesystemExplanation">Kies een specifiek bestand van het filesysteem.</string>
<string name="importOptionFilesystemButton">Van filesysteem</string>
<string name="importOptionApplicationTitle">Gebruik externe applicatie</string>
<string name="importOptionApplicationExplanation">Gebruik een externe applicatie zoals Dropbox, Google Drive of je favoriete bestandsbeheer om een bestand te openen.</string>
<string name="importOptionApplicationButton">Gebruik externe applicatie</string>
<string name="importOptionFixedTitle">Importeer van exporteerlocatie</string>
<string name="importOptionFixedExplanation">Importeer van zelfde locatie op het filesysteem waar tijdens exporteren naar geschreven is.</string>
<string name="importOptionFixedButton">gebruik exporteerlocaite</string>
<string name="about">Over</string>
<string name="app_copyright_fmt">Auteursrecht 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_copyright_fmt">Auteursrecht 2016-<xliff:g>%d</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>
</resources>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> gebruikt de volgende bronnen van derden: <xliff:g id="app_resources_list\">%s</xliff:g></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_toast">Het ID is naar het klembord gekopieerd</string>
<string name="startIntro">Start inleiding</string>
<string name="intro1Title">Welkom bij Loyalty Card Keychain\n</string>
<string name="intro1Description">Beheer je barcodegebaseerde klantenkaarten op je telefoon!\n\n</string>
<string name="intro2Title">Kaarten toevoegen\n</string>
<string name="intro2Description">Voeg een nieuwe kaart toe door in de lijst met kaarten de plus aan te raken.\n\n</string>
<string name="intro3Title">Kaarten toevoegen\n</string>
<string name="intro3Description">Om een barcode toe te voegen, scan deze met je camera of voer ze handmatig in.\n\n</string>
<string name="intro4Title">Toon kaart\n</string>
<string name="intro4Description">Om een kaart te tonen, klik in het hoofdscherm op de naam van de winkel\n\n</string>
<string name="intro5Title">Backup\n</string>
<string name="intro5Description">De kaarten kunnen worden geback-upt. Om te kaarten te exporteren of importeren, raak in het menu in het hoofdscherm Importeer/Exporteer aan.\n\n</string>
<string name="intro6Title">Terugkoppeling\n</string>
<string name="intro6Description">Deze app is gratis, vrije van advertenties en opensource. Zie details door Over aan te raken in het menu op het hoofdscher,.\n\nBeschrijft je ervaring in de app store! (:</string>
</resources>

View File

@@ -1,7 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Palette generated by Material Palette - materialpalette.com/green/blue-grey -->
<!-- Palette generated by Material IO https://material.io/color/#!/?view.left=0&view.right=0&primary.color=3F51B5&secondary.color=FFC107 -->
<resources>
<color name="colorPrimary">#4CAF50</color>
<color name="colorPrimaryDark">#388E3C</color>
<color name="colorAccent">#607D8B</color>
<color name="colorPrimary">#3f51b5</color>
<color name="colorPrimaryLight">#757de8</color>
<color name="colorPrimaryDark">#002984</color>
<color name="colorSecondary">#ffc107</color>
<color name="colorSecondaryLight">#fff350</color>
<color name="colorSecondaryDark">#c79100</color>
<color name="colorPrimaryText">#ffffff</color>
<color name="colorSecondaryText">#000000</color>
<color name="inputContrastBackground">#F8F8F8</color>
<color name="inputBackground">#FFFFFF</color>
<color name="inputBorder">#DDDDDD</color>
<color name="inputDividerBorder">#AAAAAA</color>
</resources>

View File

@@ -7,4 +7,13 @@
<dimen name="no_data_padding">22dp</dimen>
<dimen name="barcode_disp_height">200dp</dimen>
<dimen name="text_size_medium">18sp</dimen>
<dimen name="text_size_large">22sp</dimen>
<dimen name="inputBorderThickness">2dip</dimen>
<dimen name="inputBorderDividerThickness">4dip</dimen>
<dimen name="inputPadding">20dip</dimen>
<dimen name="inputSize">18sp</dimen>
</resources>

View File

@@ -1,7 +1,7 @@
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Loyalty Card Locker</string>
<string name="app_name">Loyalty Card Keychain</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>
@@ -15,20 +15,31 @@
<string name="save">Save</string>
<string name="capture">Capture Card</string>
<string name="enterCard">Enter Card</string>
<string name="editCard">Edit Card</string>
<string name="edit">Edit</string>
<string name="delete">Delete</string>
<string name="confirm">Confirm</string>
<string name="lockScreen">Block Rotation</string>
<string name="unlockScreen">Unblock Rotation</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="sendLabel">Send&#8230;</string>
<string name="addedShortcut">Added to Home Screen</string>
<string name="editCardTitle">Edit Loyalty Card</string>
<string name="addCardTitle">Add Loyalty Card</string>
<string name="viewCardTitle">View Loyalty Card</string>
<string name="scanCardBarcode">Scan Card\'s Barcode</string>
<string name="cardShortcut">Card Shortcut</string>
<string name="noCardsMessage">There are no cards, add one first</string>
<string name="barcodeImageDescription">Image of card\'s barcode</string>
<string name="noStoreError">No Store entered</string>
<string name="noCardIdError">No Card ID entered</string>
<string name="noCardExistsError">Could not lookup loyalty card</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="storeNameAndNoteFormat" translatable="false">%1$s - %2$s</string>
@@ -36,27 +47,55 @@
<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="importExportHelp">Backed up data can allow you to move your cards to another device.</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="importSuccessfulTitle">Import successful</string>
<string name="importFailedTitle">Import failed</string>
<string name="importFailed">Failed to import: %1$s</string>
<string name="exportSuccessfulTitle">Export successful</string>
<string name="exportFailedTitle">Export failed</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="exportOptionExplanation">Data is written to the top directory in external storage.</string>
<string name="importOptionFilesystemTitle">Import from filesystem</string>
<string name="importOptionFilesystemExplanation">Choose a specific file from the filesystem.</string>
<string name="importOptionFilesystemButton">From filesystem</string>
<string name="importOptionApplicationTitle">Use external application</string>
<string name="importOptionApplicationExplanation">Use an external application like Dropbox, Google Drive, or your favorite file manager to open a file.</string>
<string name="importOptionApplicationButton">Use external application</string>
<string name="importOptionFixedTitle">Import from export location</string>
<string name="importOptionFixedExplanation">Import from the same location on the filesystem that is written to on export.</string>
<string name="importOptionFixedButton">Use export location</string>
<string name="about">About</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</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="app_resources"><xliff:g id="app_name">%s</xliff:g> uses the following third-party resources: <xliff:g id="app_resources_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>
<string name="startIntro">Start Intro</string>
<string name="intro1Title">Welcome to Loyalty Card Keychain\n</string>
<string name="intro1Description">Manage your barcode-based store/loyalty cards on your phone!\n\n</string>
<string name="intro2Title">Adding Cards\n</string>
<string name="intro2Description">Add a new card by touching the plus from the card list.\n\n</string>
<string name="intro3Title">Adding Cards\n</string>
<string name="intro3Description">To add the barcode, either capture with the camera or type in manually.\n\n</string>
<string name="intro4Title">Show Card\n</string>
<string name="intro4Description">To display a card, click on the store name from the main screen\n\n</string>
<string name="intro5Title">Backup\n</string>
<string name="intro5Description">The cards can be backed-up. To export or import card data touch Import/Export in the menu on the main page.\n\n</string>
<string name="intro6Title">Feedback\n</string>
<string name="intro6Description">This app is free, ad-free, and open source. See details by touching About in the menu on the main page.\n\nPlease leave feedback in the app store! (:</string>
</resources>

View File

@@ -5,7 +5,7 @@
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorAccent">@color/colorSecondary</item>
</style>
<style name="AppTheme.NoActionBar">

View File

@@ -0,0 +1,3 @@
<paths>
<external-path name="exportPath" path="/" />
</paths>

View File

@@ -11,7 +11,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
@@ -19,8 +19,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 17)
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class DatabaseTest
{
private DBHelper db;
@@ -36,7 +36,8 @@ public class DatabaseTest
public void addRemoveOneGiftCard()
{
assertEquals(0, db.getLoyaltyCardCount());
boolean result = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString());
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString());
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
@@ -56,7 +57,8 @@ public class DatabaseTest
@Test
public void updateGiftCard()
{
boolean result = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString());
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString());
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
@@ -86,7 +88,8 @@ public class DatabaseTest
@Test
public void emptyGiftCardValues()
{
boolean result = db.insertLoyaltyCard("", "", "", "");
long id = db.insertLoyaltyCard("", "", "", "");
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
@@ -107,8 +110,9 @@ public class DatabaseTest
// that they are sorted
for(int index = CARDS_TO_ADD-1; index >= 0; index--)
{
boolean result = db.insertLoyaltyCard("store" + index, "note" + index, "cardId" + index,
long id = db.insertLoyaltyCard("store" + index, "note" + index, "cardId" + index,
BarcodeFormat.UPC_A.toString());
boolean result = (id != -1);
assertTrue(result);
}

View File

@@ -0,0 +1,148 @@
package protect.card_locker;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.view.View;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.res.builder.RobolectricPackageManager;
import static org.robolectric.Shadows.shadowOf;
import static org.junit.Assert.assertEquals;
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class ImportExportActivityTest
{
private void registerIntentHandler(String handler)
{
// Add something that will 'handle' the given intent type
RobolectricPackageManager packageManager = shadowOf(RuntimeEnvironment.application.getPackageManager());
ResolveInfo info = new ResolveInfo();
info.isDefault = true;
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.packageName = "does.not.matter";
info.activityInfo = new ActivityInfo();
info.activityInfo.applicationInfo = applicationInfo;
info.activityInfo.name = "DoesNotMatter";
info.activityInfo.exported = true;
Intent intent = new Intent(handler);
if(handler.equals(Intent.ACTION_GET_CONTENT))
{
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
}
packageManager.addResolveInfoForIntent(intent, info);
}
private void checkVisibility(Activity activity, int state, int divider, int title, int message, int button)
{
View dividerView = activity.findViewById(divider);
View titleView = activity.findViewById(title);
View messageView = activity.findViewById(message);
View buttonView = activity.findViewById(button);
assertEquals(state, dividerView.getVisibility());
assertEquals(state, titleView.getVisibility());
assertEquals(state, messageView.getVisibility());
assertEquals(state, buttonView.getVisibility());
}
@Test
public void testImportFilesystemOption()
{
for(boolean isInstalled : new Boolean[]{false, true})
{
int visibility = isInstalled ? View.VISIBLE : View.GONE;
if(isInstalled)
{
registerIntentHandler(Intent.ACTION_PICK);
}
Activity activity = Robolectric.setupActivity(ImportExportActivity.class);
checkVisibility(activity, visibility, R.id.dividerImportFilesystem,
R.id.importOptionFilesystemTitle, R.id.importOptionFilesystemExplanation,
R.id.importOptionFilesystemButton);
// Should always be gone, as its provider is never installed
checkVisibility(activity, View.GONE, R.id.dividerImportApplication,
R.id.importOptionApplicationTitle, R.id.importOptionApplicationExplanation,
R.id.importOptionApplicationButton);
// Import from file system should always be present
checkVisibility(activity, View.VISIBLE, R.id.dividerImportFixed,
R.id.importOptionFixedTitle, R.id.importOptionFixedExplanation,
R.id.importOptionFixedButton);
}
}
@Test
public void testImportApplicationOption()
{
for(boolean isInstalled : new Boolean[]{false, true})
{
int visibility = isInstalled ? View.VISIBLE : View.GONE;
if(isInstalled)
{
registerIntentHandler(Intent.ACTION_GET_CONTENT);
}
Activity activity = Robolectric.setupActivity(ImportExportActivity.class);
checkVisibility(activity, visibility, R.id.dividerImportApplication,
R.id.importOptionApplicationTitle, R.id.importOptionApplicationExplanation,
R.id.importOptionApplicationButton);
// Should always be gone, as its provider is never installed
checkVisibility(activity, View.GONE, R.id.dividerImportFilesystem,
R.id.importOptionFilesystemTitle, R.id.importOptionFilesystemExplanation,
R.id.importOptionFilesystemButton);
// Import from file system should always be present
checkVisibility(activity, View.VISIBLE, R.id.dividerImportFixed,
R.id.importOptionFixedTitle, R.id.importOptionFixedExplanation,
R.id.importOptionFixedButton);
}
}
@Test
public void testAllOptionsAvailable()
{
registerIntentHandler(Intent.ACTION_PICK);
registerIntentHandler(Intent.ACTION_GET_CONTENT);
Activity activity = Robolectric.setupActivity(ImportExportActivity.class);
checkVisibility(activity, View.VISIBLE, R.id.dividerImportApplication,
R.id.importOptionApplicationTitle, R.id.importOptionApplicationExplanation,
R.id.importOptionApplicationButton);
checkVisibility(activity, View.VISIBLE, R.id.dividerImportFilesystem,
R.id.importOptionFilesystemTitle, R.id.importOptionFilesystemExplanation,
R.id.importOptionFilesystemButton);
checkVisibility(activity, View.VISIBLE, R.id.dividerImportFixed,
R.id.importOptionFixedTitle, R.id.importOptionFixedExplanation,
R.id.importOptionFixedButton);
}
}

View File

@@ -3,6 +3,7 @@ package protect.card_locker;
import android.app.Activity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
import com.google.zxing.BarcodeFormat;
@@ -10,21 +11,25 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
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.assertNotNull;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 17)
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class ImportExportTest
{
private Activity activity;
@@ -60,7 +65,8 @@ public class ImportExportTest
{
String storeName = String.format("store, \"%4d", index);
String note = String.format("note, \"%4d", index);
boolean result = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE);
long id = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE);
boolean result = (id != -1);
assertTrue(result);
}
@@ -109,7 +115,7 @@ public class ImportExportTest
@Test
public void multipleCardsExportImport() throws IOException
{
final int NUM_CARDS = 1000;
final int NUM_CARDS = 10;
for(DataFormat format : DataFormat.values())
{
@@ -144,7 +150,7 @@ public class ImportExportTest
@Test
public void importExistingCardsNotReplace() throws IOException
{
final int NUM_CARDS = 1000;
final int NUM_CARDS = 10;
for(DataFormat format : DataFormat.values())
{
@@ -177,7 +183,7 @@ public class ImportExportTest
@Test
public void corruptedImportNothingSaved() throws IOException
{
final int NUM_CARDS = 1000;
final int NUM_CARDS = 10;
for(DataFormat format : DataFormat.values())
{
@@ -192,7 +198,11 @@ public class ImportExportTest
clearDatabase();
String corruptEntry = "ThisStringIsLikelyNotPartOfAnyFormat";
// commons-csv would throw a RuntimeException if an entry was quotes but had
// content after. For example:
// abc,def,""abc,abc
// ^ after the quote there should only be a , \n or EOF
String corruptEntry = "ThisStringIsLikelyNotPartOfAnyFormat,\"\"a";
ByteArrayInputStream inData = new ByteArrayInputStream((outData.toString() + corruptEntry).getBytes());
InputStreamReader inStream = new InputStreamReader(inData);
@@ -205,32 +215,59 @@ public class ImportExportTest
}
}
class TestTaskCompleteListener implements ImportExportTask.TaskCompleteListener
{
Boolean success;
public void onTaskComplete(boolean success)
{
this.success = success;
}
}
@Test
public void useImportExportTask()
public void useImportExportTask() throws FileNotFoundException
{
final int NUM_CARDS = 10;
final File sdcardDir = Environment.getExternalStorageDirectory();
final File exportFile = new File(sdcardDir, "LoyaltyCardLocker.csv");
for(DataFormat format : DataFormat.values())
{
addLoyaltyCards(NUM_CARDS);
// Export to whatever the default location is
ImportExportTask task = new ImportExportTask(activity, false, format);
TestTaskCompleteListener listener = new TestTaskCompleteListener();
// Export to the file
ImportExportTask task = new ImportExportTask(activity, format, exportFile, listener);
task.execute();
// Actually run the task to completion
Robolectric.flushBackgroundThreadScheduler();
// Check that the listener was executed
assertNotNull(listener.success);
assertEquals(true, listener.success);
clearDatabase();
// Import everything back from the default location
task = new ImportExportTask(activity, true, format);
listener = new TestTaskCompleteListener();
FileInputStream fileStream = new FileInputStream(exportFile);
task = new ImportExportTask(activity, format, fileStream, listener);
task.execute();
// Actually run the task to completion
Robolectric.flushBackgroundThreadScheduler();
// Check that the listener was executed
assertNotNull(listener.success);
assertEquals(true, listener.success);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
checkLoyaltyCards();

View File

@@ -11,13 +11,13 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 17)
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class LoyaltyCardCursorAdapterTest
{
private Activity activity;

View File

@@ -6,32 +6,38 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.client.android.Intents;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.res.builder.RobolectricPackageManager;
import org.robolectric.shadows.ShadowActivity;
import org.robolectric.util.ActivityController;
import org.robolectric.shadows.ShadowLog;
import org.robolectric.android.controller.ActivityController;
import java.io.IOException;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Shadows.shadowOf;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 17)
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class LoyaltyCardViewActivityTest
{
private final String BARCODE_DATA = "428311627547";
@@ -40,14 +46,28 @@ public class LoyaltyCardViewActivityTest
private final String EAN_BARCODE_DATA = "4763705295336";
private final String EAN_BARCODE_TYPE = BarcodeFormat.EAN_13.name();
enum ViewMode
{
ADD_CARD,
VIEW_CARD,
UPDATE_CARD,
;
}
@Before
public void setUp()
{
// Output logs emitted during tests so they may be accessed
ShadowLog.stream = System.out;
}
/**
* Register a handler in the package manager for a image capture intent
*/
private void registerMediaStoreIntentHandler()
{
// Add something that will 'handle' the media capture intent
RobolectricPackageManager packageManager = (RobolectricPackageManager) shadowOf(
RuntimeEnvironment.application).getPackageManager();
RobolectricPackageManager packageManager = shadowOf(RuntimeEnvironment.application.getPackageManager());
ResolveInfo info = new ResolveInfo();
info.isDefault = true;
@@ -83,12 +103,10 @@ public class LoyaltyCardViewActivityTest
assertEquals(1, 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 EditText barcodeTypeField = (EditText) activity.findViewById(R.id.barcodeType);
final Button saveButton = (Button) activity.findViewById(R.id.saveButton);
final EditText storeField = (EditText) activity.findViewById(R.id.storeNameEdit);
final EditText noteField = (EditText) activity.findViewById(R.id.noteEdit);
final TextView cardIdField = (TextView) activity.findViewById(R.id.cardIdView);
final TextView barcodeTypeField = (TextView) activity.findViewById(R.id.barcodeType);
storeField.setText(store);
noteField.setText(note);
@@ -96,7 +114,7 @@ public class LoyaltyCardViewActivityTest
barcodeTypeField.setText(barcodeType);
assertEquals(false, activity.isFinishing());
saveButton.performClick();
shadowOf(activity).clickMenuItem(R.id.action_save);
assertEquals(true, activity.isFinishing());
assertEquals(1, db.getLoyaltyCardCount());
@@ -145,12 +163,11 @@ public class LoyaltyCardViewActivityTest
}
private void checkFieldProperties(final Activity activity, final int id, final int visibility,
boolean enabled, final String contents)
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;
@@ -158,24 +175,24 @@ public class LoyaltyCardViewActivityTest
}
}
private void checkAllFields(final Activity activity, boolean editMode, final String store,
final String note, final String cardId, final String barcodeType)
private void checkAllFields(final Activity activity, ViewMode mode,
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;
int captureVisibility = (mode == ViewMode.UPDATE_CARD || mode == ViewMode.ADD_CARD) ? View.VISIBLE : View.GONE;
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);
int viewVisibility = (mode == ViewMode.VIEW_CARD) ? View.VISIBLE : View.GONE;
int editVisibility = (mode != ViewMode.VIEW_CARD) ? View.VISIBLE : View.GONE;
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);
checkFieldProperties(activity, R.id.storeNameEdit, editVisibility, store);
checkFieldProperties(activity, R.id.storeNameView, viewVisibility, store);
checkFieldProperties(activity, R.id.noteEdit, editVisibility, note);
checkFieldProperties(activity, R.id.noteView, viewVisibility, note);
checkFieldProperties(activity, R.id.cardIdView, View.VISIBLE, cardId);
checkFieldProperties(activity, R.id.cardIdDivider, cardId.isEmpty() ? View.GONE : View.VISIBLE, null);
checkFieldProperties(activity, R.id.cardIdTableRow, cardId.isEmpty() ? View.GONE : View.VISIBLE, null);
checkFieldProperties(activity, R.id.barcodeType, View.GONE, barcodeType);
checkFieldProperties(activity, R.id.captureButton, captureVisibility, null);
checkFieldProperties(activity, R.id.barcode, View.VISIBLE, null);
}
@Test
@@ -188,11 +205,11 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
checkAllFields(activity, true, "", "", "", "");
checkAllFields(activity, ViewMode.ADD_CARD, "", "", "", "");
}
@Test
public void startWithoutParametersCannotCreateGiftCard()
public void startWithoutParametersCannotCreateLoyaltyCard()
{
ActivityController activityController = Robolectric.buildActivity(LoyaltyCardViewActivity.class).create();
activityController.start();
@@ -200,33 +217,32 @@ public class LoyaltyCardViewActivityTest
activityController.resume();
Activity activity = (Activity)activityController.get();
ShadowActivity shadowActivity = shadowOf(activity);
DBHelper db = new DBHelper(activity);
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 EditText storeField = (EditText) activity.findViewById(R.id.storeNameEdit);
final EditText noteField = (EditText) activity.findViewById(R.id.noteEdit);
final TextView cardIdField = (TextView) activity.findViewById(R.id.cardIdView);
final Button saveButton = (Button) activity.findViewById(R.id.saveButton);
saveButton.performClick();
shadowActivity.clickMenuItem(R.id.action_save);
assertEquals(0, db.getLoyaltyCardCount());
storeField.setText("store");
saveButton.performClick();
shadowActivity.clickMenuItem(R.id.action_save);
assertEquals(0, db.getLoyaltyCardCount());
noteField.setText("note");
saveButton.performClick();
shadowActivity.clickMenuItem(R.id.action_save);
assertEquals(0, db.getLoyaltyCardCount());
cardIdField.setText("cardId");
saveButton.performClick();
shadowActivity.clickMenuItem(R.id.action_save);
assertEquals(0, db.getLoyaltyCardCount());
}
@Test
public void startWithoutParametersCancel()
public void startWithoutParametersBack()
{
ActivityController activityController = Robolectric.buildActivity(LoyaltyCardViewActivity.class).create();
activityController.start();
@@ -235,10 +251,8 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
final Button cancelButton = (Button) activity.findViewById(R.id.cancelButton);
assertEquals(false, activity.isFinishing());
cancelButton.performClick();
shadowOf(activity).clickMenuItem(android.R.id.home);
assertEquals(true, activity.isFinishing());
}
@@ -254,12 +268,12 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
checkAllFields(activity, true, "", "", "", "");
checkAllFields(activity, ViewMode.ADD_CARD, "", "", "", "");
// Complete barcode capture successfully
captureBarcodeWithResult(activity, R.id.captureButton, true);
checkAllFields(activity, true, "", "", BARCODE_DATA, BARCODE_TYPE);
checkAllFields(activity, ViewMode.ADD_CARD, "", "", BARCODE_DATA, BARCODE_TYPE);
// Save and check the gift card
saveLoyaltyCardWithArguments(activity, "store", "note", BARCODE_DATA, BARCODE_TYPE, true);
@@ -275,12 +289,12 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
checkAllFields(activity, true, "", "", "", "");
checkAllFields(activity, ViewMode.ADD_CARD, "", "", "", "");
// Complete barcode capture in failure
captureBarcodeWithResult(activity, R.id.captureButton, false);
checkAllFields(activity, true, "", "", "", "");
checkAllFields(activity, ViewMode.ADD_CARD, "", "", "", "");
}
@Test
@@ -293,17 +307,16 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
checkAllFields(activity, true, "", "", "", "");
checkAllFields(activity, ViewMode.ADD_CARD, "", "", "", "");
// Complete barcode capture successfully
captureBarcodeWithResult(activity, R.id.captureButton, true);
checkAllFields(activity, true, "", "", BARCODE_DATA, BARCODE_TYPE);
checkAllFields(activity, ViewMode.ADD_CARD, "", "", BARCODE_DATA, BARCODE_TYPE);
// Cancel the gift card creation
final Button cancelButton = (Button) activity.findViewById(R.id.cancelButton);
assertEquals(false, activity.isFinishing());
cancelButton.performClick();
shadowOf(activity).clickMenuItem(android.R.id.home);
assertEquals(true, activity.isFinishing());
}
@@ -340,7 +353,7 @@ public class LoyaltyCardViewActivityTest
activityController.visible();
activityController.resume();
checkAllFields(activity, true, "store", "note", BARCODE_DATA, BARCODE_TYPE);
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", BARCODE_DATA, BARCODE_TYPE);
}
@Test
@@ -356,7 +369,7 @@ public class LoyaltyCardViewActivityTest
activityController.visible();
activityController.resume();
checkAllFields(activity, false, "store", "note", BARCODE_DATA, BARCODE_TYPE);
checkAllFields(activity, ViewMode.VIEW_CARD, "store", "note", BARCODE_DATA, BARCODE_TYPE);
}
@Test
@@ -372,12 +385,12 @@ public class LoyaltyCardViewActivityTest
activityController.visible();
activityController.resume();
checkAllFields(activity, true, "store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
// Complete barcode capture successfully
captureBarcodeWithResult(activity, R.id.captureButton, true);
checkAllFields(activity, true, "store", "note", BARCODE_DATA, BARCODE_TYPE);
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", BARCODE_DATA, BARCODE_TYPE);
}
@Test
@@ -393,17 +406,59 @@ public class LoyaltyCardViewActivityTest
activityController.visible();
activityController.resume();
checkAllFields(activity, true, "store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE);
// Complete barcode capture successfully
captureBarcodeWithResult(activity, R.id.captureButton, true);
checkAllFields(activity, true, "store", "note", BARCODE_DATA, BARCODE_TYPE);
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", BARCODE_DATA, BARCODE_TYPE);
// Cancel the gift card creation
final Button cancelButton = (Button) activity.findViewById(R.id.cancelButton);
// Cancel the loyalty card creation
assertEquals(false, activity.isFinishing());
cancelButton.performClick();
shadowOf(activity).clickMenuItem(android.R.id.home);
assertEquals(true, activity.isFinishing());
}
@Test
public void checkMenu() 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();
final Menu menu = shadowOf(activity).getOptionsMenu();
assertTrue(menu != null);
// The settings and add button should be present
assertEquals(menu.size(), 2);
assertEquals("Block Rotation", menu.findItem(R.id.action_lock_unlock).getTitle().toString());
assertEquals("Edit", menu.findItem(R.id.action_edit).getTitle().toString());
}
@Test
public void startWithMissingLoyaltyCard() throws IOException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
activityController.start();
activityController.visible();
activityController.resume();
// The activity should find that the card is missing and shut down
assertTrue(activity.isFinishing());
// Make sure the activity can close down
activityController.pause();
activityController.stop();
activityController.destroy();
}
}

View File

@@ -2,8 +2,11 @@ package protect.card_locker;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.ListView;
@@ -11,12 +14,14 @@ 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.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
import org.robolectric.android.controller.ActivityController;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -24,10 +29,20 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Shadows.shadowOf;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 17)
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class MainActivityTest
{
private SharedPreferences prefs;
@Before
public void setUp()
{
// Assume that this is not the first launch
prefs = RuntimeEnvironment.application.getSharedPreferences("protect.card_locker", Context.MODE_PRIVATE);
prefs.edit().putBoolean("firstrun", false).commit();
}
@Test
public void initiallyNoLoyaltyCards() throws Exception
{
@@ -50,10 +65,11 @@ public class MainActivityTest
assertTrue(menu != null);
// The settings and add button should be present
assertEquals(menu.size(), 3);
assertEquals(menu.size(), 4);
assertEquals("Add", menu.findItem(R.id.action_add).getTitle().toString());
assertEquals("Import/Export", menu.findItem(R.id.action_import_export).getTitle().toString());
assertEquals("Start Intro", menu.findItem(R.id.action_intro).getTitle().toString());
assertEquals("About", menu.findItem(R.id.action_about).getTitle().toString());
}
@@ -100,4 +116,26 @@ public class MainActivityTest
Cursor cursor = (Cursor)list.getAdapter().getItem(0);
assertNotNull(cursor);
}
@Test
public void testFirstRunStartsIntro()
{
prefs.edit().remove("firstrun").commit();
ActivityController controller = Robolectric.buildActivity(MainActivity.class).create();
Activity activity = (Activity)controller.get();
assertTrue(activity.isFinishing() == false);
Intent next = shadowOf(activity).getNextStartedActivity();
ComponentName componentName = next.getComponent();
String name = componentName.flattenToShortString();
assertEquals("protect.card_locker/.intro.IntroActivity", name);
Bundle extras = next.getExtras();
assertNull(extras);
assertEquals(false, prefs.getBoolean("firstrun", true));
}
}

View File

@@ -3,9 +3,12 @@
buildscript {
repositories {
jcenter()
// For the Android Gradle plugin
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.2'
classpath 'com.android.tools.build:gradle:3.0.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -15,6 +18,8 @@ buildscript {
allprojects {
repositories {
jcenter()
maven { url 'https://jitpack.io' }
google()
}
}

Some files were not shown because too many files have changed in this diff Show More