Compare commits

..

32 Commits

Author SHA1 Message Date
Sylvia van Os
435ad2cfb3 Fix comment 2020-10-24 15:35:25 +02:00
Sylvia van Os
ef675fe6e5 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into feature/pkpass 2020-10-24 15:07:22 +02:00
Sylvia van Os
fe4dc868aa Fix bad merge 2020-02-22 15:00:58 +01:00
Sylvia van Os
52e32caca3 Fix merge typo 2020-02-21 16:25:24 +01:00
Sylvia van Os
d7ef204d0a Merge branch 'master' into feature/pkpass 2020-02-21 15:33:12 +01:00
Sylvia van Os
915e364454 Merge branch 'master' of ssh://github.com/brarcher/loyalty-card-locker into feature/pkpass 2020-01-12 22:50:29 +01:00
Sylvia van Os
da1d1d62e4 Merge branch 'master' of ssh://github.com/brarcher/loyalty-card-locker into feature/pkpass 2020-01-04 20:25:52 +01:00
Sylvia van Os
fd1b86b1cc Fix sharing loyalty card with icon 2019-12-18 13:26:12 +01:00
Sylvia van Os
3f4d0ab5df Grab highest quality icon 2019-12-18 12:18:32 +01:00
Sylvia van Os
1a6cad893d Load icon from Pkpass if exists 2019-12-18 12:15:32 +01:00
Sylvia van Os
2ef4d3f6e0 Fix findBugs performance warning 2019-12-16 17:03:33 +01:00
Sylvia van Os
43148cb12c Merge branch 'master' of ssh://github.com/brarcher/loyalty-card-locker into feature/pkpass 2019-12-16 16:51:25 +01:00
Sylvia van Os
80b45973ac More zip types 2019-12-16 16:45:45 +01:00
Sylvia van Os
ca8243de01 Always fall back to English and then untranslated 2019-12-16 16:34:39 +01:00
Sylvia van Os
b4a532d183 Fix tests 2019-12-16 16:09:56 +01:00
Sylvia van Os
a05f9f6ec9 Parsing fixes 2019-12-16 15:25:27 +01:00
Sylvia van Os
79bba52d93 Load translations (TODO: tests) 2019-12-15 19:10:34 +01:00
Sylvia van Os
04dcee6097 Fix some tests 2019-12-14 21:37:48 +01:00
Sylvia van Os
6cd7a3f5bf Parse extras 2019-12-14 21:00:35 +01:00
Sylvia van Os
09727038ce Don't crash if color is still not valid 2019-12-11 17:19:16 +01:00
Sylvia van Os
896da82b13 Use full .pkpass files for tests 2019-12-11 16:20:32 +01:00
Sylvia van Os
ca93f89e1b Add Eurowings support 2019-12-11 15:32:45 +01:00
Sylvia van Os
9291563cef Fix color parsing 2019-12-11 14:29:41 +01:00
Sylvia van Os
703db472fc Start implementing color parsing 2019-12-10 18:04:12 +01:00
Sylvia van Os
b043320af5 Only try to parse allowed barcode formats 2019-12-10 17:21:04 +01:00
Sylvia van Os
e1f25ed092 Don't crash on unsupported barcode 2019-12-09 19:49:04 +01:00
Sylvia van Os
05d38a0184 XZING uses uppercase typenames 2019-12-09 19:33:17 +01:00
Sylvia van Os
5a046b037e Make linter happy 2019-12-09 17:57:40 +01:00
Sylvia van Os
2f73d510e2 Unit test 2019-12-09 17:55:07 +01:00
Sylvia van Os
2c7cb4769a Parse barcodes if exists 2019-12-09 17:44:13 +01:00
Sylvia van Os
3d543dd23c Use description as note 2019-12-09 17:25:48 +01:00
Sylvia van Os
8637f8d2a1 Initial pkpass support 2019-12-09 17:08:56 +01:00
152 changed files with 2312 additions and 3887 deletions

View File

@@ -1,31 +0,0 @@
name: Compress Images on Push to Master
on:
push:
branches:
- master
paths:
- '**.jpg'
- '**.jpeg'
- '**.png'
- '**.webp'
jobs:
build:
name: calibreapp/image-actions
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@master
- name: Compress Images
id: calibre
uses: calibreapp/image-actions@master
with:
githubToken: ${{ secrets.GITHUB_TOKEN }}
compressOnly: true
- name: Create New Pull Request If Needed
if: steps.calibre.outputs.markdown != ''
uses: peter-evans/create-pull-request@master
with:
title: Compressed Images
branch-suffix: timestamp
commit-message: Compressed Images
body: ${{ steps.calibre.outputs.markdown }}

10
.tx/config Normal file
View 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

View File

