Compare commits
449 Commits
v0.12
...
feature/pk
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
435ad2cfb3 | ||
|
|
ef675fe6e5 | ||
|
|
45f266cc93 | ||
|
|
a29edd90ac | ||
|
|
799b82e199 | ||
|
|
91e2b7acdd | ||
|
|
e59fc1ea0a | ||
|
|
2926d0d716 | ||
|
|
bcc988ff2c | ||
|
|
c03c02c6ea | ||
|
|
25d2a5ec5d | ||
|
|
344f7a9bc8 | ||
|
|
e32993ceb5 | ||
|
|
efae7bda4c | ||
|
|
1cbf90a596 | ||
|
|
9d4529b06b | ||
|
|
c471c35b82 | ||
|
|
ccd848c0b8 | ||
|
|
d8776da8b6 | ||
|
|
9de6a2e0da | ||
|
|
9f1d5ad1b3 | ||
|
|
290abd9c56 | ||
|
|
72a04b570a | ||
|
|
1fccfff338 | ||
|
|
d22f8cc5e9 | ||
|
|
7fc88cd5af | ||
|
|
ab485991f1 | ||
|
|
e579168a03 | ||
|
|
38fc515372 | ||
|
|
aad074e29e | ||
|
|
4321bf5ac8 | ||
|
|
694e9b88d7 | ||
|
|
6ba4804d9a | ||
|
|
288531dff0 | ||
|
|
678f126403 | ||
|
|
3ae496427a | ||
|
|
046348612f | ||
|
|
fe4dc868aa | ||
|
|
52e32caca3 | ||
|
|
d7ef204d0a | ||
|
|
68bcd167a2 | ||
|
|
b7892df877 | ||
|
|
bc63f3163c | ||
|
|
2f562b432c | ||
|
|
b776f90b87 | ||
|
|
6877828853 | ||
|
|
c94c3cb47e | ||
|
|
454052638f | ||
|
|
1b060b26a5 | ||
|
|
7970bb8a40 | ||
|
|
8fa868e99b | ||
|
|
77a6cca57d | ||
|
|
5ab3a62e16 | ||
|
|
aa2ccac22b | ||
|
|
e1f4ed65bb | ||
|
|
72c1a57953 | ||
|
|
24061dae97 | ||
|
|
649daf43df | ||
|
|
fb9cf7a393 | ||
|
|
0d67680e7f | ||
|
|
b5d41b0ab2 | ||
|
|
171ec1cd7e | ||
|
|
54ee93b0cb | ||
|
|
fd6c66a86a | ||
|
|
91044fdc87 | ||
|
|
3600065ef8 | ||
|
|
4a7e8b6eba | ||
|
|
2c12c312e3 | ||
|
|
f8943af402 | ||
|
|
3990992f68 | ||
|
|
915e364454 | ||
|
|
d2ac0dacda | ||
|
|
1a35150677 | ||
|
|
bf48e394e9 | ||
|
|
003aba52af | ||
|
|
aad98ba55b | ||
|
|
2b50e503da | ||
|
|
ab4c360c37 | ||
|
|
c76580d6be | ||
|
|
dc8ac6b574 | ||
|
|
0c23f25aca | ||
|
|
47b9bbd30d | ||
|
|
da1d1d62e4 | ||
|
|
ce81d3afd4 | ||
|
|
cc99af13e4 | ||
|
|
ccf3d1f3d6 | ||
|
|
330ded0b5d | ||
|
|
0bcb0a7ab8 | ||
|
|
403c1fca33 | ||
|
|
ca12f8f914 | ||
|
|
d5ba632bb3 | ||
|
|
b9d158583b | ||
|
|
32b4178950 | ||
|
|
d9f97380d9 | ||
|
|
4e2fe97de5 | ||
|
|
1a3dee2c26 | ||
|
|
53f3a2109b | ||
|
|
c5c2f702d0 | ||
|
|
396801ba74 | ||
|
|
67163539d5 | ||
|
|
c1be6f2a88 | ||
|
|
fd1b86b1cc | ||
|
|
3f4d0ab5df | ||
|
|
1a6cad893d | ||
|
|
2ef4d3f6e0 | ||
|
|
43148cb12c | ||
|
|
80b45973ac | ||
|
|
ca8243de01 | ||
|
|
b4a532d183 | ||
|
|
a05f9f6ec9 | ||
|
|
c0d1b446d1 | ||
|
|
d24a418beb | ||
|
|
1f348295a0 | ||
|
|
280fe76609 | ||
|
|
8a3b623ea6 | ||
|
|
e1f66e1917 | ||
|
|
79bba52d93 | ||
|
|
787bda1c77 | ||
|
|
c83b151c08 | ||
|
|
a642b12795 | ||
|
|
04dcee6097 | ||
|
|
6cd7a3f5bf | ||
|
|
4e1fb16359 | ||
|
|
0d7d1658d6 | ||
|
|
7b7624a3e0 | ||
|
|
09727038ce | ||
|
|
896da82b13 | ||
|
|
ca93f89e1b | ||
|
|
9291563cef | ||
|
|
488125c492 | ||
|
|
3aeea02300 | ||
|
|
72227f19e8 | ||
|
|
62f7241bca | ||
|
|
80fb7e2cb3 | ||
|
|
703db472fc | ||
|
|
b043320af5 | ||
|
|
ed048e6424 | ||
|
|
e1f25ed092 | ||
|
|
05d38a0184 | ||
|
|
5a046b037e | ||
|
|
2f73d510e2 | ||
|
|
2c7cb4769a | ||
|
|
3d543dd23c | ||
|
|
8637f8d2a1 | ||
|
|
bda8abfe3d | ||
|
|
19755f4f86 | ||
|
|
9a2d195cb2 | ||
|
|
429309deef | ||
|
|
864b82d547 | ||
|
|
77a2ed29db | ||
|
|
acfb92e9fb | ||
|
|
61c2c0c84a | ||
|
|
08afc16d01 | ||
|
|
91a4461863 | ||
|
|
e37288b842 | ||
|
|
fc930f40e4 | ||
|
|
33b84ad4c3 | ||
|
|
368a2a30f6 | ||
|
|
5774064da7 | ||
|
|
8673405a50 | ||
|
|
4ec4baa53b | ||
|
|
f00be863c2 | ||
|
|
e0d85f9c8d | ||
|
|
3754243748 | ||
|
|
5ef2c4b1da | ||
|
|
8315067caf | ||
|
|
7bbf5b469c | ||
|
|
0de50e72a6 | ||
|
|
4240fd55ba | ||
|
|
29e8896059 | ||
|
|
a46200f794 | ||
|
|
061b1db245 | ||
|
|
8d3d9a21f7 | ||
|
|
9b5a731d48 | ||
|
|
5e0e8a6f67 | ||
|
|
07c74b79f3 | ||
|
|
5e836758e5 | ||
|
|
e314580ac3 | ||
|
|
2ef739fb4f | ||
|
|
bae6f746a1 | ||
|
|
22e8d7f699 | ||
|
|
48a1084c40 | ||
|
|
3d2ad1693a | ||
|
|
28b3aa2018 | ||
|
|
ed4adad087 | ||
|
|
5a898b4c4c | ||
|
|
7a78eadabb | ||
|
|
d7556b9951 | ||
|
|
c936e3c9c0 | ||
|
|
e33565abdc | ||
|
|
30419b5896 | ||
|
|
f0426b98dc | ||
|
|
bd3d67574e | ||
|
|
52a09056e8 | ||
|
|
21a8dc6867 | ||
|
|
e048fdff59 | ||
|
|
0a8c85d24c | ||
|
|
07d938701f | ||
|
|
c0c126192f | ||
|
|
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 |
27
.github/workflows/android.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Android CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Build
|
||||
run: ./gradlew assembleRelease
|
||||
- name: Check lint
|
||||
run: ./gradlew lintRelease
|
||||
- name: Run unit tests
|
||||
run: ./gradlew testReleaseUnitTest
|
||||
- name: FindBugs
|
||||
run: ./gradlew findbugs
|
||||
16
.travis.yml
@@ -1,16 +0,0 @@
|
||||
language: android
|
||||
sudo: true
|
||||
|
||||
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-25.0.2
|
||||
- echo y | android update sdk -u -a -t android-25
|
||||
- echo y | android update sdk -u -a -t extra-google-m2repository
|
||||
- echo y | android update sdk -u -a -t extra-android-m2repository
|
||||
|
||||
script: ./gradlew assembleRelease testReleaseUnitTest lintRelease findbugs
|
||||
|
||||
after_failure:
|
||||
- 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
|
||||
|
||||
188
CHANGELOG.md
@@ -1,3 +1,191 @@
|
||||
## v0.28 (2020-03-09)
|
||||
|
||||
Changes:
|
||||
- Fix barcode centering when exiting full screen ([#351](https://github.com/brarcher/loyalty-card-locker/pull/351))
|
||||
- Allow backup export location to be selected ([#352](https://github.com/brarcher/loyalty-card-locker/pull/352))
|
||||
- Update translations ([#357](https://github.com/brarcher/loyalty-card-locker/pull/357)) & ([#362](https://github.com/brarcher/loyalty-card-locker/pull/362))
|
||||
|
||||
## v0.27 (2020-01-26)
|
||||
|
||||
Changes:
|
||||
- Tapping on a barcode now moves it to the top of the screen ([#348](https://github.com/brarcher/loyalty-card-locker/pull/348))
|
||||
- Add white space around barcodes to improve scanning in dark mode ([#328](https://github.com/brarcher/loyalty-card-locker/issues/328))
|
||||
- Fix swapped import buttons. ([#346](https://github.com/brarcher/loyalty-card-locker/pull/346))
|
||||
|
||||
## v0.26.1 (2020-01-09)
|
||||
|
||||
Changes:
|
||||
- Fix issue with sharing cards without background color ([#343](https://github.com/brarcher/loyalty-card-locker/pull/343))
|
||||
|
||||
## v0.26 (2020-01-05)
|
||||
|
||||
Changes:
|
||||
- Add ability to search for a card ([#320](https://github.com/brarcher/loyalty-card-locker/pull/320))
|
||||
- Add ability to share and receive loyalty cards ([#321](https://github.com/brarcher/loyalty-card-locker/pull/321))
|
||||
- Dark mode support ([#322](https://github.com/brarcher/loyalty-card-locker/pull/322))
|
||||
- Loyalty cards can now be barcodeless (e.g. not have a barcode) ([#324](https://github.com/brarcher/loyalty-card-locker/pull/324))
|
||||
- Notes can span multiple lines ([#326](https://github.com/brarcher/loyalty-card-locker/pull/326))
|
||||
- Improvements with the sizing of notes ([#319](https://github.com/brarcher/loyalty-card-locker/pull/319))
|
||||
- Improve notification and app icon visibility ([#330](https://github.com/brarcher/loyalty-card-locker/pull/330))
|
||||
- Update target SDK to Android 10
|
||||
- Improve the following translations:
|
||||
* German
|
||||
* Italian
|
||||
* Dutch
|
||||
* Polish
|
||||
* Russian
|
||||
|
||||
## 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:
|
||||
|
||||
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.
|
||||
54
README.md
@@ -1,54 +0,0 @@
|
||||
# Loyalty Card Keychain
|
||||
[](https://travis-ci.org/brarcher/loyalty-card-locker)
|
||||
|
||||
<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:
|
||||
|
||||
- AZTEC
|
||||
- CODABAR
|
||||
- CODE_39
|
||||
- CODE_128
|
||||
- DATA_MATRIX
|
||||
- EAN_8
|
||||
- EAN_13
|
||||
- ITF
|
||||
- PDF_417
|
||||
- QR_CODE
|
||||
- UPC_A
|
||||
|
||||
If there is any interest in improving this project, kindly submit a pull request with
|
||||
proposed changes.
|
||||
|
||||
# Screenshots
|
||||
|
||||
[<img src="https://user-images.githubusercontent.com/5264535/27416124-79b09162-56d9-11e7-967b-8923177dc228.png" width=250>](https://user-images.githubusercontent.com/5264535/27416124-79b09162-56d9-11e7-967b-8923177dc228.png)
|
||||
[<img src="https://user-images.githubusercontent.com/5264535/27416127-7baea332-56d9-11e7-8a10-5be90bb02225.png" width=250>](https://user-images.githubusercontent.com/5264535/27416127-7baea332-56d9-11e7-8a10-5be90bb02225.png)
|
||||
[<img src="https://user-images.githubusercontent.com/5264535/27416128-7d50f7b2-56d9-11e7-9833-1dd962f9cf66.png" width=250>](https://user-images.githubusercontent.com/5264535/27416128-7d50f7b2-56d9-11e7-9833-1dd962f9cf66.png)
|
||||
|
||||
[<img src="https://user-images.githubusercontent.com/5264535/27416132-7ea6272c-56d9-11e7-9a52-d73424bf902c.png" width=250>](https://user-images.githubusercontent.com/5264535/27416132-7ea6272c-56d9-11e7-9a52-d73424bf902c.png)
|
||||
[<img src="https://user-images.githubusercontent.com/5264535/27416137-800aee90-56d9-11e7-9cc9-2a7dc63bb4fb.png" width=250>](https://user-images.githubusercontent.com/5264535/27416137-800aee90-56d9-11e7-9cc9-2a7dc63bb4fb.png)
|
||||
[<img src="https://user-images.githubusercontent.com/5264535/27416140-82d8211a-56d9-11e7-8031-c71d3077bdc6.png" width=250>](https://user-images.githubusercontent.com/5264535/27416140-82d8211a-56d9-11e7-8031-c71d3077bdc6.png)
|
||||
|
||||
# Building
|
||||
|
||||
To build, use the gradle wrapper scripts provided in the top level directory of the project. The following will
|
||||
compile the application and run all unit tests:
|
||||
|
||||
GNU/Linux, OSX, UNIX:
|
||||
```
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
Windows:
|
||||
```
|
||||
./gradlew.bat build
|
||||
```
|
||||
|
||||
# Thanks
|
||||
|
||||
This application uses the following image:
|
||||
- [Save](https://thenounproject.com/term/save/716011) by [Bernar Novalyi](https://thenounproject.com/bernar.novalyi)
|
||||
@@ -7,51 +7,69 @@ findbugs {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.2"
|
||||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
applicationId "protect.card_locker"
|
||||
minSdkVersion 17
|
||||
targetSdkVersion 23
|
||||
versionCode 13
|
||||
versionName "0.12"
|
||||
applicationId "me.hackerchick.catima"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode 39
|
||||
versionName "0.28"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
disable "GoogleAppIndexingWarning"
|
||||
disable "ButtonStyle"
|
||||
disable "AlwaysShowAction"
|
||||
disable "MissingTranslation"
|
||||
disable "MissingPrefix"
|
||||
}
|
||||
sourceSets {
|
||||
test {
|
||||
resources.srcDirs += ['src/test/resources']
|
||||
}
|
||||
}
|
||||
|
||||
// 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:25.3.1'
|
||||
compile 'com.android.support:design:25.3.1'
|
||||
compile 'androidx.appcompat:appcompat:1.2.0'
|
||||
compile 'com.google.android.material:material:1.2.0-alpha03'
|
||||
compile 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
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.2'
|
||||
compile 'org.apache.commons:commons-csv:1.5'
|
||||
compile 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
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.3.2"
|
||||
testCompile "org.robolectric:robolectric:4.0.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
|
||||
@@ -17,7 +17,8 @@
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:name=".LoyaltyCardLockerApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
@@ -35,24 +36,75 @@
|
||||
<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:windowSoftInputMode="stateHidden"
|
||||
android:exported="true">
|
||||
<intent-filter android:label="@string/app_name">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- Accepts URIs that begin with "https://thelastproject.github.io/Catima/share" or "https://brarcher.github.io/loyalty-card-locker/share” -->
|
||||
<data android:scheme="https"
|
||||
android:host="@string/intent_import_card_from_url_host"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix" />
|
||||
<data android:scheme="https"
|
||||
android:host="@string/intent_import_card_from_url_host_old"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_old" />
|
||||
</intent-filter>
|
||||
<intent-filter android:label="@string/app_name">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<!-- Accept pkpass files -->
|
||||
<data android:scheme="content" android:mimeType="application/octet-stream"/>
|
||||
<data android:scheme="content" android:mimeType="application/x-compressed"/>
|
||||
<data android:scheme="content" android:mimeType="application/x-zip-compressed"/>
|
||||
<data android:scheme="content" android:mimeType="application/zip"/>
|
||||
<data android:scheme="content" android:mimeType="application/vnd.apple.pkpass"/>
|
||||
<data android:scheme="content" android:mimeType="application/pkpass"/>
|
||||
<data android:scheme="content" android:mimeType="application/vndapplepkpass"/>
|
||||
<data android:scheme="content" android:mimeType="application/vnd-com.apple.pkpass"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<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=".IntroActivity"
|
||||
android:label=""
|
||||
android:name=".CardShortcutConfigure"
|
||||
android:label="@string/cardShortcut"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:theme="@style/AppTheme.NoActionBar"/>
|
||||
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="androidx.core.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>
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
@@ -20,24 +21,32 @@ import java.lang.ref.WeakReference;
|
||||
*/
|
||||
class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
private static final int MAX_WIDTH = 500;
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
// 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 WeakReference<TextView> textViewReference;
|
||||
private final String cardId;
|
||||
private final BarcodeFormat format;
|
||||
private final int imageHeight;
|
||||
private final int imageWidth;
|
||||
|
||||
BarcodeImageWriterTask(ImageView imageView, String cardIdString,
|
||||
BarcodeFormat barcodeFormat)
|
||||
BarcodeFormat barcodeFormat, TextView textView)
|
||||
{
|
||||
// Use a WeakReference to ensure the ImageView can be garbage collected
|
||||
imageViewReference = new WeakReference<>(imageView);
|
||||
textViewReference = new WeakReference<>(textView);
|
||||
|
||||
cardId = cardIdString;
|
||||
format = barcodeFormat;
|
||||
|
||||
final int MAX_WIDTH = getMaxWidth(format);
|
||||
|
||||
if(imageView.getWidth() < MAX_WIDTH)
|
||||
{
|
||||
imageHeight = imageView.getHeight();
|
||||
@@ -52,8 +61,48 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
||||
}
|
||||
}
|
||||
|
||||
BarcodeImageWriterTask(ImageView imageView, String cardIdString, BarcodeFormat barcodeFormat)
|
||||
{
|
||||
this(imageView, cardIdString, barcodeFormat, null);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (cardId.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
MultiFormatWriter writer = new MultiFormatWriter();
|
||||
BitMatrix bitMatrix;
|
||||
try
|
||||
@@ -113,6 +162,12 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
||||
{
|
||||
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;
|
||||
}
|
||||
@@ -128,16 +183,25 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
||||
}
|
||||
|
||||
imageView.setImageBitmap(result);
|
||||
TextView textView = textViewReference.get();
|
||||
|
||||
if(result != null)
|
||||
{
|
||||
Log.i(TAG, "Displaying barcode");
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
|
||||
if (textView != null) {
|
||||
textView.setVisibility(View.VISIBLE);
|
||||
textView.setText(format.name());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.i(TAG, "Barcode generation failed, removing image from display");
|
||||
imageView.setVisibility(View.GONE);
|
||||
if (textView != null) {
|
||||
textView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,19 @@ 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 androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
@@ -34,7 +36,7 @@ import java.util.Map;
|
||||
*/
|
||||
public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
// Result this activity will return
|
||||
public static final String BARCODE_CONTENTS = "contents";
|
||||
@@ -58,7 +60,7 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
BarcodeFormat.UPC_A.name()
|
||||
));
|
||||
|
||||
private Map<String, Integer> barcodeViewMap;
|
||||
private Map<String, Pair<Integer, Integer>> barcodeViewMap;
|
||||
private LinkedList<AsyncTask> barcodeGeneratorTasks = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
@@ -67,7 +69,7 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.barcode_selector_activity);
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
@@ -75,21 +77,21 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
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)
|
||||
barcodeViewMap = ImmutableMap.<String, Pair<Integer, Integer>>builder()
|
||||
.put(BarcodeFormat.AZTEC.name(), new Pair<>(R.id.aztecBarcode, R.id.aztecBarcodeText))
|
||||
.put(BarcodeFormat.CODE_39.name(), new Pair<>(R.id.code39Barcode, R.id.code39BarcodeText))
|
||||
.put(BarcodeFormat.CODE_128.name(), new Pair<>(R.id.code128Barcode, R.id.code128BarcodeText))
|
||||
.put(BarcodeFormat.CODABAR.name(), new Pair<>(R.id.codabarBarcode, R.id.codabarBarcodeText))
|
||||
.put(BarcodeFormat.DATA_MATRIX.name(), new Pair<>(R.id.datamatrixBarcode, R.id.datamatrixBarcodeText))
|
||||
.put(BarcodeFormat.EAN_8.name(), new Pair<>(R.id.ean8Barcode, R.id.ean8BarcodeText))
|
||||
.put(BarcodeFormat.EAN_13.name(), new Pair<>(R.id.ean13Barcode, R.id.ean13BarcodeText))
|
||||
.put(BarcodeFormat.ITF.name(), new Pair<>(R.id.itfBarcode, R.id.itfBarcodeText))
|
||||
.put(BarcodeFormat.PDF_417.name(), new Pair<>(R.id.pdf417Barcode, R.id.pdf417BarcodeText))
|
||||
.put(BarcodeFormat.QR_CODE.name(), new Pair<>(R.id.qrcodeBarcode, R.id.qrcodeBarcodeText))
|
||||
.put(BarcodeFormat.UPC_A.name(), new Pair<>(R.id.upcaBarcode, R.id.upcaBarcodeText))
|
||||
.build();
|
||||
|
||||
EditText cardId = (EditText) findViewById(R.id.cardId);
|
||||
EditText cardId = findViewById(R.id.cardId);
|
||||
cardId.addTextChangedListener(new TextWatcher()
|
||||
{
|
||||
@Override
|
||||
@@ -113,9 +115,14 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
// Update barcodes
|
||||
for(String key : barcodeViewMap.keySet())
|
||||
{
|
||||
ImageView image = (ImageView)findViewById(barcodeViewMap.get(key));
|
||||
createBarcodeOption(image, key, s.toString());
|
||||
ImageView image = findViewById(barcodeViewMap.get(key).first);
|
||||
TextView text = findViewById(barcodeViewMap.get(key).second);
|
||||
createBarcodeOption(image, key, s.toString(), text);
|
||||
}
|
||||
|
||||
View noBarcodeButtonView = findViewById(R.id.noBarcode);
|
||||
setButtonListener(noBarcodeButtonView, s.toString());
|
||||
noBarcodeButtonView.setEnabled(s.length() > 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -134,7 +141,22 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId)
|
||||
private void setButtonListener(final View button, final String cardId)
|
||||
{
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Log.d(TAG, "Selected no barcode");
|
||||
Intent result = new Intent();
|
||||
result.putExtra(BARCODE_FORMAT, "");
|
||||
result.putExtra(BARCODE_CONTENTS, cardId);
|
||||
BarcodeSelectorActivity.this.setResult(RESULT_OK, result);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text)
|
||||
{
|
||||
final BarcodeFormat format = BarcodeFormat.valueOf(formatType);
|
||||
if(format == null)
|
||||
@@ -179,7 +201,7 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format, text);
|
||||
barcodeGeneratorTasks.add(task);
|
||||
task.execute();
|
||||
}
|
||||
@@ -188,7 +210,7 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
else
|
||||
{
|
||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format, text);
|
||||
barcodeGeneratorTasks.add(task);
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@@ -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 androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.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 = "Catima";
|
||||
|
||||
@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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.database.Cursor;
|
||||
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVPrinter;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
@@ -23,7 +24,10 @@ public class CsvDatabaseExporter implements DatabaseExporter
|
||||
DBHelper.LoyaltyCardDbIds.STORE,
|
||||
DBHelper.LoyaltyCardDbIds.NOTE,
|
||||
DBHelper.LoyaltyCardDbIds.CARD_ID,
|
||||
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
|
||||
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
|
||||
DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR,
|
||||
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
|
||||
DBHelper.LoyaltyCardDbIds.EXTRAS);
|
||||
|
||||
Cursor cursor = db.getLoyaltyCardCursor();
|
||||
|
||||
@@ -31,11 +35,23 @@ public class CsvDatabaseExporter implements DatabaseExporter
|
||||
{
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
|
||||
|
||||
String extras;
|
||||
try {
|
||||
extras = card.extras.toJSON().toString();
|
||||
}
|
||||
catch (JSONException ex)
|
||||
{
|
||||
throw new IOException(ex);
|
||||
}
|
||||
|
||||
printer.printRecord(card.id,
|
||||
card.store,
|
||||
card.note,
|
||||
card.cardId,
|
||||
card.barcodeType);
|
||||
card.headerColor,
|
||||
card.headerTextColor,
|
||||
card.barcodeType,
|
||||
extras);
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.text.Normalizer;
|
||||
|
||||
/**
|
||||
* Class for importing a database from CSV (Comma Separate Values)
|
||||
@@ -40,7 +44,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
parser.close();
|
||||
database.setTransactionSuccessful();
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
catch(IllegalArgumentException|IllegalStateException e)
|
||||
{
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
}
|
||||
@@ -83,7 +87,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
* "key" as the key. If no such key exists, or the data is not a valid
|
||||
* int, a FormatException is thrown.
|
||||
*/
|
||||
private int extractInt(String key, CSVRecord record)
|
||||
private Integer extractInt(String key, CSVRecord record, boolean nullIsOk)
|
||||
throws FormatException
|
||||
{
|
||||
if(record.isMapped(key) == false)
|
||||
@@ -91,6 +95,12 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
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));
|
||||
@@ -108,7 +118,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record)
|
||||
throws IOException, FormatException
|
||||
{
|
||||
int id = extractInt(DBHelper.LoyaltyCardDbIds.ID, record);
|
||||
int id = extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
|
||||
|
||||
String store = extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
|
||||
if(store.isEmpty())
|
||||
@@ -125,11 +135,34 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
}
|
||||
|
||||
String barcodeType = extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, "");
|
||||
if(barcodeType.isEmpty())
|
||||
|
||||
Integer headerColor = null;
|
||||
Integer headerTextColor = null;
|
||||
|
||||
if(record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR) &&
|
||||
record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR))
|
||||
{
|
||||
throw new FormatException("No barcode type listed, but is required");
|
||||
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);
|
||||
Bitmap icon = null;
|
||||
String iconData = extractString(DBHelper.LoyaltyCardDbIds.ICON, record, "");
|
||||
if(!iconData.isEmpty())
|
||||
{
|
||||
icon = DBHelper.convertBitmapBlobToBitmap(iconData.getBytes("UTF-8"));
|
||||
}
|
||||
|
||||
ExtrasHelper extras = new ExtrasHelper();
|
||||
|
||||
try
|
||||
{
|
||||
extras.fromJSON(new JSONObject(extractString(DBHelper.LoyaltyCardDbIds.EXTRAS, record, "{}")));
|
||||
helper.insertLoyaltyCard(database, id, store, note, cardId, barcodeType, headerColor, headerTextColor, icon, extras);
|
||||
}
|
||||
catch (JSONException ex)
|
||||
{
|
||||
throw new FormatException("Invalid JSON in extras field: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,20 @@ import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class DBHelper extends SQLiteOpenHelper
|
||||
{
|
||||
public static final String DATABASE_NAME = "LoyaltyCards.db";
|
||||
public static final String DATABASE_NAME = "Catima.db";
|
||||
public static final int ORIGINAL_DATABASE_VERSION = 1;
|
||||
public static final int DATABASE_VERSION = 2;
|
||||
public static final int DATABASE_VERSION = 4;
|
||||
|
||||
static class LoyaltyCardDbIds
|
||||
{
|
||||
@@ -19,8 +27,12 @@ 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";
|
||||
public static final String ICON = "icon";
|
||||
public static final String EXTRAS = "extras";
|
||||
}
|
||||
|
||||
public DBHelper(Context context)
|
||||
@@ -28,6 +40,19 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
public static byte[] convertBitmapToBlob(Bitmap bitmap)
|
||||
{
|
||||
// https://stackoverflow.com/a/7620610
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream);
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
public static Bitmap convertBitmapBlobToBitmap(byte[] bytes)
|
||||
{
|
||||
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db)
|
||||
{
|
||||
@@ -36,8 +61,12 @@ 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)");
|
||||
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT not null," +
|
||||
LoyaltyCardDbIds.ICON + " BLOB," +
|
||||
LoyaltyCardDbIds.EXTRAS + " TEXT)");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,10 +78,30 @@ 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");
|
||||
}
|
||||
|
||||
// Upgrade from version 3 to version 4
|
||||
if(oldVersion < 4 && newVersion >= 4)
|
||||
{
|
||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||
+ " ADD COLUMN " + LoyaltyCardDbIds.ICON + " BLOB");
|
||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||
+ " ADD COLUMN " + LoyaltyCardDbIds.EXTRAS + " TEXT");
|
||||
}
|
||||
}
|
||||
|
||||
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, final Bitmap icon,
|
||||
final ExtrasHelper extras) throws JSONException
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
@@ -60,13 +109,19 @@ 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);
|
||||
contentValues.put(LoyaltyCardDbIds.ICON, icon != null ? convertBitmapToBlob(icon) : null);
|
||||
contentValues.put(LoyaltyCardDbIds.EXTRAS, extras.toJSON().toString());
|
||||
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
|
||||
return (newId != -1);
|
||||
return newId;
|
||||
}
|
||||
|
||||
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id,
|
||||
final String store, final String note, final String cardId,
|
||||
final String barcodeType)
|
||||
final String barcodeType, final Integer headerColor,
|
||||
final Integer headerTextColor, final Bitmap icon,
|
||||
final ExtrasHelper extras) throws JSONException
|
||||
{
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(LoyaltyCardDbIds.ID, id);
|
||||
@@ -74,13 +129,19 @@ 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);
|
||||
contentValues.put(LoyaltyCardDbIds.ICON, icon != null ? convertBitmapToBlob(icon) : null);
|
||||
contentValues.put(LoyaltyCardDbIds.EXTRAS, extras.toJSON().toString());
|
||||
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,
|
||||
final Bitmap icon, final ExtrasHelper extras) throws JSONException
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
@@ -88,6 +149,10 @@ 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);
|
||||
contentValues.put(LoyaltyCardDbIds.ICON, icon != null ? convertBitmapToBlob(icon) : null);
|
||||
contentValues.put(LoyaltyCardDbIds.EXTRAS, extras.toJSON().toString());
|
||||
int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,
|
||||
LoyaltyCardDbIds.ID + "=?",
|
||||
new String[]{Integer.toString(id)});
|
||||
@@ -124,16 +189,52 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
|
||||
public Cursor getLoyaltyCardCursor()
|
||||
{
|
||||
// An empty string will match everything
|
||||
return getLoyaltyCardCursor("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cursor to all loyalty cards with the filter text in either the store or note.
|
||||
*
|
||||
* @param filter
|
||||
* @return Cursor
|
||||
*/
|
||||
public Cursor getLoyaltyCardCursor(final String filter)
|
||||
{
|
||||
String actualFilter = String.format("%%%s%%", filter);
|
||||
String[] selectionArgs = { actualFilter, actualFilter };
|
||||
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor res = db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
|
||||
" ORDER BY " + LoyaltyCardDbIds.STORE, null);
|
||||
|
||||
Cursor res = db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
|
||||
" WHERE " + LoyaltyCardDbIds.STORE + " LIKE ? " +
|
||||
" OR " + LoyaltyCardDbIds.NOTE + " LIKE ? " +
|
||||
" ORDER BY " + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC", selectionArgs, null);
|
||||
return res;
|
||||
}
|
||||
|
||||
public int getLoyaltyCardCount()
|
||||
{
|
||||
// An empty string will match everything
|
||||
return getLoyaltyCardCount("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of loyalty cards with the filter text in either the store or note.
|
||||
*
|
||||
* @param filter
|
||||
* @return Integer
|
||||
*/
|
||||
public int getLoyaltyCardCount(String filter)
|
||||
{
|
||||
String actualFilter = String.format("%%%s%%", filter);
|
||||
String[] selectionArgs = { actualFilter, actualFilter };
|
||||
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbIds.TABLE, null);
|
||||
Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbIds.TABLE +
|
||||
" WHERE " + LoyaltyCardDbIds.STORE + " LIKE ? " +
|
||||
" OR " + LoyaltyCardDbIds.NOTE + " LIKE ? "
|
||||
, selectionArgs, null);
|
||||
|
||||
int numItems = 0;
|
||||
|
||||
|
||||
77
app/src/main/java/protect/card_locker/ExtrasHelper.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public class ExtrasHelper {
|
||||
private HashMap<String, LinkedHashMap<String, String>> extras = new HashMap<>();
|
||||
|
||||
public ExtrasHelper fromJSON(JSONObject json) throws JSONException
|
||||
{
|
||||
Iterator<String> languages = json.keys();
|
||||
|
||||
while(languages.hasNext())
|
||||
{
|
||||
String language = languages.next();
|
||||
Iterator<String> keys = json.getJSONObject(language).keys();
|
||||
|
||||
while(keys.hasNext())
|
||||
{
|
||||
String key = keys.next();
|
||||
addLanguageValue(language, key, json.getJSONObject(language).getString(key));
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public JSONObject toJSON() throws JSONException
|
||||
{
|
||||
return new JSONObject(extras);
|
||||
}
|
||||
|
||||
public void addLanguageValue(String language, String key, String value)
|
||||
{
|
||||
if(!extras.containsKey(language))
|
||||
{
|
||||
extras.put(language, new LinkedHashMap<String, String>());
|
||||
}
|
||||
|
||||
LinkedHashMap<String, String> values = extras.get(language);
|
||||
values.put(key, value);
|
||||
|
||||
extras.put(language, values);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public LinkedHashMap<String, String> getAllValues(String language)
|
||||
{
|
||||
return getAllValues(new String[]{language});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public LinkedHashMap<String, String> getAllValues(String[] languages)
|
||||
{
|
||||
LinkedHashMap<String, String> values = new LinkedHashMap<>();
|
||||
|
||||
// Get least preferred language (last in list) first
|
||||
// Then go further and further to the start of the list (preferred language)
|
||||
for(int i = (languages.length - 1); i >= 0; i--)
|
||||
{
|
||||
LinkedHashMap<String, String> languageValues = extras.get(languages[i]);
|
||||
|
||||
if(languageValues != null)
|
||||
{
|
||||
values.putAll(languageValues);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,14 @@ import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
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 androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -24,26 +25,29 @@ import android.widget.Button;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class ImportExportActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private static final int PERMISSIONS_EXTERNAL_STORAGE = 1;
|
||||
private static final int CHOOSE_EXPORT_FILE = 2;
|
||||
private static final int CHOOSE_EXPORT_LOCATION = 2;
|
||||
private static final int CHOOSE_EXPORTED_FILE = 3;
|
||||
|
||||
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 = (Toolbar) findViewById(R.id.toolbar);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
@@ -65,33 +69,38 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
PERMISSIONS_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
// Check that there is a file manager available
|
||||
final Intent intentCreateDocumentAction = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
intentCreateDocumentAction.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intentCreateDocumentAction.setType("text/csv");
|
||||
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "Catima.csv");
|
||||
|
||||
Button exportButton = (Button)findViewById(R.id.exportButton);
|
||||
Button exportButton = findViewById(R.id.exportButton);
|
||||
exportButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
startExport();
|
||||
chooseFileWithIntent(intentCreateDocumentAction, CHOOSE_EXPORT_LOCATION);
|
||||
}
|
||||
});
|
||||
|
||||
// Check that there is a file manager available
|
||||
final Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intentGetContentAction.setType("*/*");
|
||||
|
||||
// Check that there is an activity that can bring up a file chooser
|
||||
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
|
||||
intentPickAction.setData(Uri.parse("file://"));
|
||||
|
||||
Button importFilesystem = (Button) findViewById(R.id.importOptionFilesystemButton);
|
||||
Button importFilesystem = findViewById(R.id.importOptionFilesystemButton);
|
||||
importFilesystem.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
chooseFileWithIntent(intentPickAction);
|
||||
chooseFileWithIntent(intentGetContentAction, CHOOSE_EXPORTED_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
if(isCallable(getApplicationContext(), intentPickAction) == false)
|
||||
if(isCallable(getApplicationContext(), intentGetContentAction) == false)
|
||||
{
|
||||
findViewById(R.id.dividerImportFilesystem).setVisibility(View.GONE);
|
||||
findViewById(R.id.importOptionFilesystemTitle).setVisibility(View.GONE);
|
||||
@@ -99,73 +108,57 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
importFilesystem.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Check that there is an app that data can be imported from
|
||||
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
|
||||
|
||||
// Check that there is an application that can find content
|
||||
final Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intentGetContentAction.setType("*/*");
|
||||
|
||||
Button importApplication = (Button) findViewById(R.id.importOptionApplicationButton);
|
||||
Button importApplication = findViewById(R.id.importOptionApplicationButton);
|
||||
importApplication.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
chooseFileWithIntent(intentGetContentAction);
|
||||
chooseFileWithIntent(intentPickAction, CHOOSE_EXPORTED_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
if(isCallable(getApplicationContext(), intentGetContentAction) == false)
|
||||
if(isCallable(getApplicationContext(), intentPickAction) == false)
|
||||
{
|
||||
findViewById(R.id.dividerImportApplication).setVisibility(View.GONE);
|
||||
findViewById(R.id.importOptionApplicationTitle).setVisibility(View.GONE);
|
||||
findViewById(R.id.importOptionApplicationExplanation).setVisibility(View.GONE);
|
||||
importApplication.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
|
||||
// This option, to import from the fixed location, should always be present
|
||||
|
||||
Button importButton = (Button)findViewById(R.id.importOptionFixedButton);
|
||||
importButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
startImport(exportFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startImport(File target)
|
||||
private void startImport(final InputStream target, final Uri targetUri)
|
||||
{
|
||||
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
|
||||
{
|
||||
@Override
|
||||
public void onTaskComplete(boolean success, File file)
|
||||
public void onTaskComplete(boolean success)
|
||||
{
|
||||
onImportComplete(success, file);
|
||||
onImportComplete(success, targetUri);
|
||||
}
|
||||
};
|
||||
|
||||
importExporter = new ImportExportTask(ImportExportActivity.this,
|
||||
true, DataFormat.CSV, target, listener);
|
||||
DataFormat.CSV, target, listener);
|
||||
importExporter.execute();
|
||||
}
|
||||
|
||||
private void startExport()
|
||||
private void startExport(final OutputStream target, final Uri targetUri)
|
||||
{
|
||||
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
|
||||
{
|
||||
@Override
|
||||
public void onTaskComplete(boolean success, File file)
|
||||
public void onTaskComplete(boolean success)
|
||||
{
|
||||
onExportComplete(success, file);
|
||||
onExportComplete(success, targetUri);
|
||||
}
|
||||
};
|
||||
|
||||
importExporter = new ImportExportTask(ImportExportActivity.this,
|
||||
false, DataFormat.CSV, exportFile, listener);
|
||||
DataFormat.CSV, target, listener);
|
||||
importExporter.execute();
|
||||
}
|
||||
|
||||
@@ -220,7 +213,7 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void onImportComplete(boolean success, File path)
|
||||
private void onImportComplete(boolean success, Uri path)
|
||||
{
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
|
||||
@@ -233,10 +226,9 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
builder.setTitle(R.string.importFailedTitle);
|
||||
}
|
||||
|
||||
int messageId = success ? R.string.importedFrom : R.string.importFailed;
|
||||
int messageId = success ? R.string.importSuccessful : R.string.importFailed;
|
||||
final String message = getResources().getString(messageId);
|
||||
|
||||
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()
|
||||
{
|
||||
@@ -250,7 +242,7 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void onExportComplete(boolean success, final File path)
|
||||
private void onExportComplete(boolean success, final Uri path)
|
||||
{
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
|
||||
@@ -263,10 +255,9 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
builder.setTitle(R.string.exportFailedTitle);
|
||||
}
|
||||
|
||||
int messageId = success ? R.string.exportedTo : R.string.exportFailed;
|
||||
int messageId = success ? R.string.exportSuccessful : R.string.exportFailed;
|
||||
final String message = getResources().getString(messageId);
|
||||
|
||||
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()
|
||||
{
|
||||
@@ -286,10 +277,12 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
Uri outputUri = Uri.fromFile(path);
|
||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, outputUri);
|
||||
sendIntent.setType("text/plain");
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
|
||||
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));
|
||||
@@ -327,11 +320,11 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
return false;
|
||||
}
|
||||
|
||||
private void chooseFileWithIntent(Intent intent)
|
||||
private void chooseFileWithIntent(Intent intent, int requestCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
startActivityForResult(intent, CHOOSE_EXPORT_FILE);
|
||||
startActivityForResult(intent, requestCode);
|
||||
}
|
||||
catch (ActivityNotFoundException e)
|
||||
{
|
||||
@@ -344,34 +337,63 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
{
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (resultCode == RESULT_OK && requestCode == CHOOSE_EXPORT_FILE)
|
||||
if (resultCode != RESULT_OK || (requestCode != CHOOSE_EXPORT_LOCATION && requestCode != CHOOSE_EXPORTED_FILE))
|
||||
{
|
||||
String path = null;
|
||||
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = data.getData();
|
||||
if(uri != null && uri.toString().startsWith("/"))
|
||||
{
|
||||
uri = Uri.parse("file://" + uri.toString());
|
||||
}
|
||||
Uri uri = data.getData();
|
||||
if(uri == null)
|
||||
{
|
||||
Log.e(TAG, "Activity returned a NULL URI");
|
||||
return;
|
||||
}
|
||||
|
||||
if(uri != null)
|
||||
try
|
||||
{
|
||||
if (requestCode == CHOOSE_EXPORT_LOCATION)
|
||||
{
|
||||
path = uri.getPath();
|
||||
}
|
||||
OutputStream writer;
|
||||
if (uri.getScheme() != null)
|
||||
{
|
||||
writer = getContentResolver().openOutputStream(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer = new FileOutputStream(new File(uri.toString()));
|
||||
}
|
||||
|
||||
if(path != null)
|
||||
{
|
||||
Log.e(TAG, "Starting file import with: " + uri.toString());
|
||||
startImport(new File(path));
|
||||
Log.e(TAG, "Starting file export with: " + uri.toString());
|
||||
startExport(writer, uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.e(TAG, "Fail to make sense of URI returned from activity: " + (uri != null ? uri.toString() : "null"));
|
||||
InputStream reader;
|
||||
if(uri.getScheme() != null)
|
||||
{
|
||||
reader = getContentResolver().openInputStream(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
reader = new FileInputStream(new File(uri.toString()));
|
||||
}
|
||||
|
||||
Log.e(TAG, "Starting file export with: " + uri.toString());
|
||||
startImport(reader, uri);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch(FileNotFoundException e)
|
||||
{
|
||||
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
|
||||
Log.e(TAG, "Failed to import/export file: " + uri.toString(), e);
|
||||
if (requestCode == CHOOSE_EXPORT_LOCATION)
|
||||
{
|
||||
onExportComplete(false, uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
onImportComplete(false, uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,49 +4,63 @@ 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.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private Activity activity;
|
||||
private boolean doImport;
|
||||
private DataFormat format;
|
||||
private File target;
|
||||
private OutputStream outputStream;
|
||||
private InputStream inputStream;
|
||||
private TaskCompleteListener listener;
|
||||
|
||||
private ProgressDialog progress;
|
||||
|
||||
public ImportExportTask(Activity activity, boolean doImport, DataFormat format, File target,
|
||||
/**
|
||||
* Constructor which will setup a task for exporting to the given file
|
||||
*/
|
||||
ImportExportTask(Activity activity, DataFormat format, OutputStream output,
|
||||
TaskCompleteListener listener)
|
||||
{
|
||||
super();
|
||||
this.activity = activity;
|
||||
this.doImport = doImport;
|
||||
this.doImport = false;
|
||||
this.format = format;
|
||||
this.target = target;
|
||||
this.outputStream = output;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private boolean performImport(File importFile, DBHelper db)
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
FileInputStream fileReader = new FileInputStream(importFile);
|
||||
InputStreamReader reader = new InputStreamReader(fileReader, Charset.forName("UTF-8"));
|
||||
InputStreamReader reader = new InputStreamReader(stream, Charset.forName("UTF-8"));
|
||||
result = MultiFormatImporter.importData(db, reader, format);
|
||||
reader.close();
|
||||
}
|
||||
@@ -55,19 +69,18 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Import of '" + importFile.getAbsolutePath() + "' result: " + result);
|
||||
Log.i(TAG, "Import result: " + result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean performExport(File exportFile, DBHelper db)
|
||||
private boolean performExport(OutputStream stream, DBHelper db)
|
||||
{
|
||||
boolean result = false;
|
||||
|
||||
try
|
||||
{
|
||||
FileOutputStream fileWriter = new FileOutputStream(exportFile);
|
||||
OutputStreamWriter writer = new OutputStreamWriter(fileWriter, Charset.forName("UTF-8"));
|
||||
OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8"));
|
||||
result = MultiFormatExporter.exportData(db, writer, format);
|
||||
writer.close();
|
||||
}
|
||||
@@ -76,7 +89,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
Log.e(TAG, "Unable to export file", e);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Export of '" + exportFile.getAbsolutePath() + "' result: " + result);
|
||||
Log.i(TAG, "Export result: " + result);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -105,11 +118,11 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
|
||||
if(doImport)
|
||||
{
|
||||
result = performImport(target, db);
|
||||
result = performImport(inputStream, db);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = performExport(target, db);
|
||||
result = performExport(outputStream, db);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -117,7 +130,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
|
||||
protected void onPostExecute(Boolean result)
|
||||
{
|
||||
listener.onTaskComplete(result, target);
|
||||
listener.onTaskComplete(result);
|
||||
|
||||
progress.dismiss();
|
||||
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
|
||||
@@ -130,7 +143,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
|
||||
}
|
||||
interface TaskCompleteListener
|
||||
{
|
||||
void onTaskComplete(boolean success, File file);
|
||||
void onTaskComplete(boolean success);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
140
app/src/main/java/protect/card_locker/ImportURIHelper.java
Normal file
@@ -0,0 +1,140 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.util.Base64;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.InvalidObjectException;
|
||||
|
||||
public class ImportURIHelper {
|
||||
private static final String STORE = DBHelper.LoyaltyCardDbIds.STORE;
|
||||
private static final String NOTE = DBHelper.LoyaltyCardDbIds.NOTE;
|
||||
private static final String CARD_ID = DBHelper.LoyaltyCardDbIds.CARD_ID;
|
||||
private static final String BARCODE_TYPE = DBHelper.LoyaltyCardDbIds.BARCODE_TYPE;
|
||||
private static final String HEADER_COLOR = DBHelper.LoyaltyCardDbIds.HEADER_COLOR;
|
||||
private static final String HEADER_TEXT_COLOR = DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR;
|
||||
private static final String ICON = DBHelper.LoyaltyCardDbIds.ICON;
|
||||
private static final String EXTRAS = DBHelper.LoyaltyCardDbIds.EXTRAS;
|
||||
|
||||
private final Context context;
|
||||
private final String host;
|
||||
private final String path;
|
||||
private final String oldHost;
|
||||
private final String oldPath;
|
||||
private final String shareText;
|
||||
|
||||
public ImportURIHelper(Context context) {
|
||||
this.context = context;
|
||||
host = context.getResources().getString(R.string.intent_import_card_from_url_host);
|
||||
path = context.getResources().getString(R.string.intent_import_card_from_url_path_prefix);
|
||||
oldHost = context.getResources().getString(R.string.intent_import_card_from_url_host_old);
|
||||
oldPath = context.getResources().getString(R.string.intent_import_card_from_url_path_prefix_old);
|
||||
shareText = context.getResources().getString(R.string.intent_import_card_from_url_share_text);
|
||||
}
|
||||
|
||||
public boolean isImportUri(Uri uri) {
|
||||
return (uri.getHost().equals(host) && uri.getPath().equals(path)) || (uri.getHost().equals(oldHost) && uri.getPath().equals(oldPath));
|
||||
}
|
||||
|
||||
public LoyaltyCard parse(Uri uri) throws InvalidObjectException {
|
||||
if(!isImportUri(uri)) {
|
||||
throw new InvalidObjectException("Not an import URI");
|
||||
}
|
||||
|
||||
try {
|
||||
// These values are allowed to be null
|
||||
Integer headerColor = null;
|
||||
Integer headerTextColor = null;
|
||||
|
||||
String store = uri.getQueryParameter(STORE);
|
||||
String note = uri.getQueryParameter(NOTE);
|
||||
String cardId = uri.getQueryParameter(CARD_ID);
|
||||
String barcodeType = uri.getQueryParameter(BARCODE_TYPE);
|
||||
String unparsedHeaderColor = uri.getQueryParameter(HEADER_COLOR);
|
||||
if(unparsedHeaderColor != null)
|
||||
{
|
||||
headerColor = Integer.parseInt(unparsedHeaderColor);
|
||||
}
|
||||
String unparsedHeaderTextColor = uri.getQueryParameter(HEADER_TEXT_COLOR);
|
||||
if(unparsedHeaderTextColor != null)
|
||||
{
|
||||
headerTextColor = Integer.parseInt(unparsedHeaderTextColor);
|
||||
}
|
||||
String iconData = uri.getQueryParameter(ICON);
|
||||
Bitmap icon = null;
|
||||
|
||||
if(iconData != null && !iconData.isEmpty())
|
||||
{
|
||||
byte[] iconBytes = Base64.decode(iconData, Base64.URL_SAFE);
|
||||
icon = DBHelper.convertBitmapBlobToBitmap(iconBytes);
|
||||
}
|
||||
|
||||
String extrasData = uri.getQueryParameter(EXTRAS);
|
||||
ExtrasHelper extras = new ExtrasHelper();
|
||||
if(extrasData != null && !extrasData.isEmpty())
|
||||
{
|
||||
extras.fromJSON(new JSONObject(uri.getQueryParameter(EXTRAS)));
|
||||
}
|
||||
|
||||
return new LoyaltyCard(-1, store, note, cardId, barcodeType, headerColor, headerTextColor, icon, extras);
|
||||
} catch (NullPointerException | NumberFormatException | JSONException ex) {
|
||||
throw new InvalidObjectException("Not a valid import URI");
|
||||
}
|
||||
}
|
||||
|
||||
// Protected for usage in tests
|
||||
protected Uri toUri(LoyaltyCard loyaltyCard) throws JSONException {
|
||||
String icon = "";
|
||||
if(loyaltyCard.icon != null)
|
||||
{
|
||||
icon = Base64.encodeToString(DBHelper.convertBitmapToBlob(loyaltyCard.icon), Base64.URL_SAFE);
|
||||
}
|
||||
|
||||
Uri.Builder uriBuilder = new Uri.Builder();
|
||||
uriBuilder.scheme("https");
|
||||
uriBuilder.authority(host);
|
||||
uriBuilder.path(path);
|
||||
uriBuilder.appendQueryParameter(STORE, loyaltyCard.store);
|
||||
uriBuilder.appendQueryParameter(NOTE, loyaltyCard.note);
|
||||
uriBuilder.appendQueryParameter(CARD_ID, loyaltyCard.cardId);
|
||||
uriBuilder.appendQueryParameter(BARCODE_TYPE, loyaltyCard.barcodeType);
|
||||
if(loyaltyCard.headerColor != null)
|
||||
{
|
||||
uriBuilder.appendQueryParameter(HEADER_COLOR, loyaltyCard.headerColor.toString());
|
||||
}
|
||||
if(loyaltyCard.headerTextColor != null)
|
||||
{
|
||||
uriBuilder.appendQueryParameter(HEADER_TEXT_COLOR, loyaltyCard.headerTextColor.toString());
|
||||
}
|
||||
uriBuilder.appendQueryParameter(ICON, icon);
|
||||
uriBuilder.appendQueryParameter(EXTRAS, loyaltyCard.extras.toJSON().toString());
|
||||
|
||||
return uriBuilder.build();
|
||||
}
|
||||
|
||||
private void startShareIntent(Uri uri) {
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText + "\n" + uri.toString());
|
||||
sendIntent.setType("text/plain");
|
||||
|
||||
Intent shareIntent = Intent.createChooser(sendIntent, null);
|
||||
context.startActivity(shareIntent);
|
||||
}
|
||||
|
||||
public boolean startShareIntent(LoyaltyCard loyaltyCard) {
|
||||
try
|
||||
{
|
||||
startShareIntent(toUri(loyaltyCard));
|
||||
return true;
|
||||
}
|
||||
catch (JSONException ex) {}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
import com.github.paolorotolo.appintro.AppIntro;
|
||||
|
||||
|
||||
public class IntroActivity extends AppIntro
|
||||
{
|
||||
@Override
|
||||
public void init(Bundle savedInstanceState)
|
||||
{
|
||||
addIntroSlide(R.layout.intro1_layout);
|
||||
addIntroSlide(R.layout.intro2_layout);
|
||||
addIntroSlide(R.layout.intro3_layout);
|
||||
addIntroSlide(R.layout.intro4_layout);
|
||||
addIntroSlide(R.layout.intro5_layout);
|
||||
addIntroSlide(R.layout.intro6_layout);
|
||||
}
|
||||
|
||||
private void addIntroSlide(@LayoutRes int layout)
|
||||
{
|
||||
Fragment slide = new IntroSlide();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt("layout", layout);
|
||||
slide.setArguments(args);
|
||||
addSlide(slide);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSkipPressed(Fragment fragment) {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDonePressed(Fragment fragment) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class IntroSlide extends Fragment
|
||||
{
|
||||
int _layout;
|
||||
|
||||
@Override
|
||||
public void setArguments(Bundle bundle)
|
||||
{
|
||||
_layout = bundle.getInt("layout");
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
||||
{
|
||||
View v = inflater.inflate(_layout, container, false);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
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,12 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class LoyaltyCard
|
||||
{
|
||||
@@ -10,13 +16,31 @@ 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;
|
||||
|
||||
@Nullable
|
||||
public final Bitmap icon;
|
||||
|
||||
@Nullable
|
||||
public final ExtrasHelper extras;
|
||||
|
||||
public LoyaltyCard(final int id, final String store, final String note, final String cardId,
|
||||
final String barcodeType, final Integer headerColor, final Integer headerTextColor,
|
||||
final Bitmap icon, final ExtrasHelper extras)
|
||||
{
|
||||
this.id = id;
|
||||
this.store = store;
|
||||
this.note = note;
|
||||
this.cardId = cardId;
|
||||
this.barcodeType = barcodeType;
|
||||
this.headerColor = headerColor;
|
||||
this.headerTextColor = headerTextColor;
|
||||
this.icon = icon;
|
||||
this.extras = extras;
|
||||
}
|
||||
|
||||
public static LoyaltyCard toLoyaltyCard(Cursor cursor)
|
||||
@@ -27,6 +51,48 @@ 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);
|
||||
}
|
||||
|
||||
int iconColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ICON);
|
||||
Bitmap icon = null;
|
||||
|
||||
if(cursor.isNull(iconColumn) == false)
|
||||
{
|
||||
byte[] iconData = cursor.getBlob(iconColumn);
|
||||
icon = BitmapFactory.decodeByteArray(iconData, 0, iconData.length);
|
||||
}
|
||||
|
||||
int extrasColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXTRAS);
|
||||
ExtrasHelper extras = new ExtrasHelper();
|
||||
|
||||
if(cursor.isNull(extrasColumn) == false)
|
||||
{
|
||||
try
|
||||
{
|
||||
extras = extras.fromJSON(new JSONObject(cursor.getString(extrasColumn)));
|
||||
}
|
||||
catch (JSONException ex)
|
||||
{
|
||||
// That this is actually JSON is an implementation detail
|
||||
// The important part is that the DB is in a bad state
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return new LoyaltyCard(id, store, note, cardId, barcodeType, headerColor, headerTextColor, icon, extras);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,24 @@ package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.util.Log;
|
||||
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 +36,44 @@ 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);
|
||||
|
||||
if(loyaltyCard.icon == null)
|
||||
{
|
||||
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());
|
||||
}
|
||||
else
|
||||
{
|
||||
thumbnail.setImageBitmap(loyaltyCard.icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,578 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.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;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InvalidObjectException;
|
||||
|
||||
public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "Catima";
|
||||
protected static final String NO_BARCODE = "_NO_BARCODE_";
|
||||
|
||||
protected 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;
|
||||
Uri importLoyaltyCardUri = null;
|
||||
String importLoyaltyCardType = null;
|
||||
Integer headingColorValue = null;
|
||||
Integer headingStoreTextColorValue = null;
|
||||
Bitmap icon = null;
|
||||
ExtrasHelper extras = new ExtrasHelper();
|
||||
|
||||
DBHelper db;
|
||||
ImportURIHelper importUriHelper;
|
||||
PkpassImporter pkpassImporter;
|
||||
|
||||
private void extractIntentFields(Intent intent)
|
||||
{
|
||||
final Bundle b = intent.getExtras();
|
||||
loyaltyCardId = b != null ? b.getInt("id") : 0;
|
||||
updateLoyaltyCard = b != null && b.getBoolean("update", false);
|
||||
importLoyaltyCardType = intent.getType();
|
||||
importLoyaltyCardUri = intent.getData();
|
||||
|
||||
Log.d(TAG, "View activity: id=" + loyaltyCardId
|
||||
+ ", updateLoyaltyCard=" + Boolean.toString(updateLoyaltyCard));
|
||||
}
|
||||
|
||||
private LoyaltyCard importCard(String type, Uri uri)
|
||||
{
|
||||
// Import URI
|
||||
if(importUriHelper.isImportUri(uri))
|
||||
{
|
||||
try
|
||||
{
|
||||
return importUriHelper.parse(importLoyaltyCardUri);
|
||||
}
|
||||
catch (InvalidObjectException ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Pkpass
|
||||
try
|
||||
{
|
||||
return pkpassImporter.fromURI(uri);
|
||||
}
|
||||
catch (IOException | JSONException ex) {}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@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);
|
||||
importUriHelper = new ImportURIHelper(this);
|
||||
pkpassImporter = new PkpassImporter(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.barcodeTypeView);
|
||||
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)
|
||||
{
|
||||
super.onNewIntent(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.isEmpty() ? LoyaltyCardEditActivity.NO_BARCODE : loyaltyCard.barcodeType);
|
||||
}
|
||||
|
||||
if(headingColorValue == null)
|
||||
{
|
||||
headingColorValue = loyaltyCard.headerColor;
|
||||
if(headingColorValue == null)
|
||||
{
|
||||
headingColorValue = LetterBitmap.getDefaultColor(this, loyaltyCard.store);
|
||||
}
|
||||
}
|
||||
|
||||
if(headingStoreTextColorValue == null)
|
||||
{
|
||||
headingStoreTextColorValue = loyaltyCard.headerTextColor;
|
||||
if(headingStoreTextColorValue == null)
|
||||
{
|
||||
headingStoreTextColorValue = Color.WHITE;
|
||||
}
|
||||
}
|
||||
|
||||
extras = loyaltyCard.extras;
|
||||
|
||||
setTitle(R.string.editCardTitle);
|
||||
}
|
||||
else if(importLoyaltyCardUri != null)
|
||||
{
|
||||
// Try to parse
|
||||
LoyaltyCard importCard = importCard(importLoyaltyCardType, importLoyaltyCardUri);
|
||||
|
||||
if(importCard == null)
|
||||
{
|
||||
Toast.makeText(this, R.string.failedParsingImportUriError, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
storeFieldEdit.setText(importCard.store);
|
||||
noteFieldEdit.setText(importCard.note);
|
||||
cardIdFieldView.setText(importCard.cardId);
|
||||
barcodeTypeField.setText(importCard.barcodeType);
|
||||
headingColorValue = importCard.headerColor;
|
||||
headingStoreTextColorValue = importCard.headerTextColor;
|
||||
icon = importCard.icon;
|
||||
extras = importCard.extras;
|
||||
}
|
||||
else
|
||||
{
|
||||
setTitle(R.string.addCardTitle);
|
||||
hideBarcode();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if(headingStoreTextColorValue == null) {
|
||||
headingStoreTextColorValue = Color.WHITE;
|
||||
}
|
||||
|
||||
headingColorSample.setBackgroundColor(headingColorValue);
|
||||
headingStoreTextColorSample.setBackgroundColor(headingStoreTextColorValue);
|
||||
headingColorSelectButton.setOnClickListener(new ColorSelectListener(headingColorValue, true));
|
||||
headingStoreTextColorSelectButton.setOnClickListener(new ColorSelectListener(headingStoreTextColorValue, false));
|
||||
|
||||
if(cardIdFieldView.getText().length() > 0 && barcodeTypeField.getText().length() > 0)
|
||||
{
|
||||
if(barcodeTypeField.getText().toString().equals(NO_BARCODE))
|
||||
{
|
||||
hideBarcode();
|
||||
}
|
||||
else
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
showBarcode();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
FloatingActionButton saveButton = findViewById(R.id.fabSave);
|
||||
saveButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
doSave();
|
||||
} catch (JSONException ex) {
|
||||
Toast.makeText(getApplicationContext(), R.string.failedSavingCard, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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() throws JSONException
|
||||
{
|
||||
String store = storeFieldEdit.getText().toString();
|
||||
String note = noteFieldEdit.getText().toString();
|
||||
String cardId = cardIdFieldView.getText().toString();
|
||||
String barcodeType = barcodeTypeField.getText().toString();
|
||||
|
||||
// We do not want to save the no barcode string to the database
|
||||
// it is simply an empty there for no barcode
|
||||
if(barcodeType.equals(NO_BARCODE))
|
||||
{
|
||||
barcodeType = "";
|
||||
}
|
||||
|
||||
if(store.isEmpty())
|
||||
{
|
||||
Snackbar.make(storeFieldEdit, R.string.noStoreError, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if(cardId.isEmpty())
|
||||
{
|
||||
Snackbar.make(cardIdFieldView, R.string.noCardIdError, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if(updateLoyaltyCard)
|
||||
{
|
||||
db.updateLoyaltyCard(loyaltyCardId, store, note, cardId, barcodeType, headingColorValue, headingStoreTextColorValue, icon, extras);
|
||||
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
|
||||
}
|
||||
else
|
||||
{
|
||||
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, cardId, barcodeType, headingColorValue, headingStoreTextColorValue, icon, extras);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent)
|
||||
{
|
||||
super.onActivityResult(requestCode, resultCode, 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)
|
||||
{
|
||||
Log.i(TAG, "Read barcode id: " + contents);
|
||||
Log.i(TAG, "Read format: " + format);
|
||||
|
||||
TextView cardIdView = findViewById(R.id.cardIdView);
|
||||
cardIdView.setText(contents);
|
||||
|
||||
// Set special NO_BARCODE value to prevent onResume from overwriting it
|
||||
barcodeTypeField.setText(format.isEmpty() ? LoyaltyCardEditActivity.NO_BARCODE : format);
|
||||
onResume();
|
||||
}
|
||||
}
|
||||
|
||||
private void showBarcode() {
|
||||
barcodeImageLayout.setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.barcodeTypeDivider).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.barcodeTypeTableRow).setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void hideBarcode() {
|
||||
barcodeImageLayout.setVisibility(View.GONE);
|
||||
findViewById(R.id.barcodeTypeDivider).setVisibility(View.GONE);
|
||||
findViewById(R.id.barcodeTypeTableRow).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Application;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
public class LoyaltyCardLockerApplication extends Application {
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Settings settings = new Settings(getApplicationContext());
|
||||
AppCompatDelegate.setDefaultNightMode(settings.getTheme());
|
||||
}
|
||||
}
|
||||
@@ -1,67 +1,108 @@
|
||||
package protect.card_locker;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
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 androidx.appcompat.app.AlertDialog;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.core.widget.TextViewCompat;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
|
||||
public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "CardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
private static final double LUMINANCE_MIDPOINT = 0.5;
|
||||
|
||||
private static final int SELECT_BARCODE_REQUEST = 1;
|
||||
|
||||
EditText storeFieldEdit;
|
||||
TextView storeFieldView;
|
||||
EditText noteFieldEdit;
|
||||
TextView noteFieldView;
|
||||
TextView cardIdFieldView;
|
||||
View cardIdDivider;
|
||||
View cardIdTableRow;
|
||||
TextView barcodeTypeField;
|
||||
TextView noteView;
|
||||
View noteViewDivider;
|
||||
TextView storeName;
|
||||
ImageView barcodeImage;
|
||||
View barcodeImageLayout;
|
||||
View barcodeCaptureLayout;
|
||||
|
||||
Button captureButton;
|
||||
Button enterButton;
|
||||
|
||||
View collapsingToolbarLayout;
|
||||
int loyaltyCardId;
|
||||
boolean updateLoyaltyCard;
|
||||
boolean viewLoyaltyCard;
|
||||
|
||||
LoyaltyCard loyaltyCard;
|
||||
boolean rotationEnabled;
|
||||
DBHelper db;
|
||||
ImportURIHelper importURIHelper;
|
||||
Settings settings;
|
||||
|
||||
String cardIdString;
|
||||
BarcodeFormat format;
|
||||
|
||||
boolean backgroundNeedsDarkIcons;
|
||||
boolean barcodeIsFullscreen = false;
|
||||
ViewGroup.LayoutParams barcodeImageState;
|
||||
|
||||
private void extractIntentFields(Intent intent)
|
||||
{
|
||||
final Bundle b = intent.getExtras();
|
||||
loyaltyCardId = b != null ? b.getInt("id") : 0;
|
||||
Log.d(TAG, "View activity: id=" + loyaltyCardId);
|
||||
}
|
||||
|
||||
private Drawable getIcon(int icon, boolean dark)
|
||||
{
|
||||
Drawable unwrappedIcon = AppCompatResources.getDrawable(this, icon);
|
||||
Drawable wrappedIcon = DrawableCompat.wrap(unwrappedIcon);
|
||||
if(dark)
|
||||
{
|
||||
DrawableCompat.setTint(wrappedIcon, Color.BLACK);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawableCompat.setTintList(wrappedIcon, null);
|
||||
}
|
||||
|
||||
return wrappedIcon;
|
||||
}
|
||||
|
||||
@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)
|
||||
@@ -69,12 +110,41 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
final Bundle b = getIntent().getExtras();
|
||||
loyaltyCardId = b != null ? b.getInt("id") : 0;
|
||||
updateLoyaltyCard = b != null && b.getBoolean("update", false);
|
||||
viewLoyaltyCard = b != null && b.getBoolean("view", false);
|
||||
|
||||
db = new DBHelper(this);
|
||||
importURIHelper = new ImportURIHelper(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;
|
||||
|
||||
// Allow making barcode fullscreen on tap
|
||||
barcodeImage.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if(barcodeIsFullscreen)
|
||||
{
|
||||
setFullscreen(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
setFullscreen(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent)
|
||||
{
|
||||
super.onNewIntent(intent);
|
||||
|
||||
Log.i(TAG, "Received new intent");
|
||||
extractIntentFields(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,227 +154,186 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
|
||||
Log.i(TAG, "To view card: " + loyaltyCardId);
|
||||
|
||||
if(viewLoyaltyCard)
|
||||
if(barcodeIsFullscreen)
|
||||
{
|
||||
// The brightness value is on a scale from [0, ..., 1], where
|
||||
// '1' is the brightest. We attempt to maximize the brightness
|
||||
// to help barcode readers scan the barcode.
|
||||
Window window = getWindow();
|
||||
if(window != null)
|
||||
{
|
||||
WindowManager.LayoutParams attributes = window.getAttributes();
|
||||
attributes.screenBrightness = 1F;
|
||||
window.setAttributes(attributes);
|
||||
}
|
||||
// Completely reset state
|
||||
//
|
||||
// This prevents the barcode from taking up the entire screen
|
||||
// on resume and thus being stretched out of proportion.
|
||||
recreate();
|
||||
}
|
||||
|
||||
storeFieldEdit = (EditText) findViewById(R.id.storeNameEdit);
|
||||
storeFieldView = (TextView) findViewById(R.id.storeNameView);
|
||||
noteFieldEdit = (EditText) findViewById(R.id.noteEdit);
|
||||
noteFieldView = (TextView) findViewById(R.id.noteView);
|
||||
cardIdFieldView = (TextView) findViewById(R.id.cardIdView);
|
||||
cardIdDivider = findViewById(R.id.cardIdDivider);
|
||||
cardIdTableRow = findViewById(R.id.cardIdTableRow);
|
||||
barcodeTypeField = (TextView) findViewById(R.id.barcodeType);
|
||||
barcodeImage = (ImageView) findViewById(R.id.barcode);
|
||||
barcodeImageLayout = findViewById(R.id.barcodeLayout);
|
||||
barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
|
||||
|
||||
captureButton = (Button) findViewById(R.id.captureButton);
|
||||
enterButton = (Button) findViewById(R.id.enterButton);
|
||||
|
||||
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(storeFieldEdit.getText().length() == 0)
|
||||
{
|
||||
storeFieldEdit.setText(loyaltyCard.store);
|
||||
storeFieldView.setText(loyaltyCard.store);
|
||||
}
|
||||
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(noteFieldEdit.getText().length() == 0)
|
||||
{
|
||||
noteFieldEdit.setText(loyaltyCard.note);
|
||||
noteFieldView.setText(loyaltyCard.note);
|
||||
}
|
||||
String formatString = loyaltyCard.barcodeType;
|
||||
format = !formatString.isEmpty() ? BarcodeFormat.valueOf(formatString) : null;
|
||||
cardIdString = loyaltyCard.cardId;
|
||||
|
||||
if(cardIdFieldView.getText().length() == 0)
|
||||
{
|
||||
cardIdFieldView.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(updateLoyaltyCard)
|
||||
{
|
||||
setTitle(R.string.editCardTitle);
|
||||
|
||||
storeFieldView.setVisibility(View.GONE);
|
||||
noteFieldView.setVisibility(View.GONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
barcodeCaptureLayout.setVisibility(View.GONE);
|
||||
captureButton.setVisibility(View.GONE);
|
||||
setTitle(R.string.viewCardTitle);
|
||||
|
||||
storeFieldEdit.setVisibility(View.GONE);
|
||||
noteFieldEdit.setVisibility(View.GONE);
|
||||
}
|
||||
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);
|
||||
|
||||
storeFieldView.setVisibility(View.GONE);
|
||||
noteFieldView.setVisibility(View.GONE);
|
||||
noteView.setVisibility(View.GONE);
|
||||
noteViewDivider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
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();
|
||||
storeName.setText(loyaltyCard.store);
|
||||
storeName.setTextSize(settings.getCardTitleFontSize());
|
||||
|
||||
int textColor;
|
||||
if(loyaltyCard.headerTextColor != null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
collapsingToolbarLayout.setBackgroundColor(backgroundHeaderColor);
|
||||
|
||||
// If the background is very bright, we should use dark icons
|
||||
backgroundNeedsDarkIcons = (ColorUtils.calculateLuminance(backgroundHeaderColor) > LUMINANCE_MIDPOINT);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setHomeAsUpIndicator(getIcon(R.drawable.ic_arrow_back_white, backgroundNeedsDarkIcons));
|
||||
}
|
||||
|
||||
// Make notification area light if dark icons are needed
|
||||
if(Build.VERSION.SDK_INT >= 23)
|
||||
{
|
||||
window.getDecorView().setSystemUiVisibility(backgroundNeedsDarkIcons ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0);
|
||||
}
|
||||
if(Build.VERSION.SDK_INT >= 21)
|
||||
{
|
||||
window.setStatusBarColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
// Set shadow colour of store text so even same color on same color would be readable
|
||||
storeName.setShadowLayer(1, 1, 1, backgroundNeedsDarkIcons ? Color.BLACK : Color.WHITE);
|
||||
|
||||
if(format != null)
|
||||
{
|
||||
findViewById(R.id.barcode).setVisibility(View.VISIBLE);
|
||||
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()
|
||||
new ViewTreeObserver.OnGlobalLayoutListener()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT < 16)
|
||||
{
|
||||
barcodeImage.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
else
|
||||
@Override
|
||||
public void onGlobalLayout()
|
||||
{
|
||||
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
|
||||
Log.d(TAG, "ImageView size now known");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
findViewById(R.id.barcode).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
View.OnClickListener captureCallback = new View.OnClickListener()
|
||||
{
|
||||
FloatingActionButton editButton = findViewById(R.id.fabEdit);
|
||||
editButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
IntentIntegrator integrator = new IntentIntegrator(LoyaltyCardViewActivity.this);
|
||||
integrator.setDesiredBarcodeFormats(BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES);
|
||||
|
||||
String prompt = getResources().getString(R.string.scanCardBarcode);
|
||||
integrator.setPrompt(prompt);
|
||||
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);
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt("id", loyaltyCardId);
|
||||
bundle.putBoolean("update", true);
|
||||
intent.putExtras(bundle);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (barcodeIsFullscreen)
|
||||
{
|
||||
Snackbar.make(storeFieldEdit, R.string.noStoreError, Snackbar.LENGTH_LONG).show();
|
||||
setFullscreen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if(cardId.isEmpty() || barcodeType.isEmpty())
|
||||
{
|
||||
Snackbar.make(cardIdFieldView, R.string.noCardIdError, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if(updateLoyaltyCard)
|
||||
{
|
||||
db.updateLoyaltyCard(loyaltyCardId, store, note, cardId, barcodeType);
|
||||
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
|
||||
}
|
||||
else
|
||||
{
|
||||
db.insertLoyaltyCard(store, note, cardId, barcodeType);
|
||||
}
|
||||
|
||||
finish();
|
||||
super.onBackPressed();
|
||||
return;
|
||||
}
|
||||
|
||||
@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)
|
||||
// Always calculate lockscreen icon, it may need a black color
|
||||
boolean lockBarcodeScreenOrientation = settings.getLockBarcodeScreenOrientation();
|
||||
MenuItem item = menu.findItem(R.id.action_lock_unlock);
|
||||
setOrientatonLock(item, lockBarcodeScreenOrientation);
|
||||
if(lockBarcodeScreenOrientation)
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.card_view_menu, menu);
|
||||
item.setVisible(false);
|
||||
}
|
||||
else if(updateLoyaltyCard)
|
||||
|
||||
if(loyaltyCard != null && !loyaltyCard.extras.getAllValues(new String[]{Locale.getDefault().getLanguage(), "en", ""}).isEmpty())
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.card_update_menu, menu);
|
||||
}
|
||||
else
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.card_add_menu, menu);
|
||||
MenuItem extraItem = menu.findItem(R.id.action_view_extras);
|
||||
extraItem.setIcon(getIcon(R.drawable.ic_info_outline_white, backgroundNeedsDarkIcons));
|
||||
extraItem.setVisible(true);
|
||||
}
|
||||
|
||||
menu.findItem(R.id.action_share).setIcon(getIcon(R.drawable.ic_share_white, backgroundNeedsDarkIcons));
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@@ -313,97 +342,155 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
{
|
||||
int id = item.getItemId();
|
||||
|
||||
final Bundle b = getIntent().getExtras();
|
||||
final int loyaltyCardId = b != null ? b.getInt("id") : 0;
|
||||
|
||||
switch(id)
|
||||
{
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
break;
|
||||
|
||||
case R.id.action_delete:
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.deleteTitle);
|
||||
builder.setMessage(R.string.deleteConfirmation);
|
||||
builder.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
Log.e(TAG, "Deleting card: " + loyaltyCardId);
|
||||
|
||||
DBHelper db = new DBHelper(LoyaltyCardViewActivity.this);
|
||||
db.deleteLoyaltyCard(loyaltyCardId);
|
||||
finish();
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
|
||||
return true;
|
||||
case R.id.action_edit:
|
||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardViewActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt("id", loyaltyCardId);
|
||||
bundle.putBoolean("update", true);
|
||||
intent.putExtras(bundle);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
case R.id.action_share:
|
||||
importURIHelper.startShareIntent(loyaltyCard);
|
||||
return true;
|
||||
|
||||
case R.id.action_save:
|
||||
doSave();
|
||||
case R.id.action_lock_unlock:
|
||||
if(rotationEnabled)
|
||||
{
|
||||
setOrientatonLock(item, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
setOrientatonLock(item, false);
|
||||
}
|
||||
rotationEnabled = !rotationEnabled;
|
||||
return true;
|
||||
|
||||
case R.id.action_view_extras:
|
||||
try
|
||||
{
|
||||
displayExtrasDialog();
|
||||
}
|
||||
catch (JSONException ex)
|
||||
{
|
||||
Toast.makeText(this, R.string.failedShowingExtras, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent)
|
||||
private void setOrientatonLock(MenuItem item, boolean lock)
|
||||
{
|
||||
String contents = null;
|
||||
String format = null;
|
||||
|
||||
IntentResult result =
|
||||
IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
||||
if (result != null)
|
||||
if(lock)
|
||||
{
|
||||
Log.i(TAG, "Received barcode information from capture");
|
||||
contents = result.getContents();
|
||||
format = result.getFormatName();
|
||||
|
||||
item.setIcon(getIcon(R.drawable.ic_lock_outline_white_24dp, backgroundNeedsDarkIcons));
|
||||
item.setTitle(R.string.unlockScreen);
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
|
||||
}
|
||||
|
||||
if(requestCode == SELECT_BARCODE_REQUEST && resultCode == Activity.RESULT_OK)
|
||||
else
|
||||
{
|
||||
Log.i(TAG, "Received barcode information from capture");
|
||||
|
||||
contents = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_CONTENTS);
|
||||
format = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_FORMAT);
|
||||
}
|
||||
|
||||
if(contents != null && contents.isEmpty() == false &&
|
||||
format != null && format.isEmpty() == false)
|
||||
{
|
||||
Log.i(TAG, "Read barcode id: " + contents);
|
||||
Log.i(TAG, "Read format: " + format);
|
||||
|
||||
TextView cardIdView = (TextView)findViewById(R.id.cardIdView);
|
||||
cardIdView.setText(contents);
|
||||
|
||||
final TextView barcodeTypeField = (TextView) findViewById(R.id.barcodeType);
|
||||
barcodeTypeField.setText(format);
|
||||
onResume();
|
||||
item.setIcon(getIcon(R.drawable.ic_lock_open_white_24dp, backgroundNeedsDarkIcons));
|
||||
item.setTitle(R.string.lockScreen);
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void displayExtrasDialog() throws JSONException
|
||||
{
|
||||
StringBuilder items = new StringBuilder();
|
||||
|
||||
HashMap<String, String> extraValues = loyaltyCard.extras.getAllValues(new String[]{Locale.getDefault().getLanguage(), "en", ""});
|
||||
for(Map.Entry<String, String> entry : extraValues.entrySet())
|
||||
{
|
||||
items.append(entry.getValue() + "\n");
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(items.toString())
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* When enabled, hides the status bar and moves the barcode to the top of the screen.
|
||||
*
|
||||
* The purpose of this function is to make sure the barcode can be scanned from the phone
|
||||
* by machines which offer no space to insert the complete device.
|
||||
*/
|
||||
private void setFullscreen(boolean enable)
|
||||
{
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(enable && !barcodeIsFullscreen)
|
||||
{
|
||||
// Save previous barcodeImage state
|
||||
barcodeImageState = barcodeImage.getLayoutParams();
|
||||
|
||||
// Hide actionbar
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.hide();
|
||||
}
|
||||
|
||||
// Hide collapsingToolbar
|
||||
collapsingToolbarLayout.setVisibility(View.GONE);
|
||||
|
||||
// Set Android to fullscreen mode
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
getWindow().getDecorView().getSystemUiVisibility()
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
|
||||
// Make barcode take all space
|
||||
barcodeImage.setLayoutParams(new ConstraintLayout.LayoutParams(
|
||||
ConstraintLayout.LayoutParams.MATCH_PARENT,
|
||||
ConstraintLayout.LayoutParams.MATCH_PARENT
|
||||
));
|
||||
|
||||
// Move barcode to top
|
||||
barcodeImage.setScaleType(ImageView.ScaleType.FIT_START);
|
||||
|
||||
// Prevent centering
|
||||
barcodeImage.setAdjustViewBounds(false);
|
||||
|
||||
// Set current state
|
||||
barcodeIsFullscreen = true;
|
||||
}
|
||||
else if(!enable && barcodeIsFullscreen)
|
||||
{
|
||||
// Show actionbar
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.show();
|
||||
}
|
||||
|
||||
// Show collapsingToolbar
|
||||
collapsingToolbarLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
// Unset fullscreen mode
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
getWindow().getDecorView().getSystemUiVisibility()
|
||||
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
|
||||
// Turn barcode back to normal
|
||||
barcodeImage.setLayoutParams(barcodeImageState);
|
||||
|
||||
// Fix barcode centering
|
||||
barcodeImage.setAdjustViewBounds(true);
|
||||
|
||||
// Set current state
|
||||
barcodeIsFullscreen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
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.content.res.Configuration;
|
||||
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 androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
@@ -24,58 +28,126 @@ import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Map;
|
||||
|
||||
import protect.card_locker.preferences.SettingsActivity;
|
||||
|
||||
public class MainActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
private static final int MAIN_REQUEST_CODE = 1;
|
||||
|
||||
private Menu menu;
|
||||
protected String filter = "";
|
||||
|
||||
@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();
|
||||
updateLoyaltyCardList("");
|
||||
}
|
||||
|
||||
SharedPreferences prefs = getSharedPreferences("protect.card_locker", MODE_PRIVATE);
|
||||
if (prefs.getBoolean("firstrun", true)) {
|
||||
startIntro();
|
||||
prefs.edit().putBoolean("firstrun", false).commit();
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (menu != null)
|
||||
{
|
||||
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
|
||||
|
||||
if (!searchView.isIconified()) {
|
||||
filter = searchView.getQuery().toString();
|
||||
}
|
||||
}
|
||||
|
||||
updateLoyaltyCardList(filter);
|
||||
|
||||
FloatingActionButton addButton = findViewById(R.id.fabAdd);
|
||||
addButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
startActivityForResult(i, MAIN_REQUEST_CODE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == MAIN_REQUEST_CODE)
|
||||
{
|
||||
// We're coming back from another view so clear the search
|
||||
// We only do this now to prevent a flash of all entries right after picking one
|
||||
filter = "";
|
||||
if (menu != null)
|
||||
{
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
searchItem.collapseActionView();
|
||||
}
|
||||
|
||||
// In case the theme changed
|
||||
recreate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
public void onBackPressed() {
|
||||
if (menu == null)
|
||||
{
|
||||
super.onBackPressed();
|
||||
return;
|
||||
}
|
||||
|
||||
updateLoyaltyCardList();
|
||||
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
|
||||
|
||||
if (!searchView.isIconified()) {
|
||||
searchView.setIconified(true);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLoyaltyCardList()
|
||||
private void updateLoyaltyCardList(String filterText)
|
||||
{
|
||||
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 TextView noMatchingCardsText = findViewById(R.id.noMatchingCardsText);
|
||||
final DBHelper db = new DBHelper(this);
|
||||
|
||||
if(db.getLoyaltyCardCount() > 0)
|
||||
{
|
||||
// We want the cardList to be visible regardless of the filtered match count
|
||||
// to ensure that the noMatchingCardsText doesn't end up being shown below
|
||||
// the keyboard
|
||||
cardList.setVisibility(View.VISIBLE);
|
||||
helpText.setVisibility(View.GONE);
|
||||
if(db.getLoyaltyCardCount(filterText) > 0)
|
||||
{
|
||||
noMatchingCardsText.setVisibility(View.GONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
noMatchingCardsText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cardList.setVisibility(View.GONE);
|
||||
helpText.setVisibility(View.VISIBLE);
|
||||
noMatchingCardsText.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
Cursor cardCursor = db.getLoyaltyCardCursor();
|
||||
Cursor cardCursor = db.getLoyaltyCardCursor(filterText);
|
||||
|
||||
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
|
||||
cardList.setAdapter(adapter);
|
||||
@@ -91,11 +163,14 @@ public class MainActivity extends AppCompatActivity
|
||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
|
||||
|
||||
Intent i = new Intent(view.getContext(), LoyaltyCardViewActivity.class);
|
||||
i.setAction("");
|
||||
final Bundle b = new Bundle();
|
||||
b.putInt("id", loyaltyCard.id);
|
||||
b.putBoolean("view", true);
|
||||
i.putExtras(b);
|
||||
startActivity(i);
|
||||
|
||||
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard, i);
|
||||
|
||||
startActivityForResult(i, MAIN_REQUEST_CODE);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -115,19 +190,31 @@ public class MainActivity extends AppCompatActivity
|
||||
public boolean onContextItemSelected(MenuItem item)
|
||||
{
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
|
||||
ListView listView = (ListView) findViewById(R.id.list);
|
||||
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)
|
||||
if(card != null)
|
||||
{
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(card.store, card.cardId);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
if(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;
|
||||
Toast.makeText(this, R.string.copy_to_clipboard_toast, Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
else if(item.getItemId() == R.id.action_share)
|
||||
{
|
||||
final ImportURIHelper importURIHelper = new ImportURIHelper(this);
|
||||
if(importURIHelper.startShareIntent(card))
|
||||
{
|
||||
Toast.makeText(this, R.string.failedSharingCard, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onContextItemSelected(item);
|
||||
@@ -136,7 +223,39 @@ public class MainActivity extends AppCompatActivity
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
this.menu = menu;
|
||||
|
||||
getMenuInflater().inflate(R.menu.main_menu, menu);
|
||||
|
||||
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
|
||||
if (searchManager != null) {
|
||||
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
|
||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
|
||||
searchView.setSubmitButtonEnabled(false);
|
||||
|
||||
searchView.setOnCloseListener(new SearchView.OnCloseListener() {
|
||||
@Override
|
||||
public boolean onClose() {
|
||||
invalidateOptionsMenu();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
filter = newText;
|
||||
updateLoyaltyCardList(newText);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@@ -145,23 +264,17 @@ public class MainActivity extends AppCompatActivity
|
||||
{
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == R.id.action_add)
|
||||
{
|
||||
Intent i = new Intent(getApplicationContext(), LoyaltyCardViewActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(id == R.id.action_import_export)
|
||||
{
|
||||
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
|
||||
startActivity(i);
|
||||
startActivityForResult(i, MAIN_REQUEST_CODE);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(id == R.id.action_intro)
|
||||
if(id == R.id.action_settings)
|
||||
{
|
||||
startIntro();
|
||||
Intent i = new Intent(getApplicationContext(), SettingsActivity.class);
|
||||
startActivityForResult(i, MAIN_REQUEST_CODE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -176,14 +289,15 @@ public class MainActivity extends AppCompatActivity
|
||||
|
||||
private void displayAboutDialog()
|
||||
{
|
||||
final Map<String, String> USED_LIBRARIES = ImmutableMap.of
|
||||
(
|
||||
"Commons CSV", "https://commons.apache.org/proper/commons-csv/",
|
||||
"Guava", "https://github.com/google/guava",
|
||||
"ZXing", "https://github.com/zxing/zxing",
|
||||
"ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded",
|
||||
"AppIntro", "https://github.com/apl-devs/AppIntro"
|
||||
);
|
||||
final Map<String, String> USED_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
|
||||
(
|
||||
@@ -219,9 +333,19 @@ public class MainActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
WebView wv = new WebView(this);
|
||||
|
||||
// Set CSS for dark mode if dark mode
|
||||
String css = "";
|
||||
Configuration config = getResources().getConfiguration();
|
||||
int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||
if(currentNightMode == Configuration.UI_MODE_NIGHT_YES)
|
||||
{
|
||||
css = "<style>body {color:white; background-color:black;}</style>";
|
||||
}
|
||||
|
||||
String html =
|
||||
"<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />" +
|
||||
"<img src=\"file:///android_res/mipmap/ic_launcher.png\" alt=\"" + appName + "\"/>" +
|
||||
css +
|
||||
"<h1>" +
|
||||
String.format(getString(R.string.about_title_fmt),
|
||||
"<a href=\"" + getString(R.string.app_webpage_url)) + "\">" +
|
||||
@@ -234,10 +358,12 @@ public class MainActivity extends AppCompatActivity
|
||||
"</p><p>" +
|
||||
String.format(getString(R.string.app_revision_fmt),
|
||||
"<a href=\"" + getString(R.string.app_revision_url) + "\">" +
|
||||
getString(R.string.app_revision_url) +
|
||||
"GitHub" +
|
||||
"</a>") +
|
||||
"</p><hr/><p>" +
|
||||
String.format(getString(R.string.app_copyright_fmt), year) +
|
||||
"</p><p>" +
|
||||
getString(R.string.app_copyright_old) +
|
||||
"</p><hr/><p>" +
|
||||
getString(R.string.app_license) +
|
||||
"</p><hr/><p>" +
|
||||
@@ -258,10 +384,4 @@ public class MainActivity extends AppCompatActivity
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void startIntro()
|
||||
{
|
||||
Intent intent = new Intent(this, IntroActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import java.io.OutputStreamWriter;
|
||||
|
||||
public class MultiFormatExporter
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
/**
|
||||
* Attempts to export data to the output stream in the
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.io.InputStreamReader;
|
||||
|
||||
public class MultiFormatImporter
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
/**
|
||||
* Attempts to import data from the input stream of the
|
||||
|
||||
319
app/src/main/java/protect/card_locker/PkpassImporter.java
Normal file
@@ -0,0 +1,319 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class PkpassImporter {
|
||||
private Context context;
|
||||
|
||||
private Bitmap icon = null;
|
||||
|
||||
private HashMap<String, HashMap<String, String>> translations = new HashMap<>();
|
||||
|
||||
public PkpassImporter(Context context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private ByteArrayOutputStream readZipInputStream(ZipInputStream zipInputStream) throws IOException
|
||||
{
|
||||
byte[] buffer = new byte[2048];
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
int size;
|
||||
|
||||
while((size = zipInputStream.read(buffer, 0, buffer.length)) != -1)
|
||||
{
|
||||
byteArrayOutputStream.write(buffer, 0, size);
|
||||
}
|
||||
|
||||
return byteArrayOutputStream;
|
||||
}
|
||||
|
||||
private void loadBitmap(ByteArrayOutputStream byteArrayOutputStream)
|
||||
{
|
||||
byte[] bytes = byteArrayOutputStream.toByteArray();
|
||||
// Only keep the largest icon
|
||||
if(icon != null && bytes.length < icon.getByteCount())
|
||||
{
|
||||
return;
|
||||
}
|
||||
icon = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
// FIXME: Probably very fragile
|
||||
private void parseTranslations(String language, String iOSTranslationFileContent)
|
||||
{
|
||||
if(!translations.containsKey(language))
|
||||
{
|
||||
translations.put(language, new HashMap<String, String>());
|
||||
}
|
||||
|
||||
HashMap<String, String> values = translations.get(language);
|
||||
|
||||
for (String entry : iOSTranslationFileContent.trim().split(";"))
|
||||
{
|
||||
String[] parts = entry.split(" = ", 2);
|
||||
|
||||
// Remove all spaces around the key and value
|
||||
String key = parts[0].trim();
|
||||
String value = parts[1].trim();
|
||||
|
||||
// iOS .string files quote everything in double quotes, we don't need to keep those
|
||||
values.put(key.substring(1, key.length()-1), value.substring(1, value.length()-1));
|
||||
}
|
||||
|
||||
translations.put(language, values);
|
||||
}
|
||||
|
||||
private ExtrasHelper appendData(ExtrasHelper extrasHelper, JSONObject pkpassJSON, String styleKey, String arrayName) throws JSONException
|
||||
{
|
||||
// https://developer.apple.com/library/archive/documentation/UserExperience/Reference/PassKit_Bundle/Chapters/FieldDictionary.html#//apple_ref/doc/uid/TP40012026-CH4-SW1
|
||||
|
||||
JSONArray fields;
|
||||
// These are all optional, so don't throw an exception if they don't exist
|
||||
try
|
||||
{
|
||||
fields = pkpassJSON.getJSONObject(styleKey).getJSONArray(arrayName);
|
||||
}
|
||||
catch (JSONException ex)
|
||||
{
|
||||
return extrasHelper;
|
||||
}
|
||||
|
||||
for(int i = 0; i < fields.length(); i++)
|
||||
{
|
||||
JSONObject fieldObject = fields.getJSONObject(i);
|
||||
String key = fieldObject.getString("key");
|
||||
String label = fieldObject.getString("label");
|
||||
String value = fieldObject.getString("value");
|
||||
|
||||
// Label is optional
|
||||
if(label == null)
|
||||
{
|
||||
label = key;
|
||||
}
|
||||
|
||||
// Add the completely untranslated stuff as fallback
|
||||
String formattedUntranslatedValue = value;
|
||||
if(!label.isEmpty())
|
||||
{
|
||||
formattedUntranslatedValue = label + ": " + value;
|
||||
}
|
||||
extrasHelper.addLanguageValue("", key, formattedUntranslatedValue);
|
||||
|
||||
// Try to find translations
|
||||
for(Map.Entry<String, HashMap<String, String>> language : translations.entrySet())
|
||||
{
|
||||
String translatedLabel = label;
|
||||
if(language.getValue().containsKey(label))
|
||||
{
|
||||
translatedLabel = language.getValue().get(label);
|
||||
}
|
||||
|
||||
String translatedValue = value;
|
||||
if(language.getValue().containsKey(value))
|
||||
{
|
||||
translatedValue = language.getValue().get(value);
|
||||
}
|
||||
|
||||
String formattedValue = translatedValue;
|
||||
|
||||
if(!translatedLabel.isEmpty())
|
||||
{
|
||||
formattedValue = translatedLabel + ": " + translatedValue;
|
||||
}
|
||||
|
||||
extrasHelper.addLanguageValue(language.getKey(), key, formattedValue);
|
||||
}
|
||||
}
|
||||
|
||||
return extrasHelper;
|
||||
}
|
||||
|
||||
public LoyaltyCard fromURI(Uri uri) throws IOException, JSONException {
|
||||
return fromInputStream(context.getContentResolver().openInputStream(uri));
|
||||
}
|
||||
|
||||
public LoyaltyCard fromInputStream(InputStream inputStream) throws IOException, JSONException
|
||||
{
|
||||
String passJSONString = null;
|
||||
|
||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
|
||||
|
||||
ZipEntry entry;
|
||||
|
||||
// We first want to parse the translations
|
||||
while((entry = zipInputStream.getNextEntry()) != null) {
|
||||
if(entry.getName().endsWith("pass.strings"))
|
||||
{
|
||||
// Example: en.lproj/pass.strings
|
||||
String language = entry.getName().substring(0, entry.getName().indexOf("."));
|
||||
|
||||
parseTranslations(language, readZipInputStream(zipInputStream).toString("UTF-8"));
|
||||
}
|
||||
else if(entry.getName().equals("pass.json"))
|
||||
{
|
||||
passJSONString = readZipInputStream(zipInputStream).toString("UTF-8");
|
||||
}
|
||||
else if(entry.getName().equals("icon.png") || entry.getName().equals("icon@2x.png"))
|
||||
{
|
||||
loadBitmap(readZipInputStream(zipInputStream));
|
||||
}
|
||||
}
|
||||
|
||||
if(passJSONString == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return fromPassJSON(new JSONObject(passJSONString));
|
||||
}
|
||||
|
||||
public LoyaltyCard fromPassJSON(JSONObject json) throws JSONException
|
||||
{
|
||||
String store = json.getString("organizationName");
|
||||
String note = json.getString("description");
|
||||
|
||||
// https://developer.apple.com/library/archive/documentation/UserExperience/Reference/PassKit_Bundle/Chapters/TopLevel.html#//apple_ref/doc/uid/TP40012026-CH2-SW1
|
||||
// barcodes is the new field
|
||||
// barcode is deprecated, but used on old iOS versions, so we do fall back to it
|
||||
JSONObject barcode = null;
|
||||
JSONArray barcodes = null;
|
||||
|
||||
try {
|
||||
barcodes = json.getJSONArray("barcodes");
|
||||
} catch (JSONException ex) {
|
||||
}
|
||||
|
||||
if (barcodes != null) {
|
||||
barcode = barcodes.getJSONObject(0);
|
||||
} else {
|
||||
barcode = json.getJSONObject("barcode");
|
||||
}
|
||||
|
||||
if (barcode == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String cardId = barcode.getString("message");
|
||||
|
||||
// https://developer.apple.com/library/archive/documentation/UserExperience/Reference/PassKit_Bundle/Chapters/LowerLevel.html#//apple_ref/doc/uid/TP40012026-CH3-SW3
|
||||
// Required. Barcode format. For the barcode dictionary, you can use only the following values: PKBarcodeFormatQR, PKBarcodeFormatPDF417, or PKBarcodeFormatAztec. For dictionaries in the barcodes array, you may also use PKBarcodeFormatCode128.
|
||||
ImmutableMap<String, String> supportedBarcodeTypes = ImmutableMap.<String, String>builder()
|
||||
.put("PKBarcodeFormatQR", BarcodeFormat.QR_CODE.name())
|
||||
.put("PKBarcodeFormatPDF417", BarcodeFormat.PDF_417.name())
|
||||
.put("PKBarcodeFormatAztec", BarcodeFormat.AZTEC.name())
|
||||
.put("PKBarcodeFormatCode128", BarcodeFormat.CODE_128.name())
|
||||
.build();
|
||||
|
||||
String barcodeType = supportedBarcodeTypes.get(barcode.getString("format"));
|
||||
|
||||
if(barcodeType == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Prepare to parse colors
|
||||
Pattern rgbPattern = Pattern.compile("^rgb\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*\\)$");
|
||||
|
||||
// Optional. Background color of the pass, specified as an CSS-style RGB triple. For example, rgb(23, 187, 82).
|
||||
Integer headerColor = null;
|
||||
Matcher headerColorMatcher = rgbPattern.matcher(json.getString("backgroundColor"));
|
||||
if(headerColorMatcher.find())
|
||||
{
|
||||
headerColor = Color.rgb(
|
||||
Integer.parseInt(headerColorMatcher.group(1)),
|
||||
Integer.parseInt(headerColorMatcher.group(2)),
|
||||
Integer.parseInt(headerColorMatcher.group(3)));
|
||||
}
|
||||
if(headerColor == null)
|
||||
{
|
||||
// Maybe they violate the spec, let's parse it in a format Android understands
|
||||
// Necessary for at least Eurowings
|
||||
try
|
||||
{
|
||||
headerColor = Color.parseColor(json.getString("backgroundColor"));
|
||||
}
|
||||
catch (IllegalArgumentException ex) {}
|
||||
}
|
||||
|
||||
|
||||
// Optional. Color of the label text, specified as a CSS-style RGB triple. For example, rgb(255, 255, 255).
|
||||
Integer headerTextColor = null;
|
||||
Matcher headerTextColorMatcher = rgbPattern.matcher(json.getString("labelColor"));
|
||||
if(headerTextColorMatcher.find())
|
||||
{
|
||||
headerTextColor = Color.rgb(
|
||||
Integer.parseInt(headerTextColorMatcher.group(1)),
|
||||
Integer.parseInt(headerTextColorMatcher.group(2)),
|
||||
Integer.parseInt(headerTextColorMatcher.group(3)));
|
||||
}
|
||||
if(headerTextColor == null)
|
||||
{
|
||||
// Maybe they violate the spec, let's parse it in a format Android understands
|
||||
// Necessary for at least Eurowings
|
||||
try
|
||||
{
|
||||
headerTextColor = Color.parseColor(json.getString("labelColor"));
|
||||
}
|
||||
catch (IllegalArgumentException ex) {}
|
||||
}
|
||||
|
||||
// https://developer.apple.com/library/archive/documentation/UserExperience/Reference/PassKit_Bundle/Chapters/TopLevel.html#//apple_ref/doc/uid/TP40012026-CH2-SW6
|
||||
// There needs to be exactly one style key
|
||||
|
||||
String styleKey = null;
|
||||
ImmutableList<String> possibleStyleKeys = ImmutableList.<String>builder()
|
||||
.add("boardingPass")
|
||||
.add("coupon")
|
||||
.add("eventTicket")
|
||||
.add("generic")
|
||||
.add("storeCard")
|
||||
.build();
|
||||
for(int i = 0; i < possibleStyleKeys.size(); i++)
|
||||
{
|
||||
String possibleStyleKey = possibleStyleKeys.get(i);
|
||||
if(json.has(possibleStyleKey))
|
||||
{
|
||||
styleKey = possibleStyleKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(styleKey == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// https://developer.apple.com/library/archive/documentation/UserExperience/Reference/PassKit_Bundle/Chapters/LowerLevel.html#//apple_ref/doc/uid/TP40012026-CH3-SW14
|
||||
ExtrasHelper extras = new ExtrasHelper();
|
||||
extras = appendData(extras, json, styleKey, "headerFields");
|
||||
extras = appendData(extras, json, styleKey, "primaryFields");
|
||||
extras = appendData(extras, json, styleKey, "secondaryFields");
|
||||
extras = appendData(extras, json, styleKey, "auxiliaryFields");
|
||||
extras = appendData(extras, json, styleKey, "backFields");
|
||||
|
||||
return new LoyaltyCard(-1, store, note, cardId, barcodeType, headerColor, headerTextColor, icon, extras);
|
||||
}
|
||||
}
|
||||
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,98 @@
|
||||
package protect.card_locker.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.annotation.IntegerRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
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 String getString(@StringRes int keyId, String defaultValue)
|
||||
{
|
||||
return settings.getString(getResString(keyId), defaultValue);
|
||||
}
|
||||
|
||||
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 getTheme()
|
||||
{
|
||||
String value = getString(R.string.settings_key_theme, getResString(R.string.settings_key_system_theme));
|
||||
|
||||
if(value.equals(getResString(R.string.settings_key_light_theme)))
|
||||
{
|
||||
return AppCompatDelegate.MODE_NIGHT_NO;
|
||||
}
|
||||
else if(value.equals(getResString(R.string.settings_key_dark_theme)))
|
||||
{
|
||||
return AppCompatDelegate.MODE_NIGHT_YES;
|
||||
}
|
||||
|
||||
return AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
|
||||
}
|
||||
|
||||
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,81 @@
|
||||
package protect.card_locker.preferences;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
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(final Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
findPreference(getResources().getString(R.string.settings_key_theme)).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
{
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object o)
|
||||
{
|
||||
if(o.toString().equals(getResources().getString(R.string.settings_key_light_theme)))
|
||||
{
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
}
|
||||
else if(o.toString().equals(getResources().getString(R.string.settings_key_dark_theme)))
|
||||
{
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||
}
|
||||
|
||||
getActivity().recreate();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 6.4 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_arrow_back_white.png
Normal file
|
After Width: | Height: | Size: 233 B |
BIN
app/src/main/res/drawable-hdpi/ic_info_outline_white.png
Normal file
|
After Width: | Height: | Size: 582 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/ic_search_white.png
Normal file
|
After Width: | Height: | Size: 582 B |
BIN
app/src/main/res/drawable-hdpi/ic_share_white.png
Normal file
|
After Width: | Height: | Size: 554 B |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_arrow_back_white.png
Normal file
|
After Width: | Height: | Size: 155 B |
BIN
app/src/main/res/drawable-mdpi/ic_info_outline_white.png
Normal file
|
After Width: | Height: | Size: 371 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/ic_search_white.png
Normal file
|
After Width: | Height: | Size: 410 B |
BIN
app/src/main/res/drawable-mdpi/ic_share_white.png
Normal file
|
After Width: | Height: | Size: 397 B |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 17 KiB |
BIN
app/src/main/res/drawable-nodpi/circle.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_arrow_back_white.png
Normal file
|
After Width: | Height: | Size: 225 B |
BIN
app/src/main/res/drawable-xhdpi/ic_info_outline_white.png
Normal file
|
After Width: | Height: | Size: 742 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/ic_search_white.png
Normal file
|
After Width: | Height: | Size: 783 B |
BIN
app/src/main/res/drawable-xhdpi/ic_share_white.png
Normal file
|
After Width: | Height: | Size: 758 B |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 19 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_arrow_back_white.png
Normal file
|
After Width: | Height: | Size: 398 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_info_outline_white.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
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/ic_search_white.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_share_white.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 26 KiB |
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 |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 114 KiB |
@@ -1,23 +1,23 @@
|
||||
<?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">
|
||||
<androidx.coordinatorlayout.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
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
<androidx.appcompat.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>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="fill_parent"
|
||||
@@ -64,142 +64,243 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/barcodesLayout"/>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<Button
|
||||
android:id="@+id/noBarcode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/barcodeNoBarcode"
|
||||
android:enabled="false" />
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/aztecBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/code39BarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/code128BarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/codabarBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/datamatrixBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/ean8BarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/ean13BarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/itfBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/pdf417BarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/qrcodeBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/upcaBarcodeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10.0dip"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_weight="1.0" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -17,6 +17,14 @@
|
||||
android:text="@string/noGiftCards"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
style="@style/AppTheme.TextView.NoData"
|
||||
android:id="@+id/noMatchingCardsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/noMatchingGiftCards"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ListView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<?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">
|
||||
<androidx.coordinatorlayout.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
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
<androidx.appcompat.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>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
@@ -99,7 +99,6 @@
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/importOptionFilesystemButton" />
|
||||
|
||||
|
||||
<View
|
||||
android:id="@+id/dividerImportApplication"
|
||||
android:layout_width="fill_parent"
|
||||
@@ -129,38 +128,8 @@
|
||||
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>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,51 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,51 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,51 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,51 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,51 +0,0 @@
|
||||
<?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>
|
||||
451
app/src/main/res/layout/loyalty_card_edit_activity.xml
Normal file
@@ -0,0 +1,451 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.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">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabSave"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:src="@drawable/save_24dp"
|
||||
android:contentDescription="@string/save"
|
||||
android:layout_margin="16dp" />
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<androidx.appcompat.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" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<ScrollView android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/inputContrastBackground"
|
||||
app:layout_behavior="com.google.android.material.appbar.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="textMultiLine"
|
||||
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" />
|
||||
|
||||
<androidx.constraintlayout.widget.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"/>
|
||||
|
||||
<androidx.constraintlayout.widget.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"/>
|
||||
</androidx.constraintlayout.widget.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"/>
|
||||
</androidx.constraintlayout.widget.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" />
|
||||
|
||||
<androidx.constraintlayout.widget.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"/>
|
||||
|
||||
<androidx.constraintlayout.widget.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"/>
|
||||
</androidx.constraintlayout.widget.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"/>
|
||||
</androidx.constraintlayout.widget.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>
|
||||
|
||||
<!-- Barcode Type -->
|
||||
<View
|
||||
android:id="@+id/barcodeTypeDivider"
|
||||
android:layout_height="@dimen/inputBorderThickness"
|
||||
android:layout_width="match_parent"
|
||||
android:background="@color/inputBorder" />
|
||||
<TableRow
|
||||
android:id="@+id/barcodeTypeTableRow"
|
||||
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/barcodeTypeField"
|
||||
android:text="@string/barcodeType"
|
||||
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/barcodeTypeView"
|
||||
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/barcodeTypeField"
|
||||
android:layout_toRightOf="@id/barcodeTypeField"
|
||||
/>
|
||||
</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" />
|
||||
|
||||
<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:padding="10.0dp"
|
||||
android:background="#ffffff"
|
||||
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"
|
||||
android:background="@color/inputBackground">
|
||||
|
||||
<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>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,32 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:padding="10.0dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:padding="5.0dp"
|
||||
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: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.0dp"
|
||||
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,249 +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"
|
||||
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"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/storeNameEdit"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="@dimen/inputPadding"
|
||||
android:textSize="@dimen/inputSize"
|
||||
android:layout_toEndOf="@id/storeNameField"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/storeNameView"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="@dimen/inputPadding"
|
||||
android:textSize="@dimen/inputSize"
|
||||
android:textIsSelectable="true"
|
||||
android:layout_toEndOf="@id/storeNameField"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<View
|
||||
android:gravity="end"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="@dimen/inputBorderThickness"
|
||||
android:background="@color/inputBorder" />
|
||||
</TableRow>
|
||||
|
||||
<!-- Note -->
|
||||
<View
|
||||
android:layout_height="@dimen/inputBorderThickness"
|
||||
android:layout_width="match_parent"
|
||||
android:background="@color/inputBorder" />
|
||||
<TableRow
|
||||
android:background="@color/inputBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<View
|
||||
android:gravity="start"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="@dimen/inputBorderThickness"
|
||||
android:background="@color/inputBorder" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_weight="1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="@dimen/inputPadding"
|
||||
android:paddingEnd="@dimen/inputPadding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noteField"
|
||||
android:text="@string/note"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:textSize="@dimen/inputSize"
|
||||
android:padding="@dimen/inputPadding"
|
||||
android:layout_alignParentStart="true"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/noteEdit"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="@dimen/inputPadding"
|
||||
android:textSize="@dimen/inputSize"
|
||||
android:layout_toEndOf="@id/noteField"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noteView"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="@dimen/inputPadding"
|
||||
android:textSize="@dimen/inputSize"
|
||||
android:textIsSelectable="true"
|
||||
android:layout_toEndOf="@id/noteField"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<View
|
||||
android:gravity="end"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="@dimen/inputBorderThickness"
|
||||
android:background="@color/inputBorder" />
|
||||
</TableRow>
|
||||
|
||||
<!-- Card ID -->
|
||||
<View
|
||||
android:id="@+id/cardIdDivider"
|
||||
android:layout_height="@dimen/inputBorderThickness"
|
||||
android:layout_width="match_parent"
|
||||
android:background="@color/inputBorder" />
|
||||
<TableRow
|
||||
android:id="@+id/cardIdTableRow"
|
||||
android:background="@color/inputBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<View
|
||||
android:gravity="start"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="@dimen/inputBorderThickness"
|
||||
android:background="@color/inputBorder" />
|
||||
|
||||
<RelativeLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="@dimen/inputPadding"
|
||||
android:paddingEnd="@dimen/inputPadding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cardIdField"
|
||||
android:text="@string/cardId"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:textSize="@dimen/inputSize"
|
||||
android:padding="@dimen/inputPadding"
|
||||
android:layout_alignParentStart="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cardIdView"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="@dimen/inputPadding"
|
||||
android:textSize="@dimen/inputSize"
|
||||
android:textIsSelectable="true"
|
||||
android:layout_toEndOf="@id/cardIdField"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<View
|
||||
android:gravity="end"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="@dimen/inputBorderThickness"
|
||||
android:background="@color/inputBorder" />
|
||||
</TableRow>
|
||||
|
||||
<View
|
||||
android:layout_height="@dimen/inputBorderThickness"
|
||||
android:layout_width="match_parent"
|
||||
android:background="@color/inputBorder" />
|
||||
|
||||
<TextView android:id="@+id/barcodeType"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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>
|
||||
155
app/src/main/res/layout/loyalty_card_view_layout.xml
Normal file
@@ -0,0 +1,155 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.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"
|
||||
>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabEdit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:src="@drawable/ic_mode_edit_white_24dp"
|
||||
android:contentDescription="@string/edit"
|
||||
android:layout_margin="16dp" />
|
||||
|
||||
<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">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.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"
|
||||
android:padding="10.0dp"
|
||||
android:background="#ffffff"
|
||||
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/barcode"
|
||||
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="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="10.0dip"
|
||||
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:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:autoSizeMinTextSize="@dimen/singleCardNoteTextSizeMin"
|
||||
app:autoSizeMaxTextSize="@dimen/singleCardNoteTextSizeMax"
|
||||
android:textIsSelectable="true"
|
||||
android:scrollbars="vertical"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/drop_shadow_actionbar"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="5.0dip"
|
||||
android:layout_gravity="top"/>
|
||||
</FrameLayout>
|
||||
|
||||
<com.google.android.material.appbar.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">
|
||||
<com.google.android.material.appbar.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"/>
|
||||
<androidx.appcompat.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" />
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
<androidx.coordinatorlayout.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"
|
||||
@@ -8,20 +8,29 @@
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="protect.card_locker.MainActivity">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAdd"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:src="@drawable/ic_add_white_24dp"
|
||||
android:contentDescription="@string/action_add"
|
||||
android:layout_margin="16dp" />
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
<androidx.appcompat.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>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<include layout="@layout/content_main"/>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||