Compare commits
425 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cb5eba18f | ||
|
|
134d7fd824 | ||
|
|
1615bc38bd | ||
|
|
3ecdf71331 | ||
|
|
66a2cd438c | ||
|
|
3fdff3c85a | ||
|
|
56709d40b5 | ||
|
|
a8c4c73611 | ||
|
|
31dd0e1de2 | ||
|
|
f51e97fdf7 | ||
|
|
1fcf7101f2 | ||
|
|
dbb7a4ea62 | ||
|
|
a2fa8edcf5 | ||
|
|
fd71eda561 | ||
|
|
058676bb0f | ||
|
|
1fb59979f3 | ||
|
|
9bde7ce4da | ||
|
|
db3d93e1bc | ||
|
|
4fd7872c87 | ||
|
|
fc313f0cfa | ||
|
|
03cf97cca7 | ||
|
|
a7204eb9d5 | ||
|
|
81d7250306 | ||
|
|
580025c99e | ||
|
|
d88bede310 | ||
|
|
4c9bc911ad | ||
|
|
38d6d6ccff | ||
|
|
866eaea680 | ||
|
|
5619c1c003 | ||
|
|
b8311044db | ||
|
|
3679b9a48a | ||
|
|
727f3f32fe | ||
|
|
6fa70afac5 | ||
|
|
c5ad261392 | ||
|
|
28018eb681 | ||
|
|
28f7b3db02 | ||
|
|
10e720de91 | ||
|
|
4de952ba2d | ||
|
|
c5de27abe3 | ||
|
|
cafa09a86a | ||
|
|
1635737658 | ||
|
|
6ee60687b6 | ||
|
|
0afd9358e6 | ||
|
|
cad3b492a2 | ||
|
|
59bde6916c | ||
|
|
eb6364b932 | ||
|
|
ae7ac21785 | ||
|
|
2dd02f42f5 | ||
|
|
3b8e38001c | ||
|
|
fbb0ab27ec | ||
|
|
ceea5182d1 | ||
|
|
34c07800dd | ||
|
|
8c7a3273e8 | ||
|
|
49cd9a28c7 | ||
|
|
392024f84f | ||
|
|
165f963f7d | ||
|
|
aca44c5830 | ||
|
|
e2ed5b515a | ||
|
|
ba6705b96e | ||
|
|
9f407e5eb0 | ||
|
|
c00da00be5 | ||
|
|
c2ea640a71 | ||
|
|
ba5259f0f6 | ||
|
|
b1abfdff2e | ||
|
|
39219531b4 | ||
|
|
048edf186e | ||
|
|
ee6d415d26 | ||
|
|
5143e3fe45 | ||
|
|
306ee053c1 | ||
|
|
40871690d5 | ||
|
|
0bbbabce97 | ||
|
|
f59c485472 | ||
|
|
d8543f456c | ||
|
|
fcf88a1ace | ||
|
|
a2e99c2b9d | ||
|
|
607dff03d4 | ||
|
|
73b6c99d9e | ||
|
|
036748ee56 | ||
|
|
5cae97482f | ||
|
|
f67d27167e | ||
|
|
0832c7fc97 | ||
|
|
f5e1a2d732 | ||
|
|
7b799df98a | ||
|
|
968c3e71a1 | ||
|
|
29d8da99cd | ||
|
|
79213aa3ec | ||
|
|
50fdfe92fa | ||
|
|
4eab1121b0 | ||
|
|
d3bcd3e273 | ||
|
|
9d1c9c3309 | ||
|
|
61e039c456 | ||
|
|
0e2d725591 | ||
|
|
759d42cfcf | ||
|
|
2312788a57 | ||
|
|
0820be3986 | ||
|
|
8a11953276 | ||
|
|
182b3ff3c0 | ||
|
|
79dad1b0a6 | ||
|
|
04466edb01 | ||
|
|
af3ce5a0ff | ||
|
|
3291185812 | ||
|
|
c9df3bd1d2 | ||
|
|
057c7923a8 | ||
|
|
4195d73c53 | ||
|
|
ed08f0577a | ||
|
|
a1257a1692 | ||
|
|
a51815a172 | ||
|
|
8eee8f6a0f | ||
|
|
1817c184ae | ||
|
|
773d759ef5 | ||
|
|
cbc51cfb73 | ||
|
|
46c3d190d8 | ||
|
|
9951999ca4 | ||
|
|
4843a1093d | ||
|
|
a4bde722a6 | ||
|
|
ff5e5d5833 | ||
|
|
685eaec8b5 | ||
|
|
a3556cc66d | ||
|
|
13543ea9f6 | ||
|
|
d7bf31c308 | ||
|
|
a77a968619 | ||
|
|
70c6ee17cd | ||
|
|
c233f82dc1 | ||
|
|
be0c2507f2 | ||
|
|
abfcf9d6cd | ||
|
|
4d565b3b2f | ||
|
|
27511a4ccd | ||
|
|
12440346fa | ||
|
|
870e4d0c4a | ||
|
|
6e526de087 | ||
|
|
891636b51f | ||
|
|
017d97d08c | ||
|
|
63165ca1a5 | ||
|
|
48f40a51d1 | ||
|
|
3940ba5756 | ||
|
|
b8a36e3c45 | ||
|
|
60deaae8d5 | ||
|
|
a71da8d6dc | ||
|
|
dbbbd128dc | ||
|
|
3275279501 | ||
|
|
2f6516ffb9 | ||
|
|
41c8b78275 | ||
|
|
6ed96393d5 | ||
|
|
3519e03568 | ||
|
|
1aef3f5253 | ||
|
|
c97e80432f | ||
|
|
c6265eb9e3 | ||
|
|
935cd97f99 | ||
|
|
e65d096e76 | ||
|
|
8d38918a12 | ||
|
|
57f2c7acd4 | ||
|
|
7fb6e4a8d3 | ||
|
|
56d20939be | ||
|
|
a3968f8894 | ||
|
|
74dd292f5a | ||
|
|
d7a7528114 | ||
|
|
bffeca9033 | ||
|
|
5a88afb9f3 | ||
|
|
f775f9224b | ||
|
|
17acb8370c | ||
|
|
23766fe9f0 | ||
|
|
f1b0a26591 | ||
|
|
f1b2c0d93d | ||
|
|
4034997d7f | ||
|
|
d3bbaf39f4 | ||
|
|
ae7684921f | ||
|
|
e73974536c | ||
|
|
43072e283f | ||
|
|
a3a70d459b | ||
|
|
edfc91376f | ||
|
|
5fb68055ea | ||
|
|
085a3f10e7 | ||
|
|
4070a6b4e0 | ||
|
|
bc547a1d2b | ||
|
|
4f9c75da9e | ||
|
|
fde679751a | ||
|
|
d4720db2e7 | ||
|
|
245935242f | ||
|
|
e217bd28fb | ||
|
|
ad17bc6bf3 | ||
|
|
17ab1365bf | ||
|
|
7ebbe6413f | ||
|
|
adbc6bf999 | ||
|
|
52c41f4c49 | ||
|
|
858e317d8f | ||
|
|
898e822a28 | ||
|
|
6f45f635aa | ||
|
|
7eeb87578f | ||
|
|
badba551d0 | ||
|
|
49abf8a918 | ||
|
|
5fba338223 | ||
|
|
388a0723dc | ||
|
|
e14da63d06 | ||
|
|
1673c0229a | ||
|
|
22afeddcbc | ||
|
|
7758c61079 | ||
|
|
47b30ca9fb | ||
|
|
7b4d587b13 | ||
|
|
685ee018c6 | ||
|
|
e0915578ba | ||
|
|
c733a6c3b9 | ||
|
|
ec17255a43 | ||
|
|
1fc7baa5a0 | ||
|
|
0df411ee96 | ||
|
|
23178d9694 | ||
|
|
94accc951d | ||
|
|
0207e12aed | ||
|
|
c760465b3e | ||
|
|
f480bd0c7e | ||
|
|
5599560258 | ||
|
|
7b4c119d7d | ||
|
|
6144353079 | ||
|
|
03a5334961 | ||
|
|
04e0a5716e | ||
|
|
91e3f9f785 | ||
|
|
2ebc862e27 | ||
|
|
5d122affce | ||
|
|
9e09b9052a | ||
|
|
2753b8826f | ||
|
|
4e02252b75 | ||
|
|
647ce00e72 | ||
|
|
86a5f2fb50 | ||
|
|
dca5129031 | ||
|
|
e30eb00bf7 | ||
|
|
11e5cb8ec2 | ||
|
|
bdd5c8fbbb | ||
|
|
cd79839748 | ||
|
|
d6b47914c8 | ||
|
|
ce1acb83f0 | ||
|
|
bc360aa06c | ||
|
|
ebe6139a64 | ||
|
|
d354fd1877 | ||
|
|
00b612d6c7 | ||
|
|
a307511193 | ||
|
|
f7f358c5c3 | ||
|
|
f7b50a72a4 | ||
|
|
68ce1fe9fd | ||
|
|
e822ab0b56 | ||
|
|
e33ab682a6 | ||
|
|
0d50ad6d10 | ||
|
|
4143e5c286 | ||
|
|
15425d51aa | ||
|
|
3abcb32a75 | ||
|
|
93124a88a5 | ||
|
|
29b00e3b59 | ||
|
|
04174154d6 | ||
|
|
bf60976d10 | ||
|
|
fb7e3e12f2 | ||
|
|
641d29a6f8 | ||
|
|
0f2ee296b4 | ||
|
|
ecd2ecd517 | ||
|
|
f3e7d8f5c1 | ||
|
|
f4522fb17d | ||
|
|
824a72b156 | ||
|
|
116a44fd1c | ||
|
|
b6190d3250 | ||
|
|
82c40408f7 | ||
|
|
c6c9fb763a | ||
|
|
dda5dcc652 | ||
|
|
d039ad9fa4 | ||
|
|
5c70c98eb9 | ||
|
|
2face21985 | ||
|
|
b38ec7b753 | ||
|
|
32d5aec76b | ||
|
|
bdbcaf4b1e | ||
|
|
f54905f218 | ||
|
|
2d3bd4a375 | ||
|
|
de4ab95437 | ||
|
|
09fba71710 | ||
|
|
2b84762b99 | ||
|
|
3a4850d5cf | ||
|
|
5bc8a915d6 | ||
|
|
15cbd5b641 | ||
|
|
d980079d76 | ||
|
|
e58cbd972e | ||
|
|
1fbecd439b | ||
|
|
707306ac81 | ||
|
|
86152586cd | ||
|
|
fa33e4873e | ||
|
|
24e4ef2d9b | ||
|
|
eac4142392 | ||
|
|
f38a499bd8 | ||
|
|
0b8f52b87e | ||
|
|
c8255a2648 | ||
|
|
f28326e363 | ||
|
|
a9952d021b | ||
|
|
9274339be8 | ||
|
|
18caa01089 | ||
|
|
ac495e1bf2 | ||
|
|
a8eecb8841 | ||
|
|
abdde45669 | ||
|
|
13b2faab39 | ||
|
|
433d31c30f | ||
|
|
fa7dcd253a | ||
|
|
ac057f50e2 | ||
|
|
ef33c7a33f | ||
|
|
6028f7d674 | ||
|
|
da5f999eed | ||
|
|
0a9124c834 | ||
|
|
c173012955 | ||
|
|
64db9e593d | ||
|
|
acde4ef181 | ||
|
|
807ea25ac9 | ||
|
|
ad4476fead | ||
|
|
f2c0e3904f | ||
|
|
0eeadbf77f | ||
|
|
c0e95bd4da | ||
|
|
b7e8b8d36e | ||
|
|
2bca8b738a | ||
|
|
2b617ceca5 | ||
|
|
29838f4f73 | ||
|
|
1403d4a98e | ||
|
|
c4b860bb3d | ||
|
|
2768f4c590 | ||
|
|
8660d4e218 | ||
|
|
596d899449 | ||
|
|
0db5a6f803 | ||
|
|
bbd3782bf2 | ||
|
|
b69f2d470b | ||
|
|
ce4dc01c9d | ||
|
|
5cea7eba6c | ||
|
|
0f7d64bed6 | ||
|
|
330b6ecd54 | ||
|
|
3f5bcbb941 | ||
|
|
9c74b2a264 | ||
|
|
dfc804fbe5 | ||
|
|
fa73a30bf4 | ||
|
|
449419b647 | ||
|
|
36916b622b | ||
|
|
03f97374bd | ||
|
|
8650878d95 | ||
|
|
defb8793f6 | ||
|
|
c6b7fd6032 | ||
|
|
5960f03f9b | ||
|
|
b1df0c0baf | ||
|
|
bfb131c811 | ||
|
|
4580ee8017 | ||
|
|
d76a6d79d7 | ||
|
|
b38e035fb6 | ||
|
|
eeb41376c5 | ||
|
|
89f649e5ef | ||
|
|
4bde8f49b1 | ||
|
|
87c1bbdbf4 | ||
|
|
7029818789 | ||
|
|
b64c29bf96 | ||
|
|
24317f4d99 | ||
|
|
b9d3699038 | ||
|
|
ed3c9ceb50 | ||
|
|
0a938628cb | ||
|
|
5720da1581 | ||
|
|
5393903872 | ||
|
|
8e0a4b978d | ||
|
|
df21c2577d | ||
|
|
a7d5b6fa0c | ||
|
|
b49501e66a | ||
|
|
65d0f57358 | ||
|
|
927ac61912 | ||
|
|
fd2b84f318 | ||
|
|
ed4afed538 | ||
|
|
007427023b | ||
|
|
b4ff68eddb | ||
|
|
151b81daa7 | ||
|
|
c090af1c7d | ||
|
|
54a0f89821 | ||
|
|
f01058d5b8 | ||
|
|
e01c77681b | ||
|
|
c49ecdca56 | ||
|
|
b7672e5247 | ||
|
|
71777a74fd | ||
|
|
bebab039d7 | ||
|
|
588a573878 | ||
|
|
a9bbf8fb65 | ||
|
|
175135d695 | ||
|
|
9f08d07a6a | ||
|
|
07f50b1544 | ||
|
|
584064a4a2 | ||
|
|
484f357a82 | ||
|
|
e32b40a024 | ||
|
|
010b88d477 | ||
|
|
fe0bb7b870 | ||
|
|
925f780599 | ||
|
|
9492d206d9 | ||
|
|
a32415b4d2 | ||
|
|
5965b82251 | ||
|
|
a5650e6382 | ||
|
|
6befc99ccc | ||
|
|
0e124b2550 | ||
|
|
06a7db0e6d | ||
|
|
f2f56b06dd | ||
|
|
518f339c7a | ||
|
|
bbe6ec3741 | ||
|
|
18310fdd25 | ||
|
|
19bd64c48c | ||
|
|
c86819fc74 | ||
|
|
8edc9ce5fd | ||
|
|
a3a5a3a8db | ||
|
|
793247a48c | ||
|
|
2f86de4c1b | ||
|
|
0aa1804258 | ||
|
|
1c8ef34b8a | ||
|
|
3fd45af7d9 | ||
|
|
227af54de7 | ||
|
|
b89c5eb91c | ||
|
|
cecec15762 | ||
|
|
dc4a41088c | ||
|
|
b91d4c934a | ||
|
|
b8d4eaacad | ||
|
|
0a7d5d89cf | ||
|
|
7bbcc52ba6 | ||
|
|
14972c9cc9 | ||
|
|
213d1f64a6 | ||
|
|
f4f22055fc | ||
|
|
b99704a3d8 | ||
|
|
715d726ea3 | ||
|
|
8b887a2ee9 | ||
|
|
0248df532a | ||
|
|
8c83b85cea | ||
|
|
b8f3d891ea | ||
|
|
20338eb09b | ||
|
|
ce272fe7f1 | ||
|
|
fb34fb7451 | ||
|
|
7b2d022e92 | ||
|
|
283858a6e3 | ||
|
|
d46b3c0810 | ||
|
|
a2ff1a9e05 |
29
.travis.yml
@@ -1,27 +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
|
||||
|
||||
# Specify at least one system image,
|
||||
# if you need to run emulator(s) during your tests
|
||||
- sys-img-x86-android-17
|
||||
|
||||
script: gradle build lint 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
|
||||
|
||||
10
.tx/config
Normal file
@@ -0,0 +1,10 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[loyalty-card-locker.stringsxml]
|
||||
file_filter = app/src/main/res/values-<lang>/strings.xml
|
||||
minimum_perc = 0
|
||||
source_file = app/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
|
||||
246
CHANGELOG.md
Normal file
@@ -0,0 +1,246 @@
|
||||
## v0.25.4 (2019-10-04)
|
||||
|
||||
Changes
|
||||
- Enable app backups
|
||||
- Update French and Slovenian translations
|
||||
|
||||
## v0.25.3 (2019-03-02)
|
||||
|
||||
Changes
|
||||
- Update Russian translations
|
||||
|
||||
## v0.25.2 (2019-01-05)
|
||||
|
||||
Changes
|
||||
- Update and add translations
|
||||
|
||||
## v0.25.1 (2018-10-14)
|
||||
|
||||
Changes:
|
||||
- Fix creating new card by manually entering barcode (https://github.com/brarcher/loyalty-card-locker/issues/272)
|
||||
|
||||
## v0.25 (2018-10-07)
|
||||
|
||||
Changes:
|
||||
- Sort card list case insensitive (https://github.com/brarcher/loyalty-card-locker/pull/266)
|
||||
- Add setting to lock orientation for all cards (https://github.com/brarcher/loyalty-card-locker/pull/269)
|
||||
|
||||
## v0.24 (2018-07-31)
|
||||
|
||||
Changes:
|
||||
- Add a setting to control screen brightness when displaying a barcode (https://github.com/brarcher/loyalty-card-locker/pull/259)
|
||||
- Add Greek translations (https://github.com/brarcher/loyalty-card-locker/pull/252)
|
||||
- Add Slovenian translations (https://github.com/brarcher/loyalty-card-locker/pull/260)
|
||||
- Update translations (https://github.com/brarcher/loyalty-card-locker/pull/260, https://github.com/brarcher/loyalty-card-locker/pull/254)
|
||||
|
||||
## v0.23.4 (2018-05-12)
|
||||
|
||||
Changes:
|
||||
- Fix Spanish translations (https://github.com/brarcher/loyalty-card-locker/pull/244)
|
||||
- Update translations (https://github.com/brarcher/loyalty-card-locker/pull/244)
|
||||
|
||||
## v0.23.3 (2018-05-05)
|
||||
|
||||
Changes:
|
||||
- Added translations
|
||||
* Polish (https://github.com/brarcher/loyalty-card-locker/pull/232)
|
||||
* Spanish (https://github.com/brarcher/loyalty-card-locker/pull/232)
|
||||
* Slovak (https://github.com/brarcher/loyalty-card-locker/pull/232)
|
||||
- Updated translations (https://github.com/brarcher/loyalty-card-locker/pull/239)
|
||||
|
||||
## v0.23.2 (2018-03-11)
|
||||
|
||||
Changes:
|
||||
- Reduce min SDK from 17 to 15. (https://github.com/brarcher/loyalty-card-locker/pull/226)
|
||||
- Remove usage of legacy apache library, used only in unit tests but no longer needed. (https://github.com/brarcher/loyalty-card-locker/pull/225)
|
||||
|
||||
## v0.23.1 (2018-03-07)
|
||||
|
||||
Changes:
|
||||
- Prevent crash when rendering a barcode exhausts the application's memory. (https://github.com/brarcher/loyalty-card-locker/pull/219)
|
||||
|
||||
## v0.23 (2018-02-28)
|
||||
|
||||
Changes:
|
||||
- Reduce space in header when viewing a card. (https://github.com/brarcher/loyalty-card-locker/pull/213)
|
||||
- Disable beep when scanning a barcode. (https://github.com/brarcher/loyalty-card-locker/pull/216)
|
||||
|
||||
## v0.22 (2018-02-19)
|
||||
|
||||
Changes:
|
||||
- Update translations. (https://github.com/brarcher/loyalty-card-locker/pull/208)
|
||||
- Barcode rendering updates: (https://github.com/brarcher/loyalty-card-locker/pull/209)
|
||||
* Reload card view activity when screen is rotated, so barcode image is correct size.
|
||||
* Render 1D barcodes in a larger space, allowing them to better fill the screen.
|
||||
|
||||
## v0.21 (2018-02-17)
|
||||
|
||||
Changes
|
||||
- Add quiet space at the start/end of barcodes. (https://github.com/brarcher/loyalty-card-locker/pull/200)
|
||||
- Add options to configure the colors used for the store name font and background. (https://github.com/brarcher/loyalty-card-locker/pull/203)
|
||||
- Add options to adjust font sizes on the card listing page and single card page. (https://github.com/brarcher/loyalty-card-locker/pull/204)
|
||||
|
||||
## v0.20 (2018-02-10)
|
||||
|
||||
Changes:
|
||||
- Changes to Card view to display the note, allow the card ID to take multiple lines, and show the store name. (https://github.com/brarcher/loyalty-card-locker/pull/197)
|
||||
|
||||
## v0.19 (2018-02-01)
|
||||
|
||||
Changes:
|
||||
- Improved layout for card list. (https://github.com/brarcher/loyalty-card-locker/pull/188)
|
||||
- Improved layout when viewing a card. (https://github.com/brarcher/loyalty-card-locker/pull/190)
|
||||
|
||||
## v0.18.1 (2018-01-24)
|
||||
|
||||
Changes:
|
||||
- Workaround crash during install on some Android versions (likely Android 5 and below). (https://github.com/brarcher/loyalty-card-locker/pull/184)
|
||||
|
||||
## v0.18 (2018-01-19)
|
||||
|
||||
Changes:
|
||||
- Fix crash when importing certain types of corrupted CSV files. (https://github.com/brarcher/loyalty-card-locker/pull/177)
|
||||
- Fix importing backups directly from the file system. (https://github.com/brarcher/loyalty-card-locker/pull/180)
|
||||
- Fix importing backups from certain types of content providers. (https://github.com/brarcher/loyalty-card-locker/pull/179)
|
||||
|
||||
## 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
|
||||
91
CONTRIBUTING.md
Normal file
@@ -0,0 +1,91 @@
|
||||
How to Submit Patches to the Loyalty Card Keychain Project
|
||||
===============================================================================
|
||||
https://github.com/brarcher/budget-watch
|
||||
|
||||
This document is intended to act as a guide to help you contribute to the
|
||||
Loyalty Card Keychain project. It is not perfect, and there will always be exceptions
|
||||
to the rules described here, but by following the instructions below you
|
||||
should have a much easier time getting your work merged with the upstream
|
||||
project.
|
||||
|
||||
## Test Your Code
|
||||
|
||||
There are four possible tests you can run to verify your code. The first
|
||||
is unit tests, which check the basic functionality of the application, and
|
||||
can be run by gradle using:
|
||||
|
||||
# ./gradlew testReleaseUnitTest
|
||||
|
||||
The second and third check for common problems using static analysis.
|
||||
These are the Android lint checker, run using:
|
||||
|
||||
# ./gradlew lintRelease
|
||||
|
||||
and FindBugs, run using:
|
||||
|
||||
# ./gradlew findbugs
|
||||
|
||||
The final check is by testing the application on a live device and verifying
|
||||
the basic functionality works as expected.
|
||||
|
||||
## Make Sure Your Code is Tested
|
||||
|
||||
The Loyalty Card Keychain code uses a fair number of unit tests to verify that
|
||||
the basic functionality is working. Submissions which add functionality
|
||||
or significantly change the existing code should include additional tests
|
||||
to verify the proper operation of the proposed changes.
|
||||
|
||||
## Explain Your Work
|
||||
|
||||
At the top of every patch you should include a description of the problem you
|
||||
are trying to solve, how you solved it, and why you chose the solution you
|
||||
implemented. If you are submitting a bug fix, it is also incredibly helpful
|
||||
if you can describe/include a reproducer for the problem in the description as
|
||||
well as instructions on how to test for the bug and verify that it has been
|
||||
fixed.
|
||||
|
||||
## Sign Your Work
|
||||
|
||||
The sign-off is a simple line at the end of the patch description, which
|
||||
certifies that you wrote it or otherwise have the right to pass it on as an
|
||||
open-source patch. The "Developer's Certificate of Origin" pledge is taken
|
||||
from the Linux Kernel and the rules are pretty simple:
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
|
||||
... then you just add a line to the bottom of your patch description, with
|
||||
your real name, saying:
|
||||
|
||||
Signed-off-by: Random J Developer <random@developer.example.org>
|
||||
|
||||
## Submit Patch(es) for Review
|
||||
|
||||
Finally, you will need to submit your patches so that they can be reviewed
|
||||
and potentially merged into the main Loyalty Card Keychain repository. The preferred
|
||||
way to do this is to submit a Pull Request to the Loyalty Card Keychain project.
|
||||
Changes need to apply cleanly onto the master branch and pass all
|
||||
unit tests and produce no errors during static analysis.
|
||||
41
README.md
@@ -1,8 +1,10 @@
|
||||
# Loyalty Card Locker
|
||||
# Loyalty Card Keychain
|
||||
[](https://travis-ci.org/brarcher/loyalty-card-locker)
|
||||
|
||||
[](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,7 +23,36 @@ 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
|
||||
compile the application and run all unit tests:
|
||||
|
||||
GNU/Linux, OSX, UNIX:
|
||||
```
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
Windows:
|
||||
```
|
||||
./gradlew.bat build
|
||||
```
|
||||
|
||||
# Translating
|
||||
|
||||
If you are interested in translating this application to another language, 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)
|
||||
|
||||
@@ -7,15 +7,14 @@ findbugs {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.2"
|
||||
compileSdkVersion 27
|
||||
|
||||
defaultConfig {
|
||||
applicationId "protect.card_locker"
|
||||
minSdkVersion 17
|
||||
targetSdkVersion 23
|
||||
versionCode 4
|
||||
versionName "0.4"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 27
|
||||
versionCode 35
|
||||
versionName "0.25.4"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
@@ -27,28 +26,42 @@ android {
|
||||
disable "GoogleAppIndexingWarning"
|
||||
disable "ButtonStyle"
|
||||
disable "AlwaysShowAction"
|
||||
disable "MissingTranslation"
|
||||
disable "MissingPrefix"
|
||||
}
|
||||
|
||||
// This is for Robolectric support for SDK 23
|
||||
useLibrary 'org.apache.http.legacy'
|
||||
// Starting with Android Studio 3 Robolectric is unable to find resources.
|
||||
// The following allows it to find the resources.
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 'com.android.support:appcompat-v7:27.0.2'
|
||||
compile 'com.android.support:design:27.0.2'
|
||||
compile 'com.android.support:support-v4: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 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
compile 'com.jaredrummler:colorpicker:1.0.2'
|
||||
compile group: 'com.google.guava', name: 'guava', version: '20.0'
|
||||
compile 'com.github.apl-devs:appintro:v4.2.0'
|
||||
compile "com.vanniktech:vntnumberpickerpreference:1.0.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'
|
||||
|
||||
classes = fileTree('build/intermediates/classes/debug/')
|
||||
classes = fileTree('build/intermediates/javac/debug/compileDebugJavaWithJavac/classes')
|
||||
source = fileTree('src/main/java')
|
||||
classpath = files()
|
||||
|
||||
|
||||
7
app/proguard-rules.pro
vendored
@@ -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
|
||||
@@ -5,7 +5,8 @@
|
||||
<uses-permission
|
||||
android:name="android.permission.CAMERA"/>
|
||||
<uses-permission
|
||||
android:maxSdkVersion="18"
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<uses-feature
|
||||
@@ -16,7 +17,7 @@
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
@@ -34,9 +35,54 @@
|
||||
<activity
|
||||
android:name=".LoyaltyCardViewActivity"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:label=""
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:exported="true"/>
|
||||
<activity
|
||||
android:name=".LoyaltyCardEditActivity"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:parentActivityName="protect.card_locker.MainActivity"/>
|
||||
android:exported="true"/>
|
||||
<activity
|
||||
android:name=".BarcodeSelectorActivity"
|
||||
android:label="@string/selectBarcodeTitle"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:windowSoftInputMode="stateHidden"/>
|
||||
<activity
|
||||
android:name=".preferences.SettingsActivity"
|
||||
android:label="@string/settings"
|
||||
android:configChanges="orientation|screenSize"/>
|
||||
<activity
|
||||
android:name=".ImportExportActivity"
|
||||
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>
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* This task will generate a barcode and load it into an ImageView.
|
||||
* Only a weak reference of the ImageView is kept, so this class will not
|
||||
* prevent the ImageView from being garbage collected.
|
||||
*/
|
||||
class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
|
||||
// When drawn in a smaller window 1D barcodes for some reason end up
|
||||
// squished, whereas 2D barcodes look fine.
|
||||
private static final int MAX_WIDTH_1D = 1500;
|
||||
private static final int MAX_WIDTH_2D = 500;
|
||||
|
||||
private final WeakReference<ImageView> imageViewReference;
|
||||
private final String cardId;
|
||||
private final BarcodeFormat format;
|
||||
private final int imageHeight;
|
||||
private final int imageWidth;
|
||||
|
||||
BarcodeImageWriterTask(ImageView imageView, String cardIdString,
|
||||
BarcodeFormat barcodeFormat)
|
||||
{
|
||||
// Use a WeakReference to ensure the ImageView can be garbage collected
|
||||
imageViewReference = new WeakReference<>(imageView);
|
||||
|
||||
cardId = cardIdString;
|
||||
format = barcodeFormat;
|
||||
|
||||
final int MAX_WIDTH = getMaxWidth(format);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private int getMaxWidth(BarcodeFormat format)
|
||||
{
|
||||
switch(format)
|
||||
{
|
||||
// 2D barcodes
|
||||
case AZTEC:
|
||||
case DATA_MATRIX:
|
||||
case MAXICODE:
|
||||
case PDF_417:
|
||||
case QR_CODE:
|
||||
return MAX_WIDTH_2D;
|
||||
|
||||
// 1D barcodes:
|
||||
case CODABAR:
|
||||
case CODE_39:
|
||||
case CODE_93:
|
||||
case CODE_128:
|
||||
case EAN_8:
|
||||
case EAN_13:
|
||||
case ITF:
|
||||
case UPC_A:
|
||||
case UPC_E:
|
||||
case RSS_14:
|
||||
case RSS_EXPANDED:
|
||||
case UPC_EAN_EXTENSION:
|
||||
default:
|
||||
return MAX_WIDTH_1D;
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap doInBackground(Void... params)
|
||||
{
|
||||
MultiFormatWriter writer = new MultiFormatWriter();
|
||||
BitMatrix bitMatrix;
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
bitMatrix = writer.encode(cardId, format, imageWidth, imageHeight, null);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
// Cast a wider net here and catch any exception, as there are some
|
||||
// cases where an encoder may fail if the data is invalid for the
|
||||
// barcode type. If this happens, we want to fail gracefully.
|
||||
throw new WriterException(e);
|
||||
}
|
||||
|
||||
final int WHITE = 0xFFFFFFFF;
|
||||
final int BLACK = 0xFF000000;
|
||||
|
||||
int bitMatrixWidth = bitMatrix.getWidth();
|
||||
int bitMatrixHeight = bitMatrix.getHeight();
|
||||
|
||||
int[] pixels = new int[bitMatrixWidth * bitMatrixHeight];
|
||||
|
||||
for (int y = 0; y < bitMatrixHeight; y++)
|
||||
{
|
||||
int offset = y * bitMatrixWidth;
|
||||
for (int x = 0; x < bitMatrixWidth; x++)
|
||||
{
|
||||
int color = bitMatrix.get(x, y) ? BLACK : WHITE;
|
||||
pixels[offset + x] = color;
|
||||
}
|
||||
}
|
||||
Bitmap bitmap = Bitmap.createBitmap(bitMatrixWidth, bitMatrixHeight,
|
||||
Bitmap.Config.ARGB_8888);
|
||||
bitmap.setPixels(pixels, 0, bitMatrixWidth, 0, 0, bitMatrixWidth, bitMatrixHeight);
|
||||
|
||||
// Determine if the image needs to be scaled.
|
||||
// This is necessary because the datamatrix barcode generator
|
||||
// ignores the requested size and returns the smallest image necessary
|
||||
// to represent the barcode. If we let the ImageView scale the image
|
||||
// it will use bi-linear filtering, which results in a blurry barcode.
|
||||
// To avoid this, if scaling is needed do so without filtering.
|
||||
|
||||
int heightScale = imageHeight / bitMatrixHeight;
|
||||
int widthScale = imageWidth / bitMatrixHeight;
|
||||
int scalingFactor = Math.min(heightScale, widthScale);
|
||||
|
||||
if(scalingFactor > 1)
|
||||
{
|
||||
bitmap = Bitmap.createScaledBitmap(bitmap, bitMatrixWidth * scalingFactor, bitMatrixHeight * scalingFactor, false);
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
catch (WriterException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to generate barcode of type " + format + ": " + cardId, e);
|
||||
}
|
||||
catch(OutOfMemoryError e)
|
||||
{
|
||||
Log.w(TAG, "Insufficient memory to render barcode, "
|
||||
+ imageWidth + "x" + imageHeight + ", " + format.name()
|
||||
+ ", length=" + cardId.length(), e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onPostExecute(Bitmap result)
|
||||
{
|
||||
Log.i(TAG, "Finished generating barcode image of type " + format + ": " + cardId);
|
||||
ImageView imageView = imageViewReference.get();
|
||||
if(imageView == null)
|
||||
{
|
||||
// The ImageView no longer exists, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
imageView.setImageBitmap(result);
|
||||
|
||||
if(result != null)
|
||||
{
|
||||
Log.i(TAG, "Displaying barcode");
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.i(TAG, "Barcode generation failed, removing image from display");
|
||||
imageView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This activity is callable and will allow a user to enter
|
||||
* barcode data and generate all barcodes possible for
|
||||
* the data. The user may then select any barcode, where its
|
||||
* data and type will be returned to the caller.
|
||||
*/
|
||||
public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
|
||||
// Result this activity will return
|
||||
public static final String BARCODE_CONTENTS = "contents";
|
||||
public static final String BARCODE_FORMAT = "format";
|
||||
|
||||
// These are all the barcode types that the zxing library
|
||||
// is able to generate a barcode for, and thus should be
|
||||
// the only barcodes which we should attempt to scan.
|
||||
public static final Collection<String> SUPPORTED_BARCODE_TYPES = Collections.unmodifiableList(
|
||||
Arrays.asList(
|
||||
BarcodeFormat.AZTEC.name(),
|
||||
BarcodeFormat.CODE_39.name(),
|
||||
BarcodeFormat.CODE_128.name(),
|
||||
BarcodeFormat.CODABAR.name(),
|
||||
BarcodeFormat.DATA_MATRIX.name(),
|
||||
BarcodeFormat.EAN_8.name(),
|
||||
BarcodeFormat.EAN_13.name(),
|
||||
BarcodeFormat.ITF.name(),
|
||||
BarcodeFormat.PDF_417.name(),
|
||||
BarcodeFormat.QR_CODE.name(),
|
||||
BarcodeFormat.UPC_A.name()
|
||||
));
|
||||
|
||||
private Map<String, Integer> barcodeViewMap;
|
||||
private LinkedList<AsyncTask> barcodeGeneratorTasks = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.barcode_selector_activity);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
barcodeViewMap = ImmutableMap.<String, Integer>builder()
|
||||
.put(BarcodeFormat.AZTEC.name(), R.id.aztecBarcode)
|
||||
.put(BarcodeFormat.CODE_39.name(), R.id.code39Barcode)
|
||||
.put(BarcodeFormat.CODE_128.name(), R.id.code128Barcode)
|
||||
.put(BarcodeFormat.CODABAR.name(), R.id.codabarBarcode)
|
||||
.put(BarcodeFormat.DATA_MATRIX.name(), R.id.datamatrixBarcode)
|
||||
.put(BarcodeFormat.EAN_8.name(), R.id.ean8Barcode)
|
||||
.put(BarcodeFormat.EAN_13.name(), R.id.ean13Barcode)
|
||||
.put(BarcodeFormat.ITF.name(), R.id.itfBarcode)
|
||||
.put(BarcodeFormat.PDF_417.name(), R.id.pdf417Barcode)
|
||||
.put(BarcodeFormat.QR_CODE.name(), R.id.qrcodeBarcode)
|
||||
.put(BarcodeFormat.UPC_A.name(), R.id.upcaBarcode)
|
||||
.build();
|
||||
|
||||
EditText cardId = findViewById(R.id.cardId);
|
||||
cardId.addTextChangedListener(new TextWatcher()
|
||||
{
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after)
|
||||
{
|
||||
// Noting to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count)
|
||||
{
|
||||
Log.d(TAG, "Entered text: " + s);
|
||||
|
||||
// Stop any async tasks which may not have been started yet
|
||||
for(AsyncTask task : barcodeGeneratorTasks)
|
||||
{
|
||||
task.cancel(false);
|
||||
}
|
||||
barcodeGeneratorTasks.clear();
|
||||
|
||||
// Update barcodes
|
||||
for(String key : barcodeViewMap.keySet())
|
||||
{
|
||||
ImageView image = findViewById(barcodeViewMap.get(key));
|
||||
createBarcodeOption(image, key, s.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
final BarcodeFormat format = BarcodeFormat.valueOf(formatType);
|
||||
if(format == null)
|
||||
{
|
||||
Log.w(TAG, "Unsupported barcode format: " + formatType);
|
||||
return;
|
||||
}
|
||||
|
||||
image.setImageBitmap(null);
|
||||
image.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
Log.d(TAG, "Selected barcode type " + formatType);
|
||||
Intent result = new Intent();
|
||||
result.putExtra(BARCODE_FORMAT, formatType);
|
||||
result.putExtra(BARCODE_CONTENTS, cardId);
|
||||
BarcodeSelectorActivity.this.setResult(RESULT_OK, result);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
if(image.getHeight() == 0)
|
||||
{
|
||||
// The size of the ImageView is not yet available as it has not
|
||||
// yet been drawn. Wait for it to be drawn so the size is available.
|
||||
image.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener()
|
||||
{
|
||||
@Override
|
||||
public void onGlobalLayout()
|
||||
{
|
||||
Log.d(TAG, "Global layout finished, type: + " + formatType + ", width: " + image.getWidth());
|
||||
if (Build.VERSION.SDK_INT < 16)
|
||||
{
|
||||
image.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
image.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
|
||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format);
|
||||
barcodeGeneratorTasks.add(task);
|
||||
task.execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format);
|
||||
barcodeGeneratorTasks.add(task);
|
||||
task.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
if (item.getItemId() == android.R.id.home)
|
||||
{
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -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 = 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 = 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVPrinter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
/**
|
||||
* Class for exporting the database into CSV (Comma Separate Values)
|
||||
* format.
|
||||
*/
|
||||
public class CsvDatabaseExporter implements DatabaseExporter
|
||||
{
|
||||
public void exportData(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException
|
||||
{
|
||||
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
|
||||
|
||||
// Print the header
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
|
||||
DBHelper.LoyaltyCardDbIds.STORE,
|
||||
DBHelper.LoyaltyCardDbIds.NOTE,
|
||||
DBHelper.LoyaltyCardDbIds.CARD_ID,
|
||||
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
|
||||
DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR,
|
||||
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
|
||||
|
||||
Cursor cursor = db.getLoyaltyCardCursor();
|
||||
|
||||
while(cursor.moveToNext())
|
||||
{
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
|
||||
|
||||
printer.printRecord(card.id,
|
||||
card.store,
|
||||
card.note,
|
||||
card.cardId,
|
||||
card.headerColor,
|
||||
card.headerTextColor,
|
||||
card.barcodeType);
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
|
||||
printer.close();
|
||||
}
|
||||
}
|
||||
151
app/src/main/java/protect/card_locker/CsvDatabaseImporter.java
Normal file
@@ -0,0 +1,151 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* Class for importing a database from CSV (Comma Separate Values)
|
||||
* formatted data.
|
||||
*
|
||||
* The database's loyalty cards are expected to appear in the CSV data.
|
||||
* A header is expected for the each table showing the names of the columns.
|
||||
*/
|
||||
public class CsvDatabaseImporter implements DatabaseImporter
|
||||
{
|
||||
public void importData(DBHelper db, InputStreamReader input) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.withHeader());
|
||||
|
||||
SQLiteDatabase database = db.getWritableDatabase();
|
||||
database.beginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
for (CSVRecord record : parser)
|
||||
{
|
||||
importLoyaltyCard(database, db, record);
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
parser.close();
|
||||
database.setTransactionSuccessful();
|
||||
}
|
||||
catch(IllegalArgumentException|IllegalStateException e)
|
||||
{
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.endTransaction();
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a string from the items array. The index into the array
|
||||
* is determined by looking up the index in the fields map using the
|
||||
* "key" as the key. If no such key exists, defaultValue is returned
|
||||
* if it is not null. Otherwise, a FormatException is thrown.
|
||||
*/
|
||||
private String extractString(String key, CSVRecord record, String defaultValue)
|
||||
throws FormatException
|
||||
{
|
||||
String toReturn = defaultValue;
|
||||
|
||||
if(record.isMapped(key))
|
||||
{
|
||||
toReturn = record.get(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(defaultValue == null)
|
||||
{
|
||||
throw new FormatException("Field not used but expected: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an integer from the items array. The index into the array
|
||||
* is determined by looking up the index in the fields map using the
|
||||
* "key" as the key. If no such key exists, or the data is not a valid
|
||||
* int, a FormatException is thrown.
|
||||
*/
|
||||
private Integer extractInt(String key, CSVRecord record, boolean nullIsOk)
|
||||
throws FormatException
|
||||
{
|
||||
if(record.isMapped(key) == false)
|
||||
{
|
||||
throw new FormatException("Field not used but expected: " + key);
|
||||
}
|
||||
|
||||
String value = record.get(key);
|
||||
if(value.isEmpty() && nullIsOk)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Integer.parseInt(record.get(key));
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
throw new FormatException("Failed to parse field: " + key, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a single loyalty card into the database using the given
|
||||
* session.
|
||||
*/
|
||||
private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record)
|
||||
throws IOException, FormatException
|
||||
{
|
||||
int id = extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
|
||||
|
||||
String store = extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
|
||||
if(store.isEmpty())
|
||||
{
|
||||
throw new FormatException("No store listed, but is required");
|
||||
}
|
||||
|
||||
String note = extractString(DBHelper.LoyaltyCardDbIds.NOTE, record, "");
|
||||
|
||||
String cardId = extractString(DBHelper.LoyaltyCardDbIds.CARD_ID, record, "");
|
||||
if(cardId.isEmpty())
|
||||
{
|
||||
throw new FormatException("No card ID listed, but is required");
|
||||
}
|
||||
|
||||
String barcodeType = extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, "");
|
||||
if(barcodeType.isEmpty())
|
||||
{
|
||||
throw new FormatException("No barcode type listed, but is required");
|
||||
}
|
||||
|
||||
Integer headerColor = null;
|
||||
Integer headerTextColor = null;
|
||||
|
||||
if(record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR) &&
|
||||
record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR))
|
||||
{
|
||||
headerColor = extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true);
|
||||
headerTextColor = extractInt(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR, record, true);
|
||||
}
|
||||
|
||||
helper.insertLoyaltyCard(database, id, store, note, cardId, barcodeType, headerColor, headerTextColor);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
{
|
||||
public static final String DATABASE_NAME = "LoyaltyCards.db";
|
||||
public static final int ORIGINAL_DATABASE_VERSION = 1;
|
||||
public static final int DATABASE_VERSION = 2;
|
||||
public static final int DATABASE_VERSION = 3;
|
||||
|
||||
static class LoyaltyCardDbIds
|
||||
{
|
||||
@@ -19,6 +19,8 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
public static final String ID = "_id";
|
||||
public static final String STORE = "store";
|
||||
public static final String NOTE = "note";
|
||||
public static final String HEADER_COLOR = "headercolor";
|
||||
public static final String HEADER_TEXT_COLOR = "headertextcolor";
|
||||
public static final String CARD_ID = "cardid";
|
||||
public static final String BARCODE_TYPE = "barcodetype";
|
||||
}
|
||||
@@ -36,6 +38,8 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
|
||||
LoyaltyCardDbIds.STORE + " TEXT not null," +
|
||||
LoyaltyCardDbIds.NOTE + " TEXT not null," +
|
||||
LoyaltyCardDbIds.HEADER_COLOR + " INTEGER," +
|
||||
LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER," +
|
||||
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
|
||||
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT not null)");
|
||||
}
|
||||
@@ -49,10 +53,20 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||
+ " ADD COLUMN " + LoyaltyCardDbIds.NOTE + " TEXT not null default ''");
|
||||
}
|
||||
|
||||
// Upgrade from version 2 to version 3
|
||||
if(oldVersion < 3 && newVersion >= 3)
|
||||
{
|
||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_COLOR + " INTEGER");
|
||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean insertLoyaltyCard(final String store, final String note, final String cardId,
|
||||
final String barcodeType)
|
||||
public long insertLoyaltyCard(final String store, final String note, final String cardId,
|
||||
final String barcodeType, final Integer headerColor,
|
||||
final Integer headerTextColor)
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
@@ -60,13 +74,33 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, headerTextColor);
|
||||
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
|
||||
return newId;
|
||||
}
|
||||
|
||||
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id,
|
||||
final String store, final String note, final String cardId,
|
||||
final String barcodeType, final Integer headerColor,
|
||||
final Integer headerTextColor)
|
||||
{
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(LoyaltyCardDbIds.ID, id);
|
||||
contentValues.put(LoyaltyCardDbIds.STORE, store);
|
||||
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, headerTextColor);
|
||||
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
|
||||
return (newId != -1);
|
||||
}
|
||||
|
||||
|
||||
public boolean updateLoyaltyCard(final int id, final String store, final String note,
|
||||
final String cardId, final String barcodeType)
|
||||
final String cardId, final String barcodeType,
|
||||
final Integer headerColor, final Integer headerTextColor)
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
@@ -74,6 +108,8 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, headerTextColor);
|
||||
int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,
|
||||
LoyaltyCardDbIds.ID + "=?",
|
||||
new String[]{Integer.toString(id)});
|
||||
@@ -112,7 +148,7 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
{
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor res = db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
|
||||
" ORDER BY " + LoyaltyCardDbIds.STORE, null);
|
||||
" ORDER BY " + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC", null);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
8
app/src/main/java/protect/card_locker/DataFormat.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package protect.card_locker;
|
||||
|
||||
public enum DataFormat
|
||||
{
|
||||
CSV,
|
||||
|
||||
;
|
||||
}
|
||||
17
app/src/main/java/protect/card_locker/DatabaseExporter.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
/**
|
||||
* Interface for a class which can export the contents of the database
|
||||
* in a given format.
|
||||
*/
|
||||
public interface DatabaseExporter
|
||||
{
|
||||
/**
|
||||
* Export the database to the output stream in a given format.
|
||||
* @throws IOException
|
||||
*/
|
||||
void exportData(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException;
|
||||
}
|
||||
19
app/src/main/java/protect/card_locker/DatabaseImporter.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* Interface for a class which can import the contents of a stream
|
||||
* into the database.
|
||||
*/
|
||||
public interface DatabaseImporter
|
||||
{
|
||||
/**
|
||||
* Import data from the input stream in a given format into
|
||||
* the database.
|
||||
* @throws IOException
|
||||
* @throws FormatException
|
||||
*/
|
||||
void importData(DBHelper db, InputStreamReader input) throws IOException, FormatException, InterruptedException;
|
||||
}
|
||||
19
app/src/main/java/protect/card_locker/FormatException.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package protect.card_locker;
|
||||
|
||||
/**
|
||||
* Exception thrown when something unexpected is
|
||||
* encountered with the format of data being
|
||||
* imported or exported.
|
||||
*/
|
||||
public class FormatException extends Exception
|
||||
{
|
||||
public FormatException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FormatException(String message, Exception rootCause)
|
||||
{
|
||||
super(message, rootCause);
|
||||
}
|
||||
}
|
||||
403
app/src/main/java/protect/card_locker/ImportExportActivity.java
Normal file
@@ -0,0 +1,403 @@
|
||||
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 String TAG = "LoyaltyCardLocker";
|
||||
|
||||
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)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.import_export_activity);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
ActivityCompat.requestPermissions(ImportExportActivity.this,
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
PERMISSIONS_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
|
||||
Button exportButton = findViewById(R.id.exportButton);
|
||||
exportButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
startExport();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Check that there is an activity that can bring up a file chooser
|
||||
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
|
||||
|
||||
Button importFilesystem = 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 = 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 = findViewById(R.id.importOptionFixedButton);
|
||||
importButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
Uri uri = Uri.fromFile(exportFile);
|
||||
try
|
||||
{
|
||||
FileInputStream stream = new FileInputStream(exportFile);
|
||||
startImport(stream, uri);
|
||||
}
|
||||
catch(FileNotFoundException e)
|
||||
{
|
||||
Log.e(TAG, "Could not import file " + exportFile.getAbsolutePath(), e);
|
||||
onImportComplete(false, uri);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
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,
|
||||
DataFormat.CSV, exportFile, listener);
|
||||
importExporter.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
|
||||
{
|
||||
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE)
|
||||
{
|
||||
// If request is cancelled, the result arrays are empty.
|
||||
boolean success = grantResults.length > 0;
|
||||
|
||||
for(int grant : grantResults)
|
||||
{
|
||||
if(grant != PackageManager.PERMISSION_GRANTED)
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(success == false)
|
||||
{
|
||||
// External storage permission rejected, inform user that
|
||||
// import/export is prevented
|
||||
Toast.makeText(getApplicationContext(), R.string.noExternalStoragePermissionError,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy()
|
||||
{
|
||||
if(importExporter != null && importExporter.getStatus() != AsyncTask.Status.RUNNING)
|
||||
{
|
||||
importExporter.cancel(true);
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
int id = item.getItemId();
|
||||
|
||||
if(id == android.R.id.home)
|
||||
{
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
154
app/src/main/java/protect/card_locker/ImportExportTask.java
Normal file
@@ -0,0 +1,154 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
{
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 = false;
|
||||
this.format = format;
|
||||
this.target = target;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor which will setup a task for importing from the given InputStream.
|
||||
*/
|
||||
ImportExportTask(Activity activity, DataFormat format, InputStream input,
|
||||
TaskCompleteListener listener)
|
||||
{
|
||||
super();
|
||||
this.activity = activity;
|
||||
this.doImport = true;
|
||||
this.format = format;
|
||||
this.inputStream = input;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private boolean performImport(InputStream stream, DBHelper db)
|
||||
{
|
||||
boolean result = false;
|
||||
|
||||
try
|
||||
{
|
||||
InputStreamReader reader = new InputStreamReader(stream, Charset.forName("UTF-8"));
|
||||
result = MultiFormatImporter.importData(db, reader, format);
|
||||
reader.close();
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Import result: " + result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean performExport(File exportFile, DBHelper db)
|
||||
{
|
||||
boolean result = false;
|
||||
|
||||
try
|
||||
{
|
||||
FileOutputStream fileWriter = new FileOutputStream(exportFile);
|
||||
OutputStreamWriter writer = new OutputStreamWriter(fileWriter, Charset.forName("UTF-8"));
|
||||
result = MultiFormatExporter.exportData(db, writer, format);
|
||||
writer.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.e(TAG, "Unable to export file", e);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Export of '" + exportFile.getAbsolutePath() + "' result: " + result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void onPreExecute()
|
||||
{
|
||||
progress = new ProgressDialog(activity);
|
||||
progress.setTitle(doImport ? R.string.importing : R.string.exporting);
|
||||
|
||||
progress.setOnDismissListener(new DialogInterface.OnDismissListener()
|
||||
{
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog)
|
||||
{
|
||||
ImportExportTask.this.cancel(true);
|
||||
}
|
||||
});
|
||||
|
||||
progress.show();
|
||||
}
|
||||
|
||||
protected Boolean doInBackground(Void... nothing)
|
||||
{
|
||||
final DBHelper db = new DBHelper(activity);
|
||||
boolean result;
|
||||
|
||||
if(doImport)
|
||||
{
|
||||
result = performImport(inputStream, db);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = performExport(target, db);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void onPostExecute(Boolean result)
|
||||
{
|
||||
listener.onTaskComplete(result);
|
||||
|
||||
progress.dismiss();
|
||||
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
|
||||
}
|
||||
|
||||
protected void onCancelled()
|
||||
{
|
||||
progress.dismiss();
|
||||
Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled");
|
||||
}
|
||||
interface TaskCompleteListener
|
||||
{
|
||||
void onTaskComplete(boolean success);
|
||||
}
|
||||
|
||||
}
|
||||
140
app/src/main/java/protect/card_locker/LetterBitmap.java
Normal file
@@ -0,0 +1,140 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextPaint;
|
||||
|
||||
/**
|
||||
* Original from https://github.com/andOTP/andOTP/blob/master/app/src/main/java/org/shadowice/flocke/andotp/Utilities/LetterBitmap.java
|
||||
* which was originally from http://stackoverflow.com/questions/23122088/colored-boxed-with-letters-a-la-gmail
|
||||
* Used to create a {@link Bitmap} that contains a letter used in the English
|
||||
* alphabet or digit, if there is no letter or digit available, a default image
|
||||
* is shown instead.
|
||||
*/
|
||||
class LetterBitmap
|
||||
{
|
||||
|
||||
/**
|
||||
* The number of available tile colors
|
||||
*/
|
||||
private static final int NUM_OF_TILE_COLORS = 8;
|
||||
/**
|
||||
* The letter bitmap
|
||||
*/
|
||||
private final Bitmap mBitmap;
|
||||
/**
|
||||
* The background color of the letter bitmap
|
||||
*/
|
||||
private final Integer mColor;
|
||||
|
||||
/**
|
||||
* Constructor for <code>LetterTileProvider</code>
|
||||
*
|
||||
* @param context The {@link Context} to use
|
||||
* @param displayName The name used to create the letter for the tile
|
||||
* @param key The key used to generate the background color for the tile
|
||||
* @param tileLetterFontSize The font size used to display the letter
|
||||
* @param width The desired width of the tile
|
||||
* @param height The desired height of the tile
|
||||
* @param backgroundColor (optional) color to use for background.
|
||||
* @param textColor (optional) color to use for text.
|
||||
*/
|
||||
public LetterBitmap(Context context, String displayName, String key, int tileLetterFontSize,
|
||||
int width, int height, Integer backgroundColor, Integer textColor)
|
||||
{
|
||||
TextPaint paint = new TextPaint();
|
||||
paint.setTypeface(Typeface.create("sans-serif-light", Typeface.BOLD));
|
||||
|
||||
if(textColor != null)
|
||||
{
|
||||
paint.setColor(textColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
paint.setColor(Color.WHITE);
|
||||
}
|
||||
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
paint.setAntiAlias(true);
|
||||
|
||||
if(backgroundColor == null)
|
||||
{
|
||||
mColor = getDefaultColor(context, key);
|
||||
}
|
||||
else
|
||||
{
|
||||
mColor = backgroundColor;
|
||||
}
|
||||
|
||||
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
String firstChar = displayName.substring(0, 1);
|
||||
|
||||
final Canvas c = new Canvas();
|
||||
c.setBitmap(mBitmap);
|
||||
c.drawColor(mColor);
|
||||
|
||||
char [] firstCharArray = new char[1];
|
||||
firstCharArray[0] = firstChar.toUpperCase().charAt(0);
|
||||
paint.setTextSize(tileLetterFontSize);
|
||||
|
||||
// The bounds that enclose the letter
|
||||
Rect bounds = new Rect();
|
||||
|
||||
paint.getTextBounds(firstCharArray, 0, 1, bounds);
|
||||
c.drawText(firstCharArray, 0, 1, width / 2.0f, height / 2.0f
|
||||
+ (bounds.bottom - bounds.top) / 2.0f, paint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link Bitmap} that contains a letter used in the English
|
||||
* alphabet or digit, if there is no letter or digit available, a
|
||||
* default image is shown instead
|
||||
*/
|
||||
public Bitmap getLetterTile()
|
||||
{
|
||||
return mBitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return background color used for letter title.
|
||||
*/
|
||||
public int getBackgroundColor()
|
||||
{
|
||||
return mColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key The key used to generate the tile color
|
||||
* @return A new or previously chosen color for <code>key</code> used as the
|
||||
* tile background color
|
||||
*/
|
||||
private static int pickColor(String key, TypedArray colors)
|
||||
{
|
||||
// String.hashCode() is not supposed to change across java versions, so
|
||||
// this should guarantee the same key always maps to the same color
|
||||
final int color = Math.abs(key.hashCode()) % NUM_OF_TILE_COLORS;
|
||||
return colors.getColor(color, Color.BLACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the color which the letter tile will use if no default
|
||||
* color is provided.
|
||||
*/
|
||||
public static int getDefaultColor(Context context, String key)
|
||||
{
|
||||
final Resources res = context.getResources();
|
||||
|
||||
TypedArray colors = res.obtainTypedArray(R.array.letter_tile_colors);
|
||||
int color = pickColor(key, colors);
|
||||
colors.recycle();
|
||||
|
||||
return color;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
public class LoyaltyCard
|
||||
{
|
||||
@@ -10,13 +11,22 @@ public class LoyaltyCard
|
||||
public final String cardId;
|
||||
public final String barcodeType;
|
||||
|
||||
public LoyaltyCard(final int id, final String store, final String note, final String cardId, final String barcodeType)
|
||||
@Nullable
|
||||
public final Integer headerColor;
|
||||
|
||||
@Nullable
|
||||
public final Integer headerTextColor;
|
||||
|
||||
public LoyaltyCard(final int id, final String store, final String note, final String cardId,
|
||||
final String barcodeType, final Integer headerColor, final Integer headerTextColor)
|
||||
{
|
||||
this.id = id;
|
||||
this.store = store;
|
||||
this.note = note;
|
||||
this.cardId = cardId;
|
||||
this.barcodeType = barcodeType;
|
||||
this.headerColor = headerColor;
|
||||
this.headerTextColor = headerTextColor;
|
||||
}
|
||||
|
||||
public static LoyaltyCard toLoyaltyCard(Cursor cursor)
|
||||
@@ -27,6 +37,22 @@ public class LoyaltyCard
|
||||
String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID));
|
||||
String barcodeType = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE));
|
||||
|
||||
return new LoyaltyCard(id, store, note, cardId, barcodeType);
|
||||
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
|
||||
int headerTextColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR);
|
||||
|
||||
Integer headerColor = null;
|
||||
Integer headerTextColor = null;
|
||||
|
||||
if(cursor.isNull(headerColorColumn) == false)
|
||||
{
|
||||
headerColor = cursor.getInt(headerColorColumn);
|
||||
}
|
||||
|
||||
if(cursor.isNull(headerTextColorColumn) == false)
|
||||
{
|
||||
headerTextColor = cursor.getInt(headerTextColorColumn);
|
||||
}
|
||||
|
||||
return new LoyaltyCard(id, store, note, cardId, barcodeType, headerColor, headerTextColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,19 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
class LoyaltyCardCursorAdapter extends CursorAdapter
|
||||
{
|
||||
Settings settings;
|
||||
|
||||
public LoyaltyCardCursorAdapter(Context context, Cursor cursor)
|
||||
{
|
||||
super(context, cursor, 0);
|
||||
settings = new Settings(context);
|
||||
}
|
||||
|
||||
// The newView method is used to inflate a new view and return it,
|
||||
@@ -29,25 +35,36 @@ class LoyaltyCardCursorAdapter extends CursorAdapter
|
||||
public void bindView(View view, Context context, Cursor cursor)
|
||||
{
|
||||
// Find fields to populate in inflated template
|
||||
ImageView thumbnail = view.findViewById(R.id.thumbnail);
|
||||
TextView storeField = (TextView) view.findViewById(R.id.store);
|
||||
TextView cardIdField = (TextView) view.findViewById(R.id.cardId);
|
||||
TextView noteField = (TextView) view.findViewById(R.id.note);
|
||||
|
||||
// Extract properties from cursor
|
||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(cursor);
|
||||
|
||||
// Populate fields with extracted properties
|
||||
String storeAndNote = loyaltyCard.store;
|
||||
storeField.setText(loyaltyCard.store);
|
||||
|
||||
storeField.setTextSize(settings.getCardTitleListFontSize());
|
||||
|
||||
if(loyaltyCard.note.isEmpty() == false)
|
||||
{
|
||||
String storeNameAndNoteFormat = view.getResources().getString(R.string.storeNameAndNoteFormat);
|
||||
storeAndNote = String.format(storeNameAndNoteFormat, loyaltyCard.store, loyaltyCard.note);
|
||||
noteField.setVisibility(View.VISIBLE);
|
||||
noteField.setText(loyaltyCard.note);
|
||||
noteField.setTextSize(settings.getCardNoteListFontSize());
|
||||
}
|
||||
else
|
||||
{
|
||||
noteField.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
storeField.setText(storeAndNote);
|
||||
int tileLetterFontSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterFontSize);
|
||||
int pixelSize = context.getResources().getDimensionPixelSize(R.dimen.cardThumbnailSize);
|
||||
|
||||
String cardIdFormat = view.getResources().getString(R.string.cardIdFormat);
|
||||
String cardIdLabel = view.getResources().getString(R.string.cardId);
|
||||
String cardIdText = String.format(cardIdFormat, cardIdLabel, loyaltyCard.cardId);
|
||||
cardIdField.setText(cardIdText);
|
||||
Integer letterBackgroundColor = loyaltyCard.headerColor;
|
||||
Integer letterTextColor = loyaltyCard.headerTextColor;
|
||||
LetterBitmap letterBitmap = new LetterBitmap(context, loyaltyCard.store, loyaltyCard.store,
|
||||
tileLetterFontSize, pixelSize, pixelSize, letterBackgroundColor, letterTextColor);
|
||||
thumbnail.setImageBitmap(letterBitmap.getLetterTile());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,474 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.Button;
|
||||
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;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
|
||||
|
||||
public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "CardLocker";
|
||||
|
||||
private static final int SELECT_BARCODE_REQUEST = 1;
|
||||
|
||||
EditText storeFieldEdit;
|
||||
EditText noteFieldEdit;
|
||||
ImageView headingColorSample;
|
||||
Button headingColorSelectButton;
|
||||
ImageView headingStoreTextColorSample;
|
||||
Button headingStoreTextColorSelectButton;
|
||||
TextView cardIdFieldView;
|
||||
View cardIdDivider;
|
||||
View cardIdTableRow;
|
||||
TextView barcodeTypeField;
|
||||
ImageView barcodeImage;
|
||||
View barcodeImageLayout;
|
||||
View barcodeCaptureLayout;
|
||||
|
||||
Button captureButton;
|
||||
Button enterButton;
|
||||
|
||||
int loyaltyCardId;
|
||||
boolean updateLoyaltyCard;
|
||||
Integer headingColorValue = null;
|
||||
Integer headingStoreTextColorValue = null;
|
||||
|
||||
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);
|
||||
|
||||
Log.d(TAG, "View activity: id=" + loyaltyCardId
|
||||
+ ", updateLoyaltyCard=" + Boolean.toString(updateLoyaltyCard));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.loyalty_card_edit_activity);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
extractIntentFields(getIntent());
|
||||
|
||||
db = new DBHelper(this);
|
||||
|
||||
storeFieldEdit = findViewById(R.id.storeNameEdit);
|
||||
noteFieldEdit = findViewById(R.id.noteEdit);
|
||||
headingColorSample = findViewById(R.id.headingColorSample);
|
||||
headingColorSelectButton = findViewById(R.id.headingColorSelectButton);
|
||||
headingStoreTextColorSample = findViewById(R.id.headingStoreTextColorSample);
|
||||
headingStoreTextColorSelectButton = findViewById(R.id.headingStoreTextColorSelectButton);
|
||||
cardIdFieldView = findViewById(R.id.cardIdView);
|
||||
cardIdDivider = findViewById(R.id.cardIdDivider);
|
||||
cardIdTableRow = findViewById(R.id.cardIdTableRow);
|
||||
barcodeTypeField = findViewById(R.id.barcodeType);
|
||||
barcodeImage = findViewById(R.id.barcode);
|
||||
barcodeImageLayout = findViewById(R.id.barcodeLayout);
|
||||
barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
|
||||
|
||||
captureButton = findViewById(R.id.captureButton);
|
||||
enterButton = 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
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
Log.i(TAG, "To view card: " + loyaltyCardId);
|
||||
|
||||
if(updateLoyaltyCard)
|
||||
{
|
||||
final LoyaltyCard loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
|
||||
if(loyaltyCard == null)
|
||||
{
|
||||
Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId);
|
||||
Toast.makeText(this, R.string.noCardExistsError, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if(storeFieldEdit.getText().length() == 0)
|
||||
{
|
||||
storeFieldEdit.setText(loyaltyCard.store);
|
||||
}
|
||||
|
||||
if(noteFieldEdit.getText().length() == 0)
|
||||
{
|
||||
noteFieldEdit.setText(loyaltyCard.note);
|
||||
}
|
||||
|
||||
if(cardIdFieldView.getText().length() == 0)
|
||||
{
|
||||
cardIdFieldView.setText(loyaltyCard.cardId);
|
||||
}
|
||||
|
||||
if(barcodeTypeField.getText().length() == 0)
|
||||
{
|
||||
barcodeTypeField.setText(loyaltyCard.barcodeType);
|
||||
}
|
||||
|
||||
if(headingColorValue == null)
|
||||
{
|
||||
headingColorValue = loyaltyCard.headerColor;
|
||||
if(headingColorValue == null)
|
||||
{
|
||||
headingColorValue = LetterBitmap.getDefaultColor(this, loyaltyCard.store);
|
||||
}
|
||||
headingColorSample.setBackgroundColor(headingColorValue);
|
||||
}
|
||||
|
||||
if(headingStoreTextColorValue == null)
|
||||
{
|
||||
headingStoreTextColorValue = loyaltyCard.headerTextColor;
|
||||
if(headingStoreTextColorValue == null)
|
||||
{
|
||||
headingStoreTextColorValue = Color.WHITE;
|
||||
}
|
||||
headingStoreTextColorSample.setBackgroundColor(headingStoreTextColorValue);
|
||||
}
|
||||
|
||||
setTitle(R.string.editCardTitle);
|
||||
}
|
||||
else
|
||||
{
|
||||
setTitle(R.string.addCardTitle);
|
||||
}
|
||||
|
||||
if(headingColorValue == null)
|
||||
{
|
||||
// Select a random color to start out with.
|
||||
TypedArray colors = getResources().obtainTypedArray(R.array.letter_tile_colors);
|
||||
final int color = (int)(Math.random() * colors.length());
|
||||
headingColorValue = colors.getColor(color, Color.BLACK);
|
||||
colors.recycle();
|
||||
|
||||
headingColorSample.setBackgroundColor(headingColorValue);
|
||||
}
|
||||
|
||||
if(headingStoreTextColorValue == null)
|
||||
{
|
||||
headingStoreTextColorValue = Color.WHITE;
|
||||
headingStoreTextColorSample.setBackgroundColor(headingStoreTextColorValue);
|
||||
}
|
||||
|
||||
headingColorSelectButton.setOnClickListener(new ColorSelectListener(headingColorValue, true));
|
||||
headingStoreTextColorSelectButton.setOnClickListener(new ColorSelectListener(headingStoreTextColorValue, false));
|
||||
|
||||
if(cardIdFieldView.getText().length() > 0 && barcodeTypeField.getText().length() > 0)
|
||||
{
|
||||
String formatString = barcodeTypeField.getText().toString();
|
||||
final BarcodeFormat format = BarcodeFormat.valueOf(formatString);
|
||||
final String cardIdString = cardIdFieldView.getText().toString();
|
||||
|
||||
if(barcodeImage.getHeight() == 0)
|
||||
{
|
||||
Log.d(TAG, "ImageView size is not known known at start, waiting for load");
|
||||
// The size of the ImageView is not yet available as it has not
|
||||
// yet been drawn. Wait for it to be drawn so the size is available.
|
||||
barcodeImage.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener()
|
||||
{
|
||||
@Override
|
||||
public void onGlobalLayout()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT < 16)
|
||||
{
|
||||
barcodeImage.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
|
||||
Log.d(TAG, "ImageView size now known");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.d(TAG, "ImageView size known known, creating barcode");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
|
||||
}
|
||||
|
||||
barcodeImageLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
View.OnClickListener captureCallback = new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
IntentIntegrator integrator = new IntentIntegrator(LoyaltyCardEditActivity.this);
|
||||
integrator.setDesiredBarcodeFormats(BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES);
|
||||
|
||||
String prompt = getResources().getString(R.string.scanCardBarcode);
|
||||
integrator.setPrompt(prompt);
|
||||
integrator.setBeepEnabled(false);
|
||||
integrator.initiateScan();
|
||||
}
|
||||
};
|
||||
|
||||
captureButton.setOnClickListener(captureCallback);
|
||||
|
||||
enterButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
if(cardIdFieldView.getText().length() > 0)
|
||||
{
|
||||
cardIdDivider.setVisibility(View.VISIBLE);
|
||||
cardIdTableRow.setVisibility(View.VISIBLE);
|
||||
enterButton.setText(R.string.editCard);
|
||||
}
|
||||
else
|
||||
{
|
||||
cardIdDivider.setVisibility(View.GONE);
|
||||
cardIdTableRow.setVisibility(View.GONE);
|
||||
enterButton.setText(R.string.enterCard);
|
||||
}
|
||||
}
|
||||
|
||||
class ColorSelectListener implements View.OnClickListener
|
||||
{
|
||||
final int defaultColor;
|
||||
final boolean isBackgroundColor;
|
||||
|
||||
ColorSelectListener(int defaultColor, boolean isBackgroundColor)
|
||||
{
|
||||
this.defaultColor = defaultColor;
|
||||
this.isBackgroundColor = isBackgroundColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
ColorPickerDialog dialog = ColorPickerDialog.newBuilder().setColor(defaultColor).create();
|
||||
dialog.setColorPickerDialogListener(new ColorPickerDialogListener()
|
||||
{
|
||||
@Override
|
||||
public void onColorSelected(int dialogId, int color)
|
||||
{
|
||||
if(isBackgroundColor)
|
||||
{
|
||||
headingColorSample.setBackgroundColor(color);
|
||||
headingColorValue = color;
|
||||
}
|
||||
else
|
||||
{
|
||||
headingStoreTextColorSample.setBackgroundColor(color);
|
||||
headingStoreTextColorValue = color;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialogDismissed(int dialogId)
|
||||
{
|
||||
// Nothing to do, no change made
|
||||
}
|
||||
});
|
||||
dialog.show(getFragmentManager(), "color-picker-dialog");
|
||||
}
|
||||
}
|
||||
|
||||
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, headingColorValue, headingStoreTextColorValue);
|
||||
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
|
||||
}
|
||||
else
|
||||
{
|
||||
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, cardId, barcodeType, headingColorValue, headingStoreTextColorValue);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
if(updateLoyaltyCard)
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.card_update_menu, menu);
|
||||
}
|
||||
else
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.card_add_menu, menu);
|
||||
}
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
int id = item.getItemId();
|
||||
|
||||
switch(id)
|
||||
{
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
break;
|
||||
|
||||
case R.id.action_delete:
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.deleteTitle);
|
||||
builder.setMessage(R.string.deleteConfirmation);
|
||||
builder.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
Log.e(TAG, "Deleting card: " + loyaltyCardId);
|
||||
|
||||
DBHelper db = new DBHelper(LoyaltyCardEditActivity.this);
|
||||
db.deleteLoyaltyCard(loyaltyCardId);
|
||||
|
||||
ShortcutHelper.removeShortcut(LoyaltyCardEditActivity.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();
|
||||
|
||||
return true;
|
||||
|
||||
case R.id.action_save:
|
||||
doSave();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent)
|
||||
{
|
||||
String contents = null;
|
||||
String format = null;
|
||||
|
||||
IntentResult result =
|
||||
IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
||||
if (result != null)
|
||||
{
|
||||
Log.i(TAG, "Received barcode information from capture");
|
||||
contents = result.getContents();
|
||||
format = result.getFormatName();
|
||||
}
|
||||
|
||||
if(requestCode == SELECT_BARCODE_REQUEST && resultCode == Activity.RESULT_OK)
|
||||
{
|
||||
Log.i(TAG, "Received barcode information from typing it");
|
||||
|
||||
contents = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_CONTENTS);
|
||||
format = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_FORMAT);
|
||||
}
|
||||
|
||||
if(contents != null && contents.isEmpty() == false &&
|
||||
format != null && format.isEmpty() == false)
|
||||
{
|
||||
Log.i(TAG, "Read barcode id: " + contents);
|
||||
Log.i(TAG, "Read format: " + format);
|
||||
|
||||
TextView cardIdView = findViewById(R.id.cardIdView);
|
||||
cardIdView.setText(contents);
|
||||
|
||||
final TextView barcodeTypeField = findViewById(R.id.barcodeType);
|
||||
barcodeTypeField.setText(format);
|
||||
onResume();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,66 +2,89 @@ package protect.card_locker;
|
||||
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.widget.TextViewCompat;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
|
||||
public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "CardLocker";
|
||||
|
||||
// These are all the barcode types that the zxing library
|
||||
// is able to generate a barcode for, and thus should be
|
||||
// the only barcodes which we should attempt to scan.
|
||||
Collection<String> supportedBarcodeTypes = Collections.unmodifiableList(Arrays.asList(
|
||||
BarcodeFormat.AZTEC.name(),
|
||||
BarcodeFormat.CODE_39.name(),
|
||||
BarcodeFormat.CODE_128.name(),
|
||||
BarcodeFormat.CODABAR.name(),
|
||||
BarcodeFormat.DATA_MATRIX.name(),
|
||||
BarcodeFormat.EAN_8.name(),
|
||||
BarcodeFormat.EAN_13.name(),
|
||||
BarcodeFormat.ITF.name(),
|
||||
BarcodeFormat.PDF_417.name(),
|
||||
BarcodeFormat.QR_CODE.name(),
|
||||
BarcodeFormat.UPC_A.name()
|
||||
));
|
||||
TextView cardIdFieldView;
|
||||
TextView noteView;
|
||||
View noteViewDivider;
|
||||
TextView storeName;
|
||||
ImageView barcodeImage;
|
||||
View collapsingToolbarLayout;
|
||||
int loyaltyCardId;
|
||||
boolean rotationEnabled;
|
||||
DBHelper db;
|
||||
Settings settings;
|
||||
|
||||
private void extractIntentFields(Intent intent)
|
||||
{
|
||||
final Bundle b = intent.getExtras();
|
||||
loyaltyCardId = b != null ? b.getInt("id") : 0;
|
||||
Log.d(TAG, "View activity: id=" + loyaltyCardId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.loyalty_card_view_activity);
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
settings = new Settings(this);
|
||||
|
||||
extractIntentFields(getIntent());
|
||||
|
||||
setContentView(R.layout.loyalty_card_view_layout);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
db = new DBHelper(this);
|
||||
|
||||
cardIdFieldView = findViewById(R.id.cardIdView);
|
||||
noteView = findViewById(R.id.noteView);
|
||||
noteViewDivider = findViewById(R.id.noteViewDivider);
|
||||
storeName = findViewById(R.id.storeName);
|
||||
barcodeImage = findViewById(R.id.barcode);
|
||||
collapsingToolbarLayout = findViewById(R.id.collapsingToolbarLayout);
|
||||
|
||||
rotationEnabled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent)
|
||||
{
|
||||
Log.i(TAG, "Received new intent");
|
||||
extractIntentFields(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,213 +92,122 @@ 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 saveButton = (Button) findViewById(R.id.saveButton);
|
||||
final Button cancelButton = (Button) findViewById(R.id.cancelButton);
|
||||
|
||||
final DBHelper db = new DBHelper(this);
|
||||
|
||||
if(updateLoyaltyCard || 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 && settings.useMaxBrightnessDisplayingBarcode())
|
||||
{
|
||||
final LoyaltyCard loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
|
||||
WindowManager.LayoutParams attributes = window.getAttributes();
|
||||
attributes.screenBrightness = 1F;
|
||||
window.setAttributes(attributes);
|
||||
}
|
||||
|
||||
if(storeField.getText().length() == 0)
|
||||
{
|
||||
storeField.setText(loyaltyCard.store);
|
||||
}
|
||||
final LoyaltyCard loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
|
||||
if(loyaltyCard == null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
noteField.setText(loyaltyCard.note);
|
||||
}
|
||||
String formatString = loyaltyCard.barcodeType;
|
||||
final BarcodeFormat format = BarcodeFormat.valueOf(formatString);
|
||||
final String cardIdString = loyaltyCard.cardId;
|
||||
|
||||
if(cardIdField.getText().length() == 0)
|
||||
{
|
||||
cardIdField.setText(loyaltyCard.cardId);
|
||||
}
|
||||
cardIdFieldView.setText(loyaltyCard.cardId);
|
||||
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(cardIdFieldView,
|
||||
getResources().getInteger(R.integer.settings_card_id_min_font_size_sp)-1, settings.getCardIdFontSize(),
|
||||
1, TypedValue.COMPLEX_UNIT_SP);
|
||||
|
||||
if(barcodeTypeField.getText().length() == 0)
|
||||
{
|
||||
barcodeTypeField.setText(loyaltyCard.barcodeType);
|
||||
}
|
||||
|
||||
if(viewLoyaltyCard)
|
||||
{
|
||||
storeField.setEnabled(false);
|
||||
noteField.setEnabled(false);
|
||||
}
|
||||
|
||||
if(updateLoyaltyCard)
|
||||
{
|
||||
setTitle(R.string.editCardTitle);
|
||||
}
|
||||
else
|
||||
{
|
||||
barcodeCaptureLayout.setVisibility(View.GONE);
|
||||
captureButton.setVisibility(View.GONE);
|
||||
saveButton.setVisibility(View.GONE);
|
||||
cancelButton.setVisibility(View.GONE);
|
||||
setTitle(R.string.viewCardTitle);
|
||||
}
|
||||
if(loyaltyCard.note.length() > 0)
|
||||
{
|
||||
noteView.setText(loyaltyCard.note);
|
||||
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(noteView,
|
||||
getResources().getInteger(R.integer.settings_card_note_min_font_size_sp)-1,
|
||||
settings.getCardNoteFontSize(), 1, TypedValue.COMPLEX_UNIT_SP);
|
||||
}
|
||||
else
|
||||
{
|
||||
setTitle(R.string.addCardTitle);
|
||||
noteView.setVisibility(View.GONE);
|
||||
noteViewDivider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if(cardIdField.getText().length() == 0)
|
||||
storeName.setText(loyaltyCard.store);
|
||||
storeName.setTextSize(settings.getCardTitleFontSize());
|
||||
|
||||
int textColor;
|
||||
if(loyaltyCard.headerTextColor != null)
|
||||
{
|
||||
barcodeIdLayout.setVisibility(View.GONE);
|
||||
textColor = loyaltyCard.headerTextColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
textColor = Color.WHITE;
|
||||
}
|
||||
storeName.setTextColor(textColor);
|
||||
|
||||
int backgroundHeaderColor;
|
||||
if(loyaltyCard.headerColor != null)
|
||||
{
|
||||
backgroundHeaderColor = loyaltyCard.headerColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
backgroundHeaderColor = LetterBitmap.getDefaultColor(this, loyaltyCard.store);
|
||||
}
|
||||
|
||||
barcodeTypeLayout.setVisibility(View.GONE);
|
||||
collapsingToolbarLayout.setBackgroundColor(backgroundHeaderColor);
|
||||
|
||||
if(cardIdField.getText().length() > 0 && barcodeTypeField.getText().length() > 0)
|
||||
if(barcodeImage.getHeight() == 0)
|
||||
{
|
||||
MultiFormatWriter writer = new MultiFormatWriter();
|
||||
BitMatrix result;
|
||||
try
|
||||
{
|
||||
String formatString = barcodeTypeField.getText().toString();
|
||||
BarcodeFormat format = BarcodeFormat.valueOf(formatString);
|
||||
if(format == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Unrecognized barcode format: " + formatString);
|
||||
}
|
||||
|
||||
int generateWidth = 100;
|
||||
int generateHeight = 100;
|
||||
|
||||
String cardIdString = cardIdField.getText().toString();
|
||||
|
||||
Log.i(TAG, "Card: " + cardIdString);
|
||||
|
||||
result = writer.encode(cardIdString, format, generateWidth, generateHeight, null);
|
||||
|
||||
final int WHITE = 0xFFFFFFFF;
|
||||
final int BLACK = 0xFF000000;
|
||||
|
||||
int width = result.getWidth();
|
||||
int height = result.getHeight();
|
||||
int[] pixels = new int[width * height];
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
int offset = y * width;
|
||||
for (int x = 0; x < width; x++)
|
||||
Log.d(TAG, "ImageView size is not known known at start, waiting for load");
|
||||
// The size of the ImageView is not yet available as it has not
|
||||
// yet been drawn. Wait for it to be drawn so the size is available.
|
||||
barcodeImage.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener()
|
||||
{
|
||||
pixels[offset + x] = result.get(x, y) ? BLACK : WHITE;
|
||||
}
|
||||
}
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height,
|
||||
Bitmap.Config.ARGB_8888);
|
||||
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
|
||||
barcodeImage.setImageBitmap(bitmap);
|
||||
@Override
|
||||
public void onGlobalLayout()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT < 16)
|
||||
{
|
||||
barcodeImage.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
|
||||
barcodeIdLayout.setVisibility(View.VISIBLE);
|
||||
barcodeImageLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
catch (WriterException | IllegalArgumentException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to generate barcode", e);
|
||||
}
|
||||
Log.d(TAG, "ImageView size now known");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.d(TAG, "ImageView size known known, creating barcode");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
|
||||
}
|
||||
|
||||
View.OnClickListener captureCallback = new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
IntentIntegrator integrator = new IntentIntegrator(LoyaltyCardViewActivity.this);
|
||||
integrator.setDesiredBarcodeFormats(supportedBarcodeTypes);
|
||||
|
||||
String prompt = getResources().getString(R.string.scanCardBarcode);
|
||||
integrator.setPrompt(prompt);
|
||||
integrator.initiateScan();
|
||||
}
|
||||
};
|
||||
|
||||
captureButton.setOnClickListener(captureCallback);
|
||||
|
||||
saveButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(final View v)
|
||||
{
|
||||
String store = storeField.getText().toString();
|
||||
String 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()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
final Bundle b = getIntent().getExtras();
|
||||
final boolean updateLoyaltyCard = b != null && b.getBoolean("update", false);
|
||||
final boolean viewLoyaltyCard = b != null && b.getBoolean("view", false);
|
||||
getMenuInflater().inflate(R.menu.card_view_menu, menu);
|
||||
|
||||
if(viewLoyaltyCard)
|
||||
if(settings.getLockBarcodeScreenOrientation())
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.card_edit_menu, menu);
|
||||
}
|
||||
else if(updateLoyaltyCard)
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.card_delete_menu, menu);
|
||||
MenuItem item = menu.findItem(R.id.action_lock_unlock);
|
||||
setOrientatonLock(item, true);
|
||||
item.setVisible(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -283,20 +215,14 @@ 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 R.id.action_delete:
|
||||
Log.e(TAG, "Deleting card: " + loyaltyCardId);
|
||||
|
||||
DBHelper db = new DBHelper(this);
|
||||
db.deleteLoyaltyCard(loyaltyCardId);
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
break;
|
||||
|
||||
case R.id.action_edit:
|
||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardViewActivity.class);
|
||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt("id", loyaltyCardId);
|
||||
bundle.putBoolean("update", true);
|
||||
@@ -304,32 +230,36 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
startActivity(intent);
|
||||
finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_lock_unlock:
|
||||
if(rotationEnabled)
|
||||
{
|
||||
setOrientatonLock(item, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
setOrientatonLock(item, false);
|
||||
}
|
||||
rotationEnabled = !rotationEnabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent)
|
||||
private void setOrientatonLock(MenuItem item, boolean lock)
|
||||
{
|
||||
IntentResult result =
|
||||
IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
||||
if (result != null)
|
||||
if(lock)
|
||||
{
|
||||
String contents = result.getContents();
|
||||
String format = result.getFormatName();
|
||||
if(contents != null && contents.isEmpty() == false &&
|
||||
format != null && format.isEmpty() == false)
|
||||
{
|
||||
Log.i(TAG, "Read Contents from scan: " + contents);
|
||||
Log.i(TAG, "Read Format: " + format);
|
||||
|
||||
final EditText cardIdField = (EditText) findViewById(R.id.cardId);
|
||||
cardIdField.setText(contents);
|
||||
final EditText barcodeTypeField = (EditText) findViewById(R.id.barcodeType);
|
||||
barcodeTypeField.setText(format);
|
||||
onResume();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,56 @@
|
||||
package protect.card_locker;
|
||||
|
||||
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;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Map;
|
||||
|
||||
import protect.card_locker.intro.IntroActivity;
|
||||
import protect.card_locker.preferences.SettingsActivity;
|
||||
|
||||
public class MainActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main_activity);
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
updateLoyaltyCardList();
|
||||
|
||||
SharedPreferences prefs = getSharedPreferences("protect.card_locker", MODE_PRIVATE);
|
||||
if (prefs.getBoolean("firstrun", true)) {
|
||||
startIntro();
|
||||
prefs.edit().putBoolean("firstrun", false).commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,8 +63,8 @@ public class MainActivity extends AppCompatActivity
|
||||
|
||||
private void updateLoyaltyCardList()
|
||||
{
|
||||
final ListView cardList = (ListView) findViewById(R.id.list);
|
||||
final TextView helpText = (TextView) findViewById(R.id.helpText);
|
||||
final ListView cardList = findViewById(R.id.list);
|
||||
final TextView helpText = findViewById(R.id.helpText);
|
||||
final DBHelper db = new DBHelper(this);
|
||||
|
||||
if(db.getLoyaltyCardCount() > 0)
|
||||
@@ -55,6 +83,8 @@ public class MainActivity extends AppCompatActivity
|
||||
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
|
||||
cardList.setAdapter(adapter);
|
||||
|
||||
registerForContextMenu(cardList);
|
||||
|
||||
cardList.setOnItemClickListener(new AdapterView.OnItemClickListener()
|
||||
{
|
||||
@Override
|
||||
@@ -64,15 +94,51 @@ 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)
|
||||
{
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
if (v.getId()==R.id.list)
|
||||
{
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.card_longclick_menu, menu);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item)
|
||||
{
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
|
||||
ListView listView = findViewById(R.id.list);
|
||||
|
||||
Cursor cardCursor = (Cursor)listView.getItemAtPosition(info.position);
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
|
||||
|
||||
if(card != null && item.getItemId() == R.id.action_clipboard)
|
||||
{
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(card.store, card.cardId);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
|
||||
Toast.makeText(this, R.string.copy_to_clipboard_toast, Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
@@ -87,11 +153,129 @@ public class MainActivity extends AppCompatActivity
|
||||
|
||||
if (id == R.id.action_add)
|
||||
{
|
||||
Intent i = new Intent(getApplicationContext(), LoyaltyCardViewActivity.class);
|
||||
Intent i = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(id == R.id.action_import_export)
|
||||
{
|
||||
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(id == R.id.action_settings)
|
||||
{
|
||||
Intent i = new Intent(getApplicationContext(), SettingsActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(id == R.id.action_intro)
|
||||
{
|
||||
startIntro();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(id == R.id.action_about)
|
||||
{
|
||||
displayAboutDialog();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void displayAboutDialog()
|
||||
{
|
||||
final Map<String, String> USED_LIBRARIES = new ImmutableMap.Builder<String, String>()
|
||||
.put("Commons CSV", "https://commons.apache.org/proper/commons-csv/")
|
||||
.put("Guava", "https://github.com/google/guava")
|
||||
.put("ZXing", "https://github.com/zxing/zxing")
|
||||
.put("ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded")
|
||||
.put("AppIntro", "https://github.com/apl-devs/AppIntro")
|
||||
.put("Color Picker", "https://github.com/jaredrummler/ColorPicker")
|
||||
.put("VNTNumberPickerPreference", "https://github.com/vanniktech/VNTNumberPickerPreference")
|
||||
.build();
|
||||
|
||||
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 (Map.Entry<String, String> entry : USED_LIBRARIES.entrySet())
|
||||
{
|
||||
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);
|
||||
|
||||
String version = "?";
|
||||
try
|
||||
{
|
||||
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||
version = pi.versionName;
|
||||
}
|
||||
catch (PackageManager.NameNotFoundException e)
|
||||
{
|
||||
Log.w(TAG, "Package name not found", e);
|
||||
}
|
||||
|
||||
WebView wv = new WebView(this);
|
||||
String html =
|
||||
"<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />" +
|
||||
"<img src=\"file:///android_res/mipmap/ic_launcher.png\" alt=\"" + appName + "\"/>" +
|
||||
"<h1>" +
|
||||
String.format(getString(R.string.about_title_fmt),
|
||||
"<a href=\"" + getString(R.string.app_webpage_url)) + "\">" +
|
||||
appName +
|
||||
"</a>" +
|
||||
"</h1><p>" +
|
||||
appName +
|
||||
" " +
|
||||
String.format(getString(R.string.debug_version_fmt), version) +
|
||||
"</p><p>" +
|
||||
String.format(getString(R.string.app_revision_fmt),
|
||||
"<a href=\"" + getString(R.string.app_revision_url) + "\">" +
|
||||
getString(R.string.app_revision_url) +
|
||||
"</a>") +
|
||||
"</p><hr/><p>" +
|
||||
String.format(getString(R.string.app_copyright_fmt), year) +
|
||||
"</p><hr/><p>" +
|
||||
getString(R.string.app_license) +
|
||||
"</p><hr/><p>" +
|
||||
String.format(getString(R.string.app_libraries), appName, libs.toString()) +
|
||||
"</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)
|
||||
.setView(wv)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void startIntro()
|
||||
{
|
||||
Intent intent = new Intent(this, IntroActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
public class MultiFormatExporter
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
|
||||
/**
|
||||
* Attempts to export data to the output stream in the
|
||||
* given format, if possible.
|
||||
*
|
||||
* The output stream is closed on success.
|
||||
*
|
||||
* @return true if the database was successfully exported,
|
||||
* false otherwise. If false, partial data may have been
|
||||
* written to the output stream, and it should be discarded.
|
||||
*/
|
||||
public static boolean exportData(DBHelper db, OutputStreamWriter output, DataFormat format)
|
||||
{
|
||||
DatabaseExporter exporter = null;
|
||||
|
||||
switch(format)
|
||||
{
|
||||
case CSV:
|
||||
exporter = new CsvDatabaseExporter();
|
||||
break;
|
||||
}
|
||||
|
||||
if(exporter != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
exporter.exportData(db, output);
|
||||
return true;
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to export data", e);
|
||||
}
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to export data", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.e(TAG, "Unsupported data format exported: " + format.name());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class MultiFormatImporter
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
|
||||
/**
|
||||
* Attempts to import data from the input stream of the
|
||||
* given format into the database.
|
||||
*
|
||||
* The input stream is not closed, and doing so is the
|
||||
* responsibility of the caller.
|
||||
*
|
||||
* @return true if the database was successfully imported,
|
||||
* false otherwise. If false, no data was written to
|
||||
* the database.
|
||||
*/
|
||||
public static boolean importData(DBHelper db, InputStreamReader input, DataFormat format)
|
||||
{
|
||||
DatabaseImporter importer = null;
|
||||
|
||||
switch(format)
|
||||
{
|
||||
case CSV:
|
||||
importer = new CsvDatabaseImporter();
|
||||
break;
|
||||
}
|
||||
|
||||
if(importer != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
importer.importData(db, input);
|
||||
return true;
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to input data", e);
|
||||
}
|
||||
catch(FormatException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to input data", e);
|
||||
}
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to input data", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.e(TAG, "Unsupported data format imported: " + format.name());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
153
app/src/main/java/protect/card_locker/ShortcutHelper.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
19
app/src/main/java/protect/card_locker/intro/IntroSlide1.java
Normal 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;
|
||||
}
|
||||
}
|
||||
20
app/src/main/java/protect/card_locker/intro/IntroSlide2.java
Normal 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;
|
||||
}
|
||||
}
|
||||
19
app/src/main/java/protect/card_locker/intro/IntroSlide3.java
Normal 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;
|
||||
}
|
||||
}
|
||||
19
app/src/main/java/protect/card_locker/intro/IntroSlide4.java
Normal 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;
|
||||
}
|
||||
}
|
||||
19
app/src/main/java/protect/card_locker/intro/IntroSlide5.java
Normal 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;
|
||||
}
|
||||
}
|
||||
19
app/src/main/java/protect/card_locker/intro/IntroSlide6.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package protect.card_locker.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.IntegerRes;
|
||||
import android.support.annotation.StringRes;
|
||||
|
||||
import protect.card_locker.R;
|
||||
|
||||
public class Settings
|
||||
{
|
||||
private Context context;
|
||||
private SharedPreferences settings;
|
||||
|
||||
public Settings(Context context)
|
||||
{
|
||||
this.context = context;
|
||||
this.settings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
private String getResString(@StringRes int resId)
|
||||
{
|
||||
return context.getString(resId);
|
||||
}
|
||||
|
||||
private int getResInt(@IntegerRes int resId)
|
||||
{
|
||||
return context.getResources().getInteger(resId);
|
||||
}
|
||||
|
||||
private int getInt(@StringRes int keyId, @IntegerRes int defaultId)
|
||||
{
|
||||
return settings.getInt(getResString(keyId), getResInt(defaultId));
|
||||
}
|
||||
|
||||
private boolean getBoolean(@StringRes int keyId, boolean defaultValue)
|
||||
{
|
||||
return settings.getBoolean(getResString(keyId), defaultValue);
|
||||
}
|
||||
|
||||
public int getCardTitleListFontSize()
|
||||
{
|
||||
return getInt(R.string.settings_key_card_title_list_font_size, R.integer.settings_card_title_list_font_size_sp);
|
||||
}
|
||||
|
||||
public int getCardNoteListFontSize()
|
||||
{
|
||||
return getInt(R.string.settings_key_card_note_list_font_size, R.integer.settings_card_note_list_font_size_sp);
|
||||
}
|
||||
|
||||
public int getCardTitleFontSize()
|
||||
{
|
||||
return getInt(R.string.settings_key_card_title_font_size, R.integer.settings_card_title_font_size_sp);
|
||||
}
|
||||
|
||||
public int getCardIdFontSize()
|
||||
{
|
||||
return getInt(R.string.settings_key_card_id_font_size, R.integer.settings_card_id_font_size_sp);
|
||||
}
|
||||
|
||||
public int getCardNoteFontSize()
|
||||
{
|
||||
return getInt(R.string.settings_key_card_note_font_size, R.integer.settings_card_note_font_size_sp);
|
||||
}
|
||||
|
||||
public boolean useMaxBrightnessDisplayingBarcode()
|
||||
{
|
||||
return getBoolean(R.string.settings_key_display_barcode_max_brightness, true);
|
||||
}
|
||||
|
||||
public boolean getLockBarcodeScreenOrientation()
|
||||
{
|
||||
return getBoolean(R.string.settings_key_lock_barcode_orientation, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package protect.card_locker.preferences;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import protect.card_locker.R;
|
||||
|
||||
public class SettingsActivity extends AppCompatActivity
|
||||
{
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
// Display the fragment as the main content.
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, new SettingsFragment())
|
||||
.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
int id = item.getItemId();
|
||||
|
||||
if(id == android.R.id.home)
|
||||
{
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragment
|
||||
{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
app/src/main/res/drawable-hdpi/app_icon_intro.png
Executable file
|
After Width: | Height: | Size: 20 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_import_export_white_24dp.png
Normal file
|
After Width: | Height: | Size: 172 B |
BIN
app/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png
Normal file
|
After Width: | Height: | Size: 306 B |
BIN
app/src/main/res/drawable-hdpi/ic_lock_outline_white_24dp.png
Normal file
|
After Width: | Height: | Size: 303 B |
BIN
app/src/main/res/drawable-hdpi/intro2_image.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
app/src/main/res/drawable-hdpi/intro3_image.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
app/src/main/res/drawable-hdpi/intro4_image.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/drawable-hdpi/intro5_image.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
app/src/main/res/drawable-hdpi/save_24dp.png
Normal file
|
After Width: | Height: | Size: 519 B |
BIN
app/src/main/res/drawable-mdpi/app_icon_intro.png
Executable file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_import_export_white_24dp.png
Normal file
|
After Width: | Height: | Size: 132 B |
BIN
app/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png
Normal file
|
After Width: | Height: | Size: 200 B |
BIN
app/src/main/res/drawable-mdpi/ic_lock_outline_white_24dp.png
Normal file
|
After Width: | Height: | Size: 198 B |
BIN
app/src/main/res/drawable-mdpi/intro2_image.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
app/src/main/res/drawable-mdpi/intro3_image.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
app/src/main/res/drawable-mdpi/intro4_image.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
app/src/main/res/drawable-mdpi/intro5_image.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
app/src/main/res/drawable-mdpi/save_24dp.png
Normal file
|
After Width: | Height: | Size: 406 B |
BIN
app/src/main/res/drawable-nodpi/circle.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/src/main/res/drawable-xhdpi/app_icon_intro.png
Executable file
|
After Width: | Height: | Size: 28 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_import_export_white_24dp.png
Normal file
|
After Width: | Height: | Size: 202 B |
BIN
app/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png
Normal file
|
After Width: | Height: | Size: 354 B |
BIN
app/src/main/res/drawable-xhdpi/ic_lock_outline_white_24dp.png
Normal file
|
After Width: | Height: | Size: 343 B |
BIN
app/src/main/res/drawable-xhdpi/intro2_image.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
app/src/main/res/drawable-xhdpi/intro3_image.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
app/src/main/res/drawable-xhdpi/intro4_image.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
app/src/main/res/drawable-xhdpi/intro5_image.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
app/src/main/res/drawable-xhdpi/save_24dp.png
Normal file
|
After Width: | Height: | Size: 651 B |
BIN
app/src/main/res/drawable-xxhdpi/app_icon_intro.png
Executable file
|
After Width: | Height: | Size: 50 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_import_export_white_24dp.png
Normal file
|
After Width: | Height: | Size: 252 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png
Normal file
|
After Width: | Height: | Size: 512 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_lock_outline_white_24dp.png
Normal file
|
After Width: | Height: | Size: 494 B |
BIN
app/src/main/res/drawable-xxhdpi/intro2_image.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
app/src/main/res/drawable-xxhdpi/intro3_image.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
app/src/main/res/drawable-xxhdpi/intro4_image.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
app/src/main/res/drawable-xxhdpi/intro5_image.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
app/src/main/res/drawable-xxhdpi/save_24dp.png
Normal file
|
After Width: | Height: | Size: 853 B |
BIN
app/src/main/res/drawable-xxxhdpi/app_icon_intro.png
Executable file
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 328 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_lock_open_white_24dp.png
Normal file
|
After Width: | Height: | Size: 665 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_lock_outline_white_24dp.png
Normal file
|
After Width: | Height: | Size: 651 B |
BIN
app/src/main/res/drawable-xxxhdpi/intro2_image.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/intro3_image.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/intro4_image.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/intro5_image.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/save_24dp.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
205
app/src/main/res/layout/barcode_selector_activity.xml
Normal file
@@ -0,0 +1,205 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:id="@+id/scrollView">
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView android:textSize="20sp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingStart="20.0dip"
|
||||
android:paddingEnd="20.0dip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enterBarcodeInstructions" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/barcodeIdLayout">
|
||||
<TextView android:textSize="16.0sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingStart="20.0dip"
|
||||
android:paddingEnd="20.0dip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@+id/cardId"
|
||||
android:text="@string/cardId" />
|
||||
<EditText android:id="@+id/cardId"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/barcodesLayout"/>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/aztecBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/code39Barcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/code128Barcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/codabarBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/datamatrixBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/ean8Barcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/ean13Barcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/itfBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/pdf417Barcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/qrcodeBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/barcode_disp_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/upcaBarcode"
|
||||
android:contentDescription="@string/barcodeImageDescription"
|
||||
android:layout_weight="1.0"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
@@ -5,10 +5,6 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:context="protect.card_locker.MainActivity"
|
||||
tools:showIn="@layout/main_activity">
|
||||
|
||||
166
app/src/main/res/layout/import_export_activity.xml
Normal file
@@ -0,0 +1,166 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<ScrollView 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="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
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:textSize="@dimen/text_size_medium"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/importExportHelp"/>
|
||||
|
||||
<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>
|
||||
51
app/src/main/res/layout/intro1_layout.xml
Normal 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>
|
||||
51
app/src/main/res/layout/intro2_layout.xml
Normal 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>
|
||||
51
app/src/main/res/layout/intro3_layout.xml
Normal 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>
|
||||
51
app/src/main/res/layout/intro4_layout.xml
Normal 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>
|
||||
51
app/src/main/res/layout/intro5_layout.xml
Normal 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>
|
||||
51
app/src/main/res/layout/intro6_layout.xml
Normal 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>
|
||||
389
app/src/main/res/layout/loyalty_card_edit_activity.xml
Normal file
@@ -0,0 +1,389 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<ScrollView android:layout_width="fill_parent"
|
||||
android:layout_height="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">
|
||||
|
||||
<!-- 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"
|
||||
android:layout_alignParentLeft="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"
|
||||
android:layout_toRightOf="@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"
|
||||
android:layout_alignParentLeft="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"
|
||||
android:layout_toRightOf="@id/noteField"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
|
||||
<View
|
||||
android:gravity="end"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="@dimen/inputBorderThickness"
|
||||
android:background="@color/inputBorder" />
|
||||
</TableRow>
|
||||
|
||||
<!-- Store Header Background Color -->
|
||||
<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" />
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
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/headingField"
|
||||
android:text="@string/storeTextBackgroundColorTitle"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:textSize="@dimen/inputSize"
|
||||
android:padding="@dimen/inputPadding"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:id="@+id/headingColorSampleBorder"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="@dimen/colorSamplePadding"
|
||||
app:layout_constraintStart_toEndOf="@id/headingField"
|
||||
app:layout_constraintEnd_toStartOf="@+id/headingColorSelectButton"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:background="@android:color/black">
|
||||
<ImageView
|
||||
android:id="@+id/headingColorSample"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="1dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:background="@android:color/white"
|
||||
android:contentDescription="@string/storeNameBackgroundColorDescription"/>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/headingColorSelectButton"
|
||||
android:text="@string/change"
|
||||
android:padding="@dimen/inputPadding"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toEndOf="@id/headingColorSampleBorder"
|
||||
android:layout_toRightOf="@id/headingColorSampleBorder"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
||||
<View
|
||||
android:gravity="end"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="@dimen/inputBorderThickness"
|
||||
android:background="@color/inputBorder" />
|
||||
</TableRow>
|
||||
|
||||
<!-- Store Text Color -->
|
||||
<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" />
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
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/storeTextField"
|
||||
android:text="@string/storeTextColorTitle"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:textSize="@dimen/inputSize"
|
||||
android:padding="@dimen/inputPadding"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:id="@+id/headingStoreTextColorSampleBorder"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="@dimen/colorSamplePadding"
|
||||
app:layout_constraintStart_toEndOf="@id/storeTextField"
|
||||
app:layout_constraintEnd_toStartOf="@+id/headingStoreTextColorSelectButton"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:background="@android:color/black">
|
||||
<ImageView
|
||||
android:id="@+id/headingStoreTextColorSample"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="1dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:background="@android:color/white"
|
||||
android:contentDescription="@string/storeNameColorDescription"/>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/headingStoreTextColorSelectButton"
|
||||
android:text="@string/change"
|
||||
android:padding="@dimen/inputPadding"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toEndOf="@id/headingStoreTextColorSampleBorder"
|
||||
android:layout_toRightOf="@id/headingStoreTextColorSampleBorder"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
||||
<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"
|
||||
android:layout_alignParentLeft="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"
|
||||
android:layout_toRightOf="@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"
|
||||
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">
|
||||
|
||||
<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>
|
||||
|
||||
</TableLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
@@ -1,32 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="5.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="5.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="true">
|
||||
<TextView android:textSize="20.0sp"
|
||||
android:id="@+id/store"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:maxLines="1"/>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:baselineAligned="false"
|
||||
android:padding="@dimen/activity_margin">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="@dimen/cardThumbnailSize"
|
||||
android:layout_height="@dimen/cardThumbnailSize"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:layout_marginRight="@dimen/activity_margin"
|
||||
android:src="@mipmap/ic_launcher"
|
||||
android:contentDescription="@string/thumbnailDescription"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/valueLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/store"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/storeNameTextSize"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/note"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textSize="@dimen/noteTextSize"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="5.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="true">
|
||||
<TextView android:textSize="20.0sp"
|
||||
android:layout_gravity="start"
|
||||
android:id="@+id/cardId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="protect.budgetwatch.AccountsActivity">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:id="@+id/scrollView">
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="10.0dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView android:textSize="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" />
|
||||
<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>
|
||||
</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"/>
|
||||
</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>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
142
app/src/main/res/layout/loyalty_card_view_layout.xml
Normal file
@@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/coordinator_layout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
>
|
||||
|
||||
<FrameLayout
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.constraint.Guideline
|
||||
android:id="@+id/centerGuideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.5"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/barcode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="20.0dip"
|
||||
android:layout_marginBottom="10.0dip"
|
||||
android:layout_marginStart="15.0dip"
|
||||
android:layout_marginEnd="15.0dip"
|
||||
app:layout_constraintBottom_toTopOf="@+id/centerGuideline"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:contentDescription="@string/barcodeImageDescription"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cardIdView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginLeft="10.0dip"
|
||||
android:layout_marginRight="10.0dip"
|
||||
app:layout_constraintTop_toBottomOf="@id/centerGuideline"
|
||||
app:layout_constraintBottom_toTopOf="@+id/noteViewDivider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:textAlignment="center"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:autoSizeMinTextSize="@dimen/singleCardCardIdTextSizeMin"
|
||||
app:autoSizeMaxTextSize="@dimen/singleCardCardIdTextSizeMax"
|
||||
android:ellipsize="end"
|
||||
android:textIsSelectable="true"/>
|
||||
|
||||
<View
|
||||
android:id="@id/noteViewDivider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@android:color/black"
|
||||
app:layout_constraintTop_toBottomOf="@id/cardIdView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/noteView"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@id/noteView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:ellipsize="end"
|
||||
android:layout_gravity="bottom"
|
||||
app:layout_constraintTop_toBottomOf="@id/noteViewDivider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:autoSizeMinTextSize="@dimen/singleCardNoteTextSizeMin"
|
||||
app:autoSizeMaxTextSize="@dimen/singleCardNoteTextSizeMax"
|
||||
android:textIsSelectable="true"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/drop_shadow_actionbar"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="5.0dip"
|
||||
android:layout_gravity="top"/>
|
||||
</FrameLayout>
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:id="@+id/app_bar_layout"
|
||||
android:background="@android:color/transparent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:weightSum="1.0"
|
||||
android:fitsSystemWindows="true">
|
||||
<android.support.design.widget.CollapsingToolbarLayout
|
||||
android:id="@+id/collapsingToolbarLayout"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="56.0dip"
|
||||
android:layout_weight="1.0"
|
||||
app:expandedTitleMarginStart="48dp"
|
||||
app:expandedTitleMarginEnd="64dp"
|
||||
app:contentScrim="?colorPrimary"
|
||||
app:expandedTitleGravity="top">
|
||||
<TextView
|
||||
android:id="@+id/storeName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="40sp"
|
||||
android:textAlignment="center"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="?actionBarSize"
|
||||
android:layout_marginBottom="?actionBarSize"
|
||||
app:layout_collapseMode="parallax"
|
||||
android:fitsSystemWindows="true"/>
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@id/toolbar"
|
||||
android:background="@android:color/transparent"
|
||||
android:theme="@style/CardView.ActionBarTheme"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
app:contentInsetStart="72.0dip"
|
||||
app:layout_collapseMode="pin" />
|
||||
</android.support.design.widget.CollapsingToolbarLayout>
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
10
app/src/main/res/menu/card_add_menu.xml
Normal 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>
|
||||
9
app/src/main/res/menu/card_longclick_menu.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_clipboard"
|
||||
android:title="@string/copy_to_clipboard"
|
||||
app:showAsAction="always"/>
|
||||
</menu>
|
||||
@@ -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>
|
||||