@@ -1,48 +1,6 @@
# Changelog
## v1.2.0 (2020-11-17)
Changes:
- Add swiping between groups on the home screen
- Fix crash with cards lacking header colour
## v1.1.0 (2020-11-11)
Changes:
- Improved edit UI
- Removed header text colour option (now automatically generated based on brightness)
- Updated translations
## v1.0.1 (2020-11-07)
Changes:
- Fix crash in search with no groups
## v1.0 (2020-11-06)
Changes:
- Added rounded edges to card icons on main overview
- Added support for grouping entries
## v0.29 (2020-10-29)
Changes:
- Rebrand to Catima
- Removed intro
- Add floating action buttons
- Fix Android 5 crash when opening About screen
- Add favourites support
- Fix disabled auto-rotate being ignored
## 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))
@@ -50,7 +8,6 @@ Changes:
## 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))
@@ -58,13 +15,11 @@ Changes:
## 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))
@@ -74,203 +29,178 @@ Changes:
- 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
* German
* Italian
* Dutch
* Polish
* Russian
## v0.25.4 (2019-10-04)
Changes:
Changes
- Enable app backups
- Update French and Slovenian translations
## v0.25.3 (2019-03-02)
Changes:
Changes
- Update Russian translations
## v0.25.2 (2019-01-05)
Changes:
Changes
- Update and add translations
## v0.25.1 (2018-10-14)
Changes:
- Fix creating new card by manually entering barcode ([issue #272](https://github.com/brarcher/loyalty-card-locker/issues/272))
- 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 ([pull #266](https://github.com/brarcher/loyalty-card-locker/pull/266))
- Add setting to lock orientation for all cards ([pull #269](https://github.com/brarcher/loyalty-card-locker/pull/269)
- 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 ([pull #259](https://github.com/brarcher/loyalty-card-locker/pull/259))
- Add Greek translations ([pull #252](https://github.com/brarcher/loyalty-card-locker/pull/252))
- Add Slovenian translations ([pull #260](https://github.com/brarcher/loyalty-card-locker/pull/260))
- Update translations ([pull #260](https://github.com/brarcher/loyalty-card-locker/pull/260), [pull #254](https://github.com/brarcher/loyalty-card-locker/pull/254))
- 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 ([pull #244](https://github.com/brarcher/loyalty-card-locker/pull/244))
- Update translations ([pull #244](https://github.com/brarcher/loyalty-card-locker/pull/244))
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 ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
- Spanish ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
- Slovak ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
- Updated translations ([pull #239](https://github.com/brarcher/loyalty-card-locker/pull/239))
* 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. ([pull #226](https://github.com/brarcher/loyalty-card-locker/pull/226))
- Remove usage of legacy apache library, used only in unit tests but no longer needed. ([pull #225](https://github.com/brarcher/loyalty-card-locker/pull/225))
- 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. ([pull #219](https://github.com/brarcher/loyalty-card-locker/pull/219))
- 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. ([pull #213](https://github.com/brarcher/loyalty-card-locker/pull/213))
- Disable beep when scanning a barcode. ([pull #216](https://github.com/brarcher/loyalty-card-locker/pull/216))
- 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. ([pull #208](https://github.com/brarcher/loyalty-card-locker/pull/208))
- Barcode rendering updates: ([pull #209](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.
- 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. ([pull #200](https://github.com/brarcher/loyalty-card-locker/pull/200))
- Add options to configure the colors used for the store name font and background. ([pull #203](https://github.com/brarcher/loyalty-card-locker/pull/203))
- Add options to adjust font sizes on the card listing page and single card page. ([pull #204](https://github.com/brarcher/loyalty-card-locker/pull/204))
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. ([pull #197](https://github.com/brarcher/loyalty-card-locker/pull/197))
- 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. ([pull #188](https://github.com/brarcher/loyalty-card-locker/pull/188))
- Improved layout when viewing a card. ([pull #190](https://github.com/brarcher/loyalty-card-locker/pull/190))
- 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). ([pull #184](https://github.com/brarcher/loyalty-card-locker/pull/184))
- 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. ([pull #177](https://github.com/brarcher/loyalty-card-locker/pull/177))
- Fix importing backups directly from the file system. ([pull #180](https://github.com/brarcher/loyalty-card-locker/pull/180))
- Fix importing backups from certain types of content providers. ([pull #179](https://github.com/brarcher/loyalty-card-locker/pull/179))
- 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. ([pull #170](https://github.com/brarcher/loyalty-card-locker/pull/170))
- New icon and color scheme. ([pull #171](https://github.com/brarcher/loyalty-card-locker/pull/171))
- 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. ([pull #161](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. ([pull #163](https://github.com/brarcher/loyalty-card-locker/pull/163))
- 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. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
- Remove widget, as it was a poor substitute for shortcuts. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
- Fix exporting backups on Android 7+. ([pull #153](https://github.com/brarcher/loyalty-card-locker/pull/153))
- Report more accurate mime type when exporting backup data. ([pull #156](https://github.com/brarcher/loyalty-card-locker/pull/156))
- Fix bug where a card could not be edited. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
- 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. ([pull #145](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. ([pull #142](https://github.com/brarcher/loyalty-card-locker/pull/142))
- 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. ([pull #128](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. ([pull #132](https://github.com/brarcher/loyalty-card-locker/pull/132))
- Fix case where layout IDs for intro wizard could not be found. ([pull #128](https://github.com/brarcher/loyalty-card-locker/pull/128))
- 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. ([pull #126](https://github.com/brarcher/loyalty-card-locker/pull/126))
- Update German and French translations. ([pull #122](https://github.com/brarcher/loyalty-card-locker/pull/122), [pull #124](https://github.com/brarcher/loyalty-card-locker/pull/124), [pull #125](https://github.com/brarcher/loyalty-card-locker/pull/125))
- A change in v0.11 reduced the memory usage of barcode drawing, but affected the barcode dimensions. This is now changed to maintain the barcode dimensions while reducing memory usage. (https://github.com/brarcher/loyalty-card-locker/pull/126)
- Update German and French translations. (https://github.com/brarcher/loyalty-card-locker/pull/122, https://github.com/brarcher/loyalty-card-locker/pull/124, https://github.com/brarcher/loyalty-card-locker/pull/125)
## v0.11.1 (2017-06-29)
Changes:
- Prevent a crash when rotation the screen in the first run intro wizard.
## v0.11 (2017-06-26)
Improvements:
- When editing a card ID, pre-populate the existing ID to start. ([pull #94](https://github.com/brarcher/loyalty-card-locker/pull/94))
- Limit the width of generated barcodes to reduce memory usage and out of memory errors. ([pull #103](https://github.com/brarcher/loyalty-card-locker/pull/103))
- When editing a card, change the "Enter Card" button to say "Edit Card" if a card ID already exists. ([pull #104](https://github.com/brarcher/loyalty-card-locker/pull/104))
- Change the color scheme to be softer and compatible with the app icon, and change the layout when viewing a card to be cleaner. ([pull #107](https://github.com/brarcher/loyalty-card-locker/pull/107))
- Add an intro wizard which launches on the app's first launch. ([pull #108](https://github.com/brarcher/loyalty-card-locker/pull/108))
- When editing a card ID, pre-populate the existing ID to start. (https://github.com/brarcher/loyalty-card-locker/pull/94)
- Limit the width of generated barcodes to reduce memory usage and out of memory errors. (https://github.com/brarcher/loyalty-card-locker/pull/103)
- When editing a card, change the "Enter Card" button to say "Edit Card" if a card ID already exists. (https://github.com/brarcher/loyalty-card-locker/pull/104)
- Change the color scheme to be softer and compatible with the app icon, and change the layout when viewing a card to be cleaner. (https://github.com/brarcher/loyalty-card-locker/pull/107)
- Add an intro wizard which launches on the app's first launch. (https://github.com/brarcher/loyalty-card-locker/pull/108)
## v0.10 (2017-02-12)
Improvements:
- Changed the default import/export filename. ([pull #84](https://github.com/brarcher/loyalty-card-locker/pull/84))
- Correct string on the import/export page. ([pull #87](https://github.com/brarcher/loyalty-card-locker/pull/87))
- Improve layout of card view page. The text should be easier to read, and is selectable with a long click. ([pull #91](https://github.com/brarcher/loyalty-card-locker/pull/91))
- Changed the default import/export filename. (https://github.com/brarcher/loyalty-card-locker/pull/84)
- Correct string on the import/export page. (https://github.com/brarcher/loyalty-card-locker/pull/87)
- Improve layout of card view page. The text should be easier to read, and is selectable with a long click. (https://github.com/brarcher/loyalty-card-locker/pull/91)
## v0.9 (2017-01-17)
@@ -278,50 +208,43 @@ The "Locker" part of the name was not intuitive. To help remedy this a new appli
Additional features/improvements:
- Importing/Exporting cards was changed to be more flexible. ([pull #76](https://github.com/brarcher/loyalty-card-locker/pull/76))
- Translations for Lithuanian added. ([pull #62](https://github.com/brarcher/loyalty-card-locker/pull/62))
- Translations for French added. ([pull #80](https://github.com/brarcher/loyalty-card-locker/pull/80))
- Importing/Exporting cards was changed to be more flexible. (https://github.com/brarcher/loyalty-card-locker/pull/76)
- Translations for Lithuanian added. (https://github.com/brarcher/loyalty-card-locker/pull/62)
- Translations for French added. (https://github.com/brarcher/loyalty-card-locker/pull/80)
## v0.8 (2016-11-22)
New features/improvements:
- Screen brightness increased to its maximum when displaying a card, to help barcode scanners successfully capture the barcode. ([pull #54](https://github.com/brarcher/loyalty-card-locker/pull/54))
- Add a delete confirmation when deleting a card. ([pull #55](https://github.com/brarcher/loyalty-card-locker/pull/55))
- Add translations for German ([pull #57](https://github.com/brarcher/loyalty-card-locker/pull/57)) and Czech ([pull #58](https://github.com/brarcher/loyalty-card-locker/pull/58)).
- Clarification change for Italian translation. ([pull #66](https://github.com/brarcher/loyalty-card-locker/pull/66))
- Screen brightness increased to its maximum when displaying a card, to help barcode scanners successfully capture the barcode. (https://github.com/brarcher/loyalty-card-locker/pull/54)
- Add a delete confirmation when deleting a card. (https://github.com/brarcher/loyalty-card-locker/pull/55)
- Add translations for German (https://github.com/brarcher/loyalty-card-locker/pull/57) and Czech (https://github.com/brarcher/loyalty-card-locker/pull/58).
- Clarification change for Italian translation. (https://github.com/brarcher/loyalty-card-locker/pull/66)
## v0.7 (2016-07-14)
New features/improvements:
- Long-click of a card brings up option to copy card ID to the clipboard. ([pull #49](https://github.com/brarcher/loyalty-card-locker/issues/49))
- Long-click of a card brings up option to copy card ID to the clipboard. (https://github.com/brarcher/loyalty-card-locker/issues/49)
Bug fixes:
- Back button on Input/Export view now works, moving user to main view
## v0.6 (2016-05-23)
New features/improvements:
- Allow user to enter barcode manually. If a user elects to enter a barcode manually, a list of all valid and supported barcode images is displayed. The user then may select the barcode image which matches what the user wants. [issue #33](https://github.com/brarcher/loyalty-card-locker/issues/33), [pull #44](https://github.com/brarcher/loyalty-card-locker/pull/44)
- Allow user to enter barcode manually. If a user elects to enter a barcode manually, a list of all valid and supported barcode images is displayed. The user then may select the barcode image which matches what the user wants. https://github.com/brarcher/loyalty-card-locker/issues/33, https://github.com/brarcher/loyalty-card-locker/pull/44
Bug fixes:
- Resolve issue where some displayed barcodes were blurry. ([issue #37](https://github.com/brarcher/loyalty-card-locker/issues/37))
- Resolve issue where some displayed barcodes were blurry. (https://github.com/brarcher/loyalty-card-locker/issues/37)
## v0.5 (2016-05-16)
New features/improvements:
- An about dialog can be opened from the main screen, which gives details about the application and project on GitHub ([issue #19](https://github.com/brarcher/loyalty-card-locker/issues/19))
- Allow loyalty card information to be imported from/exported to a CSV file in external storage ([issue #36](https://github.com/brarcher/loyalty-card-locker/issues/36), [issue #20](https://github.com/brarcher/loyalty-card-locker/issues/20))
- An about dialog can be opened from the main screen, which gives details about the application and project on GitHub (https://github.com/brarcher/loyalty-card-locker/issues/19)
- Allow loyalty card information to be imported from/exported to a CSV file in external storage (https://github.com/brarcher/loyalty-card-locker/issues/36 https://github.com/brarcher/loyalty-card-locker/issues/20)
## v0.4 (2016-04-09)
New features/improvements:
- Dutch translation
- Allow name field to be editable after adding loyalty card
- Add an optional note field
@@ -333,18 +256,17 @@ Bug fixes:
## v0.3 (2016-02-11)
- Now officially supports the following list of 1D and 2D barcodes:
- AZTEC
- CODABAR
- CODE_39
- CODE_128
- DATA_MATRIX
- EAN_8
- EAN_13
- ITF
- PDF_417
- QR_CODE
- UPC_A
* AZTEC
* CODABAR
* CODE_39
* CODE_128
* DATA_MATRIX
* EAN_8
* EAN_13
* ITF
* PDF_417
* QR_CODE
* UPC_A
- Generated barcodes are larger, easier to scan from a scanning device
## v0.2 (2016-02-07)
@@ -353,6 +275,7 @@ Bug fixes:
- Support for all 1D barcode types. (Originally only product 1D barcodes were supported)
- Add required camera permission, which was initially missing.
## v0.1 (2016-01-30)
- Ability to create/edit/delete loyalty cards

View File

@@ -1,3 +0,0 @@
source "https://rubygems.org"
gem "fastlane"

View File

@@ -1,180 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
aws-eventstream (1.1.0)
aws-partitions (1.388.0)
aws-sdk-core (3.109.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.39.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.83.1)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.2)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.20)
declarative-option (0.1.0)
digest-crc (0.6.1)
rake (~> 13.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.0)
excon (0.78.0)
faraday (1.1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday_middleware (1.0.0)
faraday (~> 1.0)
fastimage (2.2.0)
fastlane (2.165.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.37.0, < 0.39.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-api-client (0.38.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.4.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.1)
google-cloud-storage (1.29.1)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
googleauth (0.14.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.14)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)
json (2.3.1)
jwt (2.2.2)
memoist (0.16.2)
mini_magick (4.10.1)
mini_mime (1.0.2)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.0)
os (1.1.1)
plist (3.5.0)
public_suffix (4.0.6)
rake (13.0.1)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
ruby2_keywords (0.0.2)
rubyzip (2.3.0)
security (0.1.3)
signet (0.14.0)
addressable (~> 2.3)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.19.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
fastlane
BUNDLED WITH
2.1.4

View File

@@ -13,8 +13,8 @@ android {
applicationId "me.hackerchick.catima"
minSdkVersion 16
targetSdkVersion 29
versionCode 44
versionName "1.2.0"
versionCode 39
versionName "0.28"
}
buildTypes {
release {
@@ -32,6 +32,11 @@ android {
disable "MissingTranslation"
disable "MissingPrefix"
}
sourceSets {
test {
resources.srcDirs += ['src/test/resources']
}
}
// Starting with Android Studio 3 Robolectric is unable to find resources.
// The following allows it to find the resources.
@@ -43,21 +48,20 @@ android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.journeyapps:zxing-android-embedded:3.5.0@aar'
implementation 'com.google.zxing:core:3.3.0'
implementation 'org.apache.commons:commons-csv:1.5'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.jaredrummler:colorpicker:1.0.2'
implementation group: 'com.google.guava', name: 'guava', version: '20.0'
implementation 'com.github.apl-devs:appintro:v4.2.0'
implementation "com.vanniktech:vntnumberpickerpreference:1.0.0"
implementation 'androidx.cardview:cardview:1.0.0'
testImplementation 'junit:junit:4.12'
testImplementation "org.robolectric:robolectric:4.0.2"
compile fileTree(dir: 'libs', include: ['*.jar'])
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.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:4.0.2"
}
task findbugs(type: FindBugs, dependsOn: 'assembleDebug') {

View File

Binary file not shown.

View File

@@ -1 +0,0 @@
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":41,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]

View File

@@ -12,4 +12,4 @@ public class ApplicationTest extends ApplicationTestCase<Application>
{
super(Application.class);
}
}
}

View File

@@ -33,11 +33,6 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".ManageGroupsActivity"
android:label="@string/groups"
android:theme="@style/AppTheme.NoActionBar">
</activity>
<activity
android:name=".LoyaltyCardViewActivity"
android:theme="@style/AppTheme.NoActionBar"
@@ -47,13 +42,14 @@
<activity
android:name=".LoyaltyCardEditActivity"
android:theme="@style/AppTheme.NoActionBar"
android:configChanges="orientation|screenSize"
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://github.com/brarcher/loyalty-card-locker/” -->
<!-- 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" />
@@ -61,22 +57,39 @@
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: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=".CardShortcutConfigure"
android:label="@string/cardShortcut"
android:configChanges="orientation|screenSize"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT"/>

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -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;
@@ -18,55 +19,39 @@ public class CsvDatabaseExporter implements DatabaseExporter
{
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
// Print the version
printer.printRecord("2");
printer.println();
// Print the header for groups
printer.printRecord(DBHelper.LoyaltyCardDbGroups.ID);
Cursor groupCursor = db.getGroupCursor();
while(groupCursor.moveToNext())
{
Group group = Group.toGroup(groupCursor);
printer.printRecord(group._id);
if(Thread.currentThread().isInterrupted())
{
throw new InterruptedException();
}
}
groupCursor.close();
// Print an empty line
printer.println();
// Print the header for cards
// Print the header
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
DBHelper.LoyaltyCardDbIds.STORE,
DBHelper.LoyaltyCardDbIds.NOTE,
DBHelper.LoyaltyCardDbIds.CARD_ID,
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR,
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
DBHelper.LoyaltyCardDbIds.STAR_STATUS);
DBHelper.LoyaltyCardDbIds.EXTRAS);
Cursor cardCursor = db.getLoyaltyCardCursor();
Cursor cursor = db.getLoyaltyCardCursor();
while(cardCursor.moveToNext())
while(cursor.moveToNext())
{
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
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.headerColor,
card.headerTextColor,
card.barcodeType,
card.starStatus);
extras);
if(Thread.currentThread().isInterrupted())
{
@@ -74,32 +59,7 @@ public class CsvDatabaseExporter implements DatabaseExporter
}
}
cardCursor.close();
// Print an empty line
printer.println();
// Print the header for card group mappings
printer.printRecord(DBHelper.LoyaltyCardDbIdsGroups.cardID,
DBHelper.LoyaltyCardDbIdsGroups.groupID);
Cursor cardCursor2 = db.getLoyaltyCardCursor();
while(cardCursor2.moveToNext())
{
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor2);
for (Group group : db.getLoyaltyCardGroups(card.id)) {
printer.printRecord(card.id, group._id);
}
if(Thread.currentThread().isInterrupted())
{
throw new InterruptedException();
}
}
cardCursor2.close();
cursor.close();
printer.close();
}

View File

@@ -1,16 +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.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.List;
import java.text.Normalizer;
/**
* Class for importing a database from CSV (Comma Separate Values)
@@ -22,34 +23,6 @@ import java.util.List;
public class CsvDatabaseImporter implements DatabaseImporter
{
public void importData(DBHelper db, InputStreamReader input) throws IOException, FormatException, InterruptedException
{
BufferedReader bufferedReader = new BufferedReader(input);
bufferedReader.mark(100);
Integer version = 1;
try {
version = Integer.parseInt(bufferedReader.readLine());
} catch (NumberFormatException _e) {
// Assume version 1
}
bufferedReader.reset();
switch (version) {
case 1:
parseV1(db, bufferedReader);
break;
case 2:
parseV2(db, bufferedReader);
break;
default:
throw new FormatException(String.format("No code to parse version %s", version));
}
}
public void parseV1(DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
{
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.withHeader());
@@ -82,116 +55,6 @@ public class CsvDatabaseImporter implements DatabaseImporter
}
}
public void parseV2(DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
{
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
Integer part = 0;
String stringPart = "";
try {
while (true) {
String tmp = input.readLine();
if (tmp == null || tmp.isEmpty()) {
switch (part) {
case 0:
// This is the version info, ignore
break;
case 1:
parseV2Groups(db, database, stringPart);
break;
case 2:
parseV2Cards(db, database, stringPart);
break;
case 3:
parseV2CardGroups(db, database, stringPart);
break;
default:
throw new FormatException("Issue parsing CSV data, too many parts for v2 parsing");
}
if (tmp == null) {
break;
}
part += 1;
stringPart = "";
} else {
stringPart += tmp + "\n";
}
}
database.setTransactionSuccessful();
} catch (FormatException e) {
throw new FormatException("Issue parsing CSV data", e);
} finally {
database.endTransaction();
database.close();
}
}
public void parseV2Groups(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
{
// Parse groups
final CSVParser groupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
try {
for (CSVRecord record : groupParser) {
importGroup(database, db, record);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
}
groupParser.close();
} catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
}
}
public void parseV2Cards(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
{
// Parse cards
final CSVParser cardParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
try {
for (CSVRecord record : cardParser) {
importLoyaltyCard(database, db, record);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
}
cardParser.close();
} catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
}
}
public void parseV2CardGroups(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
{
// Parse card group mappings
final CSVParser cardGroupParser = new CSVParser(new StringReader(data), CSVFormat.RFC4180.withHeader());
try {
for (CSVRecord record : cardGroupParser) {
importCardGroupMapping(database, db, record);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
}
cardGroupParser.close();
} catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
}
}
/**
* Extract a string from the items array. The index into the array
* is determined by looking up the index in the fields map using the
@@ -274,47 +137,32 @@ public class CsvDatabaseImporter implements DatabaseImporter
String barcodeType = extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, "");
Integer headerColor = null;
Integer headerTextColor = null;
if(record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR))
if(record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR) &&
record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR))
{
headerColor = extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true);
headerTextColor = extractInt(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR, record, true);
}
int starStatus = 0;
try {
starStatus = extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record, false);
} catch (FormatException _e ) {
// This field did not exist in versions 0.28 and before
// We catch this exception so we can still import old backups
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);
}
if (starStatus != 1) starStatus = 0;
helper.insertLoyaltyCard(database, id, store, note, cardId, barcodeType, headerColor, starStatus);
}
/**
* Import a single group into the database using the given
* session.
*/
private void importGroup(SQLiteDatabase database, DBHelper helper, CSVRecord record)
throws IOException, FormatException
{
String id = extractString(DBHelper.LoyaltyCardDbGroups.ID, record, null);
helper.insertGroup(database, id);
}
/**
* Import a single card to group mapping into the database using the given
* session.
*/
private void importCardGroupMapping(SQLiteDatabase database, DBHelper helper, CSVRecord record)
throws IOException, FormatException
{
Integer cardId = extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record, false);
String groupId = extractString(DBHelper.LoyaltyCardDbIdsGroups.groupID, record, null);
List<Group> cardGroups = helper.getLoyaltyCardGroups(cardId);
cardGroups.add(helper.getGroup(groupId));
helper.setLoyaltyCardGroups(database, cardId, cardGroups);
}
}
}

View File

@@ -1,26 +1,25 @@
package protect.card_locker;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Color;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.util.ArrayList;
import java.util.List;
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 = "Catima.db";
public static final int ORIGINAL_DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 5;
static class LoyaltyCardDbGroups
{
public static final String TABLE = "groups";
public static final String ID = "_id";
}
public static final int DATABASE_VERSION = 4;
static class LoyaltyCardDbIds
{
@@ -32,14 +31,8 @@ public class DBHelper extends SQLiteOpenHelper
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 STAR_STATUS = "starstatus";
}
static class LoyaltyCardDbIdsGroups
{
public static final String TABLE = "cardsGroups";
public static final String cardID = "cardId";
public static final String groupID = "groupId";
public static final String ICON = "icon";
public static final String EXTRAS = "extras";
}
public DBHelper(Context context)
@@ -47,14 +40,23 @@ 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)
{
// create table for card groups
db.execSQL("create table " + LoyaltyCardDbGroups.TABLE + "(" +
LoyaltyCardDbGroups.ID + " TEXT primary key not null)");
// create table for cards
// create table for gift cards
db.execSQL("create table " + LoyaltyCardDbIds.TABLE + "(" +
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
LoyaltyCardDbIds.STORE + " TEXT not null," +
@@ -63,13 +65,8 @@ public class DBHelper extends SQLiteOpenHelper
LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER," +
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT not null," +
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0' )");
// create associative table for cards in groups
db.execSQL("create table " + LoyaltyCardDbIdsGroups.TABLE + "(" +
LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
LoyaltyCardDbIdsGroups.groupID + " TEXT," +
"primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID +"))");
LoyaltyCardDbIds.ICON + " BLOB," +
LoyaltyCardDbIds.EXTRAS + " TEXT)");
}
@Override
@@ -95,25 +92,16 @@ public class DBHelper extends SQLiteOpenHelper
if(oldVersion < 4 && newVersion >= 4)
{
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'");
}
// Upgrade from version 4 to version 5
if(oldVersion < 5 && newVersion >= 5)
{
db.execSQL("create table " + LoyaltyCardDbGroups.TABLE + "(" +
LoyaltyCardDbGroups.ID + " TEXT primary key not null)");
db.execSQL("create table " + LoyaltyCardDbIdsGroups.TABLE + "(" +
LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
LoyaltyCardDbIdsGroups.groupID + " TEXT," +
"primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID +"))");
+ " ADD COLUMN " + LoyaltyCardDbIds.ICON + " BLOB");
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.EXTRAS + " TEXT");
}
}
public long insertLoyaltyCard(final String store, final String note, final String cardId,
final String barcodeType, final Integer headerColor,
final int starStatus)
final Integer headerTextColor, final Bitmap icon,
final ExtrasHelper extras) throws JSONException
{
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
@@ -122,16 +110,18 @@ public class DBHelper extends SQLiteOpenHelper
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, Color.WHITE);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
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;
}
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id, final String store,
final String note, final String cardId,
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id,
final String store, final String note, final String cardId,
final String barcodeType, final Integer headerColor,
final int starStatus)
final Integer headerTextColor, final Bitmap icon,
final ExtrasHelper extras) throws JSONException
{
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.ID, id);
@@ -140,15 +130,18 @@ public class DBHelper extends SQLiteOpenHelper
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, Color.WHITE);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
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 Integer headerColor)
final Integer headerColor, final Integer headerTextColor,
final Bitmap icon, final ExtrasHelper extras) throws JSONException
{
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
@@ -157,18 +150,9 @@ public class DBHelper extends SQLiteOpenHelper
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, Color.WHITE);
int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,
LoyaltyCardDbIds.ID + "=?",
new String[]{Integer.toString(id)});
return (rowsUpdated == 1);
}
public boolean updateLoyaltyCardStarStatus(final int id, final int starStatus)
{
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
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)});
@@ -186,7 +170,6 @@ public class DBHelper extends SQLiteOpenHelper
if(data.getCount() == 1)
{
data.moveToFirst();
card = LoyaltyCard.toLoyaltyCard(data);
}
@@ -195,76 +178,12 @@ public class DBHelper extends SQLiteOpenHelper
return card;
}
public List<Group> getLoyaltyCardGroups(final int id)
{
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE + " g " +
" LEFT JOIN " + LoyaltyCardDbIdsGroups.TABLE + " ig ON ig." + LoyaltyCardDbIdsGroups.groupID + " = g." + LoyaltyCardDbGroups.ID +
" where " + LoyaltyCardDbIdsGroups.cardID + "=?" +
" ORDER BY " + LoyaltyCardDbIdsGroups.groupID, new String[]{String.format("%d", id)});
List<Group> groups = new ArrayList<>();
if (!data.moveToFirst()) {
return groups;
}
groups.add(Group.toGroup(data));
while (data.moveToNext()) {
groups.add(Group.toGroup(data));
}
return groups;
}
public void setLoyaltyCardGroups(final int id, List<Group> groups)
{
SQLiteDatabase db = getWritableDatabase();
// First delete lookup table entries associated with this card
db.delete(LoyaltyCardDbIdsGroups.TABLE,
LoyaltyCardDbIdsGroups.cardID + " = ? ",
new String[]{String.format("%d", id)});
// Then create entries for selected values
for (Group group : groups) {
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIdsGroups.cardID, id);
contentValues.put(LoyaltyCardDbIdsGroups.groupID, group._id);
db.insert(LoyaltyCardDbIdsGroups.TABLE, null, contentValues);
}
}
public void setLoyaltyCardGroups(final SQLiteDatabase db, final int id, List<Group> groups)
{
// First delete lookup table entries associated with this card
db.delete(LoyaltyCardDbIdsGroups.TABLE,
LoyaltyCardDbIdsGroups.cardID + " = ? ",
new String[]{String.format("%d", id)});
// Then create entries for selected values
for (Group group : groups) {
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIdsGroups.cardID, id);
contentValues.put(LoyaltyCardDbIdsGroups.groupID, group._id);
db.insert(LoyaltyCardDbIdsGroups.TABLE, null, contentValues);
}
}
public boolean deleteLoyaltyCard (final int id)
{
SQLiteDatabase db = getWritableDatabase();
// Delete card
int rowsDeleted = db.delete(LoyaltyCardDbIds.TABLE,
int rowsDeleted = db.delete(LoyaltyCardDbIds.TABLE,
LoyaltyCardDbIds.ID + " = ? ",
new String[]{String.format("%d", id)});
// And delete lookup table entries associated with this card
db.delete(LoyaltyCardDbIdsGroups.TABLE,
LoyaltyCardDbIdsGroups.cardID + " = ? ",
new String[]{String.format("%d", id)});
return (rowsDeleted == 1);
}
@@ -281,52 +200,16 @@ public class DBHelper extends SQLiteOpenHelper
* @return Cursor
*/
public Cursor getLoyaltyCardCursor(final String filter)
{
return getLoyaltyCardCursor(filter, null);
}
/**
* Returns a cursor to all loyalty cards with the filter text in either the store or note in a certain group.
*
* @param filter
* @param group
* @return Cursor
*/
public Cursor getLoyaltyCardCursor(final String filter, Group group)
{
String actualFilter = String.format("%%%s%%", filter);
String[] selectionArgs = { actualFilter, actualFilter };
StringBuilder groupFilter = new StringBuilder();
String limitString = "";
SQLiteDatabase db = getReadableDatabase();
if (group != null) {
List<Integer> allowedIds = getGroupCardIds(group._id);
// Empty group
if (allowedIds.size() > 0) {
groupFilter.append("AND (");
for (int i = 0; i < allowedIds.size(); i++) {
groupFilter.append(LoyaltyCardDbIds.ID + " = " + allowedIds.get(i));
if (i != allowedIds.size() - 1) {
groupFilter.append(" OR ");
}
}
groupFilter.append(") ");
} else {
limitString = "LIMIT 0";
}
}
Cursor res = db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
" WHERE (" + LoyaltyCardDbIds.STORE + " LIKE ? " +
" OR " + LoyaltyCardDbIds.NOTE + " LIKE ? )" +
groupFilter.toString() +
" ORDER BY " + LoyaltyCardDbIds.STAR_STATUS + " DESC," + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC " +
limitString, selectionArgs, null);
" WHERE " + LoyaltyCardDbIds.STORE + " LIKE ? " +
" OR " + LoyaltyCardDbIds.NOTE + " LIKE ? " +
" ORDER BY " + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC", selectionArgs, null);
return res;
}
@@ -365,172 +248,5 @@ public class DBHelper extends SQLiteOpenHelper
return numItems;
}
/**
* Returns a cursor to all groups.
*
* @return Cursor
*/
public Cursor getGroupCursor()
{
SQLiteDatabase db = getReadableDatabase();
Cursor res = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE +
" ORDER BY " + LoyaltyCardDbGroups.ID + " COLLATE NOCASE ASC", null, null);
return res;
}
public List<Group> getGroups() {
Cursor data = getGroupCursor();
List<Group> groups = new ArrayList<>();
if (!data.moveToFirst()) {
return groups;
}
groups.add(Group.toGroup(data));
while (data.moveToNext()) {
groups.add(Group.toGroup(data));
}
return groups;
}
public Group getGroup(final String groupName)
{
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE +
" where " + LoyaltyCardDbGroups.ID + "=?", new String[]{groupName});
Group group = null;
if(data.getCount() == 1)
{
data.moveToFirst();
group = Group.toGroup(data);
}
data.close();
return group;
}
public int getGroupCount()
{
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbGroups.TABLE, null);
int numItems = 0;
if(data.getCount() == 1)
{
data.moveToFirst();
numItems = data.getInt(0);
}
data.close();
return numItems;
}
public List<Integer> getGroupCardIds(final String groupName)
{
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.rawQuery("SELECT " + LoyaltyCardDbIdsGroups.cardID +
" FROM " + LoyaltyCardDbIdsGroups.TABLE +
" WHERE " + LoyaltyCardDbIdsGroups.groupID + " =? ", new String[]{groupName});
List<Integer> cardIds = new ArrayList<>();
if (!data.moveToFirst()) {
return cardIds;
}
cardIds.add(data.getInt(0));
while (data.moveToNext()) {
cardIds.add(data.getInt(0));
}
data.close();
return cardIds;
}
public long insertGroup(final String name)
{
if (name.isEmpty()) return -1;
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbGroups.ID, name);
final long newId = db.insert(LoyaltyCardDbGroups.TABLE, null, contentValues);
return newId;
}
public boolean insertGroup(final SQLiteDatabase db, final String name)
{
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbGroups.ID, name);
final long newId = db.insert(LoyaltyCardDbGroups.TABLE, null, contentValues);
return (newId != -1);
}
public boolean updateGroup(final String groupName, final String newName)
{
if (newName.isEmpty()) return false;
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbGroups.ID, newName);
try {
int rowsUpdated = db.update(LoyaltyCardDbGroups.TABLE, contentValues,
LoyaltyCardDbGroups.ID + "=?",
new String[]{groupName});
return (rowsUpdated == 1);
} catch (android.database.sqlite.SQLiteConstraintException _e) {
return false;
}
}
public boolean deleteGroup(final String groupName)
{
SQLiteDatabase db = getWritableDatabase();
// Delete group
int rowsDeleted = db.delete(LoyaltyCardDbGroups.TABLE,
LoyaltyCardDbGroups.ID + " = ? ",
new String[]{groupName});
// And delete lookup table entries associated with this group
db.delete(LoyaltyCardDbIdsGroups.TABLE,
LoyaltyCardDbIdsGroups.groupID + " = ? ",
new String[]{groupName});
return (rowsDeleted == 1);
}
public int getGroupCardCount(final String groupName)
{
SQLiteDatabase db = getReadableDatabase();
Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbIdsGroups.TABLE +
" where " + LoyaltyCardDbIdsGroups.groupID + "=?",
new String[]{groupName});
int numItems = 0;
if(data.getCount() == 1)
{
data.moveToFirst();
numItems = data.getInt(0);
}
data.close();
return numItems;
}
}

View 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;
}
}

View File

@@ -1,20 +0,0 @@
package protect.card_locker;
import android.database.Cursor;
public class Group
{
public final String _id;
public Group(final String _id)
{
this._id = _id;
}
public static Group toGroup(Cursor cursor)
{
String _id = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbGroups.ID));
return new Group(_id);
}
}

View File

@@ -1,53 +0,0 @@
package protect.card_locker;
import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.TextView;
import protect.card_locker.preferences.Settings;
class GroupCursorAdapter extends CursorAdapter
{
Settings settings;
DBHelper db;
public GroupCursorAdapter(Context context, Cursor cursor)
{
super(context, cursor, 0);
settings = new Settings(context);
db = new DBHelper(context);
}
// The newView method is used to inflate a new view and return it,
// you don't bind any data to the view at this point.
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
{
return LayoutInflater.from(context).inflate(R.layout.group_layout, parent, false);
}
// The bindView method is used to bind all data to a given view
// such as setting the text on a TextView.
@Override
public void bindView(View view, Context context, Cursor cursor)
{
// Find fields to populate in inflated template
TextView nameField = (TextView) view.findViewById(R.id.name);
TextView countField = (TextView) view.findViewById(R.id.cardCount);
// Extract properties from cursor
Group group = Group.toGroup(cursor);
// Populate fields with extracted properties
nameField.setText(group._id);
countField.setText(String.format(context.getString(R.string.groupCardCount), db.getGroupCardCount(group._id)));
nameField.setTextSize(settings.getCardTitleListFontSize());
countField.setTextSize(settings.getCardNoteListFontSize());
}
}

View File

@@ -2,7 +2,13 @@ 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 {
@@ -10,8 +16,10 @@ public class ImportURIHelper {
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;
@@ -24,12 +32,12 @@ public class ImportURIHelper {
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 = "brarcher.github.io";
oldPath = "/loyalty-card-locker/share";
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);
}
private boolean isImportUri(Uri uri) {
public boolean isImportUri(Uri uri) {
return (uri.getHost().equals(host) && uri.getPath().equals(path)) || (uri.getHost().equals(oldHost) && uri.getPath().equals(oldPath));
}
@@ -47,22 +55,46 @@ public class ImportURIHelper {
String note = uri.getQueryParameter(NOTE);
String cardId = uri.getQueryParameter(CARD_ID);
String barcodeType = uri.getQueryParameter(BARCODE_TYPE);
if (store == null || note == null || cardId == null || barcodeType == null) throw new InvalidObjectException("Not a valid import URI");
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;
return new LoyaltyCard(-1, store, note, cardId, barcodeType, headerColor, headerTextColor, 0);
} catch (NullPointerException | NumberFormatException ex) {
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) {
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);
@@ -75,7 +107,13 @@ public class ImportURIHelper {
{
uriBuilder.appendQueryParameter(HEADER_COLOR, loyaltyCard.headerColor.toString());
}
//StarStatus will not be exported
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();
}
@@ -89,7 +127,14 @@ public class ImportURIHelper {
context.startActivity(shareIntent);
}
public void startShareIntent(LoyaltyCard loyaltyCard) {
startShareIntent(toUri(loyaltyCard));
public boolean startShareIntent(LoyaltyCard loyaltyCard) {
try
{
startShareIntent(toUri(loyaltyCard));
return true;
}
catch (JSONException ex) {}
return false;
}
}

View File

@@ -1,9 +1,13 @@
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
{
public final int id;
@@ -18,11 +22,15 @@ public class LoyaltyCard
@Nullable
public final Integer headerTextColor;
public final int starStatus;
@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 int starStatus)
final Bitmap icon, final ExtrasHelper extras)
{
this.id = id;
this.store = store;
@@ -31,7 +39,8 @@ public class LoyaltyCard
this.barcodeType = barcodeType;
this.headerColor = headerColor;
this.headerTextColor = headerTextColor;
this.starStatus = starStatus;
this.icon = icon;
this.extras = extras;
}
public static LoyaltyCard toLoyaltyCard(Cursor cursor)
@@ -41,8 +50,6 @@ public class LoyaltyCard
String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID));
String barcodeType = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE));
int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS));
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
int headerTextColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR);
@@ -60,6 +67,32 @@ public class LoyaltyCard
headerTextColor = cursor.getInt(headerTextColorColumn);
}
return new LoyaltyCard(id, store, note, cardId, barcodeType, headerColor, headerTextColor, starred);
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);
}
}

View File

@@ -2,27 +2,24 @@ package protect.card_locker;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.PorterDuff;
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;
boolean darkModeEnabled;
public LoyaltyCardCursorAdapter(Context context, Cursor cursor)
{
super(context, cursor, 0);
settings = new Settings(context);
darkModeEnabled= MainActivity.isDarkModeEnabled(context);
}
// The newView method is used to inflate a new view and return it,
@@ -40,14 +37,8 @@ class LoyaltyCardCursorAdapter extends CursorAdapter
{
// Find fields to populate in inflated template
ImageView thumbnail = view.findViewById(R.id.thumbnail);
TextView storeField = view.findViewById(R.id.store);
TextView noteField = view.findViewById(R.id.note);
ImageView star = view.findViewById(R.id.star);
if(darkModeEnabled)
{
star.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
}
TextView storeField = (TextView) view.findViewById(R.id.store);
TextView noteField = (TextView) view.findViewById(R.id.note);
// Extract properties from cursor
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(cursor);
@@ -57,7 +48,7 @@ class LoyaltyCardCursorAdapter extends CursorAdapter
storeField.setTextSize(settings.getCardTitleListFontSize());
if(!loyaltyCard.note.isEmpty())
if(loyaltyCard.note.isEmpty() == false)
{
noteField.setVisibility(View.VISIBLE);
noteField.setText(loyaltyCard.note);
@@ -68,9 +59,21 @@ class LoyaltyCardCursorAdapter extends CursorAdapter
noteField.setVisibility(View.GONE);
}
if (loyaltyCard.starStatus!=0) star.setVisibility(View.VISIBLE);
else star.setVisibility(View.GONE);
int tileLetterFontSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterFontSize);
int pixelSize = context.getResources().getDimensionPixelSize(R.dimen.cardThumbnailSize);
thumbnail.setImageBitmap(Utils.generateIcon(context, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile());
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);
}
}
}

View File

@@ -4,30 +4,23 @@ 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.chip.Chip;
import com.google.android.material.chip.ChipGroup;
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.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
@@ -40,24 +33,29 @@ 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;
import java.util.ArrayList;
import java.util.List;
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;
ImageView thumbnail;
EditText storeFieldEdit;
EditText noteFieldEdit;
ChipGroup groupsChips;
View cardAndBarcodeLayout;
ImageView headingColorSample;
Button headingColorSelectButton;
ImageView headingStoreTextColorSample;
Button headingStoreTextColorSelectButton;
TextView cardIdFieldView;
View barcodeTypeView;
AutoCompleteTextView barcodeTypeField;
View cardIdDivider;
View cardIdTableRow;
TextView barcodeTypeField;
ImageView barcodeImage;
View barcodeImageLayout;
View barcodeCaptureLayout;
@@ -68,22 +66,53 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
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)
{
@@ -102,74 +131,24 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
db = new DBHelper(this);
importUriHelper = new ImportURIHelper(this);
pkpassImporter = new PkpassImporter(this);
thumbnail = findViewById(R.id.thumbnail);
storeFieldEdit = findViewById(R.id.storeNameEdit);
noteFieldEdit = findViewById(R.id.noteEdit);
groupsChips = findViewById(R.id.groupChips);
cardAndBarcodeLayout = findViewById(R.id.cardAndBarcodeLayout);
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);
barcodeTypeView = findViewById(R.id.barcodeTypeView);
barcodeTypeField = findViewById(R.id.barcodeTypeField);
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);
storeFieldEdit.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
generateIcon(s.toString());
}
@Override
public void afterTextChanged(Editable s) { }
});
cardIdFieldView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String formatString = barcodeTypeField.getText().toString();
if (!formatString.isEmpty()) {
if (formatString.equals(getString(R.string.noBarcode))) {
hideBarcode();
} else {
generateBarcode(s.toString(), BarcodeFormat.valueOf(formatString));
}
}
}
@Override
public void afterTextChanged(Editable s) { }
});
barcodeTypeField.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!s.toString().isEmpty()) {
if (s.toString().equals(getString(R.string.noBarcode))) {
hideBarcode();
} else {
generateBarcode(cardIdFieldView.getText().toString(), BarcodeFormat.valueOf(s.toString()));
}
}
}
@Override
public void afterTextChanged(Editable s) { }
});
}
@Override
@@ -222,7 +201,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
if(barcodeTypeField.getText().length() == 0)
{
barcodeTypeField.setText(loyaltyCard.barcodeType.isEmpty() ? getString(R.string.noBarcode) : loyaltyCard.barcodeType);
barcodeTypeField.setText(loyaltyCard.barcodeType.isEmpty() ? LoyaltyCardEditActivity.NO_BARCODE : loyaltyCard.barcodeType);
}
if(headingColorValue == null)
@@ -234,15 +213,26 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
}
}
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;
try {
importCard = importUriHelper.parse(importLoyaltyCardUri);
} catch (InvalidObjectException ex) {
LoyaltyCard importCard = importCard(importLoyaltyCardType, importLoyaltyCardUri);
if(importCard == null)
{
Toast.makeText(this, R.string.failedParsingImportUriError, Toast.LENGTH_LONG).show();
finish();
return;
@@ -253,6 +243,9 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
cardIdFieldView.setText(importCard.cardId);
barcodeTypeField.setText(importCard.barcodeType);
headingColorValue = importCard.headerColor;
headingStoreTextColorValue = importCard.headerTextColor;
icon = importCard.icon;
extras = importCard.extras;
}
else
{
@@ -260,35 +253,6 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
hideBarcode();
}
if(groupsChips.getChildCount() == 0)
{
List<Group> existingGroups = db.getGroups();
List<Group> loyaltyCardGroups = db.getLoyaltyCardGroups(loyaltyCardId);
if (existingGroups.isEmpty()) {
groupsChips.setVisibility(View.GONE);
} else {
groupsChips.setVisibility(View.VISIBLE);
}
for (Group group : db.getGroups()) {
Chip chip = (Chip) getLayoutInflater().inflate(R.layout.layout_chip_choice, groupsChips, false);
chip.setText(group._id);
chip.setTag(group);
chip.setChecked(false);
for (Group loyaltyCardGroup : loyaltyCardGroups) {
if (loyaltyCardGroup._id.equals(group._id)) {
chip.setChecked(true);
break;
}
}
groupsChips.addView(chip);
}
}
if(headingColorValue == null)
{
// Select a random color to start out with.
@@ -298,22 +262,59 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
colors.recycle();
}
thumbnail.setOnClickListener(new ColorSelectListener(headingColorValue));
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)
{
String formatString = barcodeTypeField.getText().toString();
if(formatString.isEmpty() || formatString.equals(getString(R.string.noBarcode)))
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();
generateBarcode(cardIdString, format);
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();
}
}
@@ -334,62 +335,60 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
captureButton.setOnClickListener(captureCallback);
enterButton.setOnClickListener(new EditCardIdAndBarcode());
barcodeImage.setOnClickListener(new EditCardIdAndBarcode());
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)
{
cardAndBarcodeLayout.setVisibility(View.VISIBLE);
cardIdDivider.setVisibility(View.VISIBLE);
cardIdTableRow.setVisibility(View.VISIBLE);
enterButton.setText(R.string.editCard);
}
else
{
cardAndBarcodeLayout.setVisibility(View.GONE);
cardIdDivider.setVisibility(View.GONE);
cardIdTableRow.setVisibility(View.GONE);
enterButton.setText(R.string.enterCard);
}
ArrayList<String> barcodeList = new ArrayList<>(BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES);
barcodeList.add(0, getString(R.string.noBarcode));
ArrayAdapter<String> barcodeAdapter = new ArrayAdapter<>(this, android.R.layout.select_dialog_item, barcodeList);
barcodeTypeField.setAdapter(barcodeAdapter);
FloatingActionButton saveButton = findViewById(R.id.fabSave);
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doSave();
try {
doSave();
} catch (JSONException ex) {
Toast.makeText(getApplicationContext(), R.string.failedSavingCard, Toast.LENGTH_LONG).show();
}
}
});
generateIcon(storeFieldEdit.getText().toString());
}
class EditCardIdAndBarcode implements 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);
}
}
class ColorSelectListener implements View.OnClickListener
{
final int defaultColor;
final boolean isBackgroundColor;
ColorSelectListener(int defaultColor)
ColorSelectListener(int defaultColor, boolean isBackgroundColor)
{
this.defaultColor = defaultColor;
this.isBackgroundColor = isBackgroundColor;
}
@Override
@@ -401,9 +400,16 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
@Override
public void onColorSelected(int dialogId, int color)
{
headingColorValue = color;
generateIcon(storeFieldEdit.getText().toString());
if(isBackgroundColor)
{
headingColorSample.setBackgroundColor(color);
headingColorValue = color;
}
else
{
headingStoreTextColorSample.setBackgroundColor(color);
headingStoreTextColorValue = color;
}
}
@Override
@@ -416,7 +422,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
}
}
private void doSave()
private void doSave() throws JSONException
{
String store = storeFieldEdit.getText().toString();
String note = noteFieldEdit.getText().toString();
@@ -425,7 +431,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
// 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(getString(R.string.noBarcode)))
if(barcodeType.equals(NO_BARCODE))
{
barcodeType = "";
}
@@ -442,25 +448,16 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
return;
}
List<Group> selectedGroups = new ArrayList<>();
for (Integer chipId : groupsChips.getCheckedChipIds()) {
Chip chip = groupsChips.findViewById(chipId);
selectedGroups.add((Group) chip.getTag());
}
if(updateLoyaltyCard)
{ //update of "starStatus" not necessary, since it cannot be changed in this activity (only in ViewActivity)
db.updateLoyaltyCard(loyaltyCardId, store, note, cardId, barcodeType, headingColorValue);
{
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, 0);
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, cardId, barcodeType, headingColorValue, headingStoreTextColorValue, icon, extras);
}
db.setLoyaltyCardGroups(loyaltyCardId, selectedGroups);
finish();
}
@@ -562,61 +559,20 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
cardIdView.setText(contents);
// Set special NO_BARCODE value to prevent onResume from overwriting it
barcodeTypeField.setText(format.isEmpty() ? getString(R.string.noBarcode) : format);
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);
}
private void generateBarcode(final String cardId, final BarcodeFormat barcodeFormat) {
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, cardId, barcodeFormat).execute();
}
});
} else {
Log.d(TAG, "ImageView size known known, creating barcode");
new BarcodeImageWriterTask(barcodeImage, cardId, barcodeFormat).execute();
}
showBarcode();
}
private void generateIcon(String store) {
if (headingColorValue == null) {
return;
}
thumbnail.setBackgroundColor(headingColorValue);
LetterBitmap letterBitmap = Utils.generateIcon(this, store, headingColorValue);
if (letterBitmap != null) {
thumbnail.setImageBitmap(letterBitmap.getLetterTile());
} else {
thumbnail.setImageBitmap(null);
}
thumbnail.setMinimumWidth(thumbnail.getHeight());
findViewById(R.id.barcodeTypeDivider).setVisibility(View.GONE);
findViewById(R.id.barcodeTypeTableRow).setVisibility(View.GONE);
}
}

View File

@@ -1,5 +1,7 @@
package protect.card_locker;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
@@ -7,7 +9,9 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
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;
@@ -30,11 +34,19 @@ import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.zxing.BarcodeFormat;
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 = "Catima";
private static final double LUMINANCE_MIDPOINT = 0.5;
TextView cardIdFieldView;
TextView noteView;
@@ -52,7 +64,6 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
String cardIdString;
BarcodeFormat format;
boolean starred;
boolean backgroundNeedsDarkIcons;
boolean barcodeIsFullscreen = false;
ViewGroup.LayoutParams barcodeImageState;
@@ -197,6 +208,17 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
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)
{
@@ -209,19 +231,8 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
collapsingToolbarLayout.setBackgroundColor(backgroundHeaderColor);
int textColor;
if(Utils.needsDarkForeground(backgroundHeaderColor))
{
textColor = Color.BLACK;
}
else
{
textColor = Color.WHITE;
}
storeName.setTextColor(textColor);
// If the background is very bright, we should use dark icons
backgroundNeedsDarkIcons = Utils.needsDarkForeground(backgroundHeaderColor);
backgroundNeedsDarkIcons = (ColorUtils.calculateLuminance(backgroundHeaderColor) > LUMINANCE_MIDPOINT);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
@@ -314,29 +325,18 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
item.setVisible(false);
}
loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
starred = loyaltyCard.starStatus != 0;
if(loyaltyCard != null && !loyaltyCard.extras.getAllValues(new String[]{Locale.getDefault().getLanguage(), "en", ""}).isEmpty())
{
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);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
if (starred) {
menu.findItem(R.id.action_star_unstar).setIcon(getIcon(R.drawable.ic_starred_white, backgroundNeedsDarkIcons));
menu.findItem(R.id.action_star_unstar).setTitle(R.string.unstar);
}
else {
menu.findItem(R.id.action_star_unstar).setIcon(getIcon(R.drawable.ic_unstarred_white, backgroundNeedsDarkIcons));
menu.findItem(R.id.action_star_unstar).setTitle(R.string.star);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
@@ -364,17 +364,22 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
rotationEnabled = !rotationEnabled;
return true;
case R.id.action_star_unstar:
starred = !starred;
db.updateLoyaltyCardStarStatus(loyaltyCardId, starred ? 1 : 0);
invalidateOptionsMenu();
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);
}
private void setOrientatonLock(MenuItem item, boolean lock)
{
if(lock)
@@ -388,10 +393,33 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
{
item.setIcon(getIcon(R.drawable.ic_lock_open_white_24dp, backgroundNeedsDarkIcons));
item.setTitle(R.string.lockScreen);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
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.
*

View File

@@ -2,10 +2,11 @@ package protect.card_locker;
import android.app.SearchManager;
import android.content.ClipData;
import android.content.ClipboardManager;
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;
@@ -17,35 +18,31 @@ import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import android.util.Log;
import android.view.ContextMenu;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import com.google.common.collect.ImmutableMap;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import protect.card_locker.preferences.SettingsActivity;
public class MainActivity extends AppCompatActivity implements GestureDetector.OnGestureListener
public class MainActivity extends AppCompatActivity
{
private static final String TAG = "Catima";
private static final int MAIN_REQUEST_CODE = 1;
private Menu menu;
private GestureDetector gestureDetector;
protected String filter = "";
protected int selectedTab = 0;
@Override
protected void onCreate(Bundle savedInstanceState)
@@ -55,43 +52,7 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
updateLoyaltyCardList(filter, null);
TabLayout groupsTabLayout = findViewById(R.id.groups);
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
selectedTab = tab.getPosition();
updateLoyaltyCardList(filter, tab.getTag());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
gestureDetector = new GestureDetector(this, this);
View.OnTouchListener gestureTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(final View v, final MotionEvent event){
return gestureDetector.onTouchEvent(event);
}
};
final View helpText = findViewById(R.id.helpText);
final View noMatchingCardsText = findViewById(R.id.noMatchingCardsText);
final View list = findViewById(R.id.list);
helpText.setOnTouchListener(gestureTouchListener);
noMatchingCardsText.setOnTouchListener(gestureTouchListener);
list.setOnTouchListener(gestureTouchListener);
updateLoyaltyCardList("");
}
@Override
@@ -107,22 +68,7 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
}
}
TabLayout groupsTabLayout = findViewById(R.id.groups);
boolean hasReset = updateTabGroups(groupsTabLayout);
Object group = null;
if (groupsTabLayout.getTabCount() != 0) {
TabLayout.Tab tab = groupsTabLayout.getTabAt(0);
if (!hasReset) {
tab = groupsTabLayout.getTabAt(selectedTab);
}
groupsTabLayout.selectTab(tab);
group = tab.getTag();
}
updateLoyaltyCardList(filter, group);
updateLoyaltyCardList(filter);
FloatingActionButton addButton = findViewById(R.id.fabAdd);
addButton.setOnClickListener(new View.OnClickListener() {
@@ -167,31 +113,17 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
if (!searchView.isIconified()) {
searchView.setIconified(true);
} else {
TabLayout groupsTabLayout = findViewById(R.id.groups);
if (groupsTabLayout.getVisibility() == View.VISIBLE && selectedTab != 0) {
selectedTab = 0;
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(0));
} else {
super.onBackPressed();
}
super.onBackPressed();
}
}
private void updateLoyaltyCardList(String filterText, Object tag)
private void updateLoyaltyCardList(String filterText)
{
Group group = null;
if (tag != null) {
group = (Group) tag;
}
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);
Cursor cardCursor = db.getLoyaltyCardCursor(filterText, group);
if(db.getLoyaltyCardCount() > 0)
{
// We want the cardList to be visible regardless of the filtered match count
@@ -199,7 +131,7 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
// the keyboard
cardList.setVisibility(View.VISIBLE);
helpText.setVisibility(View.GONE);
if(cardCursor.getCount() > 0)
if(db.getLoyaltyCardCount(filterText) > 0)
{
noMatchingCardsText.setVisibility(View.GONE);
}
@@ -215,6 +147,8 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
noMatchingCardsText.setVisibility(View.GONE);
}
Cursor cardCursor = db.getLoyaltyCardCursor(filterText);
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
cardList.setAdapter(adapter);
@@ -241,53 +175,6 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
});
}
public boolean updateTabGroups(TabLayout groupsTabLayout)
{
final DBHelper db = new DBHelper(this);
List<Group> newGroups = db.getGroups();
if (newGroups.size() == 0) {
groupsTabLayout.removeAllTabs();
groupsTabLayout.setVisibility(View.GONE);
return true;
}
// -1 because there is an "All" tab
boolean isChanged = groupsTabLayout.getTabCount() - 1 != newGroups.size();
if (!isChanged) {
for (int i = 0; i < newGroups.size(); i++) {
if (!((Group) groupsTabLayout.getTabAt(i + 1).getTag())._id.equals(newGroups.get(i)._id)) {
isChanged = true;
break;
}
}
}
if (isChanged) {
groupsTabLayout.removeAllTabs();
TabLayout.Tab allTab = groupsTabLayout.newTab();
allTab.setText(R.string.all);
allTab.setTag(null);
groupsTabLayout.addTab(allTab);
for (Group group : newGroups) {
TabLayout.Tab tab = groupsTabLayout.newTab();
tab.setText(group._id);
tab.setTag(group);
groupsTabLayout.addTab(tab);
}
groupsTabLayout.setVisibility(View.VISIBLE);
return true;
}
return false;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)
{
@@ -308,20 +195,26 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
Cursor cardCursor = (Cursor)listView.getItemAtPosition(info.position);
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
if(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;
}
else if(item.getItemId() == R.id.action_share)
{
final ImportURIHelper importURIHelper = new ImportURIHelper(this);
importURIHelper.startShareIntent(card);
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);
@@ -357,10 +250,7 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
@Override
public boolean onQueryTextChange(String newText) {
filter = newText;
TabLayout groupsTabLayout = findViewById(R.id.groups);
updateLoyaltyCardList(newText, groupsTabLayout.getTabCount() > 0 ? groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition()).getTag() : null);
updateLoyaltyCardList(newText);
return true;
}
});
@@ -374,13 +264,6 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
{
int id = item.getItemId();
if (id == R.id.action_manage_groups)
{
Intent i = new Intent(getApplicationContext(), ManageGroupsActivity.class);
startActivityForResult(i, MAIN_REQUEST_CODE);
return true;
}
if(id == R.id.action_import_export)
{
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
@@ -453,7 +336,9 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
// Set CSS for dark mode if dark mode
String css = "";
if(isDarkModeEnabled(this))
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>";
}
@@ -499,76 +384,4 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
})
.show();
}
protected static boolean isDarkModeEnabled(Context inputContext)
{
Configuration config = inputContext.getResources().getConfiguration();
int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
return (currentNightMode == Configuration.UI_MODE_NIGHT_YES);
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.d(TAG, "On fling");
TabLayout groupsTabLayout = findViewById(R.id.groups);
if (groupsTabLayout.getTabCount() < 2) {
return false;
}
Integer currentTab = groupsTabLayout.getSelectedTabPosition();
// Swipe right
if (velocityX < -150) {
Integer nextTab = currentTab + 1;
if (nextTab == groupsTabLayout.getTabCount()) {
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(0));
} else {
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(nextTab));
}
return true;
}
// Swipe left
if (velocityX > 150) {
Integer nextTab = currentTab - 1;
if (nextTab < 0) {
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(groupsTabLayout.getTabCount() - 1));
} else {
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(nextTab));
}
return true;
}
return false;
}
}

View File

@@ -1,186 +0,0 @@
package protect.card_locker;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.Bundle;
import android.text.InputType;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
public class ManageGroupsActivity extends AppCompatActivity
{
private static final String TAG = "Catima";
private AlertDialog newGroupDialog;
private final DBHelper db = new DBHelper(this);
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.manage_groups_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null)
{
actionBar.setDisplayHomeAsUpEnabled(true);
}
newGroupDialog = createNewGroupDialog();
updateGroupList();
}
@Override
protected void onResume() {
super.onResume();
updateGroupList();
FloatingActionButton addButton = findViewById(R.id.fabAdd);
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
newGroupDialog.show();
}
});
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
private void updateGroupList()
{
final ListView groupList = findViewById(R.id.list);
final TextView helpText = findViewById(R.id.helpText);
final DBHelper db = new DBHelper(this);
if(db.getGroupCount() > 0)
{
groupList.setVisibility(View.VISIBLE);
helpText.setVisibility(View.GONE);
}
else
{
groupList.setVisibility(View.GONE);
helpText.setVisibility(View.VISIBLE);
}
Cursor groupCursor = db.getGroupCursor();
final GroupCursorAdapter adapter = new GroupCursorAdapter(this, groupCursor);
groupList.setAdapter(adapter);
registerForContextMenu(groupList);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
}
return super.onOptionsItemSelected(item);
}
public void editGroup(View view) {
LinearLayout parentRow = (LinearLayout) view.getParent();
TextView groupNameTextView = (TextView) parentRow.findViewById(R.id.name);
final String groupName = (String) groupNameTextView.getText();
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.enter_group_name);
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText(groupName);
builder.setView(input);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
db.updateGroup(groupName, input.getText().toString());
updateGroupList();
}
});
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
public void deleteGroup(View view) {
LinearLayout parentRow = (LinearLayout) view.getParent();
TextView groupNameTextView = (TextView) parentRow.findViewById(R.id.name);
final String groupName = (String) groupNameTextView.getText();
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.deleteConfirmationGroup);
builder.setMessage(groupName);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
db.deleteGroup(groupName);
updateGroupList();
}
});
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
private AlertDialog createNewGroupDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.enter_group_name);
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
builder.setView(input);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
db.insertGroup(input.getText().toString());
updateGroupList();
}
});
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
AlertDialog dialog = builder.create();
return dialog;
}
}

View 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);
}
}

View File

@@ -1,30 +0,0 @@
package protect.card_locker;
import android.content.Context;
import android.graphics.Color;
import androidx.core.graphics.ColorUtils;
public class Utils {
static final double LUMINANCE_MIDPOINT = 0.5;
static public LetterBitmap generateIcon(Context context, String store, Integer backgroundColor) {
if (store.length() == 0) {
return null;
}
int tileLetterFontSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterFontSize);
int pixelSize = context.getResources().getDimensionPixelSize(R.dimen.cardThumbnailSize);
if (backgroundColor == null) {
backgroundColor = LetterBitmap.getDefaultColor(context, store);
}
return new LetterBitmap(context, store, store,
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
}
static public boolean needsDarkForeground(Integer backgroundColor) {
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
}
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 B

After

Width:  |  Height:  |  Size: 519 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 B

After

Width:  |  Height:  |  Size: 406 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 B

After

Width:  |  Height:  |  Size: 651 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 829 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 809 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 B

After

Width:  |  Height:  |  Size: 853 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 758 B

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,66 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="192dp"
android:height="192dp"
android:viewportWidth="50.8"
android:viewportHeight="50.8">
<path
android:pathData="M14.3354,20.1954l20.7318,-9.2304l5.7612,12.9398l-20.7318,9.2304z"
android:strokeLineJoin="miter"
android:strokeWidth="0.529167"
android:fillColor="#f0f0f0"
android:strokeColor="#c80000"/>
<path
android:pathData="M14.8755,10.9648l23.2041,10.3311l-6.8874,15.4694l-23.2041,-10.3311z"
android:strokeLineJoin="miter"
android:strokeWidth="0.529167"
android:fillColor="#f0f0f0"
android:strokeColor="#c80000"/>
<path
android:pathData="M16.5599,16.1348l26.5459,7.6119l-4.5489,15.8639l-26.5459,-7.6119z"
android:strokeLineJoin="round"
android:strokeWidth="1.5875"
android:fillColor="#c80000"
android:strokeColor="#c80000"
android:fillType="evenOdd"/>
<path
android:pathData="M12.011,15.4955h27.6157v16.5032h-27.6157z"
android:strokeLineJoin="round"
android:strokeWidth="1.5875"
android:fillColor="#ff0000"
android:strokeColor="#ff0000"
android:fillType="evenOdd"/>
<path
android:pathData="M7.8471,23.7471a4.3659,8.5899 0,1 0,8.7317 0a4.3659,8.5899 0,1 0,-8.7317 0z"
android:strokeLineJoin="round"
android:strokeWidth="0.91078"
android:fillColor="#ff0000"
android:strokeColor="#ff0000"/>
<path
android:pathData="m24.4983,25.781a1.6711,1.6711 0,0 1,-1.3809 1.6457,1.6711 1.6711,0 0,1 -1.8605,-1.0741"
android:strokeLineJoin="round"
android:strokeWidth="0.529167"
android:fillColor="#00000000"
android:strokeColor="#f0f0f0"
android:strokeLineCap="round"/>
<path
android:pathData="m27.7991,26.333a1.6711,1.6711 0,0 1,-1.8605 1.0741,1.6711 1.6711,0 0,1 -1.3809,-1.6457"
android:strokeLineJoin="round"
android:strokeWidth="0.529167"
android:fillColor="#00000000"
android:strokeColor="#f0f0f0"
android:strokeLineCap="round"/>
<path
android:pathData="m16.0606,22.271 l2.6458,-2.6458 2.6458,2.6458"
android:strokeLineJoin="miter"
android:strokeWidth="0.529167"
android:fillColor="#00000000"
android:strokeColor="#f0f0f0"
android:strokeLineCap="butt"/>
<path
android:pathData="m27.7023,22.271 l2.6458,-2.6458 2.6458,2.6458"
android:strokeLineJoin="miter"
android:strokeWidth="0.529167"
android:fillColor="#00000000"
android:strokeColor="#f0f0f0"
android:strokeLineCap="butt"/>
</vector>

View File

@@ -1,71 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="1.5307087"
android:scaleY="1.5307087"
android:translateX="15.12"
android:translateY="15.12">
<path
android:pathData="M14.3354,20.1954l20.7318,-9.2304l5.7612,12.9398l-20.7318,9.2304z"
android:strokeLineJoin="miter"
android:strokeWidth="0.529167"
android:fillColor="#f0f0f0"
android:strokeColor="#c80000"/>
<path
android:pathData="M14.8755,10.9648l23.2041,10.3311l-6.8874,15.4694l-23.2041,-10.3311z"
android:strokeLineJoin="miter"
android:strokeWidth="0.529167"
android:fillColor="#f0f0f0"
android:strokeColor="#c80000"/>
<path
android:pathData="M16.5599,16.1348l26.5459,7.6119l-4.5489,15.8639l-26.5459,-7.6119z"
android:strokeLineJoin="round"
android:strokeWidth="1.5875"
android:fillColor="#c80000"
android:strokeColor="#c80000"
android:fillType="evenOdd"/>
<path
android:pathData="M12.011,15.4955h27.6157v16.5032h-27.6157z"
android:strokeLineJoin="round"
android:strokeWidth="1.5875"
android:fillColor="#ff0000"
android:strokeColor="#ff0000"
android:fillType="evenOdd"/>
<path
android:pathData="M7.8471,23.7471a4.3659,8.5899 0,1 0,8.7317 0a4.3659,8.5899 0,1 0,-8.7317 0z"
android:strokeLineJoin="round"
android:strokeWidth="0.91078"
android:fillColor="#ff0000"
android:strokeColor="#ff0000"/>
<path
android:pathData="m24.4983,25.781a1.6711,1.6711 0,0 1,-1.3809 1.6457,1.6711 1.6711,0 0,1 -1.8605,-1.0741"
android:strokeLineJoin="round"
android:strokeWidth="0.529167"
android:fillColor="#00000000"
android:strokeColor="#f0f0f0"
android:strokeLineCap="round"/>
<path
android:pathData="m27.7991,26.333a1.6711,1.6711 0,0 1,-1.8605 1.0741,1.6711 1.6711,0 0,1 -1.3809,-1.6457"
android:strokeLineJoin="round"
android:strokeWidth="0.529167"
android:fillColor="#00000000"
android:strokeColor="#f0f0f0"
android:strokeLineCap="round"/>
<path
android:pathData="m16.0606,22.271 l2.6458,-2.6458 2.6458,2.6458"
android:strokeLineJoin="miter"
android:strokeWidth="0.529167"
android:fillColor="#00000000"
android:strokeColor="#f0f0f0"
android:strokeLineCap="butt"/>
<path
android:pathData="m27.7023,22.271 l2.6458,-2.6458 2.6458,2.6458"
android:strokeLineJoin="miter"
android:strokeWidth="0.529167"
android:fillColor="#00000000"
android:strokeColor="#f0f0f0"
android:strokeLineCap="butt"/>
</group>
</vector>

View File

@@ -13,8 +13,7 @@
style="@style/AppTheme.TextView.NoData"
android:id="@+id/helpText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:layout_height="wrap_content"
android:text="@string/noGiftCards"
android:visibility="gone"/>
@@ -22,14 +21,13 @@
style="@style/AppTheme.TextView.NoData"
android:id="@+id/noMatchingCardsText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:layout_height="wrap_content"
android:text="@string/noMatchingGiftCards"
android:visibility="gone"/>
<ListView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:id="@+id/list"
android:visibility="gone"/>
</RelativeLayout>

View File

@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
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">
<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/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/storeNameTextSize"
android:textStyle="bold"/>
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/cardCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
android:textSize="@dimen/noteTextSize"/>
</LinearLayout>
<ImageButton
android:id="@+id/edit"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_marginLeft="@dimen/activity_margin"
android:src="@drawable/ic_mode_edit_white_24dp"
android:contentDescription="@string/edit"
app:tint="#000000"
android:onClick="editGroup" />
<ImageButton
android:id="@+id/delete"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_marginLeft="@dimen/activity_margin"
android:src="@drawable/ic_delete_white_24dp"
android:contentDescription="@string/delete"
app:tint="#000000"
android:onClick="deleteGroup"/>
</LinearLayout>

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="protect.card_locker.MainActivity"
tools:showIn="@layout/main_activity">
<TextView
style="@style/AppTheme.TextView.NoData"
android:id="@+id/helpText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/noGroups"
android:visibility="gone"/>
<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/list"
android:visibility="gone"/>
</RelativeLayout>

View File

@@ -1,9 +0,0 @@
<com.google.android.material.chip.Chip xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:paddingRight="8dp"
style="@style/Widget.MaterialComponents.Chip.Choice"
app:checkedIconVisible="true"
android:textAppearance="?android:attr/textAppearance" />

View File

@@ -32,191 +32,420 @@
android:layout_height="wrap_content"
android:background="@color/inputContrastBackground"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<TableLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Store -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/inputPadding"
android:paddingTop="@dimen/inputPadding"
android:orientation="horizontal">
<androidx.cardview.widget.CardView
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_marginEnd="@dimen/activity_margin"
android:layout_marginRight="@dimen/activity_margin"
android:layout_gravity="center_vertical"
app:cardCornerRadius="4dp"
android:paddingHorizontal="@dimen/inputPadding"
app:cardElevation="0dp">
<ImageView
android:id="@+id/thumbnail"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:contentDescription="@string/thumbnailDescription"
android:src="@mipmap/ic_launcher"/>
</androidx.cardview.widget.CardView>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/storeNameField"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/storeName">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/storeNameEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<!-- Note -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/inputPadding"
android:paddingTop="@dimen/inputPadding"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/noteField"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/note">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/noteEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<!-- Group -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/inputPadding"
android:paddingTop="@dimen/inputPadding"
android:orientation="horizontal">
<com.google.android.material.chip.ChipGroup
android:id="@+id/groupChips"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:padding="@dimen/inputPadding"
android:textSize="@dimen/inputSize" />
</LinearLayout>
<!-- Buttons -->
<TableLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:shrinkColumns="1"
android:background="@color/inputContrastBackground">
android:layout_height="wrap_content"
android:shrinkColumns="1"
android:background="@color/inputContrastBackground">
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@color/inputBackground">
<!-- 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" />
<LinearLayout android:orientation="horizontal"
android:padding="10.0dip"
android:layout_width="fill_parent"
<RelativeLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/barcodeCaptureLayout">
<Button android:id="@+id/captureButton"
android:layout_margin="@dimen/inputMargin"
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_margin="@dimen/inputMargin"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/enterCard"
android:layout_weight="1.0"/>
</LinearLayout>
</LinearLayout>
</TableLayout>
android:paddingRight="@dimen/inputPadding"
android:paddingEnd="@dimen/inputPadding">
<!-- Card ID and Barcode type -->
<LinearLayout
android:id="@+id/cardAndBarcodeLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/inputPadding"
android:paddingTop="@dimen/inputPadding"
android:orientation="horizontal">
<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 -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/cardIdField"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:hint="@string/cardId">
<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" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/cardIdView"
<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">
</com.google.android.material.textfield.TextInputLayout>
<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"
/>
<!-- Barcode type -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/barcodeTypeView"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:hint="@string/barcodeType">
<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>
<AutoCompleteTextView
android:id="@+id/barcodeTypeField"
<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:inputType="none"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
android:paddingRight="@dimen/inputPadding"
android:paddingEnd="@dimen/inputPadding">
<!-- Barcode -->
<View
android:layout_height="@dimen/inputBorderThickness"
android:layout_width="match_parent" />
<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"
/>
<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>
</TableLayout>
<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>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -8,22 +8,14 @@
android:baselineAligned="false"
android:padding="@dimen/activity_margin">
<androidx.cardview.widget.CardView
<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"
app:cardCornerRadius="4dp"
app:cardElevation="0dp">
<ImageView
android:id="@+id/thumbnail"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:contentDescription="@string/thumbnailDescription"
android:src="@mipmap/ic_launcher"/>
</androidx.cardview.widget.CardView>
android:src="@mipmap/ic_launcher"
android:contentDescription="@string/thumbnailDescription"/>
<LinearLayout
android:orientation="vertical"
@@ -60,12 +52,5 @@
android:ellipsize="end"
android:textSize="@dimen/noteTextSize"/>
</LinearLayout>
<ImageView
android:id="@+id/star"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_marginLeft="@dimen/activity_margin"
android:src="@drawable/ic_starred_white"
android:tint="#000000"
android:contentDescription="@string/starImage"/>
</LinearLayout>

View File

@@ -29,13 +29,6 @@
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
<com.google.android.material.tabs.TabLayout
android:id="@+id/groups"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable"
android:visibility="gone"/>
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_main"/>

View File

@@ -1,36 +0,0 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="protect.card_locker.MainActivity">
<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">
<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>
<include layout="@layout/group_main"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -13,8 +13,9 @@
android:title="@string/share"
app:showAsAction="always"/>
<item
android:id="@+id/action_star_unstar"
android:icon="@drawable/ic_unstarred_white"
android:title="@string/star"
app:showAsAction="always" />
android:id="@+id/action_view_extras"
android:icon="@drawable/ic_info_outline_white"
android:title="@string/moreInfo"
app:showAsAction="always"
android:visible="false"/>
</menu>

View File

@@ -8,11 +8,6 @@
android:icon="@drawable/ic_search_white"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always|collapseActionView"/>
<item
android:id="@+id/action_manage_groups"
android:icon="@drawable/ic_folder_white"
android:title="@string/groups"
app:showAsAction="always"/>
<item
android:id="@+id/action_import_export"
android:icon="@drawable/ic_import_export_white_24dp"

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Normal file → Executable file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -47,6 +47,7 @@
<string name="importOptionApplicationButton">Použít externí aplikaci</string>
<string name="about">O aplikaci</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Licensed under the GPLv3.</string>
<string name="about_title_fmt">O aplikaci <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Verze: <xliff:g id="version">%s</xliff:g></string>

View File

@@ -1,9 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_search">Suche</string>
<string name="action_add">Neu</string>
<string name="noGiftCards">Sie haben noch keine Kundenkarte angelegt. Über den \"+\"-Button oben rechts können welche angelegt werden.\n\nDiese App ermöglicht es, Kundenkarten immer mit zu führen.</string>
<string name="noMatchingGiftCards">Es passen keine Kundenkarten zum Filter. Bitte probiere verschiedene Begriffe aus.</string>
<string name="noMatchingGiftCards">Es passen keine Kundenkarten zum Suchfilter. Bitte probiere verschiedene Begriffe aus.</string>
<string name="storeName">Geschäft</string>
<string name="note">Notiz</string>
<string name="cardId">Kartennummer</string>
@@ -17,19 +20,19 @@
<string name="confirm">Bestätigen</string>
<string name="lockScreen">Rotation blockieren</string>
<string name="unlockScreen">Rotation zulassen</string>
<string name="star">Zu den Favoriten hinzufügen</string>
<string name="unstar">Aus der Favoritenliste entfernen</string>
<string name="deleteTitle">Karte entfernen</string>
<string name="deleteConfirmation">Bitte bestätigen Sie, dass diese Karte gelöscht werden soll.</string>
<string name="ok">Ok</string>
<string name="copy_to_clipboard">Kopiere die Nummer in die Zwischenablage</string>
<string name="sendLabel">Senden</string>
<string name="sendLabel">Senden&#8230;</string>
<string name="editCardTitle">Kundenkarte bearbeiten</string>
<string name="addCardTitle">Neue Kundenkarte</string>
<string name="scanCardBarcode">Strichcode scannen</string>
<string name="scanCardBarcode">Barcode scannen</string>
<string name="cardShortcut">Shortcut zu einer Karte</string>
<string name="noCardsMessage">Es ist noch keine Karte vorhanden, bitte zuerst eine hinzufügen</string>
<string name="barcodeImageDescription">Bild des Strichcodes</string>
<string name="barcodeImageDescription">Bild des Barcodes</string>
<string name="noStoreError">Kein Geschäft angegeben</string>
<string name="noCardIdError">Keine Kartennummer angegeben</string>
<string name="noCardExistsError">Karte konnte nicht gefunden werden</string>
@@ -45,28 +48,34 @@
<string name="exportFailed">Export fehlgeschlagen</string>
<string name="importing">Importiere…</string>
<string name="exporting">Exportiere…</string>
<string name="noExternalStoragePermissionError">Ohne die Berechtigung für den externen Speicher kann kein Import oder Export erfolgen</string>
<string name="noExternalStoragePermissionError">Ohne die Berechtigung für den externen Speicher kann kein Import oder Export erfolgen.</string>
<string name="importOptionFilesystemTitle">Importiere aus Dateisystem</string>
<string name="importOptionFilesystemExplanation">Wähle eine Datei aus dem Speicher aus.</string>
<string name="importOptionFilesystemButton">Aus Dateisystem</string>
<string name="importOptionApplicationTitle">Externe Anwendung verwenden</string>
<string name="importOptionApplicationTitle">Externe App verwenden</string>
<string name="importOptionApplicationExplanation">Wählen Sie eine Datei aus einer App wie Dropbox, Google Drive, oder Ihrem bevorzugten Dateisystem aus.</string>
<string name="importOptionApplicationButton">Nutze eine externe App</string>
<string name="about">Über</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Lizensiert unter der GPLv3.</string>
<string name="about_title_fmt">Über <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Version: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Informationen zu dieser Version: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> benutzt die folgenden Fremdbibliotheken: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> verwendet folgenden Dritt-Ressourcen: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Strichcode auswählen</string>
<string name="selectBarcodeTitle">Barcode auswählen</string>
<string name="copy_to_clipboard_toast">Nummer in die Zwischenablage kopiert</string>
<string name="thumbnailDescription">Vorschaubild für die Karte</string>
<string name="change">Anpassen</string>
<string name="storeTextColorTitle">Textfarbe</string>
<string name="storeTextBackgroundColorTitle">Hintergrundfarbe</string>
<string name="storeNameBackgroundColorDescription">Hintergrundfarbe für den Geschäftsnamen</string>
<string name="storeNameColorDescription">Farbe für den Geschäftsnamen</string>
<string name="settings">Einstellungen</string>
<string name="settings_category_title_ui">Benutzeroberfläche</string>
<string name="settings_card_title_list_font_size">Schriftgröße des Titels in der Listenansicht</string>
@@ -74,30 +83,6 @@
<string name="settings_card_title_font_size">Schriftgröße des Titels</string>
<string name="settings_card_id_font_size">Schriftgröße der Kartennummer</string>
<string name="settings_card_note_font_size">Schriftgröße der Notiz</string>
<string name="settings_display_barcode_max_brightness">Helligkeit bei Strichcode Ansicht erhöhen</string>
<string name="settings_lock_barcode_orientation">Strichcodeausrichtung sperren</string>
<string name="exportSuccessful">Kundenkartendaten erflogreich exportiert</string>
<string name="importSuccessful">Kundenkartendaten erflogreich importiert</string>
<string name="intent_import_card_from_url_share_text">Ich will eine Karte mit Ihnen teilen</string>
<string name="settings_dark_theme">Dunkel</string>
<string name="settings_light_theme">Hell</string>
<string name="settings_system_theme">Systemvorgabe</string>
<string name="settings_theme">Design</string>
<string name="enterBarcodeInstructions">Geben Sie die Karten-ID ein und wählen Sie das Bild aus, das den Strichcode darstellt, den Sie verwenden möchten, oder wählen Sie „Diese Karte hat keinen Strichcode“, um keinen Strichcode zu verwenden.</string>
<string name="app_copyright_old">Basiert auf Loyalty Card Keychain, Copyright 2016-2020 Branden Archer.</string>
<string name="exportOptionExplanation">Die Daten werden an einen Ort Ihrer Wahl geschrieben.</string>
<string name="failedParsingImportUriError">Der Import-URI konnte nicht analysiert werden</string>
<string name="addedShortcut">Verknüpfung hinzugefügt</string>
<string name="share">Teilen</string>
<string name="barcodeNoBarcode">Diese Karte hat keinen Strichcode</string>
<string name="barcodeType">Strichcode-Typ</string>
<string name="starImage">Favoritenstern</string>
<string name="deleteConfirmationGroup">Bitte bestätigen Sie, dass Sie diese Gruppe löschen möchten</string>
<string name="all">Alle</string>
<string name="groupCardCount">&lt;xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\"&gt;%d&lt;/xliff:g&gt; Karten</string>
<string name="noGroups">Sie haben im Moment keine Gruppen. Klicken Sie auf die Schaltfläche + (Plus), um loszulegen.
\n
\nKarten können Gruppen zugewiesen werden, um sie leichter zu finden.</string>
<string name="groups">Gruppen</string>
<string name="enter_group_name">Geben Sie den Gruppennamen ein</string>
</resources>
<string name="settings_display_barcode_max_brightness">Helligkeit bei Barcode Ansicht erhöhen</string>
<string name="settings_lock_barcode_orientation">Barcodeausrichtung sperren</string>
</resources>

View File

@@ -54,6 +54,7 @@
<string name="importOptionApplicationButton">Χρήση εξωτερικής εφαρμογής</string>
<string name="about">Σχετικά</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Άδεια χρήσης υπό το GPLv3.</string>
<string name="about_title_fmt">Σχετικά με <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Έκδοση: <xliff:g id="version">%s</xliff:g></string>

View File

@@ -1,88 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_add">Añadir</string>
<string name="noGiftCards">Actualmente no tiene ninguna tarjeta guardada. Pulse el botón «+» para comenzar.
\n
\nCatima le permite llevar sus tarjetas en el teléfono para que estén siempre a su alcance.</string>
<string name="noGiftCards">Actualmente no tienes ninguna tarjeta guardada. Presiona el botón \"+\" para comenzar.\n\nEsta cartera te permite llevar tus tarjetas de fidelización en tu teléfono para que estén siempre a tu alcance.</string>
<string name="storeName">Tienda</string>
<string name="note">Nota</string>
<string name="cardId">Id. de tarjeta</string>
<string name="cardId">ID de la Tarjeta</string>
<string name="cancel">Cancelar</string>
<string name="save">Guardar</string>
<string name="capture">Capturar tarjeta</string>
<string name="enterCard">Introducir tarjeta</string>
<string name="editCard">Editar tarjeta</string>
<string name="capture">Escanear Tarjeta</string>
<string name="enterCard">Introducir Tarjeta</string>
<string name="editCard">Editar Tarjeta</string>
<string name="edit">Editar</string>
<string name="delete">Eliminar</string>
<string name="confirm">Confirmar</string>
<string name="lockScreen">Bloquear rotación</string>
<string name="unlockScreen">Desbloquear rotación</string>
<string name="deleteTitle">Eliminar tarjeta</string>
<string name="deleteConfirmation">Confirme que quiere eliminar esta tarjeta.</string>
<string name="ok">Aceptar</string>
<string name="copy_to_clipboard">Copiar id. en portapapeles</string>
<string name="sendLabel">Enviar</string>
<string name="editCardTitle">Editar tarjeta</string>
<string name="addCardTitle">Añadir tarjeta</string>
<string name="scanCardBarcode">Escanear código de barras de la tarjeta</string>
<string name="cardShortcut">Atajo de tarjeta</string>
<string name="noCardsMessage">No hay ninguna tarjeta; añada una primero</string>
<string name="lockScreen">Bloquear Rotación de Pantalla</string>
<string name="unlockScreen">Rotación de Pantalla Automática</string>
<string name="deleteTitle">Eliminar Tarjeta de Fidelización</string>
<string name="deleteConfirmation">Por favor, confirma si quieres eliminar esta tarjeta.</string>
<string name="ok">OK</string>
<string name="copy_to_clipboard">Copiar ID al portapapeles</string>
<string name="sendLabel">Enviar&#8230;</string>
<string name="editCardTitle">Editar Tarjeta de Fidelización</string>
<string name="addCardTitle">Añadir Tarjeta de Fidelización</string>
<string name="scanCardBarcode">Escanear el Código de Barras de la Tarjeta</string>
<string name="cardShortcut">Atajo de Tarjeta</string>
<string name="noCardsMessage">No hay ninguna tarjeta, añade una primero</string>
<string name="barcodeImageDescription">Imagen del código de barras de la tarjeta</string>
<string name="noStoreError">Establecimiento no especificado</string>
<string name="noCardIdError">Id. de tarjeta no especificado</string>
<string name="noCardIdError">ID de la Tarjeta no especificado</string>
<string name="noCardExistsError">No se ha podido encontrar la tarjeta de fidelización</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="importExport">Importar/exportar</string>
<string name="importExport">Importar/Exportar</string>
<string name="exportName">Exportar</string>
<string name="importExportHelp">La copia de respaldo le permite transferir sus tarjetas a otro dispositivo.</string>
<string name="importExportHelp">La copia de seguridad te permite transferir tus tarjetas a otro dispositivo.</string>
<string name="importSuccessfulTitle">Datos importados correctamente</string>
<string name="importFailedTitle">Falló la importación</string>
<string name="importFailed">No se pudo importar</string>
<string name="importFailedTitle">Ha ocurrido un error al importar los datos</string>
<string name="importFailed">Error al importar</string>
<string name="exportSuccessfulTitle">Datos exportados correctamente</string>
<string name="exportFailedTitle">Falló la exportación</string>
<string name="exportFailed">No se pudo exportar</string>
<string name="importing">Importando</string>
<string name="exporting">Exportando</string>
<string name="exportFailedTitle">Ha ocurrido un error al exportar los datos</string>
<string name="exportFailed">Error al exportar</string>
<string name="importing">Importando&#8230;</string>
<string name="exporting">Exportando&#8230;</string>
<string name="noExternalStoragePermissionError">No se pueden importar o exportar tarjetas sin el permiso de almacenamiento</string>
<string name="importOptionFilesystemTitle">Importar desde el sistema de archivos</string>
<string name="importOptionFilesystemExplanation">Elegir un archivo concreto del sistema de archivos.</string>
<string name="importOptionFilesystemExplanation">Seleccionar un archivo del sistema de archivos</string>
<string name="importOptionFilesystemButton">Desde el sistema de archivos</string>
<string name="importOptionApplicationTitle">Utilizar aplicación externa</string>
<string name="importOptionApplicationTitle">Usar una applicación externa</string>
<string name="importOptionApplicationExplanation">Use una aplicación externa como Dropbox, Google Drive o tu gestor de archivos favoritos para abrir un archivo.</string>
<string name="importOptionApplicationButton">Utilizar aplicación externa</string>
<string name="importOptionApplicationButton">Usar aplicación externa</string>
<string name="about">Acerca de</string>
<string name="app_license">Disponible en virtud de la GPLv3.</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Licencia GPLv3</string>
<string name="about_title_fmt">Acerca de <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versión: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Información sobre la Revisión: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> utiliza las siguientes librerías de terceros: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> utiliza los siguientes recursos de terceros: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Selecciona el Código de Barras</string>
<string name="copy_to_clipboard_toast">ID de la Tarjeta copiado al portapapeles</string>
<string name="thumbnailDescription">Miniatura para la tarjeta</string>
<string name="change">Cambiar</string>
<string name="storeTextColorTitle">Color de texto de establecimiento</string>
<string name="storeTextBackgroundColorTitle">Color principal</string>
<string name="storeTextColorTitle">Color del Texto de la Tienda</string>
<string name="storeTextBackgroundColorTitle">Color Principal</string>
<string name="storeNameBackgroundColorDescription">Color de fondo para el texto de la tienda</string>
<string name="storeNameColorDescription">Color del texto de la tienda</string>
<string name="settings">Configuración</string>
<string name="settings_category_title_ui">Interfaz de usuario</string>
<string name="settings_category_title_ui">Interfaz de Usuario</string>
<string name="settings_card_title_list_font_size">Tamaño de la letra para el título (lista)</string>
<string name="settings_card_note_list_font_size">Tamaño de la letra para notas (lista)</string>
<string name="settings_card_title_font_size">Tamaño de la letra para el título</string>
<string name="settings_card_id_font_size">Tamaño de la letra para el ID de la tarjeta</string>
<string name="settings_card_note_font_size">Tamaño de la letra para notas</string>
<string name="settings_display_barcode_max_brightness">Iluminar vista de código de barras</string>
<string name="exportSuccessful">Se exportaron correctamente los datos de tarjetas de fidelización</string>
<string name="importSuccessful">Se importaron correctamente los datos de tarjetas de fidelización</string>
<string name="intent_import_card_from_url_share_text">Quiero compartirle una tarjeta</string>
<string name="settings_lock_barcode_orientation">Bloquear orientación de código de barras</string>
<string name="settings_dark_theme">Tema oscuro</string>
<string name="settings_light_theme">Tema claro</string>
<string name="settings_system_theme">Tema del sistema</string>
<string name="settings_theme">Tema</string>
<string name="enterBarcodeInstructions">Introduzca el identificador de tarjeta y seleccione la imagen que represente el código de barras que se utilizará, o bien, elija «Esta tarjeta no tiene código de barras» para no utilizar ninguno.</string>
<string name="app_copyright_old">Basado en Loyalty Card Keychain, derechos de autor 2016-2020 de Branden Archer.</string>
<string name="exportOptionExplanation">Los datos se guardarán en la ubicación que elija.</string>
<string name="failedParsingImportUriError">No se pudo procesar el URI de importación</string>
<string name="addedShortcut">Atajo añadido</string>
<string name="share">Compartir</string>
<string name="barcodeNoBarcode">Esta tarjeta no tiene código de barras</string>
<string name="barcodeType">Tipo de código de barras</string>
<string name="noMatchingGiftCards">Ninguna tarjeta coincide con el filtro de búsqueda. Pruebe con términos distintos.</string>
<string name="action_search">Buscar</string>
</resources>
</resources>

View File

@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_add">Ajouter</string>
<string name="noGiftCards">Aucune carte de fidélité enregistrée. Appuyez sur le bouton \"+\" (plus) pour commencer.\n\nLoyalty Card Locker vous permet d\'enregistrer vos cartes de fidélité sur votre téléphone pour toujours les avoir à portée de main.</string>
<string name="storeName">Nom</string>
<string name="note">Note</string>
@@ -16,16 +18,18 @@
<string name="lockScreen">Désactiver la rotation</string>
<string name="unlockScreen">Activer la rotation</string>
<string name="deleteTitle">Supprimer la carte de fidélité</string>
<string name="deleteConfirmation">Confirmez que vous souhaitez supprimer cette carte.</string>
<string name="deleteConfirmation">Confirmez que vous souhaitez supprimer cette carte</string>
<string name="ok">OK</string>
<string name="copy_to_clipboard">Copier le numéro dans le presse-papier</string>
<string name="sendLabel">Envoyer</string>
<string name="sendLabel">Envoyer&#8230;</string>
<string name="editCardTitle">Modifier la carte de fidélité</string>
<string name="addCardTitle">Ajouter une carte de fidélité</string>
<string name="scanCardBarcode">Scanner le code-barres</string>
<string name="scanCardBarcode">Flasher le code-barres de la carte</string>
<string name="cardShortcut">Raccourci de carte</string>
<string name="noCardsMessage">Il n\'y a aucune carte. Ajoutez-en une d\'abord</string>
<string name="noCardsMessage">Il n\'y a aucune carte. Ajoutez en une d\'abord.</string>
<string name="barcodeImageDescription">Image du code-barres de la carte</string>
<string name="noStoreError">Aucun nom n\'a été saisi</string>
<string name="noCardIdError">Aucun numéro n\'a été saisi</string>
<string name="noCardExistsError">N\'a pas pu retrouver la carte de fidélité</string>
@@ -39,32 +43,38 @@
<string name="exportSuccessfulTitle">Exporté avec succès</string>
<string name="exportFailedTitle">Échec de l\'export</string>
<string name="exportFailed">Échec de l\'export </string>
<string name="importing">Import </string>
<string name="exporting">Export </string>
<string name="importing">Import &#8230;</string>
<string name="exporting">Export &#8230;</string>
<string name="noExternalStoragePermissionError">Impossible d\'importer ou d\'exporter les données sans l\'autorisation d\'accès au stockage externe</string>
<string name="importOptionFilesystemTitle">Importer depuis le système de fichiers</string>
<string name="importOptionFilesystemTitle">Importer depuis le système de fichiers.</string>
<string name="importOptionFilesystemExplanation">Choisissez le fichier à importer.</string>
<string name="importOptionFilesystemButton">Système de fichiers</string>
<string name="importOptionApplicationTitle">Utiliser une application externe</string>
<string name="importOptionApplicationTitle">Application externe</string>
<string name="importOptionApplicationExplanation">Utilisez une application externe comme Dropbox, Google Drive, ou votre gestionnaire de fichiers favori pour ouvrir un fichier.</string>
<string name="importOptionApplicationButton">Application externe</string>
<string name="about">À propos</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Licence GPLv3.</string>
<string name="about_title_fmt">À propos de <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Version : <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Notes sur les versions : <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> utilise les bibliothèques-tierces suivantes : <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> utilise les ressources-tierces suivantes : <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Choisissez le code-barres</string>
<string name="selectBarcodeTitle">Choisissez le code-barre</string>
<string name="copy_to_clipboard_toast">Numéro de carte copié dans le presse-papier</string>
<string name="thumbnailDescription">Miniature pour la carte</string>
<string name="change">Modifier</string>
<string name="storeTextColorTitle">Couleur du texte du magasin</string>
<string name="storeTextBackgroundColorTitle">Couleur du titre</string>
<string name="storeNameBackgroundColorDescription">Couleur de fond du texte du magasin</string>
<string name="storeNameColorDescription">Couleur pour le nom du magasin</string>
<string name="settings">Paramètres</string>
<string name="settings_category_title_ui">Interface utilisateur</string>
<string name="settings_category_title_ui">Interface</string>
<string name="settings_card_title_list_font_size">Taille de police pour les titres de carte en liste</string>
<string name="settings_card_note_list_font_size">Taille de police pour les notes de carte en liste</string>
<string name="settings_card_title_font_size">Taille de police pour les titres de carte</string>
@@ -72,32 +82,4 @@
<string name="settings_card_note_font_size">Taille de police pour les notes de carte</string>
<string name="settings_display_barcode_max_brightness">Augmenter la luminosité du code-barres</string>
<string name="settings_lock_barcode_orientation">Verrouiller l\'orientation du code-barres</string>
<string name="exportSuccessful">Carte de fidélité exportée avec succès</string>
<string name="importSuccessful">Carte de fidélité importée avec succès</string>
<string name="intent_import_card_from_url_share_text">Je veux partager une carte avec toi</string>
<string name="settings_dark_theme">Thème sombre</string>
<string name="settings_light_theme">Thème clair</string>
<string name="settings_system_theme">Thème du système</string>
<string name="settings_theme">Thème</string>
<string name="enterBarcodeInstructions">Entrez l\'identifiant de la carte puis sélectionnez l\'image qui représente le code-barres que vous souhaitez utiliser, ou sélectionnez « Cette carte n\'a pas de code-barres » pour ne pas utiliser de code-barres.</string>
<string name="app_copyright_old">Basé sur Loyalty Card Keychain, copyright 2016-2020 Branden Archer.</string>
<string name="exportOptionExplanation">Les données seront écrites à un emplacement de votre choix.</string>
<string name="failedParsingImportUriError">Impossible d\'analyser l\'URI d\'importation</string>
<string name="addedShortcut">Raccourci ajouté</string>
<string name="share">Partager</string>
<string name="barcodeNoBarcode">Cette carte n\'a pas de code-barres</string>
<string name="barcodeType">Type de code-barres</string>
<string name="noMatchingGiftCards">Aucune carte ne correspond au filtre. Veuillez essayer d\'autres termes.</string>
<string name="action_search">Rechercher</string>
<string name="unstar">Retirer des favoris</string>
<string name="star">Ajouter aux favoris</string>
<string name="starImage">Étoile favorite</string>
<string name="deleteConfirmationGroup">Confirmez que vous voulez supprimer cette carte</string>
<string name="all">Tous</string>
<string name="groupCardCount">&lt;xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\"&gt;%d&lt;/xliff:g&gt; cartes</string>
<string name="noGroups">Vous n\'avez aucun groupe pour le moment. Cliquez sur le bouton + (plus) pour commencer.
\n
\nLes cartes peuvent être attribuées à des groupes pour les rendre plus faciles à trouver.</string>
<string name="groups">Groupes</string>
<string name="enter_group_name">Entrez le nom du groupe</string>
</resources>
</resources>

View File

@@ -1,13 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_search">Cerca</string>
<string name="action_add">Aggiungi</string>
<string name="noGiftCards">Non hai ancora alcuna carta fedeltà. Premi sul bottone +(più) in alto per incominciare.\n\nL\'app ti permette di portare con te le tue carte fedeltà, così da averle sempre a disposizione.</string>
<string name="noMatchingGiftCards">Nessuna carta fedeltà corrisponde al filtro. Riprovare con altri valori.</string>
<string name="noMatchingGiftCards">Nessuna carta fedeltà corrisponde al filtro. Riprovare con altri valori</string>
<string name="storeName">Negozio</string>
<string name="note">Note</string>
<string name="cardId">Codice</string>
<string name="barcodeNoBarcode">Questa carta non ha un codice a barre</string>
<string name="cancel">Annulla</string>
<string name="save">Salva</string>
<string name="capture">Scansione carta</string>
@@ -23,13 +27,15 @@
<string name="ok">Ok</string>
<string name="copy_to_clipboard">Copia ID negli appunti</string>
<string name="share">Condividi</string>
<string name="sendLabel">Invia</string>
<string name="sendLabel">Invia&#8230;</string>
<string name="editCardTitle">Modifica carta</string>
<string name="addCardTitle">Aggiungi carta</string>
<string name="scanCardBarcode">Scansiona codice carta</string>
<string name="cardShortcut">Scorciatoia per la carta</string>
<string name="noCardsMessage">Non ci sono carte. Aggiungine prima una</string>
<string name="barcodeImageDescription">Immagine del codice a barre della carta</string>
<string name="noStoreError">Nessun negozio inserito</string>
<string name="noCardIdError">Nessun codice carta inserito</string>
<string name="noCardExistsError">Impossibile trovare la carta</string>
@@ -44,31 +50,38 @@
<string name="exportSuccessfulTitle">Esportazione avvenuta con successo</string>
<string name="exportFailedTitle">Esportazione fallita</string>
<string name="exportFailed">Impossibile esportare</string>
<string name="importing">Importazione in corso</string>
<string name="exporting">Esportazione in corso</string>
<string name="noExternalStoragePermissionError">Impossibile importare o esportare i dati senza il permesso per l\'uso della memoria esterna</string>
<string name="importing">Importazione in corso&#8230;</string>
<string name="exporting">Esportazione in corso&#8230;</string>
<string name="noExternalStoragePermissionError">Impossibile importare o esportare i dati senza il permesso per l\'uso della memoria esterna.</string>
<string name="importOptionFilesystemTitle">Importa dal file system</string>
<string name="importOptionFilesystemExplanation">Scegli un file dal file system.</string>
<string name="importOptionFilesystemButton">Dal file system</string>
<string name="importOptionApplicationTitle">Usa un\'applicazione esterna</string>
<string name="importOptionApplicationExplanation">Usa un\'applicazione esterna come Dropbox, Google Drive o il tuo file manager preferito per aprire il file.</string>
<string name="importOptionApplicationButton">Usa un\'applicazione esterna</string>
<string name="about">Informazioni</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Pubblicato sotto licenza GPLv3.</string>
<string name="about_title_fmt">Informazioni su <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versione: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Informazione sulla versione: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> usa le seguenti librerie di terze parti: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> usa le seguenti risorse di terze parti: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Seleziona codice a barre</string>
<string name="enterBarcodeInstructions">Immettere l\'ID della carta, quindi selezionare l\'immagine che rappresenta il codice a barre desiderato oppure selezionare «Questa carta non ha un codice a barre» per non valorizzare il dato.</string>
<string name="enterBarcodeInstructions">Immettere l\'ID della carta, quindi selezionare l\'immagine che rappresenta il codice a barre desiderato oppure selezionare &#8220;Questa carta non ha un codice a barre&#8221; per non valorizzare il dato.</string>
<string name="copy_to_clipboard_toast">ID della carta copiato negli appunti</string>
<string name="thumbnailDescription">Miniatura carta</string>
<string name="change">Cambia</string>
<string name="storeTextColorTitle">Colore titolo</string>
<string name="storeTextBackgroundColorTitle">Colore scheda</string>
<string name="storeNameBackgroundColorDescription">Colore di sfondo del titolo carta</string>
<string name="storeNameColorDescription">Colore del titolo carta</string>
<string name="settings">Impostazioni</string>
<string name="settings_category_title_ui">Interfaccia utente</string>
<string name="settings_theme">Tema</string>
@@ -83,21 +96,4 @@
<string name="settings_display_barcode_max_brightness">Aumenta luminosità dello schermo quando apro un codice a barre</string>
<string name="settings_lock_barcode_orientation">Blocca orientamento del codice a barre</string>
<string name="intent_import_card_from_url_share_text">Voglio condividere una carta fedeltà con te</string>
<string name="exportSuccessful">Dati della carta fedeltà esportati correttamente</string>
<string name="importSuccessful">Dati della carta fedeltà importati correttamente</string>
<string name="app_copyright_old">Basato su Loyalty Card Keychain, copyright 2016-2020 Branden Archer.</string>
<string name="exportOptionExplanation">I dati verranno scritti in una posizione a tua scelta.</string>
<string name="addedShortcut">Scorciatoia aggiunta</string>
<string name="barcodeType">Tipo di codice a barre</string>
<string name="unstar">Rimuovi dai preferiti</string>
<string name="star">Aggiungi ai preferiti</string>
<string name="starImage">Stella preferita</string>
<string name="deleteConfirmationGroup">Conferma di voler eliminare questo gruppo</string>
<string name="all">Tutti</string>
<string name="groupCardCount">&lt;xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\"&gt;%d&lt;/xliff:g&gt; carte</string>
<string name="noGroups">Al momento non hai gruppi. Fai clic sul pulsante + (più) per iniziare.
\n
\nLe carte possono essere assegnate a gruppi per renderle più facili da trovare.</string>
<string name="groups">Gruppi</string>
<string name="enter_group_name">Inserisci il nome del gruppo</string>
</resources>
</resources>

View File

@@ -34,6 +34,7 @@
<string name="exporting">Eksportuoja&#8230;</string>
<string name="noExternalStoragePermissionError">Negalima importuoti/eksportuoti kortelių be išorinės atminties leidimo</string>
<string name="about">Apie</string>
<string name="app_copyright_fmt">Visos teisės saugomos 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Licenzijuota pagal GPLv3.</string>
<string name="about_title_fmt">Apie <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versija: <xliff:g id="version">%s</xliff:g></string>

View File

@@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_add">Legg til</string>
<string name="noGiftCards">Klikk på \"+\" (pluss)-knappen for å legge til et kort først.
\n
\nDa har du dem alltid hendig.</string>
<string name="noGiftCards">Du har ingen kundekort for øyeblikket. Klikk på \"+\" (pluss)-knappen øverst for å komme igang.\n\nDa har du dem alltid hendig.</string>
<string name="storeName">Butikk</string>
<string name="note">Merknad</string>
<string name="cardId">Kort-ID</string>
@@ -32,36 +30,41 @@
<string name="barcodeImageDescription">Bilde av kortets strekkode</string>
<string name="noStoreError">Ingen butikk angitt</string>
<string name="noCardIdError">Ingen kort-ID innskrevet</string>
<string name="noCardExistsError">Kunne ikke finne kort</string>
<string name="noCardExistsError">Kunne ikke finne kundekort</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="importExport">Import/eksport</string>
<string name="exportName">Eksporter</string>
<string name="importExportHelp">Sikkerhetskopiering av kort lar deg flytte dem til en annen enhet.</string>
<string name="importExportHelp">Sikkerhetskopiering av data lar deg flytte kortene til en annen enhet.</string>
<string name="importSuccessfulTitle">Importert</string>
<string name="importFailedTitle">Kunne ikke importere</string>
<string name="importFailed">Klarte ikke å importere kort</string>
<string name="importFailed">Klarte ikke å importere</string>
<string name="exportSuccessfulTitle">Eksportert</string>
<string name="exportFailedTitle">Kunne ikke eksportere</string>
<string name="exportFailed">Kunne ikke eksportere kort</string>
<string name="exportFailed">Klarte ikke å eksportere</string>
<string name="importing">Importerer…</string>
<string name="exporting">Exporterer…</string>
<string name="noExternalStoragePermissionError">Innvilg lagringstilgang til eksternlager for å importere eller eksportere kort først</string>
<string name="exportOptionExplanation">Data skrives dit du ønsker det.</string>
<string name="noExternalStoragePermissionError">Kan ikke importere eller eksportere kort uten tilgang til ekstern lagring</string>
<string name="exportOptionExplanation">Data skrives til rotmappen i eksternt lagringsområde.</string>
<string name="importOptionFilesystemTitle">Importer fra filsystem</string>
<string name="importOptionFilesystemExplanation">Velg spesifikk fil fra filsystemet.</string>
<string name="importOptionFilesystemButton">Fra filsystem</string>
<string name="importOptionApplicationTitle">Bruk eksternt program</string>
<string name="importOptionApplicationExplanation">Bruk hvilket som helst program, eller din favoritt-filutforsker for å åpne en fil.</string>
<string name="importOptionApplicationTitle">Brukt eksternt program</string>
<string name="importOptionApplicationExplanation">Bruk eksternt program som Nextcloud, eller din favorittfilbehandler til å åpne ei fil.</string>
<string name="importOptionApplicationButton">Bruk eksternt program</string>
<string name="about">Om</string>
<string name="app_license">Gemenhetslig fri programvare, lisensiert GPLv3+.</string>
<string name="app_copyright_fmt">Kopirett 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Lisensiert GPLv3+.</string>
<string name="about_title_fmt">Om <xliff:g id="app_name">%s</xliff:g>
</string>
<string name="debug_version_fmt">Versjon: <xliff:g id="version">%s</xliff:g>
</string>
<string name="app_revision_fmt">Utgivelsesinfo: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> brukfer følgende tredjepartsbibliotek: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> bruker følgende tredjepartsressurser: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">
<xliff:g id="app_name">%s</xliff:g> brukfer følgende tredjepartsbibliotek: <xliff:g id="app_libraries_list">%s</xliff:g>
</string>
<string name="app_resources">
<xliff:g id="app_name">%s</xliff:g> bruker følgende tredjepartsressurser: <xliff:g id="app_resources_list">%s</xliff:g>
</string>
<string name="selectBarcodeTitle">Velg strekkode</string>
<string name="enterBarcodeInstructions">Skriv inn strekkodeverdien og velg så bildet som representerer strekkoden du ønsker å bruke.</string>
<string name="copy_to_clipboard_toast">Kort-ID kopiert til utklippstavle</string>
@@ -80,31 +83,4 @@
<string name="settings_card_note_font_size">Skriftstørrelse for kortmerknad</string>
<string name="settings_display_barcode_max_brightness">Lysere strekkodevisning</string>
<string name="settings_lock_barcode_orientation">Lås strekkodesideretning</string>
<string name="exportSuccessful">Kortdata eksportert</string>
<string name="importSuccessful">Kortdata importert</string>
<string name="intent_import_card_from_url_share_text">Jeg ønsker å dele et kort med deg</string>
<string name="settings_dark_theme">Mørk</string>
<string name="settings_light_theme">Lys</string>
<string name="settings_system_theme">System</string>
<string name="settings_theme">Drakt</string>
<string name="app_copyright_old">Basert på Kundekortknippe, opphavsrett 20162020 Branden Archer.</string>
<string name="failedParsingImportUriError">Kunne ikke tolke importerings-URI</string>
<string name="share">Del</string>
<string name="barcodeNoBarcode">Dette kortet har ingen strekkode</string>
<string name="noMatchingGiftCards">Fant ikke noe. Prøv å endre søket.</string>
<string name="action_search">Søk</string>
<string name="starImage">Favorittstjerne</string>
<string name="unstar">Fjern fra favoritter</string>
<string name="star">Legg til i favoritter</string>
<string name="groupCardCount">&lt;xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\"&gt;%d&lt;/xliff:g&gt; kort</string>
<string name="noGroups">Klikk på «+»-tegnet for å legge til grupper først.
\n
\nGrupper gjør ting enklere å finne.
\n
\nKort kan tildeles grupper for å gjøre dem enklere å finne.</string>
<string name="deleteConfirmationGroup">Bekreft at du ønsker å slette denne gruppen</string>
<string name="all">Alle</string>
<string name="groups">Grupper</string>
<string name="enter_group_name">Skriv inn gruppenavn</string>
<string name="noBarcode">Ingen strekkode</string>
</resources>
</resources>

View File

@@ -1,14 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_search">Zoeken</string>
<string name="action_add">Toevoegen</string>
<string name="noGiftCards">Je hebt nog geen klantenkaarten toegevoegd. Druk op de knop \'+\' (plus) om te beginnen.\n\nMet Klantenkaartkluis beheer je je klantenkaarten op je smartphone of tablet zodat ze altijd binnen handbereik zijn.</string>
<string name="noMatchingGiftCards">Geen kaarten komen overeen met het filter. Probeer alsjeblieft wat andere termen.</string>
<string name="noMatchingGiftCards">Er zijn geen kaarten die overeenkomen met je zoekopdracht. Probeer het opnieuw.</string>
<string name="storeName">Winkel</string>
<string name="note">Aantekening</string>
<string name="cardId">Kaartnummer</string>
<string name="barcodeType">Soort barcode</string>
<string name="barcodeNoBarcode">Deze kaart heeft geen barcode</string>
<string name="cancel">Annuleren</string>
<string name="save">Opslaan</string>
<string name="capture">Scan een kaart</string>
@@ -24,14 +28,16 @@
<string name="ok">Oké</string>
<string name="copy_to_clipboard">Kaartnummer kopiëren naar klembord</string>
<string name="share">Delen</string>
<string name="sendLabel">Versturen</string>
<string name="sendLabel">Versturen&#8230;</string>
<string name="addedShortcut">Snelkoppeling is toegevoegd</string>
<string name="editCardTitle">Klantenkaart bewerken</string>
<string name="addCardTitle">Klantenkaart toevoegen</string>
<string name="scanCardBarcode">Scan de barcode van de kaart</string>
<string name="cardShortcut">Kaartsnelkoppeling</string>
<string name="noCardsMessage">Je hebt nog geen kaarten toegevoegd.</string>
<string name="barcodeImageDescription">Afbeelding van barcode</string>
<string name="noStoreError">Geen winkelnaam ingevoerd</string>
<string name="noCardIdError">Geen kaartnummer ingevoerd</string>
<string name="noCardExistsError">De klantenkaart kan niet worden opgevraagd</string>
@@ -46,32 +52,39 @@
<string name="exportSuccessfulTitle">Exporteren voltooid</string>
<string name="exportFailedTitle">Exporteren mislukt</string>
<string name="exportFailed">Het exporteren is mislukt</string>
<string name="importing">Bezig met importeren...</string>
<string name="exporting">Bezig met exporteren...</string>
<string name="importing">Bezig met importeren&#8230;...</string>
<string name="exporting">Bezig met exporteren&#8230;...</string>
<string name="noExternalStoragePermissionError">Het importeren of exporteren van kaarten is niet mogelijk zonder de machtiging \'externe opslag\'.</string>
<string name="exportOptionExplanation">De gegevens worden weggeschreven op een locatie naar keuze.</string>
<string name="importOptionFilesystemTitle">Importeren uit bestandssysteem</string>
<string name="importOptionFilesystemExplanation">Kies een specifiek bestand uit het bestandssysteem.</string>
<string name="importOptionFilesystemButton">Uit bestandssysteem</string>
<string name="importOptionApplicationTitle">Externe toepassing gebruiken</string>
<string name="importOptionApplicationTitle">Externe app gebruiken</string>
<string name="importOptionApplicationExplanation">Open een bestand middels een externe app, zoals Dropbox, Google Drive of je favoriete bestandsbeheerder.</string>
<string name="importOptionApplicationButton">Externe app gebruiken</string>
<string name="about">Over</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Uitgebracht onder de GPLv3-licentie.</string>
<string name="about_title_fmt">Over <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versie: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Versie-informatie: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> gebruikt de volgende bibliotheken van externe partijen: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> gebruikt de volgende bronnen van externe partijen: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Barcode toevoegen</string>
<string name="enterBarcodeInstructions">Voer de barcode in en kies daarna de afbeelding van de barcode die je wilt gebruiken., of druk op Deze kaart heeft geen barcode om geen barcode te gebruiken.</string>
<string name="enterBarcodeInstructions">Voer de barcode in en kies daarna de afbeelding van de barcode die je wilt gebruiken., of druk op &#8220;Deze kaart heeft geen barcode&#8221; om geen barcode te gebruiken.</string>
<string name="copy_to_clipboard_toast">Kaartnummer is gekopieerd naar het klembord</string>
<string name="thumbnailDescription">Miniatuurvoorbeeld van kaart</string>
<string name="change">Aanpassen</string>
<string name="storeTextColorTitle">Tekstkleur van winkelnaam</string>
<string name="storeTextBackgroundColorTitle">Kopkleur</string>
<string name="storeNameBackgroundColorDescription">Achtergrondkleur van winkeltekst</string>
<string name="storeNameColorDescription">Kleur van winkeltekst</string>
<string name="settings">Instellingen</string>
<string name="settings_category_title_ui">Uiterlijk en bediening</string>
<string name="settings_theme">Thema</string>
@@ -86,18 +99,4 @@
<string name="settings_display_barcode_max_brightness">Scherm helderder maken bij tonen van barcode</string>
<string name="settings_lock_barcode_orientation">Barcode-oriëntatie vergrendelen</string>
<string name="intent_import_card_from_url_share_text">Ik wil een klantenkaart met je delen</string>
<string name="all">Alles</string>
<string name="importSuccessful">De import van klantenkaartgegevens was succesvol</string>
<string name="deleteConfirmationGroup">Bevestig dat u deze groep wilt verwijderen</string>
<string name="groupCardCount">&lt;xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\"&gt;%d&lt;/xliff:g&gt; kaart</string>
<string name="noGroups">Je hebt geen groepen op dit moment. Klik op de + (plus) knop om te beginnen.
\n
\nKaarten kunnen worden toegewezen aan groepen om ze gemakkelijker te vinden.</string>
<string name="exportSuccessful">De export van klantenkaartgegevens was succesvol</string>
<string name="groups">Groep</string>
<string name="enter_group_name">Voer groepsnaam in</string>
<string name="starImage">Favoriete ster</string>
<string name="app_copyright_old">Gebaseerd op Loyalty Card Keychain, copyright 2016-2020 Branden Archer.</string>
<string name="unstar">Verwijder uit favorieten</string>
<string name="star">Toevoegen aan favorieten</string>
</resources>
</resources>

View File

@@ -1,13 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_search">Szukaj</string>
<string name="action_add">Dodaj</string>
<string name="noGiftCards">W tej chwili nie masz żadnych kart lojalnościowych. Kliknij przycisk \"+\" (plus) u góry, aby rozpocząć. \n\nLoyalty Locker Card pozwala nosić karty lojalnościowe w telefonie, dzięki czemu są zawsze w zasięgu ręki.</string>
<string name="noMatchingGiftCards">Nic nie znaleziono. Spróbuj zmienić wyszukiwanie.</string>
<string name="noMatchingGiftCards">Żadne karty lojalnościowe nie pasują do filtra wyszukiwania. Spróbuj użyć innych warunków.</string>
<string name="storeName">Sklep</string>
<string name="note">Notatka</string>
<string name="cardId">Identyfikator karty</string>
<string name="barcodeNoBarcode">Ta karta nie ma kodu kreskowego</string>
<string name="cancel">Anuluj</string>
<string name="save">Zapisz</string>
<string name="capture">Zeskanuj kod</string>
@@ -23,13 +27,15 @@
<string name="ok">OK</string>
<string name="copy_to_clipboard">Skopiuj identyfikator do schowka</string>
<string name="share">Udostępnij</string>
<string name="sendLabel">Wyślij</string>
<string name="sendLabel">Wyślij&#8230;</string>
<string name="editCardTitle">Edytuj kartę lojalnościową</string>
<string name="addCardTitle">Dodaj kartę lojalnościową</string>
<string name="scanCardBarcode">Zeskanuj kod kreskowy karty lojalnościowej</string>
<string name="cardShortcut">Skrót karty</string>
<string name="noCardsMessage">Najpierw dodaj kartę</string>
<string name="noCardsMessage">Nie ma kart, należy dodać pierwszą</string>
<string name="barcodeImageDescription">Obraz kodu kreskowego karty</string>
<string name="noStoreError">Nie wprowadzono nazwy sklepu</string>
<string name="noCardIdError">Nie wprowadzono identyfikatora karty</string>
<string name="noCardExistsError">Nie można wyszukać karty lojalnościowej</string>
@@ -38,37 +44,44 @@
<string name="importExport">Importuj/Eksportuj</string>
<string name="exportName">Eksportuj</string>
<string name="importExportHelp">Zarchiwizowane dane umożliwiają przeniesienie kart na inne urządzenie.</string>
<string name="importSuccessfulTitle">Zaimportowano</string>
<string name="importSuccessfulTitle">Zaimportowano pomyślnie</string>
<string name="importFailedTitle">Import nie powiódł się</string>
<string name="importFailed">Nie udało się zaimportować</string>
<string name="exportSuccessfulTitle">Wyeksportowano</string>
<string name="exportSuccessfulTitle">Wyeksportowano pomyślnie</string>
<string name="exportFailedTitle">Eksport nie powiódł się</string>
<string name="exportFailed">Nie udało się wyeksportować</string>
<string name="importing">Importowanie</string>
<string name="exporting">Eksportowanie</string>
<string name="noExternalStoragePermissionError">Najpierw przyznaj uprawnienia pamięci zewnętrznej do importowania lub eksportowania kart</string>
<string name="importing">Importowanie&#8230;</string>
<string name="exporting">Eksportowanie&#8230;</string>
<string name="noExternalStoragePermissionError">Nie można importować ani eksportować kart bez uprawnień do pamięci zewnętrznej</string>
<string name="importOptionFilesystemTitle">Importuj z systemu plików</string>
<string name="importOptionFilesystemExplanation">Wybierz określony plik z systemu plików.</string>
<string name="importOptionFilesystemButton">Z systemu plików</string>
<string name="importOptionApplicationTitle">Użyj zewnętrznej aplikacji</string>
<string name="importOptionApplicationExplanation">Użyj dowolnej aplikacji lub ulubionego menedżera plików, aby otworzyć plik.</string>
<string name="importOptionApplicationExplanation">Użyj zewnętrznej aplikacji jak Dropbox, Google Drive, bądź ulubiony menedżer plików, aby otworzyć plik.</string>
<string name="importOptionApplicationButton">Użyj zewnętrznej aplikacji</string>
<string name="about">O aplikacji</string>
<string name="app_license">Wolne oprogramowanie copylefted, licencjonowane GPLv3 +.</string>
<string name="app_copyright_fmt">© 2016 — <xliff:g>%d</xliff:g> Branden Archer.</string>
<string name="app_license">Licencjonowane na podstawie GNU General Public License, wersji 3.0.</string>
<string name="about_title_fmt">O <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Wersja: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Info o wydaniu: &lt;xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\" id=\"app_revision_url\"&gt;%s&lt;/xliff:g&gt;</string>
<string name="app_revision_fmt">Informacje o wersji: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> wykorzystuje następujące biblioteki osób trzecich: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> wykorzystuje następujące zasoby osób trzecich: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Wybierz kod kreskowy</string>
<string name="enterBarcodeInstructions">Wprowadź identyfikator karty, a następnie wybierz obraz reprezentujący kod kreskowy, którego chcesz użyć, lub wybierz Ta karta nie ma kodu kreskowego, aby nie używać kodu kreskowego.</string>
<string name="enterBarcodeInstructions">Wprowadź identyfikator karty, a następnie wybierz obraz reprezentujący kod kreskowy, którego chcesz użyć, lub wybierz &#8220;Ta karta nie ma kodu kreskowego&#8221;, aby nie używać kodu kreskowego.</string>
<string name="copy_to_clipboard_toast">Skopiowano identyfikator karty do schowka</string>
<string name="thumbnailDescription">Miniaturka karty</string>
<string name="change">Zmień</string>
<string name="storeTextColorTitle">Kolor tekstu nazwy sklepu</string>
<string name="storeTextBackgroundColorTitle">Kolor nagłówka</string>
<string name="storeNameBackgroundColorDescription">Kolor tła tekstu nazwy sklepu</string>
<string name="storeNameColorDescription">Kolor dla tekstu sklepu</string>
<string name="settings">Ustawienia</string>
<string name="settings_category_title_ui">Interfejs użytkownika</string>
<string name="settings_theme">Motyw</string>
@@ -83,22 +96,4 @@
<string name="settings_display_barcode_max_brightness">Rozjaśnij widok kodu kreskowego</string>
<string name="settings_lock_barcode_orientation">Zablokuj autoobracanie kodów kreskowych</string>
<string name="intent_import_card_from_url_share_text">Chcę udostępnić Ci kartę lojalnościową</string>
<string name="groupCardCount">&lt;xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\"&gt;%d&lt;/xliff:g&gt; karty</string>
<string name="deleteConfirmationGroup">Potwierdź, że chcesz usunąć tę grupę</string>
<string name="all">Wszystko</string>
<string name="noGroups">Kliknij przycisk + (plus), aby najpierw dodać grupy.
\n
\nGrupy ułatwiają wyszukiwanie.</string>
<string name="groups">Grupy</string>
<string name="enter_group_name">Wpisz nazwę grupy</string>
<string name="exportSuccessful">Dane karty zostały wyeksportowane</string>
<string name="importSuccessful">Zaimportowano dane karty</string>
<string name="starImage">Ulubiona gwiazda</string>
<string name="app_copyright_old">Na podstawie Loyalty Card Keychain, prawa autorskie 20162020 Branden Archer.</string>
<string name="exportOptionExplanation">Dane zostaną zapisane w wybranym przez Ciebie miejscu.</string>
<string name="addedShortcut">Dodano skrót</string>
<string name="unstar">Usuń z ulubionych</string>
<string name="star">Dodaj do ulubionych</string>
<string name="noBarcode">Brak kodu kreskowego</string>
<string name="barcodeType">Typ kodu kreskowego</string>
</resources>
</resources>

View File

@@ -1,16 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_search">Поиск</string>
<string name="action_add">Добавить карту</string>
<string name="noGiftCards">Нажмите кнопку + (плюс), чтобы сначала добавить карту.
\n
\nCatima хранит ваши карты на устройстве, поэтому они всегда под рукой.</string>
<string name="noMatchingGiftCards">"Ничего не найдено. Попробуйте изменить свой поиск."</string>
<string name="noGiftCards">Пока нет ни одной карты. Нажмите на кнопку «+» (плюс) сверху для добавления.\n\n«Карты лояльности» позволяют хранить карты скидок в телефоне, так что они всегда будут под рукой.</string>
<string name="noMatchingGiftCards">Карт не найдено, попробуйте поискать по-другому.</string>
<string name="storeName">Магазин</string>
<string name="note">Примечание</string>
<string name="cardId">Номер карты</string>
<string name="barcodeType">Тип штрихкода</string>
<string name="barcodeNoBarcode">Эта карта без штрихкода</string>
<string name="cancel">Отменить</string>
<string name="save">Сохранить</string>
<string name="capture">Сканировать карту</string>
@@ -26,14 +28,16 @@
<string name="ok">ОК</string>
<string name="copy_to_clipboard">Скопировать номер карты в буфер обмена</string>
<string name="share">Переслать</string>
<string name="sendLabel">Отправить</string>
<string name="sendLabel">Отправить&#8230;</string>
<string name="addedShortcut">Добавленный ярлык</string>
<string name="editCardTitle">Редактировать карту</string>
<string name="addCardTitle">Добавить карту</string>
<string name="scanCardBarcode">Отсканируйте штрих-код</string>
<string name="cardShortcut">Ярлык карты</string>
<string name="noCardsMessage">Сначала добавьте карту</string>
<string name="barcodeImageDescription">Изображение штрих-кода карты</string>
<string name="noCardsMessage">Карт нет, добавьте одну для начала</string>
<string name="barcodeImageDescription">Изображение штрих-кода</string>
<string name="noStoreError">Название магазина не указано</string>
<string name="noCardIdError">Номер карты не указан</string>
<string name="noCardExistsError">Карта не найдена</string>
@@ -41,46 +45,53 @@
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="importExport">Импорт/Экспорт</string>
<string name="exportName">Экспорт</string>
<string name="importExportHelp">Резервное копирование карт позволяет переместить их на другое устройство.</string>
<string name="importSuccessfulTitle">Импортировано</string>
<string name="importExportHelp">Сохранение карт позволяет перенести их на другое устройство.</string>
<string name="importSuccessfulTitle">Успешный импорт</string>
<string name="importFailedTitle">Импорт не удался</string>
<string name="importFailed">Не удалось импортировать карты</string>
<string name="exportSuccessfulTitle">Экспортировано</string>
<string name="importFailed">Не удалось импортировать</string>
<string name="exportSuccessfulTitle">Успешный экспорт</string>
<string name="exportFailedTitle">Экспорт не удался</string>
<string name="exportFailed">Не удалось экспортировать</string>
<string name="importing">Импорт</string>
<string name="exporting">Экспорт</string>
<string name="importing">Импорт&#8230;</string>
<string name="exporting">Экспорт&#8230;</string>
<string name="noExternalStoragePermissionError">Импорт или экспорт невозможен без разрешения на доступ к хранилищу</string>
<string name="exportOptionExplanation">Данные будут записаны в выбранное место.</string>
<string name="importOptionFilesystemTitle">Импорт из файловой системы</string>
<string name="importOptionFilesystemExplanation">Выберете файл на файловой системе.</string>
<string name="importOptionFilesystemButton">Выбрать файл</string>
<string name="importOptionApplicationTitle">Использование другого приложения</string>
<string name="importOptionApplicationExplanation">Используйте любое приложение или ваш любимый файловый менеджер, чтобы открыть файл.</string>
<string name="importOptionApplicationExplanation">Использовать другое приложение такое как Dropbox, Google Drive, или ваш любимый файловый менеджер чтобы открыть файл.</string>
<string name="importOptionApplicationButton">Использовать другое приложение</string>
<string name="about">О программе</string>
<string name="app_license">Авторское лево свободного программного обеспечения, лицензия GPLv3+.</string>
<string name="app_copyright_fmt">Все права защищены 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Лицензия GPLv3.</string>
<string name="about_title_fmt">О программе <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Версия: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Информация о версиях: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> использует следующие сторонние библиотеки: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> использует следующие сторонние ресурсы: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Выбор штрих-кода</string>
<string name="enterBarcodeInstructions">Введите ID карты и выберите тип штрих-кода.</string>
<string name="copy_to_clipboard_toast">Номер карты скопирован в буфер обмена</string>
<string name="thumbnailDescription">Логотип карты</string>
<string name="change">Изменить</string>
<string name="storeTextColorTitle">Цвет текста</string>
<string name="storeTextBackgroundColorTitle">Цвет фона</string>
<string name="storeNameBackgroundColorDescription">Цвет фона названия магазина</string>
<string name="storeNameColorDescription">Цвет текста названия магазина</string>
<string name="settings">Настройки</string>
<string name="settings_category_title_ui">Внешний вид</string>
<string name="settings_theme">Тема</string>
<string name="settings_system_theme">Системная</string>
<string name="settings_light_theme">Светлая</string>
<string name="settings_dark_theme">Тёмная</string>
<string name="settings_card_title_list_font_size">Размер шрифта для названий карт</string>
<string name="settings_system_theme">Системная тема</string>
<string name="settings_light_theme">Светлая тема</string>
<string name="settings_dark_theme">Тёмная тема</string>
<string name="settings_card_title_list_font_size">Размер шрифта названия карты для списка</string>
<string name="settings_card_note_list_font_size">Размер шрифта примечания для списка</string>
<string name="settings_card_title_font_size">Размер шрифта названия карты</string>
<string name="settings_card_id_font_size">Размер шрифта номера карты</string>
@@ -88,19 +99,4 @@
<string name="settings_display_barcode_max_brightness">Максимальная яркость при показе карты</string>
<string name="settings_lock_barcode_orientation">Портретная ориентация экрана при показе карты</string>
<string name="intent_import_card_from_url_share_text">Я хочу поделиться картой с вами</string>
<string name="exportSuccessful">Данные карты экспортированы</string>
<string name="all">Все</string>
<string name="groupCardCount">&lt;xliff:g xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\"&gt;%d&lt;/xliff:g&gt; карт</string>
<string name="noGroups">Нажмите кнопку + (плюс), чтобы сначала добавить группы.
\n
\nГруппы упрощают поиск.</string>
<string name="groups">Группы</string>
<string name="enter_group_name">Введите название группы</string>
<string name="importSuccessful">Данные карты импортированы</string>
<string name="starImage">Любимая звезда</string>
<string name="app_copyright_old">На основе Loyalty Card Keychain, авторские права 20162020 Branden Archer.</string>
<string name="unstar">Удалить из избранного</string>
<string name="star">Добавить в избранное</string>
<string name="noBarcode">Нет штрих-кода</string>
<string name="deleteConfirmationGroup">Подтвердите, что хотите удалить эту группу</string>
</resources>
</resources>

View File

@@ -54,6 +54,7 @@
<string name="importOptionApplicationButton">Použiť externú aplikáciu</string>
<string name="about">O aplikácii</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Licencované GPLv3.</string>
<string name="about_title_fmt">O <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Verzia: <xliff:g id="version">%s</xliff:g></string>

View File

@@ -54,6 +54,7 @@
<string name="importOptionApplicationButton">Uporabi zunanjo aplikacijo</string>
<string name="about">Več o aplikaciji</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Licencirano s skladu z GPLv3.</string>
<string name="about_title_fmt">Več o <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Verzija: <xliff:g id="version">%s</xliff:g></string>

View File

@@ -1,6 +1,6 @@
<resources>>
<style name="AppTheme.NoActionBar" parent="AppTheme">
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>

View File

@@ -21,8 +21,7 @@
<dimen name="inputBorderThickness">2dip</dimen>
<dimen name="inputBorderDividerThickness">4dip</dimen>
<dimen name="inputMargin">2dip</dimen>
<dimen name="inputPadding">10dip</dimen>
<dimen name="inputPadding">20dip</dimen>
<dimen name="colorSamplePadding">10dip</dimen>
<dimen name="inputSize">18sp</dimen>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#223355</color>
</resources>

View File

@@ -5,18 +5,14 @@
<string name="action_search">Search</string>
<string name="action_add">Add</string>
<string name="noGiftCards">Click the "+" (plus) button to add a card first.\n\nCatima carries your cards on the device, so they are always within reach.</string>
<string name="noMatchingGiftCards">Didn\'t find anything. Try changing your search.</string>
<string name="noGiftCards">You don\'t have any cards at the moment. Click the "+" (plus) button to get started.\n\nCatima lets you carry your cards on your phone, so they are always within reach.</string>
<string name="noMatchingGiftCards">No cards match the search filter. Please try some different terms.</string>
<string name="storeName">Store</string>
<string name="note">Note</string>
<string name="cardId">Card ID</string>
<string name="barcodeType">Barcode type</string>
<string name="barcodeNoBarcode">This card has no barcode</string>
<string name="noBarcode">No barcode</string>
<string name="star">Add to favorites</string>
<string name="unstar">Remove from favorites</string>
<string name="cancel">Cancel</string>
<string name="save">Save</string>
@@ -24,12 +20,13 @@
<string name="enterCard">Enter Card</string>
<string name="editCard">Edit Card</string>
<string name="edit">Edit</string>
<string name="moreInfo">More Info</string>
<string name="delete">Delete</string>
<string name="confirm">Confirm</string>
<string name="lockScreen">Block Rotation</string>
<string name="unlockScreen">Unblock Rotation</string>
<string name="deleteTitle">Remove Card</string>
<string name="deleteConfirmation">Please confirm you want to delete this card.</string>
<string name="deleteConfirmation">Please confirm that you want to delete this card.</string>
<string name="ok">OK</string>
<string name="copy_to_clipboard">Copy ID to clipboard</string>
<string name="share">Share</string>
@@ -37,44 +34,47 @@
<string name="addedShortcut">Added shortcut</string>
<string name="editCardTitle">Edit Card</string>
<string name="addCardTitle">Add Card</string>
<string name="scanCardBarcode">Scan Card Barcode</string>
<string name="scanCardBarcode">Scan Card\'s Barcode</string>
<string name="cardShortcut">Card Shortcut</string>
<string name="noCardsMessage">Add a card first</string>
<string name="noCardsMessage">There are no cards, add one first</string>
<string name="barcodeImageDescription">Image of card barcode</string>
<string name="barcodeImageDescription">Image of card\'s barcode</string>
<string name="noStoreError">No store entered</string>
<string name="noCardIdError">No card ID entered</string>
<string name="noCardExistsError">Could not find card</string>
<string name="noStoreError">No Store entered</string>
<string name="noCardIdError">No Card ID entered</string>
<string name="noCardExistsError">Could not lookup loyalty card</string>
<string name="failedSharingCard">Could not share card</string>
<string name="failedShowingExtras">Could not show extra information: data not correctly formatted</string>
<string name="failedSavingCard">Could not save card</string>
<string name="failedParsingImportUriError">Could not parse the import URI</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="importExport">Import/Export</string>
<string name="exportName">Export</string>
<string name="importExportHelp">Backing up your cards allows you to move them to another device.</string>
<string name="importSuccessfulTitle">Imported</string>
<string name="importExportHelp">Backed up data can allow you to move your cards to another device.</string>
<string name="importSuccessfulTitle">Import successful</string>
<string name="importFailedTitle">Import failed</string>
<string name="importFailed">Could not import cards</string>
<string name="exportSuccessfulTitle">Exported</string>
<string name="importFailed">Failed to import</string>
<string name="exportSuccessfulTitle">Export successful</string>
<string name="exportFailedTitle">Export failed</string>
<string name="exportFailed">Could not export cards</string>
<string name="exportFailed">Failed to export</string>
<string name="importing">Importing&#8230;</string>
<string name="exporting">Exporting&#8230;</string>
<string name="noExternalStoragePermissionError">Grant external storage permission to import or export cards first</string>
<string name="exportOptionExplanation">The data will be written to a location of your choice.</string>
<string name="noExternalStoragePermissionError">Unable to import or export cards without the external storage permission</string>
<string name="exportOptionExplanation">Data will be written to a location of your choice.</string>
<string name="importOptionFilesystemTitle">Import from filesystem</string>
<string name="importOptionFilesystemExplanation">Choose a specific file from the filesystem.</string>
<string name="importOptionFilesystemButton">From filesystem</string>
<string name="importOptionApplicationTitle">Use external app</string>
<string name="importOptionApplicationExplanation">Use any app or your favorite file manager to open a file.</string>
<string name="importOptionApplicationButton">Use external app</string>
<string name="importOptionApplicationTitle">Use external ap1plication</string>
<string name="importOptionApplicationExplanation">Use an external application like Dropbox, Google Drive, or your favorite file manager to open a file.</string>
<string name="importOptionApplicationButton">Use external application</string>
<string name="about">About</string>
<string name="app_copyright_fmt" translatable="false">Copyright 2019<xliff:g>%d</xliff:g> Sylvia van Os.</string>
<string name="app_copyright_old">Based on Loyalty Card Keychain, copyright 20162020 Branden Archer.</string>
<string name="app_license">Copylefted libre software, licensed GPLv3+.</string>
<string name="app_copyright_fmt">Copyright 2019-<xliff:g>%d</xliff:g> Sylvia van Os.</string>
<string name="app_copyright_old">Based on Loyalty Card Keychain, copyright 2016-2020 Branden Archer.</string>
<string name="app_license">Licensed under the GPLv3.</string>
<string name="about_title_fmt">About <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Version: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Revision Info: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_revision_fmt">Revision Information: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> uses the following third-party libraries: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> uses the following third-party resources: <xliff:g id="app_resources_list">%s</xliff:g></string>
@@ -84,7 +84,6 @@
<string name="copy_to_clipboard_toast">Card ID copied to clipboard</string>
<string name="thumbnailDescription">Thumbnail for card</string>
<string name="starImage">Favorite star</string>
<string name="change">Change</string>
<string name="storeTextColorTitle">Store Text Color</string>
@@ -96,15 +95,15 @@
<string name="settings_category_title_ui">User interface</string>
<string name="settings_theme">Theme</string>
<string name="settings_key_theme" translatable="false">pref_theme</string>
<string name="settings_system_theme">System</string>
<string name="settings_system_theme">System theme</string>
<string name="settings_key_system_theme" translatable="false">system</string>
<string name="settings_light_theme">Light</string>
<string name="settings_light_theme">Light theme</string>
<string name="settings_key_light_theme" translatable="false">light</string>
<string name="settings_dark_theme">Dark</string>
<string name="settings_dark_theme">Dark theme</string>
<string name="settings_key_dark_theme" translatable="false">dark</string>
<string name="settings_card_title_list_font_size">Font size for card titles</string>
<string name="settings_card_title_list_font_size">Card title list font size</string>
<string name="settings_key_card_title_list_font_size" translatable="false">pref_card_title_list_font_size_sp</string>
<string name="settings_card_note_list_font_size">Font size for card note list </string>
<string name="settings_card_note_list_font_size">Card note list font size</string>
<string name="settings_key_card_note_list_font_size" translatable="false">pref_card_note_list_font_size_sp</string>
<string name="settings_card_title_font_size">Card title font size</string>
<string name="settings_key_card_title_font_size" translatable="false">pref_card_title_font_size_sp</string>
@@ -122,13 +121,6 @@
<string name="intent_import_card_from_url_path_prefix" translatable="false">/Catima/share</string>
<string name="intent_import_card_from_url_host_old" translatable="false">brarcher.github.io</string>
<string name="intent_import_card_from_url_path_prefix_old" translatable="false">/loyalty-card-locker/share</string>
<string name="importSuccessful">Card data imported</string>
<string name="exportSuccessful">Card data exported</string>
<string name="enter_group_name">Enter group name</string>
<string name="groups">Groups</string>
<string name="noGroups">Click the "+" (plus) button to add groups first.\n\nGroups make things easier to find.</string>
<string name="groupCardCount"><xliff:g>%d</xliff:g> cards</string>
<string name="all">All</string>
<string name="deleteConfirmationGroup">Please confirm you want to delete this group</string>
<string name="importSuccessful">Successfully imported loyalty card data</string>
<string name="exportSuccessful">Successfully exported loyalty card data</string>
</resources>

View File

@@ -1,24 +1,23 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<style name="AppTheme" parent="Theme.AppCompat.DayNight.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorSecondary">@color/colorSecondary</item>
<item name="colorAccent">@color/colorSecondary</item>
</style>
<style name="AppTheme.NoActionBar" parent="AppTheme">
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar"/>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.MaterialComponents.Light"/>
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
<style name="CardView.ActionBarTheme" parent="@style/ThemeOverlay.MaterialComponents.ActionBar">
<style name="CardView.ActionBarTheme" parent="@style/ThemeOverlay.AppCompat.ActionBar">
<!-- THIS is where you can color the arrow! -->
<item name="colorControlNormal">@android:color/white</item>
</style>

View File

@@ -2,12 +2,17 @@ package protect.card_locker;
import android.app.Activity;
import android.content.ContentValues;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import com.google.zxing.BarcodeFormat;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -15,11 +20,7 @@ import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -32,19 +33,24 @@ public class DatabaseTest
private static final Integer DEFAULT_HEADER_COLOR = Color.BLACK;
private static final Integer DEFAULT_HEADER_TEXT_COLOR = Color.WHITE;
private static Bitmap DEFAULT_ICON = BitmapFactory.decodeResource(Resources.getSystem(), R.mipmap.ic_launcher);
private static ExtrasHelper DEFAULT_EXTRAS;
@Before
public void setUp()
public void setUp() throws JSONException
{
DEFAULT_EXTRAS = new ExtrasHelper();
DEFAULT_EXTRAS.addLanguageValue("en", "key", "value");
Activity activity = Robolectric.setupActivity(MainActivity.class);
db = new DBHelper(activity);
}
@Test
public void addRemoveOneGiftCard()
public void addRemoveOneGiftCard() throws JSONException
{
assertEquals(0, db.getLoyaltyCardCount());
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0);
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR, DEFAULT_ICON, DEFAULT_EXTRAS);
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
@@ -54,7 +60,6 @@ public class DatabaseTest
assertEquals("store", loyaltyCard.store);
assertEquals("note", loyaltyCard.note);
assertEquals("cardId", loyaltyCard.cardId);
assertEquals(0, loyaltyCard.starStatus);
assertEquals(BarcodeFormat.UPC_A.toString(), loyaltyCard.barcodeType);
result = db.deleteLoyaltyCard(1);
@@ -64,14 +69,14 @@ public class DatabaseTest
}
@Test
public void updateGiftCard()
public void updateGiftCard() throws JSONException
{
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0);
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR, DEFAULT_ICON, DEFAULT_EXTRAS);
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
result = db.updateLoyaltyCard(1, "store1", "note1", "cardId1", BarcodeFormat.AZTEC.toString(), DEFAULT_HEADER_COLOR);
result = db.updateLoyaltyCard(1, "store1", "note1", "cardId1", BarcodeFormat.AZTEC.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR, DEFAULT_ICON, DEFAULT_EXTRAS);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
@@ -80,46 +85,25 @@ public class DatabaseTest
assertEquals("store1", loyaltyCard.store);
assertEquals("note1", loyaltyCard.note);
assertEquals("cardId1", loyaltyCard.cardId);
assertEquals(0, loyaltyCard.starStatus);
assertEquals(BarcodeFormat.AZTEC.toString(), loyaltyCard.barcodeType);
assertEquals(DEFAULT_EXTRAS.toJSON().toString(), loyaltyCard.extras.toJSON().toString());
}
@Test
public void updateGiftCardOnlyStar()
{
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0);
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
result = db.updateLoyaltyCardStarStatus(1, 1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard loyaltyCard = db.getLoyaltyCard(1);
assertNotNull(loyaltyCard);
assertEquals("store", loyaltyCard.store);
assertEquals("note", loyaltyCard.note);
assertEquals("cardId", loyaltyCard.cardId);
assertEquals(1, loyaltyCard.starStatus);
assertEquals(BarcodeFormat.UPC_A.toString(), loyaltyCard.barcodeType);
}
@Test
public void updateMissingGiftCard()
public void updateMissingGiftCard() throws JSONException
{
assertEquals(0, db.getLoyaltyCardCount());
boolean result = db.updateLoyaltyCard(1, "store1", "note1", "cardId1",
BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR);
BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR, DEFAULT_ICON, DEFAULT_EXTRAS);
assertEquals(false, result);
assertEquals(0, db.getLoyaltyCardCount());
}
@Test
public void emptyGiftCardValues()
public void emptyGiftCardValues() throws JSONException
{
long id = db.insertLoyaltyCard("", "", "", "", null, 0);
long id = db.insertLoyaltyCard("", "", "", "", null, null, null, new ExtrasHelper());
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
@@ -130,10 +114,11 @@ public class DatabaseTest
assertEquals("", loyaltyCard.note);
assertEquals("", loyaltyCard.cardId);
assertEquals("", loyaltyCard.barcodeType);
assertEquals("{}", loyaltyCard.extras.toJSON().toString());
}
@Test
public void giftCardsViaCursor()
public void giftCardsViaCursor() throws JSONException
{
final int CARDS_TO_ADD = 10;
@@ -142,7 +127,7 @@ public class DatabaseTest
for(int index = CARDS_TO_ADD-1; index >= 0; index--)
{
long id = db.insertLoyaltyCard("store" + index, "note" + index, "cardId" + index,
BarcodeFormat.UPC_A.toString(), index, 0);
BarcodeFormat.UPC_A.toString(), index, index*2, DEFAULT_ICON, DEFAULT_EXTRAS);
boolean result = (id != -1);
assertTrue(result);
}
@@ -163,65 +148,9 @@ public class DatabaseTest
assertEquals("cardId"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID)));
assertEquals("cardId"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID)));
assertEquals(BarcodeFormat.UPC_A.toString(), cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE)));
assertEquals(0, cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS)));
assertEquals(index, cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR)));
cursor.moveToNext();
}
assertTrue(cursor.isAfterLast());
}
@Test
public void giftCardsViaCursorWithOneStarred() //sorting test; stared card should appear first
{
final int CARDS_TO_ADD = 10;
long id;
// Add the gift cards in reverse order and add one with STAR, to ensure
// that they are sorted
for(int index = CARDS_TO_ADD-1; index >= 0; index--)
{
if (index == CARDS_TO_ADD-1) {
id = db.insertLoyaltyCard("store" + index, "note" + index, "cardId" + index,
BarcodeFormat.UPC_A.toString(), index, 1);
}
else {
id = db.insertLoyaltyCard("store" + index, "note" + index, "cardId" + index,
BarcodeFormat.UPC_A.toString(), index, 0);
}
boolean result = (id != -1);
assertTrue(result);
}
assertEquals(CARDS_TO_ADD, db.getLoyaltyCardCount());
Cursor cursor = db.getLoyaltyCardCursor();
assertNotNull(cursor);
assertEquals(CARDS_TO_ADD, cursor.getCount());
cursor.moveToFirst();
int index = CARDS_TO_ADD-1 ;
assertEquals("store"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE)));
assertEquals("note"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE)));
assertEquals("cardId"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID)));
assertEquals("cardId"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID)));
assertEquals(BarcodeFormat.UPC_A.toString(), cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE)));
assertEquals(1, cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS)));
assertEquals(index, cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR)));
cursor.moveToNext();
for(index = 0; index < CARDS_TO_ADD-1; index++)
{
assertEquals("store"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE)));
assertEquals("note"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE)));
assertEquals("cardId"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID)));
assertEquals("cardId"+index, cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID)));
assertEquals(BarcodeFormat.UPC_A.toString(), cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE)));
assertEquals(0, cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS)));
assertEquals(index, cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR)));
assertEquals(index*2, cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR)));
assertEquals("{\"en\":{\"key\":\"value\"}}", cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXTRAS)));
cursor.moveToNext();
}
@@ -233,8 +162,6 @@ public class DatabaseTest
{
// Delete the tables as they exist now
database.execSQL("drop table " + DBHelper.LoyaltyCardDbIds.TABLE);
database.execSQL("drop table " + DBHelper.LoyaltyCardDbGroups.TABLE);
database.execSQL("drop table " + DBHelper.LoyaltyCardDbIdsGroups.TABLE);
// Create the table as it existed in revision 1
database.execSQL("create table " + DBHelper.LoyaltyCardDbIds.TABLE + "(" +
@@ -258,162 +185,7 @@ public class DatabaseTest
}
@Test
public void addRemoveOneGroup()
{
assertEquals(0, db.getGroupCount());
long id = db.insertGroup("group one");
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getGroupCount());
Group group = db.getGroup("group one");
assertNotNull(group);
assertEquals("group one", group._id);
result = db.deleteGroup("group one");
assertTrue(result);
assertEquals(0, db.getGroupCount());
assertNull(db.getGroup("group one"));
}
@Test
public void updateGroup()
{
long id = db.insertGroup("group one");
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getGroupCount());
result = db.updateGroup("group one", "group one renamed");
assertTrue(result);
assertEquals(1, db.getGroupCount());
// Group one no longer exists
Group group = db.getGroup("group one");
assertNull(group);
// But group one renamed does
Group group2 = db.getGroup("group one renamed");
assertNotNull(group2);
assertEquals("group one renamed", group2._id);
}
@Test
public void updateMissingGroup()
{
assertEquals(0, db.getGroupCount());
boolean result = db.updateGroup("group one", "new name");
assertEquals(false, result);
assertEquals(0, db.getGroupCount());
}
@Test
public void emptyGroupValues()
{
long id = db.insertGroup("");
boolean result = (id != -1);
assertFalse(result);
assertEquals(0, db.getLoyaltyCardCount());
}
@Test
public void duplicateGroupName()
{
assertEquals(0, db.getGroupCount());
long id = db.insertGroup("group one");
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getGroupCount());
Group group = db.getGroup("group one");
assertNotNull(group);
assertEquals("group one", group._id);
// Should fail on duplicate
long id2 = db.insertGroup("group one");
boolean result2 = (id2 != -1);
assertFalse(result2);
assertEquals(1, db.getGroupCount());
}
@Test
public void updateGroupDuplicate()
{
long id = db.insertGroup("group one");
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getGroupCount());
long id2 = db.insertGroup("group two");
boolean result2 = (id2 != -1);
assertTrue(result2);
assertEquals(2, db.getGroupCount());
// Should fail when trying to rename group two to one
boolean result3 = db.updateGroup("group two", "group one");
assertFalse(result3);
assertEquals(2, db.getGroupCount());
// Rename failed so both should still be the same
Group group = db.getGroup("group one");
assertNotNull(group);
assertEquals("group one", group._id);
Group group2 = db.getGroup("group two");
assertNotNull(group2);
assertEquals("group two", group2._id);
}
@Test
public void cardAddAndRemoveGroups()
{
// Create card
assertEquals(0, db.getLoyaltyCardCount());
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, 0);
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
// Create two groups to only one card
assertEquals(0, db.getGroupCount());
long gid = db.insertGroup("one");
boolean gresult = (gid != -1);
assertTrue(gresult);
long gid2 = db.insertGroup("two");
boolean gresult2 = (gid2 != -1);
assertTrue(gresult2);
assertEquals(2, db.getGroupCount());
Group group1 = db.getGroup("one");
// Card has no groups by default
List<Group> cardGroups = db.getLoyaltyCardGroups(1);
assertEquals(0, cardGroups.size());
// Add one groups to card
List<Group> groupList1 = new ArrayList<>();
groupList1.add(group1);
db.setLoyaltyCardGroups(1, groupList1);
List<Group> cardGroups1 = db.getLoyaltyCardGroups(1);
assertEquals(1, cardGroups1.size());
assertEquals(cardGroups1.get(0)._id, group1._id);
assertEquals(1, db.getGroupCardCount("one"));
assertEquals(0, db.getGroupCardCount("two"));
// Remove groups
db.setLoyaltyCardGroups(1, new ArrayList<Group>());
List<Group> cardGroups2 = db.getLoyaltyCardGroups(1);
assertEquals(0, cardGroups2.size());
assertEquals(0, db.getGroupCardCount("one"));
assertEquals(0, db.getGroupCardCount("two"));
}
@Test
public void databaseUpgradeFromVersion1()
public void databaseUpgradeFromVersion1() throws JSONException
{
SQLiteDatabase database = db.getWritableDatabase();
@@ -434,6 +206,7 @@ public class DatabaseTest
assertEquals("", card.note);
assertEquals(null, card.headerColor);
assertEquals(null, card.headerTextColor);
assertEquals("{}", card.extras.toJSON().toString());
database.close();
}

View File

@@ -9,6 +9,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.view.View;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -16,6 +17,7 @@ import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowPackageManager;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)

View File

@@ -3,10 +3,14 @@ package protect.card_locker;
import android.app.Activity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Environment;
import com.google.zxing.BarcodeFormat;
import org.apache.tools.ant.filters.StringInputStream;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -23,10 +27,9 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -45,10 +48,14 @@ public class ImportExportTest
private final String BARCODE_DATA = "428311627547";
private final String BARCODE_TYPE = BarcodeFormat.UPC_A.name();
private ExtrasHelper EXTRAS;
@Before
public void setUp()
public void setUp() throws JSONException
{
EXTRAS = new ExtrasHelper();
EXTRAS.addLanguageValue("en", "key", "value");
activity = Robolectric.setupActivity(MainActivity.class);
db = new DBHelper(activity);
nowMs = System.currentTimeMillis();
@@ -63,14 +70,14 @@ public class ImportExportTest
* an index in the store name.
* @param cardsToAdd
*/
private void addLoyaltyCards(int cardsToAdd)
private void addLoyaltyCards(int cardsToAdd) throws JSONException
{
// Add in reverse order to test sorting
for(int index = cardsToAdd; index > 0; index--)
{
String storeName = String.format("store, \"%4d", index);
String note = String.format("note, \"%4d", index);
long id = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE, index, 0);
long id = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE, index, index*2, null, EXTRAS);
boolean result = (id != -1);
assertTrue(result);
}
@@ -78,50 +85,12 @@ public class ImportExportTest
assertEquals(cardsToAdd, db.getLoyaltyCardCount());
}
private void addLoyaltyCardsFiveStarred()
{
int cardsToAdd = 9;
// Add in reverse order to test sorting
for(int index = cardsToAdd; index > 4; index--)
{
String storeName = String.format("store, \"%4d", index);
String note = String.format("note, \"%4d", index);
long id = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE, index, 1);
boolean result = (id != -1);
assertTrue(result);
}
for(int index = cardsToAdd-5; index > 0; index--)
{
String storeName = String.format("store, \"%4d", index);
String note = String.format("note, \"%4d", index);
//if index is even
long id = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE, index, 0);
boolean result = (id != -1);
assertTrue(result);
}
assertEquals(cardsToAdd, db.getLoyaltyCardCount());
}
private void addGroups(int groupsToAdd)
{
// Add in reverse order to test sorting
for(int index = groupsToAdd; index > 0; index--)
{
String groupName = String.format("group, \"%4d", index);
long id = db.insertGroup(groupName);
boolean result = (id != -1);
assertTrue(result);
}
assertEquals(groupsToAdd, db.getGroupCount());
}
/**
* Check that all of the cards follow the pattern
* specified in addLoyaltyCards(), and are in sequential order
* where the smallest card's index is 1
*/
private void checkLoyaltyCards()
private void checkLoyaltyCards() throws JSONException
{
Cursor cursor = db.getLoyaltyCardCursor();
int index = 1;
@@ -138,79 +107,8 @@ public class ImportExportTest
assertEquals(BARCODE_DATA, card.cardId);
assertEquals(BARCODE_TYPE, card.barcodeType);
assertEquals(Integer.valueOf(index), card.headerColor);
assertEquals(0, card.starStatus);
index++;
}
cursor.close();
}
/**
* Check that all of the cards follow the pattern
* specified in addLoyaltyCardsSomeStarred(), and are in sequential order
* with starred ones first
*/
private void checkLoyaltyCardsFiveStarred()
{
Cursor cursor = db.getLoyaltyCardCursor();
int index = 5;
while(index<10)
{
cursor.moveToNext();
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
String expectedStore = String.format("store, \"%4d", index);
String expectedNote = String.format("note, \"%4d", index);
assertEquals(expectedStore, card.store);
assertEquals(expectedNote, card.note);
assertEquals(BARCODE_DATA, card.cardId);
assertEquals(BARCODE_TYPE, card.barcodeType);
assertEquals(Integer.valueOf(index), card.headerColor);
assertEquals(1, card.starStatus);
index++;
}
index = 1;
while(cursor.moveToNext() && index<5)
{
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
String expectedStore = String.format("store, \"%4d", index);
String expectedNote = String.format("note, \"%4d", index);
assertEquals(expectedStore, card.store);
assertEquals(expectedNote, card.note);
assertEquals(BARCODE_DATA, card.cardId);
assertEquals(BARCODE_TYPE, card.barcodeType);
assertEquals(Integer.valueOf(index), card.headerColor);
assertEquals(0, card.starStatus);
index++;
}
cursor.close();
}
/**
* Check that all of the groups follow the pattern
* specified in addGroups(), and are in sequential order
* where the smallest group's index is 1
*/
private void checkGroups()
{
Cursor cursor = db.getGroupCursor();
int index = 1;
while(cursor.moveToNext())
{
Group group = Group.toGroup(cursor);
String expectedGroupName = String.format("group, \"%4d", index);
assertEquals(expectedGroupName, group._id);
assertEquals(Integer.valueOf(index*2), card.headerTextColor);
assertEquals("{\"en\":{\"key\":\"value\"}}", card.extras.toJSON().toString());
index++;
}
@@ -230,7 +128,7 @@ public class ImportExportTest
}
@Test
public void multipleCardsExportImport() throws IOException
public void multipleCardsExportImport() throws IOException, JSONException
{
final int NUM_CARDS = 10;
@@ -265,132 +163,7 @@ public class ImportExportTest
}
@Test
public void multipleCardsExportImportSomeStarred() throws IOException
{
final int NUM_CARDS = 9;
for(DataFormat format : DataFormat.values())
{
addLoyaltyCardsFiveStarred();
ByteArrayOutputStream outData = new ByteArrayOutputStream();
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
boolean result = MultiFormatExporter.exportData(db, outStream, format);
assertTrue(result);
outStream.close();
clearDatabase();
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
InputStreamReader inStream = new InputStreamReader(inData);
// Import the CSV data
result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV);
assertTrue(result);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
checkLoyaltyCardsFiveStarred();
// Clear the database for the next format under test
clearDatabase();
}
}
private List<String> groupsToGroupNames(List<Group> groups)
{
List<String> groupNames = new ArrayList<>();
for (Group group : groups) {
groupNames.add(group._id);
}
return groupNames;
}
@Test
public void multipleCardsExportImportWithGroups() throws IOException
{
final int NUM_CARDS = 10;
final int NUM_GROUPS = 3;
for(DataFormat format : DataFormat.values())
{
addLoyaltyCards(NUM_CARDS);
addGroups(NUM_GROUPS);
List<Group> emptyGroup = new ArrayList<>();
List<Group> groupsForOne = new ArrayList<>();
groupsForOne.add(db.getGroup("group, \" 1"));
List<Group> groupsForTwo = new ArrayList<>();
groupsForTwo.add(db.getGroup("group, \" 1"));
groupsForTwo.add(db.getGroup("group, \" 2"));
List<Group> groupsForThree = new ArrayList<>();
groupsForThree.add(db.getGroup("group, \" 1"));
groupsForThree.add(db.getGroup("group, \" 2"));
groupsForThree.add(db.getGroup("group, \" 3"));
List<Group> groupsForFour = new ArrayList<>();
groupsForFour.add(db.getGroup("group, \" 1"));
groupsForFour.add(db.getGroup("group, \" 2"));
groupsForFour.add(db.getGroup("group, \" 3"));
List<Group> groupsForFive = new ArrayList<>();
groupsForFive.add(db.getGroup("group, \" 1"));
groupsForFive.add(db.getGroup("group, \" 3"));
db.setLoyaltyCardGroups(1, groupsForOne);
db.setLoyaltyCardGroups(2, groupsForTwo);
db.setLoyaltyCardGroups(3, groupsForThree);
db.setLoyaltyCardGroups(4, groupsForFour);
db.setLoyaltyCardGroups(5, groupsForFive);
ByteArrayOutputStream outData = new ByteArrayOutputStream();
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
boolean result = MultiFormatExporter.exportData(db, outStream, format);
assertTrue(result);
outStream.close();
clearDatabase();
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
InputStreamReader inStream = new InputStreamReader(inData);
// Import the CSV data
result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV);
assertTrue(result);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
assertEquals(NUM_GROUPS, db.getGroupCount());
checkLoyaltyCards();
checkGroups();
assertEquals(groupsToGroupNames(groupsForOne), groupsToGroupNames(db.getLoyaltyCardGroups(1)));
assertEquals(groupsToGroupNames(groupsForTwo), groupsToGroupNames(db.getLoyaltyCardGroups(2)));
assertEquals(groupsToGroupNames(groupsForThree), groupsToGroupNames(db.getLoyaltyCardGroups(3)));
assertEquals(groupsToGroupNames(groupsForFour), groupsToGroupNames(db.getLoyaltyCardGroups(4)));
assertEquals(groupsToGroupNames(groupsForFive), groupsToGroupNames(db.getLoyaltyCardGroups(5)));
assertEquals(emptyGroup, db.getLoyaltyCardGroups(6));
assertEquals(emptyGroup, db.getLoyaltyCardGroups(7));
assertEquals(emptyGroup, db.getLoyaltyCardGroups(8));
assertEquals(emptyGroup, db.getLoyaltyCardGroups(9));
assertEquals(emptyGroup, db.getLoyaltyCardGroups(10));
// Clear the database for the next format under test
clearDatabase();
}
}
@Test
public void importExistingCardsNotReplace() throws IOException
public void importExistingCardsNotReplace() throws IOException, JSONException
{
final int NUM_CARDS = 10;
@@ -423,7 +196,7 @@ public class ImportExportTest
}
@Test
public void corruptedImportNothingSaved() throws IOException
public void corruptedImportNothingSaved() throws IOException, JSONException
{
final int NUM_CARDS = 10;
@@ -468,12 +241,12 @@ public class ImportExportTest
}
@Test
public void useImportExportTask() throws FileNotFoundException
public void useImportExportTask() throws FileNotFoundException, JSONException
{
final int NUM_CARDS = 10;
final File sdcardDir = Environment.getExternalStorageDirectory();
final File exportFile = new File(sdcardDir, "Catima.csv");
final File exportFile = new File(sdcardDir, "LoyaltyCardLocker.csv");
for(DataFormat format : DataFormat.values())
{
@@ -521,17 +294,15 @@ public class ImportExportTest
}
@Test
public void importWithoutColorsV1() throws IOException
public void importWithoutColors() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
DBHelper.LoyaltyCardDbIds.STORE + "," +
DBHelper.LoyaltyCardDbIds.NOTE + "," +
DBHelper.LoyaltyCardDbIds.CARD_ID + "," +
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE + "," +
DBHelper.LoyaltyCardDbIds.STAR_STATUS + "\n";
csvText += "1,store,note,12345,type,0";
DBHelper.LoyaltyCardDbIds.STORE + "," +
DBHelper.LoyaltyCardDbIds.NOTE + "," +
DBHelper.LoyaltyCardDbIds.CARD_ID + "," +
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE + "\n";
csvText += "1,store,note,12345,type";
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
InputStreamReader inStream = new InputStreamReader(inputStream);
@@ -547,12 +318,12 @@ public class ImportExportTest
assertEquals("note", card.note);
assertEquals("12345", card.cardId);
assertEquals("type", card.barcodeType);
assertEquals(0, card.starStatus);
assertNull(card.headerColor);
assertNull(card.headerTextColor);
}
@Test
public void importWithoutNullColorsV1() throws IOException
public void importWithoutNullColors() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
@@ -561,10 +332,8 @@ public class ImportExportTest
DBHelper.LoyaltyCardDbIds.CARD_ID + "," +
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE + "," +
DBHelper.LoyaltyCardDbIds.HEADER_COLOR + "," +
DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR + "," +
DBHelper.LoyaltyCardDbIds.STAR_STATUS + "\n";
csvText += "1,store,note,12345,type,,,0";
DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR + "\n";
csvText += "1,store,note,12345,type,,";
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
InputStreamReader inStream = new InputStreamReader(inputStream);
@@ -580,12 +349,12 @@ public class ImportExportTest
assertEquals("note", card.note);
assertEquals("12345", card.cardId);
assertEquals("type", card.barcodeType);
assertEquals(0, card.starStatus);
assertNull(card.headerColor);
assertNull(card.headerTextColor);
}
@Test
public void importWithoutInvalidColorsV1() throws IOException
public void importWithoutInvalidColors() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
@@ -594,10 +363,8 @@ public class ImportExportTest
DBHelper.LoyaltyCardDbIds.CARD_ID + "," +
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE + "," +
DBHelper.LoyaltyCardDbIds.HEADER_COLOR + "," +
DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR + "," +
DBHelper.LoyaltyCardDbIds.STAR_STATUS + "\n";
csvText += "1,store,note,12345,type,not a number,invalid,0";
DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR + "\n";
csvText += "1,store,note,12345,type,not a number,invalid";
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
InputStreamReader inStream = new InputStreamReader(inputStream);
@@ -609,7 +376,7 @@ public class ImportExportTest
}
@Test
public void importWithNoBarcodeTypeV1() throws IOException
public void importWithNoBarcodeType() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
@@ -618,10 +385,8 @@ public class ImportExportTest
DBHelper.LoyaltyCardDbIds.CARD_ID + "," +
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE + "," +
DBHelper.LoyaltyCardDbIds.HEADER_COLOR + "," +
DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR + "," +
DBHelper.LoyaltyCardDbIds.STAR_STATUS + "\n";
csvText += "1,store,note,12345,,1,1,0";
DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR + "\n";
csvText += "1,store,note,12345,,1,1";
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
InputStreamReader inStream = new InputStreamReader(inputStream);
@@ -637,128 +402,7 @@ public class ImportExportTest
assertEquals("note", card.note);
assertEquals("12345", card.cardId);
assertEquals("", card.barcodeType);
assertEquals(0, card.starStatus);
assertEquals(1, (long) card.headerColor);
}
@Test
public void importWithStarredFieldV1() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
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.STAR_STATUS + "\n";
csvText += "1,store,note,12345,type,1,1,1";
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
InputStreamReader inStream = new InputStreamReader(inputStream);
// Import the CSV data
boolean result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV);
assertEquals(true, result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);
assertEquals("store", card.store);
assertEquals("note", card.note);
assertEquals("12345", card.cardId);
assertEquals("type", card.barcodeType);
assertEquals(1, card.starStatus);
assertEquals(1, (long) card.headerColor);
}
@Test
public void importWithNoStarredFieldV1() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
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.STAR_STATUS + "\n";
csvText += "1,store,note,12345,type,1,1,";
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
InputStreamReader inStream = new InputStreamReader(inputStream);
// Import the CSV data
boolean result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV);
assertEquals(true, result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);
assertEquals("store", card.store);
assertEquals("note", card.note);
assertEquals("12345", card.cardId);
assertEquals("type", card.barcodeType);
assertEquals(0, card.starStatus);
assertEquals(1, (long) card.headerColor);
}
@Test
public void importWithInvalidStarFieldV1() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
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.STAR_STATUS + "\n";
csvText += "1,store,note,12345,type,1,1,2";
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
InputStreamReader inStream = new InputStreamReader(inputStream);
// Import the CSV data
boolean result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
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.STAR_STATUS + "\n";
csvText += "1,store,note,12345,type,1,1,text";
inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
inStream = new InputStreamReader(inputStream);
// Import the CSV data
result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);
assertEquals("store", card.store);
assertEquals("note", card.note);
assertEquals("12345", card.cardId);
assertEquals("type", card.barcodeType);
assertEquals(0, card.starStatus);
assertEquals(1, (long) card.headerColor);
assertEquals(1, (long) card.headerTextColor);
}
}

View File

@@ -1,18 +1,25 @@
package protect.card_locker;
import android.app.Activity;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import com.google.zxing.BarcodeFormat;
import org.json.JSONException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.io.InvalidObjectException;
import java.io.InvalidObjectException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static protect.card_locker.DBHelper.LoyaltyCardDbIds;
@@ -32,13 +39,18 @@ public class ImportURITest {
}
@Test
public void ensureNoDataLoss() throws InvalidObjectException
public void ensureNoDataLoss() throws InvalidObjectException, JSONException
{
// Generate card
db.insertLoyaltyCard("store", "note", BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, Color.BLACK, 1);
Bitmap icon = BitmapFactory.decodeResource(Resources.getSystem(), R.mipmap.ic_launcher);
assertNotNull(icon);
ExtrasHelper extrasHelper = new ExtrasHelper();
extrasHelper.addLanguageValue("en", "key", "value");
db.insertLoyaltyCard("store", "note", BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, Color.BLACK, Color.WHITE, icon, extrasHelper);
// Get card
LoyaltyCard card = db.getLoyaltyCard(1);
assertNotNull(card.icon);
// Card to URI
Uri cardUri = importURIHelper.toUri(card);
@@ -50,17 +62,26 @@ public class ImportURITest {
assertEquals(card.barcodeType, parsedCard.barcodeType);
assertEquals(card.cardId, parsedCard.cardId);
assertEquals(card.headerColor, parsedCard.headerColor);
assertEquals(card.headerTextColor, parsedCard.headerTextColor);
assertEquals(card.note, parsedCard.note);
assertEquals(card.store, parsedCard.store);
// No export of starStatus for single cards foreseen therefore 0 will be imported
assertEquals(0, parsedCard.starStatus);
assertEquals(card.icon.getWidth(), parsedCard.icon.getWidth());
assertEquals(card.icon.getHeight(), parsedCard.icon.getHeight());
for(int i = 0; i < card.icon.getWidth(); i++)
{
for(int j = 0; j < card.icon.getHeight(); j++)
{
assertEquals(card.icon.getPixel(i, j), parsedCard.icon.getPixel(i, j));
}
}
assertEquals(card.extras.toJSON().toString(), parsedCard.extras.toJSON().toString());
}
@Test
public void ensureNoCrashOnMissingHeaderFields() throws InvalidObjectException
public void ensureNoCrashOnMissingHeaderFields() throws InvalidObjectException, JSONException
{
// Generate card
db.insertLoyaltyCard("store", "note", BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, null, 0);
db.insertLoyaltyCard("store", "note", BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, null, null, null, new ExtrasHelper());
// Get card
LoyaltyCard card = db.getLoyaltyCard(1);
@@ -90,35 +111,4 @@ public class ImportURITest {
// Desired behaviour
}
}
@Test
public void failToParseBadData()
{
try {
//"stare" instead of store
importURIHelper.parse(Uri.parse("https://brarcher.github.io/loyalty-card-locker/share?stare=store&note=note&cardid=12345&barcodetype=ITF&headercolor=-416706"));
assertTrue(false); // Shouldn't get here
} catch(InvalidObjectException ex) {
// Desired behaviour
}
}
@Test
public void parseAdditionalUnforeseenData()
{
LoyaltyCard parsedCard = null;
try {
parsedCard = importURIHelper.parse(Uri.parse("https://brarcher.github.io/loyalty-card-locker/share?store=store&note=note&cardid=12345&barcodetype=ITF&headercolor=-416706&headertextcolor=-1&notforeseen=no"));
} catch (InvalidObjectException e) {
e.printStackTrace();
}
// Compare everything
assertEquals("ITF", parsedCard.barcodeType);
assertEquals("12345", parsedCard.cardId);
assertEquals("note", parsedCard.note);
assertEquals("store", parsedCard.store);
assertEquals(Integer.valueOf(-416706), parsedCard.headerColor);
assertEquals(0, parsedCard.starStatus);
}
}

View File

@@ -6,11 +6,12 @@ import android.database.Cursor;
import android.graphics.Color;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.zxing.BarcodeFormat;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -82,9 +83,9 @@ public class LoyaltyCardCursorAdapterTest
@Test
public void TestCursorAdapterEmptyNote()
public void TestCursorAdapterEmptyNote() throws JSONException
{
db.insertLoyaltyCard("store", "", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0);
db.insertLoyaltyCard("store", "", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, null, new ExtrasHelper());
LoyaltyCard card = db.getLoyaltyCard(1);
Cursor cursor = db.getLoyaltyCardCursor();
@@ -96,9 +97,9 @@ public class LoyaltyCardCursorAdapterTest
}
@Test
public void TestCursorAdapterWithNote()
public void TestCursorAdapterWithNote() throws JSONException
{
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, null, new ExtrasHelper());
LoyaltyCard card = db.getLoyaltyCard(1);
Cursor cursor = db.getLoyaltyCardCursor();
@@ -110,9 +111,9 @@ public class LoyaltyCardCursorAdapterTest
}
@Test
public void TestCursorAdapterFontSizes()
public void TestCursorAdapterFontSizes() throws JSONException
{
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, null, new ExtrasHelper());
LoyaltyCard card = db.getLoyaltyCard(1);
Cursor cursor = db.getLoyaltyCardCursor();
@@ -126,29 +127,4 @@ public class LoyaltyCardCursorAdapterTest
view = createView(cursor);
checkView(view, card.store, card.note, true);
}
@Test
public void TestCursorAdapterStarring()
{
db.insertLoyaltyCard("storeA", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0);
db.insertLoyaltyCard("storeB", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 1);
db.insertLoyaltyCard("storeC", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 1);
Cursor cursor = db.getLoyaltyCardCursor();
cursor.moveToFirst();
View view = createView(cursor);
ImageView star = view.findViewById(R.id.star);
assertEquals(View.VISIBLE, star.getVisibility());
cursor.moveToNext();
view = createView(cursor);
star = view.findViewById(R.id.star);
assertEquals(View.VISIBLE, star.getVisibility());
cursor.moveToNext();
view = createView(cursor);
star = view.findViewById(R.id.star);
assertEquals(View.GONE, star.getVisibility());
}
}

View File

@@ -5,6 +5,7 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Shadows.shadowOf;
import static protect.card_locker.LoyaltyCardEditActivity.NO_BARCODE;
import android.app.Activity;
import android.content.Intent;
@@ -28,6 +29,7 @@ import android.widget.TextView;
import androidx.core.widget.TextViewCompat;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.client.android.Intents;
import org.json.JSONException;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
@@ -110,7 +112,7 @@ public class LoyaltyCardViewActivityTest
final EditText storeField = activity.findViewById(R.id.storeNameEdit);
final EditText noteField = activity.findViewById(R.id.noteEdit);
final TextView cardIdField = activity.findViewById(R.id.cardIdView);
final TextView barcodeTypeField = activity.findViewById(R.id.barcodeTypeField);
final TextView barcodeTypeField = activity.findViewById(R.id.barcodeTypeView);
storeField.setText(store);
noteField.setText(note);
@@ -129,7 +131,7 @@ public class LoyaltyCardViewActivityTest
assertEquals(cardId, card.cardId);
// The special "No barcode" string shouldn't actually be written to the loyalty card
if(barcodeType.equals(activity.getApplicationContext().getString(R.string.noBarcode)))
if(barcodeType.equals(NO_BARCODE))
{
assertEquals("", card.barcodeType);
}
@@ -237,9 +239,10 @@ public class LoyaltyCardViewActivityTest
checkFieldProperties(activity, R.id.storeNameEdit, editVisibility, store);
checkFieldProperties(activity, R.id.noteEdit, editVisibility, note);
checkFieldProperties(activity, R.id.cardAndBarcodeLayout, cardId.isEmpty() ? View.GONE : View.VISIBLE, null);
checkFieldProperties(activity, R.id.cardIdView, View.VISIBLE, cardId);
checkFieldProperties(activity, R.id.barcodeTypeField, View.VISIBLE, barcodeType);
checkFieldProperties(activity, R.id.cardIdDivider, cardId.isEmpty() ? View.GONE : View.VISIBLE, null);
checkFieldProperties(activity, R.id.cardIdTableRow, cardId.isEmpty() ? View.GONE : View.VISIBLE, null);
checkFieldProperties(activity, R.id.barcodeTypeView, View.VISIBLE, barcodeType);
checkFieldProperties(activity, R.id.captureButton, captureVisibility, null);
checkFieldProperties(activity, R.id.barcode, View.VISIBLE, null);
}
@@ -256,7 +259,7 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
checkAllFields(activity, ViewMode.ADD_CARD, "", "", "", "");
assertEquals(View.GONE, activity.findViewById(R.id.cardAndBarcodeLayout).getVisibility());
assertEquals(View.GONE, activity.findViewById(R.id.barcodeTypeTableRow).getVisibility());
}
@Test
@@ -392,13 +395,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithLoyaltyCardEditModeCheckDisplay() throws IOException
public void startWithLoyaltyCardEditModeCheckDisplay() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -408,13 +411,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithLoyaltyCardViewModeCheckDisplay() throws IOException
public void startWithLoyaltyCardViewModeCheckDisplay() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -424,13 +427,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithLoyaltyCardWithBarcodeUpdateBarcode() throws IOException
public void startWithLoyaltyCardWithBarcodeUpdateBarcode() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -445,13 +448,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithLoyaltyCardWithReceiptUpdateReceiptCancel() throws IOException
public void startWithLoyaltyCardWithReceiptUpdateReceiptCancel() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -471,13 +474,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void checkMenu() throws IOException
public void checkMenu() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -486,12 +489,12 @@ public class LoyaltyCardViewActivityTest
final Menu menu = shadowOf(activity).getOptionsMenu();
assertTrue(menu != null);
// The share, settings, add and star button should be present
// The rotation,share and info button should be present
assertEquals(menu.size(), 3);
assertEquals("Block Rotation", menu.findItem(R.id.action_lock_unlock).getTitle().toString());
assertEquals("Share", menu.findItem(R.id.action_share).getTitle().toString());
assertEquals("Add to favorites", menu.findItem(R.id.action_star_unstar).getTitle().toString());
assertEquals("More Info", menu.findItem(R.id.action_view_extras).getTitle().toString());
}
@Test
@@ -515,13 +518,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithoutParametersViewBack()
public void startWithoutParametersViewBack() throws JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -533,13 +536,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithoutColors()
public void startWithoutColors() throws JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, null, 0);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, null, null, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -551,13 +554,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startLoyaltyCardWithoutColorsSave() throws IOException
public void startLoyaltyCardWithoutColorsSave() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, null, 0);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, null, null, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -568,30 +571,30 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startLoyaltyCardWithExplicitNoBarcodeSave() throws IOException
public void startLoyaltyCardWithExplicitNoBarcodeSave() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, "", Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, "", Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
activityController.resume();
// Save and check the loyalty card
saveLoyaltyCardWithArguments(activity, "store", "note", BARCODE_DATA, activity.getApplicationContext().getString(R.string.noBarcode), false);
saveLoyaltyCardWithArguments(activity, "store", "note", BARCODE_DATA, NO_BARCODE, false);
}
@Test
public void removeBarcodeFromLoyaltyCard() throws IOException
public void removeBarcodeFromLoyaltyCard() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -604,21 +607,21 @@ public class LoyaltyCardViewActivityTest
selectBarcodeWithResult(activity, R.id.enterButton, BARCODE_DATA, "", true);
// Check if the barcode type is NO_BARCODE as expected
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", BARCODE_DATA, activity.getApplicationContext().getString(R.string.noBarcode));
assertEquals(View.GONE, activity.findViewById(R.id.barcodeLayout).getVisibility());
checkAllFields(activity, ViewMode.UPDATE_CARD, "store", "note", BARCODE_DATA, NO_BARCODE);
assertEquals(View.GONE, activity.findViewById(R.id.barcodeTypeTableRow).getVisibility());
// Check if the special NO_BARCODE string doesn't get saved
saveLoyaltyCardWithArguments(activity, "store", "note", BARCODE_DATA, activity.getApplicationContext().getString(R.string.noBarcode), false);
saveLoyaltyCardWithArguments(activity, "store", "note", BARCODE_DATA, NO_BARCODE, false);
}
@Test
public void startCheckFontSizes()
public void startCheckFontSizes() throws JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
final int STORE_FONT_SIZE = 50;
final int CARD_FONT_SIZE = 40;
@@ -652,7 +655,7 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void checkScreenOrientationLockSetting()
public void checkScreenOrientationLockSetting() throws JSONException
{
for(boolean locked : new boolean[] {false, true})
{
@@ -660,7 +663,7 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
settings.edit()
@@ -689,42 +692,54 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void checkPushStarIcon()
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity) activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK,0);
activityController.start();
activityController.visible();
activityController.resume();
assertEquals(false, activity.isFinishing());
final Menu menu = shadowOf(activity).getOptionsMenu();
assertTrue(menu != null);
// The share, settings and star button should be present
assertEquals(menu.size(), 3);
assertEquals("Add to favorites", menu.findItem(R.id.action_star_unstar).getTitle().toString());
shadowOf(activity).clickMenuItem(R.id.action_star_unstar);
assertEquals("Remove from favorites", menu.findItem(R.id.action_star_unstar).getTitle().toString());
shadowOf(activity).clickMenuItem(R.id.action_star_unstar);
assertEquals("Add to favorites", menu.findItem(R.id.action_star_unstar).getTitle().toString());
}
@Test
public void checkBarcodeFullscreenWorkflow()
public void checkMoreInfoNoExtras() throws JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.resume();
activityController.visible();
assertEquals(false, activity.isFinishing());
MenuItem item = shadowOf(activity).getOptionsMenu().findItem(R.id.action_view_extras);
assertEquals(false, item.isVisible());
}
@Test
public void checkMoreInfoExtras() throws JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
ExtrasHelper extrasHelper = new ExtrasHelper();
extrasHelper.addLanguageValue("en", "key", "value");
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, extrasHelper);
activityController.start();
activityController.resume();
activityController.visible();
assertEquals(false, activity.isFinishing());
MenuItem item = shadowOf(activity).getOptionsMenu().findItem(R.id.action_view_extras);
assertEquals(true, item.isVisible());
}
@Test
public void checkBarcodeFullscreenWorkflow() throws JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -782,6 +797,27 @@ public class LoyaltyCardViewActivityTest
@Test
public void importCard()
{
Uri importUri = Uri.parse("https://thelastproject.github.io/Catima/share?store=Example%20Store&note=&cardid=123456&barcodetype=AZTEC&headercolor=-416706&headertextcolor=-1&icon=&extras={}");
Intent intent = new Intent();
intent.setData(importUri);
ActivityController activityController = Robolectric.buildActivity(LoyaltyCardEditActivity.class, intent).create();
activityController.start();
activityController.visible();
activityController.resume();
Activity activity = (Activity)activityController.get();
checkAllFields(activity, ViewMode.ADD_CARD, "Example Store", "", "123456", "AZTEC");
assertEquals(-416706, ((ColorDrawable) activity.findViewById(R.id.headingColorSample).getBackground()).getColor());
assertEquals(-1, ((ColorDrawable) activity.findViewById(R.id.headingStoreTextColorSample).getBackground()).getColor());
}
@Test
public void importCardOldURL()
{
Uri importUri = Uri.parse("https://thelastproject.github.io/Catima/share?store=Example%20Store&note=&cardid=123456&barcodetype=AZTEC&headercolor=-416706&headertextcolor=-1");
@@ -797,26 +833,7 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
checkAllFields(activity, ViewMode.ADD_CARD, "Example Store", "", "123456", "AZTEC");
assertEquals(-416706, ((ColorDrawable) activity.findViewById(R.id.thumbnail).getBackground()).getColor());
}
@Test
public void importCardOldURL()
{
Uri importUri = Uri.parse("https://brarcher.github.io/loyalty-card-locker/share?store=Example%20Store&note=&cardid=123456&barcodetype=AZTEC&headercolor=-416706&headertextcolor=-1");
Intent intent = new Intent();
intent.setData(importUri);
ActivityController activityController = Robolectric.buildActivity(LoyaltyCardEditActivity.class, intent).create();
activityController.start();
activityController.visible();
activityController.resume();
Activity activity = (Activity)activityController.get();
checkAllFields(activity, ViewMode.ADD_CARD, "Example Store", "", "123456", "AZTEC");
assertEquals(-416706, ((ColorDrawable) activity.findViewById(R.id.thumbnail).getBackground()).getColor());
assertEquals(-416706, ((ColorDrawable) activity.findViewById(R.id.headingColorSample).getBackground()).getColor());
assertEquals(-1, ((ColorDrawable) activity.findViewById(R.id.headingStoreTextColorSample).getBackground()).getColor());
}
}

View File

@@ -2,28 +2,29 @@ package protect.card_locker;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;
import com.google.android.material.tabs.TabLayout;
import com.google.zxing.BarcodeFormat;
import org.json.JSONException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.android.controller.ActivityController;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -60,10 +61,9 @@ public class MainActivityTest
final Menu menu = shadowOf(activity).getOptionsMenu();
assertTrue(menu != null);
// The settings, import/export, groups, search and add button should be present
assertEquals(menu.size(), 5);
// The settings, search and add button should be present
assertEquals(menu.size(), 4);
assertEquals("Search", menu.findItem(R.id.action_search).getTitle().toString());
assertEquals("Groups", menu.findItem(R.id.action_manage_groups).getTitle().toString());
assertEquals("Import/Export", menu.findItem(R.id.action_import_export).getTitle().toString());
assertEquals("About", menu.findItem(R.id.action_about).getTitle().toString());
assertEquals("Settings", menu.findItem(R.id.action_settings).getTitle().toString());
@@ -83,7 +83,7 @@ public class MainActivityTest
}
@Test
public void addOneLoyaltyCard()
public void addOneLoyaltyCard() throws JSONException
{
ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create();
@@ -98,7 +98,7 @@ public class MainActivityTest
assertEquals(0, list.getCount());
DBHelper db = new DBHelper(mainActivity);
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0);
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, null, new ExtrasHelper());
assertEquals(View.VISIBLE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
@@ -117,105 +117,7 @@ public class MainActivityTest
}
@Test
public void addFourLoyaltyCardsTwoStarred() // Main screen showing starred cards on top correctly
{
ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create();
Activity mainActivity = (Activity)activityController.get();
activityController.start();
activityController.resume();
TextView helpText = mainActivity.findViewById(R.id.helpText);
TextView noMatchingCardsText = mainActivity.findViewById(R.id.noMatchingCardsText);
ListView list = mainActivity.findViewById(R.id.list);
assertEquals(0, list.getCount());
DBHelper db = new DBHelper(mainActivity);
db.insertLoyaltyCard("storeB", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0);
db.insertLoyaltyCard("storeA", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0);
db.insertLoyaltyCard("storeD", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 1);
db.insertLoyaltyCard("storeC", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 1);
assertEquals(View.VISIBLE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.GONE, list.getVisibility());
activityController.pause();
activityController.resume();
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(4, list.getAdapter().getCount());
Cursor cursor = (Cursor)list.getAdapter().getItem(0);
assertNotNull(cursor);
assertEquals("storeC",cursor.getString(cursor.getColumnIndex("store")));
cursor = (Cursor)list.getAdapter().getItem(1);
assertNotNull(cursor);
assertEquals("storeD",cursor.getString(cursor.getColumnIndex("store")));
cursor = (Cursor)list.getAdapter().getItem(2);
assertNotNull(cursor);
assertEquals("storeA",cursor.getString(cursor.getColumnIndex("store")));
cursor = (Cursor)list.getAdapter().getItem(3);
assertNotNull(cursor);
assertEquals("storeB",cursor.getString(cursor.getColumnIndex("store")));
}
@Test
public void testGroups()
{
ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create();
Activity mainActivity = (Activity)activityController.get();
activityController.start();
activityController.resume();
DBHelper db = new DBHelper(mainActivity);
TabLayout groupTabs = mainActivity.findViewById(R.id.groups);
// No group tabs by default
assertEquals(0, groupTabs.getTabCount());
// Having at least one group should create two tabs: One all and one for each group
db.insertGroup("One");
activityController.pause();
activityController.resume();
assertEquals(2, groupTabs.getTabCount());
assertEquals("All", groupTabs.getTabAt(0).getText().toString());
assertEquals("One", groupTabs.getTabAt(1).getText().toString());
// Adding another group should have them sorted alphabetically
db.insertGroup("Alphabetical two");
activityController.pause();
activityController.resume();
assertEquals(3, groupTabs.getTabCount());
assertEquals("All", groupTabs.getTabAt(0).getText().toString());
assertEquals("Alphabetical two", groupTabs.getTabAt(1).getText().toString());
assertEquals("One", groupTabs.getTabAt(2).getText().toString());
// Removing a group should also change the list
db.deleteGroup("Alphabetical two");
activityController.pause();
activityController.resume();
assertEquals(2, groupTabs.getTabCount());
assertEquals("All", groupTabs.getTabAt(0).getText().toString());
assertEquals("One", groupTabs.getTabAt(1).getText().toString());
// Removing the last group should make the tabs disappear
db.deleteGroup("One");
activityController.pause();
activityController.resume();
assertEquals(0, groupTabs.getTabCount());
}
@Test
public void testFiltering()
public void testFiltering() throws JSONException
{
ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create();
@@ -226,16 +128,10 @@ public class MainActivityTest
TextView helpText = mainActivity.findViewById(R.id.helpText);
TextView noMatchingCardsText = mainActivity.findViewById(R.id.noMatchingCardsText);
ListView list = mainActivity.findViewById(R.id.list);
TabLayout groupTabs = mainActivity.findViewById(R.id.groups);
DBHelper db = new DBHelper(mainActivity);
db.insertLoyaltyCard("The First Store", "Initial note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0);
db.insertLoyaltyCard("The Second Store", "Secondary note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, 0);
db.insertGroup("Group one");
List<Group> groups = new ArrayList<>();
groups.add(db.getGroup("Group one"));
db.setLoyaltyCardGroups(1, groups);
db.insertLoyaltyCard("The First Store", "Initial note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, null, new ExtrasHelper());
db.insertLoyaltyCard("The Second Store", "Secondary note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.pause();
activityController.resume();
@@ -257,27 +153,6 @@ public class MainActivityTest
assertEquals(2, list.getCount());
// Switch to Group one
groupTabs.selectTab(groupTabs.getTabAt(1));
activityController.pause();
activityController.resume();
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(1, list.getCount());
// Switch back to all groups
groupTabs.selectTab(groupTabs.getTabAt(0));
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(2, list.getCount());
mainActivity.filter = "first";
activityController.pause();
@@ -289,27 +164,6 @@ public class MainActivityTest
assertEquals(1, list.getCount());
// Switch to Group one
groupTabs.selectTab(groupTabs.getTabAt(1));
activityController.pause();
activityController.resume();
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(1, list.getCount());
// Switch back to all groups
groupTabs.selectTab(groupTabs.getTabAt(0));
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(1, list.getCount());
mainActivity.filter = "initial";
activityController.pause();
@@ -321,27 +175,6 @@ public class MainActivityTest
assertEquals(1, list.getCount());
// Switch to Group one
groupTabs.selectTab(groupTabs.getTabAt(1));
activityController.pause();
activityController.resume();
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(1, list.getCount());
// Switch back to all groups
groupTabs.selectTab(groupTabs.getTabAt(0));
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(1, list.getCount());
mainActivity.filter = "second";
activityController.pause();
@@ -353,27 +186,6 @@ public class MainActivityTest
assertEquals(1, list.getCount());
// Switch to Group one
groupTabs.selectTab(groupTabs.getTabAt(1));
activityController.pause();
activityController.resume();
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.VISIBLE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(0, list.getCount());
// Switch back to all groups
groupTabs.selectTab(groupTabs.getTabAt(0));
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(1, list.getCount());
mainActivity.filter = "company";
activityController.pause();
@@ -385,27 +197,6 @@ public class MainActivityTest
assertEquals(0, list.getCount());
// Switch to Group one
groupTabs.selectTab(groupTabs.getTabAt(1));
activityController.pause();
activityController.resume();
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.VISIBLE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(0, list.getCount());
// Switch back to all groups
groupTabs.selectTab(groupTabs.getTabAt(0));
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.VISIBLE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(0, list.getCount());
mainActivity.filter = "";
activityController.pause();
@@ -416,26 +207,5 @@ public class MainActivityTest
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(2, list.getCount());
// Switch to Group one
groupTabs.selectTab(groupTabs.getTabAt(1));
activityController.pause();
activityController.resume();
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(1, list.getCount());
// Switch back to all groups
groupTabs.selectTab(groupTabs.getTabAt(0));
assertEquals(View.GONE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(2, list.getCount());
}
}

View File

@@ -0,0 +1,384 @@
package protect.card_locker;
import android.app.Activity;
import android.graphics.Color;
import android.net.Uri;
import com.google.zxing.BarcodeFormat;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowContentResolver;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import static org.junit.Assert.assertEquals;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)
public class PkpassTest {
private PkpassImporter pkpassImporter;
@Before
public void setUp()
{
Activity activity = Robolectric.setupActivity(MainActivity.class);
pkpassImporter = new PkpassImporter(activity);
}
@Test
public void parseGenericExample() throws IOException, JSONException
{
// https://github.com/keefmoon/Passbook-Example-Code/blob/master/Pass-Example-Generic/Pass-Example-Generic.pkpass
InputStream inputStream = getClass().getResourceAsStream("Pass-Example-Generic.pkpass");
LoyaltyCard card = pkpassImporter.fromInputStream(inputStream);
// Compare everything
assertEquals(BarcodeFormat.QR_CODE.name(), card.barcodeType);
assertEquals("0000001", card.cardId);
assertEquals("Staff Pass for Employee Number 001", card.note);
assertEquals("Passbook Example Company", card.store);
assertEquals(String.valueOf(Color.rgb(90, 90, 90)), card.headerColor.toString());
assertEquals(String.valueOf(Color.rgb(255, 255, 255)), card.headerTextColor.toString());
// Check if all the extras got parsed correctly
ExtrasHelper extras = card.extras;
// Expecting no English entries
assertEquals(0, extras.getAllValues("en").keySet().size());
// Expecting 7 untranslated entries
assertEquals(7, extras.getAllValues("").keySet().size());
// Untranslated and English + untranslated fallback should return the same
assertEquals(extras.getAllValues(""), extras.getAllValues(new String[]{"en", ""}));
// Check all 7 values
Iterator<Map.Entry<String, String>> extrasKeys = extras.getAllValues("").entrySet().iterator();
// 1
Map.Entry<String, String> entry = extrasKeys.next();
assertEquals("staffNumber", entry.getKey());
assertEquals("Staff Number: 001", entry.getValue());
// 2
entry = extrasKeys.next();
assertEquals("staffName", entry.getKey());
assertEquals("Name: Peter Brooke", entry.getValue());
// 3
entry = extrasKeys.next();
assertEquals("telephoneExt", entry.getKey());
assertEquals("Extension: 9779", entry.getValue());
// 4
entry = extrasKeys.next();
assertEquals("jobTitle", entry.getKey());
assertEquals("Job Title: Chief Pass Creator", entry.getValue());
// 5
entry = extrasKeys.next();
assertEquals("expiryDate", entry.getKey());
assertEquals("Expiry Date: 2013-12-31T00:00-23:59", entry.getValue());
// 6
entry = extrasKeys.next();
assertEquals("managersName", entry.getKey());
assertEquals("Manager's Name: Paul Bailey", entry.getValue());
// 7
entry = extrasKeys.next();
assertEquals("managersExt", entry.getKey());
assertEquals("Manager's Extension: 9673", entry.getValue());
}
@Test
public void parseEurowingsTicket() throws IOException, JSONException
{
// https://github.com/brarcher/loyalty-card-locker/issues/309#issuecomment-563465333
InputStream inputStream = getClass().getResourceAsStream("Eurowings.pkpass");
LoyaltyCard card = pkpassImporter.fromInputStream(inputStream);
// Compare everything
assertEquals(BarcodeFormat.AZTEC.name(), card.barcodeType);
assertEquals("M1DOE/JOHN JBZPPP CGNDBVEW 0954 251A016E0073 148>5181W 9250BEW 00000000000002A0000000000000 0 N", card.cardId);
assertEquals("Eurowings Boarding Pass", card.note);
assertEquals("EUROWINGS", card.store);
// Violates the spec, but we want to support it anyway...
assertEquals(String.valueOf(Color.parseColor("#FFFFFF")), card.headerColor.toString());
assertEquals(String.valueOf(Color.parseColor("#AA0061")), card.headerTextColor.toString());
// Check if all the extras got parsed correctly
ExtrasHelper extras = card.extras;
// Expect 18 English values
assertEquals(18, extras.getAllValues("en").size());
// Expect 18 German values
assertEquals(18, extras.getAllValues("de").size());
// Expect 18 untranslated values
assertEquals(18, extras.getAllValues("").size());
// Expect no French values
assertEquals(0, extras.getAllValues("fr").size());
// Check all 18 English, German and untranslated values
Iterator<Map.Entry<String, String>> englishKeys = extras.getAllValues("en").entrySet().iterator();
Iterator<Map.Entry<String, String>> germanKeys = extras.getAllValues("de").entrySet().iterator();
Iterator<Map.Entry<String, String>> untranslatedKeys = extras.getAllValues("").entrySet().iterator();
// 1
Map.Entry<String, String> englishEntry = englishKeys.next();
Map.Entry<String, String> germanEntry = germanKeys.next();
Map.Entry<String, String> untranslatedEntry = untranslatedKeys.next();
assertEquals("gate", englishEntry.getKey());
assertEquals("gate", germanEntry.getKey());
assertEquals("gate", untranslatedEntry.getKey());
assertEquals("Gate: B61", englishEntry.getValue());
assertEquals("Gate: B61", germanEntry.getValue());
assertEquals("gate_str: B61", untranslatedEntry.getValue());
// 2
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("seat", englishEntry.getKey());
assertEquals("seat", germanEntry.getKey());
assertEquals("seat", untranslatedEntry.getKey());
assertEquals("Seat: 16E", englishEntry.getValue());
assertEquals("Sitz: 16E", germanEntry.getValue());
assertEquals("seat_str: 16E", untranslatedEntry.getValue());
// 3
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("origin", englishEntry.getKey());
assertEquals("origin", germanEntry.getKey());
assertEquals("origin", untranslatedEntry.getKey());
assertEquals("Cologne-Bonn: CGN", englishEntry.getValue());
assertEquals("Cologne-Bonn: CGN", germanEntry.getValue());
assertEquals("Cologne-Bonn: CGN", untranslatedEntry.getValue());
// 4
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("destination", englishEntry.getKey());
assertEquals("destination", germanEntry.getKey());
assertEquals("destination", untranslatedEntry.getKey());
assertEquals("Dubrovnik: DBV", englishEntry.getValue());
assertEquals("Dubrovnik: DBV", germanEntry.getValue());
assertEquals("Dubrovnik: DBV", untranslatedEntry.getValue());
// 5
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("name", englishEntry.getKey());
assertEquals("name", germanEntry.getKey());
assertEquals("name", untranslatedEntry.getKey());
assertEquals("Name: John Doe", englishEntry.getValue());
assertEquals("Name: John Doe", germanEntry.getValue());
assertEquals("name_str: John Doe", untranslatedEntry.getValue());
// 6
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("status", englishEntry.getKey());
assertEquals("status", germanEntry.getKey());
assertEquals("status", untranslatedEntry.getKey());
assertEquals("Status: -", englishEntry.getValue());
assertEquals("Status: -", germanEntry.getValue());
assertEquals("status_str: -", untranslatedEntry.getValue());
// 7
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("boardinggroup", englishEntry.getKey());
assertEquals("boardinggroup", germanEntry.getKey());
assertEquals("boardinggroup", untranslatedEntry.getKey());
assertEquals("Group: GROUP 1", englishEntry.getValue());
assertEquals("Gruppe: GROUP 1", germanEntry.getValue());
assertEquals("boardinggroup_str: GROUP 1", untranslatedEntry.getValue());
// 8
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("tarif", englishEntry.getKey());
assertEquals("tarif", germanEntry.getKey());
assertEquals("tarif", untranslatedEntry.getKey());
assertEquals("Fare: SMART", englishEntry.getValue());
assertEquals("Tarif: SMART", germanEntry.getValue());
assertEquals("fare_str: SMART", untranslatedEntry.getValue());
// 9
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("flightNumber", englishEntry.getKey());
assertEquals("flightNumber", germanEntry.getKey());
assertEquals("flightNumber", untranslatedEntry.getKey());
assertEquals("Flight: EW 954", englishEntry.getValue());
assertEquals("Flug: EW 954", germanEntry.getValue());
assertEquals("flight_str: EW 954", untranslatedEntry.getValue());
// 10
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("departureDate", englishEntry.getKey());
assertEquals("departureDate", germanEntry.getKey());
assertEquals("departureDate", untranslatedEntry.getKey());
assertEquals("Date: 08/09/2019", englishEntry.getValue());
assertEquals("Datum: 08/09/2019", germanEntry.getValue());
assertEquals("date_str: 08/09/2019", untranslatedEntry.getValue());
// 11
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("boarding", englishEntry.getKey());
assertEquals("boarding", germanEntry.getKey());
assertEquals("boarding", untranslatedEntry.getKey());
assertEquals("Boarding: 05:00", englishEntry.getValue());
assertEquals("Boarding: 05:00", germanEntry.getValue());
assertEquals("boarding_str: 05:00", untranslatedEntry.getValue());
// 12
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("closure", englishEntry.getKey());
assertEquals("closure", germanEntry.getKey());
assertEquals("closure", untranslatedEntry.getKey());
assertEquals("Gate closure: 05:15", englishEntry.getValue());
assertEquals("Gate Schließt: 05:15", germanEntry.getValue());
assertEquals("closure_str: 05:15", untranslatedEntry.getValue());
// 13
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("info", englishEntry.getKey());
assertEquals("info", germanEntry.getKey());
assertEquals("info", untranslatedEntry.getKey());
assertEquals("Eurowings wishes you a pleasant flight .\r\n" +
"\r\n" +
"We kindly ask you to be present at your departure gate on time.", englishEntry.getValue());
assertEquals("Eurowings wünscht Ihnen einen angenehmen Flug.\r\n" +
"\r\n" +
"Wir bitten Sie, sich zur angegeben Boarding Zeit am Gate einzufinden.", germanEntry.getValue());
assertEquals("info_content_str", untranslatedEntry.getValue());
// 14
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("recordlocator", englishEntry.getKey());
assertEquals("recordlocator", germanEntry.getKey());
assertEquals("recordlocator", untranslatedEntry.getKey());
assertEquals("Booking code: JBZPPP", englishEntry.getValue());
assertEquals("Buchungscode: JBZPPP", germanEntry.getValue());
assertEquals("recordlocator_str: JBZPPP", untranslatedEntry.getValue());
// 15
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("sequence", englishEntry.getKey());
assertEquals("sequence", germanEntry.getKey());
assertEquals("sequence", untranslatedEntry.getKey());
assertEquals("Sequence: 73", englishEntry.getValue());
assertEquals("Sequenz: 73", germanEntry.getValue());
assertEquals("sequence_str: 73", untranslatedEntry.getValue());
// 16
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("notice", englishEntry.getKey());
assertEquals("notice", germanEntry.getKey());
assertEquals("notice", untranslatedEntry.getKey());
assertEquals("Notice: Please note that although your flight may be delayed, you will still need to check in and go to your departure gate on time as scheduled.\r\n" +
"\r\n" +
"Carry on one item of hand luggage (8 kg, 55 x 40 x 23 cm) for free.", englishEntry.getValue());
assertEquals("Hinweis: Bitte beachten Sie, dass obwohl Ihr Flug verspätet sein mag, Sie dennoch wie geplant pünktlich am Check-in und am Abfluggate erscheinen müssen.\r\n" +
"\r\n" +
"Kostenlose Mitnahme eines Handgepäckstücks (8 Kg, 55 x 40 x 23cm).", germanEntry.getValue());
assertEquals("notice_str: notice_content_str", untranslatedEntry.getValue());
// 17
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("baggage", englishEntry.getKey());
assertEquals("baggage", germanEntry.getKey());
assertEquals("baggage", untranslatedEntry.getKey());
assertEquals("Carrying liquids in hand luggage: In addition to other restrictions on hand luggage, there are still restrictions on liquids and gels brought by the passenger or purchased before the security control on all departures within the European Union, as well as to many other countries (including Switzerland, Russia, Iceland, Croatia, Israel, Egypt, Morocco, Tunisia and Norway):\r\n" +
"\r\n" +
"- All liquids (such as toiletries and cosmetics, gels, pastes, creams, lotions, mixtures of liquids and solids, perfumes, pressurised containers, cans, water bottles etc) as well as wax and gel-like substances may only be carried on board in amounts less than 100ml or 100g.\r\n" +
"\r\n" +
"- These liquids or substances must be packed in closed containers in a transparent, re-sealable plastic bag (max. contents 1 kg).\r\n" +
"\r\n" +
"- It is the passengers responsibility to purchase this bag before departure. They are available in many supermarkets, e.g. as freezer bags. It is currently not possible for passengers to obtain or purchase the required bags from Eurowings check-in.\r\n" +
"\r\n" +
"- Prescription medicines and baby food may still be carried in hand baggage. The passenger must prove that such medicines and/or baby food are needed during the flight.\r\n" +
"\r\n" +
"- Products and bags which do not meet the requirements or are only sealed with a rubber band or similar will unfortunately have to be surrendered by passengers\r\n" +
"\r\n" +
"In order to pass through the airport as quickly as possible, you are strongly advised to pack any liquids or gels which are not essential for your journey on board the aircraft in your checked baggage if possible.\r\n" +
"\r\n" +
"As a matter of course, liquids from the Travel Value / Duty Free shops which have been purchased after you have passed through security are still allowed on board.\r\n" +
"\r\n" +
"Eurowings shall not be liable for any items which passengers are prohibited from carrying in their hand baggage for security reasons and are required to surrender at the security checkpoint.", englishEntry.getValue());
assertEquals("Mitnahme von Flüssigkeiten im Handgepäck: Neben den sonstigen Beschränkungen für das Handgepäck ist für alle Abflüge innerhalb der Europäischen Union sowie vielen weiteren Ländern (u.a. Schweiz, Russland, Island, Kroatien, Israel, Ägypten, Marokko, Tunesien, Norwegen) die Mitnahme von vor der Fluggastkontrolle erworbenen bzw. mitgebrachten Flüssigkeiten und Gels nur noch eingeschränkt erlaubt:\r\n" +
"\r\n" +
"- Sämtliche Flüssigkeiten (wie Kosmetik- und Toilettenartikel, Gels, Pasten, Cremes, Lotionen, Gemische aus flüssigen und festen Stoffen, Parfums, Behälter unter Druck, Dosen, Wasserflaschen etc.) sowie wachs- oder gelartige Stoffe dürfen nur noch in Behältnissen bis zu 100 ml bzw. 100 g mit an Bord genommen werden.\r\n" +
"\r\n" +
"- Diese Flüssigkeiten bzw. Stoffe müssen in einem transparenten, wiederverschließbaren Plastikbeutel (max. 1 kg Inhalt) vollständig geschlossen, verpackt sein.\r\n" +
"\r\n" +
"- Diese Beutel müssen Fluggäste selbst vor dem Abflug erwerben. Sie sind in vielen Supermärkten z. B. als Gefrierbeutel erhältlich. Es besteht zurzeit keine Möglichkeit, entsprechende Plastikbeutel am Eurowings Check-In zu erwerben bzw. auszugeben.\r\n" +
"\r\n" +
"- Verschreibungspflichtige Medikamente sowie Babynahrung dürfen weiterhin im Handgepäck transportiert werden. Der Fluggast muss nachweisen, dass die Medikamente und Babynahrung während des Fluges benötigt werden.\r\n" +
"\r\n" +
"- Produkte und Beutel, die nicht den Maßgaben entsprechen oder die nur mit Gummiband oder ähnlichem verschlossen sind, müssen leider abgegeben werden.\r\n" +
"\r\n" +
"Flüssigkeiten und Gels, die Sie nicht zwingend während Ihres Aufenthalts an Bord benötigen, sollten zur raschen Fluggastabfertigung nach Möglichkeit im aufzugebenden Gepäck untergebracht werden.\r\n" +
"\r\n" +
"Selbstverständlich ist die Mitnahme von allen Flüssigkeiten/Gels/Getränken aus Travel-Value oder Duty Free-Shops, die nach der Fluggastkontrolle erworben werden, weiterhin erlaubt.\r\n" +
"\r\n" +
"Eurowings übernimmt keine Haftung für Gegenstände, die der Fluggast nicht im Handgepäck mitführen darf und deshalb aus Sicherheitsgründen an der Fluggastkontrolle abgeben muss.", germanEntry.getValue());
assertEquals("baggage_str: baggage_content_str", untranslatedEntry.getValue());
// 18
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("contact", englishEntry.getKey());
assertEquals("contact", germanEntry.getKey());
assertEquals("contact", untranslatedEntry.getKey());
assertEquals("Contact: https://mobile.eurowings.com/booking/StaticContactInfo.aspx?culture=en-GB&back=home", englishEntry.getValue());
assertEquals("Kontakt: Sie erreichen das deutsche Call Center unter der Telefonnummer\r\n" +
"\r\n" +
"0180 6 320 320 ( 0:00 Uhr - 24:00 Uhr )\r\n" +
"\r\n" +
"(0,20 € pro Anruf aus dem Festnetz der Deutschen Telekom - Mobilfunk maximal 0,60 € pro Anruf).", germanEntry.getValue());
assertEquals("contact_str: contact_content_str", untranslatedEntry.getValue());
}
}

View File

Binary file not shown.

View File

Binary file not shown.

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