Compare commits

..

2 Commits

Author SHA1 Message Date
Sylvia van Os
a434397551 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into fix/deprecatedProgressDialog 2021-12-18 14:48:13 +01:00
Sylvia van Os
ae1ccab059 Replace ProgressDialog with notification 2021-11-28 21:48:48 +01:00
704 changed files with 2886 additions and 7398 deletions

View File

@@ -17,8 +17,7 @@ jobs:
days-before-close: 90 days-before-close: 90
close-issue-message: 'This issue is missing necessary information and cannot be worked on in its current state. It has therefore been closed to keep the issue tracker clean. If you have more information, feel free to reopen it.' close-issue-message: 'This issue is missing necessary information and cannot be worked on in its current state. It has therefore been closed to keep the issue tracker clean. If you have more information, feel free to reopen it.'
close-pr-message: 'This PR is missing necessary information and cannot be merged in its current state. It has therefore been closed to keep the issue tracker clean. If you have more information, feel free to reopen it.' close-pr-message: 'This PR is missing necessary information and cannot be merged in its current state. It has therefore been closed to keep the issue tracker clean. If you have more information, feel free to reopen it.'
only-labels: 'state: needs info' only-labels: 'needs info'
stale-issue-label: 'state: needs info' stale-issue-label: 'needs info'
stale-pr-label: 'state: needs info' stale-pr-label: 'needs info'
remove-stale-when-updated: false remove-stale-when-updated: false
enable-statistics: true

View File

@@ -1,105 +1,8 @@
# Changelog # Changelog
## Unreleased - 115 ## Unreleased - 96
- Open image in gallery on long-press
## v2.20.0 - 114
- Add Monochrome icon for Android 13
- Improve first launch screen
- Fidme import fixes
## v2.19.0 - 113
- Add previous and next buttons to the loyalty card view
- Fix foreground colour on edit button
- Replace floppy disk save icon with checkmark
## v2.18.2 - 112
- Make the possibility to set a custom header more visible
## v2.18.1 - 111
- Arabic language support
- Display archived card count in group overview
- Fix balance parsing bugs (made cards not savable in Arabic and other language with non-Western numbers)
- Fix custom theme not applying to main screen correctly
- Improve display of selected cards
- Fix crash when leaving cardview in RTL layouts for cards with expiry or balance
- Fix back arrow in card view pointing the wrong way in RTL layouts
## v2.17.1 - 109
- Fix incorrect text colour on "No barcode" button
## v2.17.0 - 108
- Add card duplication feature
- Don't allow choosing expiry before 1970 (they never worked anyway)
- Add support for archiving cards
- Move delete from edit to view
- Remove rotation lock icon in favour of a new rotation lock setting
## v2.16.3 - 107 (2022-04-15)
- Stocard import fixes
## v2.16.2 - 106 (2022-03-31)
- Fix some character sequences being shown as a single character
## v2.16.1 - 105 (2022-03-25)
- Fix gray block appearing on invalid value for barcode
- Stocard import fixes
## v2.16.0 - 104 (2022-03-09)
- Save card detail expansion state
- Minor UI fixes
## v2.15.2 - 103 (2022-02-11)
- Fix manual language selection not applying everywhere
- Fix crash in edit view on regionless locale
## v2.15.1 - 102 (2022-02-10)
- Various minor fixes
- Fix crash when using Norwegian translation
## v2.15.0 - 101 (2022-02-06)
- Fix cropper not using theme colour
- Fix minor theming issues
- Add pure black dark theme for OLED screens
## v2.14.1 - 100 (2022-01-15)
- Hide search, expand and sort icons until there is at least 1 card
- Various theming fixes
## v2.14.0 - 99 (2022-01-14)
- Material You redesign
## v2.13.1 - 98 (2022-01-09)
- Fix various TalkBack-related bugs
## v2.13.0 - 97 (2022-01-03)
- Fixed pressing the save button multiple times creating multiple entries
- Lower card header size when hiding details to fit even more cards
- Restructure edit screen
- Improve star icon contrast in main view
## v2.12.0 - 96 (2021-12-23)
- Add CODE 93 support - Add CODE 93 support
- Various minor bugfixes and improvements
## v2.11.2 - 95 (2021-12-04) ## v2.11.2 - 95 (2021-12-04)

View File

@@ -8,20 +8,7 @@ to the rules described here, but by following the instructions below you
should have a much easier time getting your work merged with the upstream should have a much easier time getting your work merged with the upstream
project. project.
## Translation Changes ## Test Your Code
Translation changes are managed through [Weblate](https://hosted.weblate.org/projects/catima/).
Please do not supply translation updates directly through GitHub.
Weblate requires an account to translate changes, so please log in before
you start translating.
While using Weblate, please do not ignore any of its warnings. They exist
for good reason.
## Code Changes
### Test Your Code
There are four possible tests you can run to verify your code. The first There are four possible tests you can run to verify your code. The first
is unit tests, which check the basic functionality of the application, and is unit tests, which check the basic functionality of the application, and
@@ -41,14 +28,14 @@ and SpotBugs, run using:
The final check is by testing the application on a live device and verifying The final check is by testing the application on a live device and verifying
the basic functionality works as expected. the basic functionality works as expected.
### Make Sure Your Code is Tested ## Make Sure Your Code is Tested
The Catima code uses a fair number of unit tests to verify that The Catima code uses a fair number of unit tests to verify that
the basic functionality is working. Submissions which add functionality the basic functionality is working. Submissions which add functionality
or significantly change the existing code should include additional tests or significantly change the existing code should include additional tests
to verify the proper operation of the proposed changes. to verify the proper operation of the proposed changes.
### Explain Your Work ## Explain Your Work
At the top of every patch you should include a description of the problem you At the top of every patch you should include a description of the problem you
are trying to solve, how you solved it, and why you chose the solution you are trying to solve, how you solved it, and why you chose the solution you
@@ -57,7 +44,7 @@ if you can describe/include a reproducer for the problem in the description as
well as instructions on how to test for the bug and verify that it has been well as instructions on how to test for the bug and verify that it has been
fixed. fixed.
### Sign Your Work ## Sign Your Work
The sign-off is a simple line at the end of the patch description, which The sign-off is a simple line at the end of the patch description, which
certifies that you wrote it or otherwise have the right to pass it on as an certifies that you wrote it or otherwise have the right to pass it on as an
@@ -95,7 +82,7 @@ your real name, saying:
Signed-off-by: Random J Developer <random@developer.example.org> Signed-off-by: Random J Developer <random@developer.example.org>
### Submit Patch(es) for Review ## Submit Patch(es) for Review
Finally, you will need to submit your patches so that they can be reviewed Finally, you will need to submit your patches so that they can be reviewed
and potentially merged into the main Catima repository. The preferred and potentially merged into the main Catima repository. The preferred

View File

@@ -1,3 +0,0 @@
github: TheLastProject
custom:
- "https://paypal.me/sylviavanos"

View File

@@ -1,30 +1,29 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
CFPropertyList (3.0.5) CFPropertyList (3.0.3)
rexml
addressable (2.8.0) addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15) artifactory (3.0.15)
atomos (0.1.3) atomos (0.1.3)
aws-eventstream (1.2.0) aws-eventstream (1.2.0)
aws-partitions (1.597.0) aws-partitions (1.501.0)
aws-sdk-core (3.131.1) aws-sdk-core (3.121.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0) aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
jmespath (~> 1, >= 1.6.1) jmespath (~> 1.0)
aws-sdk-kms (1.57.0) aws-sdk-kms (1.48.0)
aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-core (~> 3, >= 3.120.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.114.0) aws-sdk-s3 (1.102.0)
aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-core (~> 3, >= 3.120.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4) aws-sigv4 (~> 1.4)
aws-sigv4 (1.5.0) aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4) babosa (1.0.4)
claide (1.1.0) claide (1.0.3)
colored (1.2) colored (1.2)
colored2 (3.1.2) colored2 (3.1.2)
commander (4.6.0) commander (4.6.0)
@@ -35,19 +34,18 @@ GEM
domain_name (0.5.20190701) domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6) dotenv (2.7.6)
emoji_regex (3.2.3) emoji_regex (3.2.2)
excon (0.92.3) excon (0.85.0)
faraday (1.10.0) faraday (1.7.2)
faraday-em_http (~> 1.0) faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0) faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1) faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0) faraday-httpclient (~> 1.0.1)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0) faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0) faraday-net_http_persistent (~> 1.1)
faraday-patron (~> 1.0) faraday-patron (~> 1.0)
faraday-rack (~> 1.0) faraday-rack (~> 1.0)
faraday-retry (~> 1.0) multipart-post (>= 1.2, < 3)
ruby2_keywords (>= 0.0.4) ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7) faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0) faraday (>= 0.8.0)
@@ -56,17 +54,14 @@ GEM
faraday-em_synchrony (1.0.0) faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0) faraday-excon (1.1.0)
faraday-httpclient (1.0.1) faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1) faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0) faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0) faraday-patron (1.0.0)
faraday-rack (1.0.0) faraday-rack (1.0.0)
faraday-retry (1.0.3) faraday_middleware (1.1.0)
faraday_middleware (1.2.0)
faraday (~> 1.0) faraday (~> 1.0)
fastimage (2.2.6) fastimage (2.2.5)
fastlane (2.206.2) fastlane (2.193.1)
CFPropertyList (>= 2.3, < 4.0.0) CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0) addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0) artifactory (~> 3.0)
@@ -106,9 +101,9 @@ GEM
xcpretty (~> 0.3.0) xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3) xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3) gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.21.0) google-apis-androidpublisher_v3 (0.11.0)
google-apis-core (>= 0.4, < 2.a) google-apis-core (>= 0.4, < 2.a)
google-apis-core (0.5.0) google-apis-core (0.4.1)
addressable (~> 2.5, >= 2.5.1) addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a) googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a) httpclient (>= 2.8.1, < 3.a)
@@ -117,53 +112,53 @@ GEM
retriable (>= 2.0, < 4.a) retriable (>= 2.0, < 4.a)
rexml rexml
webrick webrick
google-apis-iamcredentials_v1 (0.10.0) google-apis-iamcredentials_v1 (0.7.0)
google-apis-core (>= 0.4, < 2.a) google-apis-core (>= 0.4, < 2.a)
google-apis-playcustomapp_v1 (0.7.0) google-apis-playcustomapp_v1 (0.5.0)
google-apis-core (>= 0.4, < 2.a) google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.14.0) google-apis-storage_v1 (0.6.0)
google-apis-core (>= 0.4, < 2.a) google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0) google-cloud-core (1.6.0)
google-cloud-env (~> 1.0) google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0) google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0) google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 3.0) faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.2.0) google-cloud-errors (1.1.0)
google-cloud-storage (1.36.2) google-cloud-storage (1.34.1)
addressable (~> 2.8) addressable (~> 2.5)
digest-crc (~> 0.4) digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1) google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1) google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.6) google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a) googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0) mini_mime (~> 1.0)
googleauth (1.1.3) googleauth (0.17.1)
faraday (>= 0.17.3, < 3.a) faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0) jwt (>= 1.4, < 3.0)
memoist (~> 0.16) memoist (~> 0.16)
multi_json (~> 1.11) multi_json (~> 1.11)
os (>= 0.9, < 2.0) os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a) signet (~> 0.15)
highline (2.0.3) highline (2.0.3)
http-cookie (1.0.5) http-cookie (1.0.4)
domain_name (~> 0.5) domain_name (~> 0.5)
httpclient (2.8.3) httpclient (2.8.3)
jmespath (1.6.1) jmespath (1.4.0)
json (2.6.2) json (2.5.1)
jwt (2.4.1) jwt (2.2.3)
memoist (0.16.2) memoist (0.16.2)
mini_magick (4.11.0) mini_magick (4.11.0)
mini_mime (1.1.2) mini_mime (1.1.1)
multi_json (1.15.0) multi_json (1.15.0)
multipart-post (2.0.0) multipart-post (2.0.0)
nanaimo (0.3.0) nanaimo (0.3.0)
naturally (2.2.1) naturally (2.2.1)
optparse (0.1.1) optparse (0.1.1)
os (1.1.4) os (1.1.1)
plist (3.6.0) plist (3.6.0)
public_suffix (4.0.7) public_suffix (4.0.6)
rake (13.0.6) rake (13.0.6)
representable (3.2.0) representable (3.1.1)
declarative (< 0.1.0) declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0) trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0) uber (< 0.2.0)
@@ -173,9 +168,9 @@ GEM
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
rubyzip (2.3.2) rubyzip (2.3.2)
security (0.1.3) security (0.1.3)
signet (0.16.1) signet (0.16.0)
addressable (~> 2.8) addressable (~> 2.8)
faraday (>= 0.17.5, < 3.0) faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0) jwt (>= 1.5, < 3.0)
multi_json (~> 1.10) multi_json (~> 1.10)
simctl (1.6.8) simctl (1.6.8)
@@ -184,7 +179,7 @@ GEM
terminal-notifier (2.0.0) terminal-notifier (2.0.0)
terminal-table (1.8.0) terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (~> 1.1, >= 1.1.1)
trailblazer-option (0.1.2) trailblazer-option (0.1.1)
tty-cursor (0.7.1) tty-cursor (0.7.1)
tty-screen (0.8.1) tty-screen (0.8.1)
tty-spinner (0.9.3) tty-spinner (0.9.3)
@@ -192,8 +187,8 @@ GEM
uber (0.1.0) uber (0.1.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.8.2) unf_ext (0.0.8)
unicode-display_width (1.8.0) unicode-display_width (1.7.0)
webrick (1.7.0) webrick (1.7.0)
word_wrap (1.0.0) word_wrap (1.0.0)
xcodeproj (1.21.0) xcodeproj (1.21.0)

View File

@@ -18,8 +18,8 @@ android {
applicationId "me.hackerchick.catima" applicationId "me.hackerchick.catima"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 31
versionCode 114 versionCode 95
versionName "2.20.0" versionName "2.11.2"
vectorDrawables.useSupportLibrary true vectorDrawables.useSupportLibrary true
multiDexEnabled true multiDexEnabled true
@@ -80,25 +80,25 @@ android {
dependencies { dependencies {
// AndroidX // AndroidX
implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.exifinterface:exifinterface:1.3.3' implementation 'androidx.exifinterface:exifinterface:1.3.3'
implementation 'androidx.palette:palette:1.0.0' implementation 'androidx.palette:palette:1.0.0'
implementation 'androidx.preference:preference:1.2.0' implementation 'androidx.preference:preference:1.1.1'
implementation 'com.google.android.material:material:1.6.1' implementation 'com.google.android.material:material:1.4.0'
implementation 'com.github.yalantis:ucrop:2.2.8' implementation 'com.github.yalantis:ucrop:2.2.7'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
// Splash Screen // Splash Screen
implementation 'androidx.core:core-splashscreen:1.0.0' implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
// Third-party // Third-party
implementation 'com.journeyapps:zxing-android-embedded:4.3.0@aar' implementation 'com.journeyapps:zxing-android-embedded:4.3.0@aar'
implementation 'com.google.zxing:core:3.5.0' implementation 'com.google.zxing:core:3.4.1'
implementation 'org.apache.commons:commons-csv:1.9.0' implementation 'org.apache.commons:commons-csv:1.9.0'
implementation 'com.jaredrummler:colorpicker:1.1.0' implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.4' implementation 'com.github.invissvenska:NumberPickerPreference:1.0.4'
implementation 'net.lingala.zip4j:zip4j:2.11.2' implementation 'net.lingala.zip4j:zip4j:2.9.1'
// SpotBugs // SpotBugs
implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0' implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0'
@@ -106,7 +106,7 @@ dependencies {
// Testing // Testing
testImplementation 'androidx.test:core:1.4.0' testImplementation 'androidx.test:core:1.4.0'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.9' testImplementation 'org.robolectric:robolectric:4.7.3'
} }
tasks.withType(SpotBugsTask) { tasks.withType(SpotBugsTask) {

View File

@@ -116,7 +116,7 @@
</activity> </activity>
<activity <activity
android:name=".UCropWrapper" android:name="com.yalantis.ucrop.UCropActivity"
android:theme="@style/AppTheme.NoActionBar" /> android:theme="@style/AppTheme.NoActionBar" />
<provider <provider
@@ -134,5 +134,7 @@
<action android:name="android.service.controls.ControlsProviderService" /> <action android:name="android.service.controls.ControlsProviderService" />
</intent-filter> </intent-filter>
</service> </service>
</application> </application>
</manifest> </manifest>

View File

@@ -1,6 +1,5 @@
package protect.card_locker; package protect.card_locker;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@@ -10,7 +9,6 @@ import android.util.Log;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
@@ -65,7 +63,6 @@ public class AboutActivity extends CatimaAppCompatActivity implements View.OnCli
USED_LIBRARIES.add(new ThirdPartyInfo("Color Picker", "https://github.com/jaredrummler/ColorPicker", "Apache 2.0")); USED_LIBRARIES.add(new ThirdPartyInfo("Color Picker", "https://github.com/jaredrummler/ColorPicker", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("Commons CSV", "https://commons.apache.org/proper/commons-csv/", "Apache 2.0")); USED_LIBRARIES.add(new ThirdPartyInfo("Commons CSV", "https://commons.apache.org/proper/commons-csv/", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference", "GNU LGPL 3.0")); USED_LIBRARIES.add(new ThirdPartyInfo("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference", "GNU LGPL 3.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("uCrop", "https://github.com/Yalantis/uCrop", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("Zip4j", "https://github.com/srikanth-lingala/zip4j", "Apache 2.0")); USED_LIBRARIES.add(new ThirdPartyInfo("Zip4j", "https://github.com/srikanth-lingala/zip4j", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("ZXing", "https://github.com/zxing/zxing", "Apache 2.0")); USED_LIBRARIES.add(new ThirdPartyInfo("ZXing", "https://github.com/zxing/zxing", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded", "Apache 2.0")); USED_LIBRARIES.add(new ThirdPartyInfo("ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded", "Apache 2.0"));
@@ -154,13 +151,13 @@ public class AboutActivity extends CatimaAppCompatActivity implements View.OnCli
} else if (id == R.id.translate) { } else if (id == R.id.translate) {
url = "https://hosted.weblate.org/engage/catima/"; url = "https://hosted.weblate.org/engage/catima/";
} else if (id == R.id.license) { } else if (id == R.id.license) {
url = "https://github.com/CatimaLoyalty/Android/blob/master/LICENSE"; url = "https://github.com/TheLastProject/Catima/blob/master/LICENSE";
} else if (id == R.id.repo) { } else if (id == R.id.repo) {
url = "https://github.com/CatimaLoyalty/Android/"; url = "https://github.com/TheLastProject/Catima/";
} else if (id == R.id.privacy) { } else if (id == R.id.privacy) {
url = "https://catima.app/privacy-policy/"; url = "https://catima.app/privacy-policy/";
} else if (id == R.id.report_error) { } else if (id == R.id.report_error) {
url = "https://github.com/CatimaLoyalty/Android/issues"; url = "https://github.com/TheLastProject/Catima/issues";
} else if (id == R.id.rate) { } else if (id == R.id.rate) {
url = "https://play.google.com/store/apps/details?id=me.hackerchick.catima"; url = "https://play.google.com/store/apps/details?id=me.hackerchick.catima";
} else { } else {
@@ -169,12 +166,7 @@ public class AboutActivity extends CatimaAppCompatActivity implements View.OnCli
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url)); intent.setData(Uri.parse(url));
try { startActivity(intent);
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.failedToOpenUrl, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
} }
} }

View File

@@ -5,7 +5,6 @@ import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.util.Log; import android.util.Log;
import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
@@ -17,7 +16,6 @@ import com.google.zxing.common.BitMatrix;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import protect.card_locker.async.CompatCallable; import protect.card_locker.async.CompatCallable;
import protect.card_locker.barcodes.Barcode;
/** /**
* This task will generate a barcode and load it into an ImageView. * This task will generate a barcode and load it into an ImageView.
@@ -39,7 +37,7 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
private final WeakReference<ImageView> imageViewReference; private final WeakReference<ImageView> imageViewReference;
private final WeakReference<TextView> textViewReference; private final WeakReference<TextView> textViewReference;
private String cardId; private String cardId;
private final Barcode format; private final CatimaBarcode format;
private final int imageHeight; private final int imageHeight;
private final int imageWidth; private final int imageWidth;
private final boolean showFallback; private final boolean showFallback;
@@ -47,8 +45,8 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
BarcodeImageWriterTask( BarcodeImageWriterTask(
Context context, ImageView imageView, String cardIdString, Context context, ImageView imageView, String cardIdString,
Barcode barcodeFormat, TextView textView, CatimaBarcode barcodeFormat, TextView textView,
boolean showFallback, Runnable callback, boolean roundCornerPadding boolean showFallback, Runnable callback
) { ) {
mContext = context; mContext = context;
@@ -62,37 +60,83 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
cardId = cardIdString; cardId = cardIdString;
format = barcodeFormat; format = barcodeFormat;
int padding = 0;
// Some barcodes already have internal whitespace and shouldn't get extra padding
// TODO: Get rid of this hack by somehow detecting this extra whitespace
if (roundCornerPadding && !barcodeFormat.hasInternalPadding()) {
padding = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, context.getResources().getDisplayMetrics()));
}
final int MAX_WIDTH = getMaxWidth(format); final int MAX_WIDTH = getMaxWidth(format);
int tempImageHeight;
int tempImageWidth;
if (imageView.getWidth() < MAX_WIDTH) { if (imageView.getWidth() < MAX_WIDTH) {
tempImageHeight = imageView.getHeight(); imageHeight = imageView.getHeight();
tempImageWidth = imageView.getWidth(); imageWidth = imageView.getWidth();
} else { } else {
// Scale down the image to reduce the memory needed to produce it // Scale down the image to reduce the memory needed to produce it
tempImageWidth = MAX_WIDTH; imageWidth = MAX_WIDTH;
double ratio = (double) MAX_WIDTH / (double) imageView.getWidth(); double ratio = (double) MAX_WIDTH / (double) imageView.getWidth();
tempImageHeight = (int) (imageView.getHeight() * ratio); imageHeight = (int) (imageView.getHeight() * ratio);
} }
// Ensure space for padding if wanted
imageWidth = tempImageWidth;
imageHeight = tempImageHeight - padding;
this.showFallback = showFallback; this.showFallback = showFallback;
} }
private int getMaxWidth(Barcode format) { private int getMaxWidth(CatimaBarcode format) {
return format.is2D() ? MAX_WIDTH_2D : MAX_WIDTH_1D; switch (format.format()) {
// 2D barcodes
case AZTEC:
case DATA_MATRIX:
case MAXICODE:
case PDF_417:
case QR_CODE:
return MAX_WIDTH_2D;
// 1D barcodes:
case CODABAR:
case CODE_39:
case CODE_93:
case CODE_128:
case EAN_8:
case EAN_13:
case ITF:
case UPC_A:
case UPC_E:
case RSS_14:
case RSS_EXPANDED:
case UPC_EAN_EXTENSION:
default:
return MAX_WIDTH_1D;
}
}
private String getFallbackString(CatimaBarcode format) {
switch (format.format()) {
// 2D barcodes
case AZTEC:
return "AZTEC";
case DATA_MATRIX:
return "DATA_MATRIX";
case PDF_417:
return "PDF_417";
case QR_CODE:
return "QR_CODE";
// 1D barcodes:
case CODABAR:
return "C0C";
case CODE_39:
return "CODE_39";
case CODE_93:
return "CODE_93";
case CODE_128:
return "CODE_128";
case EAN_8:
return "32123456";
case EAN_13:
return "5901234123457";
case ITF:
return "1003";
case UPC_A:
return "123456789012";
case UPC_E:
return "0123456";
default:
throw new IllegalArgumentException("No fallback known for this barcode type");
}
} }
private Bitmap generate() { private Bitmap generate() {
@@ -168,7 +212,7 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
if (showFallback && !Thread.currentThread().isInterrupted()) { if (showFallback && !Thread.currentThread().isInterrupted()) {
Log.i(TAG, "Barcode generation failed, generating fallback..."); Log.i(TAG, "Barcode generation failed, generating fallback...");
cardId = format.exampleValue(); cardId = getFallbackString(format);
bitmap = generate(); bitmap = generate();
return bitmap; return bitmap;
} }

View File

@@ -18,9 +18,6 @@ import java.util.ArrayList;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
import protect.card_locker.barcodes.BarcodeWithValue;
/** /**
* This activity is callable and will allow a user to enter * This activity is callable and will allow a user to enter
@@ -89,10 +86,10 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements
private void generateBarcodes(String value) { private void generateBarcodes(String value) {
// Update barcodes // Update barcodes
ArrayList<BarcodeWithValue> barcodes = new ArrayList<>(); ArrayList<CatimaBarcodeWithValue> barcodes = new ArrayList<>();
for (BarcodeFormat barcodeFormat : BarcodeFactory.getAllFormats()) { for (BarcodeFormat barcodeFormat : CatimaBarcode.barcodeFormats) {
Barcode catimaBarcode = BarcodeFactory.fromBarcode(barcodeFormat); CatimaBarcode catimaBarcode = CatimaBarcode.fromBarcode(barcodeFormat);
barcodes.add(new BarcodeWithValue(catimaBarcode, value)); barcodes.add(new CatimaBarcodeWithValue(catimaBarcode, value));
} }
mAdapter.setBarcodes(barcodes); mAdapter.setBarcodes(barcodes);
} }
@@ -121,7 +118,7 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements
@Override @Override
public void onRowClicked(int inputPosition, View view) { public void onRowClicked(int inputPosition, View view) {
BarcodeWithValue barcodeWithValue = mAdapter.getItem(inputPosition); CatimaBarcodeWithValue barcodeWithValue = mAdapter.getItem(inputPosition);
CatimaBarcode catimaBarcode = barcodeWithValue.catimaBarcode(); CatimaBarcode catimaBarcode = barcodeWithValue.catimaBarcode();
if (!mAdapter.isValid(view)) { if (!mAdapter.isValid(view)) {

View File

@@ -13,11 +13,8 @@ import android.widget.TextView;
import java.util.ArrayList; import java.util.ArrayList;
import protect.card_locker.async.TaskHandler; import protect.card_locker.async.TaskHandler;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
import protect.card_locker.barcodes.BarcodeWithValue;
public class BarcodeSelectorAdapter extends ArrayAdapter<BarcodeWithValue> { public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue> {
private static final String TAG = "Catima"; private static final String TAG = "Catima";
private final TaskHandler mTasks = new TaskHandler(); private final TaskHandler mTasks = new TaskHandler();
@@ -32,12 +29,12 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<BarcodeWithValue> {
void onRowClicked(int inputPosition, View view); void onRowClicked(int inputPosition, View view);
} }
public BarcodeSelectorAdapter(Context context, ArrayList<BarcodeWithValue> barcodes, BarcodeSelectorListener barcodeSelectorListener) { public BarcodeSelectorAdapter(Context context, ArrayList<CatimaBarcodeWithValue> barcodes, BarcodeSelectorListener barcodeSelectorListener) {
super(context, 0, barcodes); super(context, 0, barcodes);
mListener = barcodeSelectorListener; mListener = barcodeSelectorListener;
} }
public void setBarcodes(ArrayList<BarcodeWithValue> barcodes) { public void setBarcodes(ArrayList<CatimaBarcodeWithValue> barcodes) {
clear(); clear();
addAll(barcodes); addAll(barcodes);
notifyDataSetChanged(); notifyDataSetChanged();
@@ -46,9 +43,9 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<BarcodeWithValue> {
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
BarcodeWithValue barcodeWithValue = getItem(position); CatimaBarcodeWithValue catimaBarcodeWithValue = getItem(position);
Barcode catimaBarcode = barcodeWithValue.barcode(); CatimaBarcode catimaBarcode = catimaBarcodeWithValue.catimaBarcode();
String value = barcodeWithValue.value(); String value = catimaBarcodeWithValue.value();
ViewHolder viewHolder; ViewHolder viewHolder;
if (convertView == null) { if (convertView == null) {
@@ -76,10 +73,9 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<BarcodeWithValue> {
} }
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text) { private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text) {
final Barcode format = BarcodeFactory.fromName(formatType); final CatimaBarcode format = CatimaBarcode.fromName(formatType);
image.setImageBitmap(null); image.setImageBitmap(null);
image.setClipToOutline(true);
if (image.getHeight() == 0) { if (image.getHeight() == 0) {
// The size of the ImageView is not yet available as it has not // The size of the ImageView is not yet available as it has not
@@ -93,13 +89,13 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<BarcodeWithValue> {
Log.d(TAG, "Generating barcode for type " + formatType); Log.d(TAG, "Generating barcode for type " + formatType);
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null, true); BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter); mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
} }
}); });
} else { } else {
Log.d(TAG, "Generating barcode for type " + formatType); Log.d(TAG, "Generating barcode for type " + formatType);
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null, true); BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter); mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
} }
} }

View File

@@ -4,10 +4,9 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat; import androidx.core.content.pm.ShortcutManagerCompat;
@@ -17,10 +16,9 @@ import androidx.recyclerview.widget.RecyclerView;
/** /**
* The configuration screen for creating a shortcut. * The configuration screen for creating a shortcut.
*/ */
public class CardShortcutConfigure extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener { public class CardShortcutConfigure extends AppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
static final String TAG = "Catima"; static final String TAG = "Catima";
private SQLiteDatabase mDatabase; private SQLiteDatabase mDatabase;
private LoyaltyCardCursorAdapter mAdapter;
@Override @Override
public void onCreate(Bundle bundle) { public void onCreate(Bundle bundle) {
@@ -35,34 +33,26 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
setContentView(R.layout.simple_toolbar_list_activity); setContentView(R.layout.simple_toolbar_list_activity);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(R.string.shortcutSelectCard); toolbar.setTitle(R.string.shortcutSelectCard);
setSupportActionBar(toolbar);
// If there are no cards, bail // If there are no cards, bail
int cardCount = DBHelper.getLoyaltyCardCount(mDatabase); if (DBHelper.getLoyaltyCardCount(mDatabase) == 0) {
if (cardCount == 0) {
Toast.makeText(this, R.string.noCardsMessage, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.noCardsMessage, Toast.LENGTH_LONG).show();
finish(); finish();
} }
// If all cards are archived, bail
if (DBHelper.getArchivedCardsCount(mDatabase) == cardCount) {
Toast.makeText(this, R.string.noUnarchivedCardsMessage, Toast.LENGTH_LONG).show();
finish();
}
final RecyclerView cardList = findViewById(R.id.list); final RecyclerView cardList = findViewById(R.id.list);
GridLayoutManager layoutManager = (GridLayoutManager) cardList.getLayoutManager(); GridLayoutManager layoutManager = (GridLayoutManager) cardList.getLayoutManager();
if (layoutManager != null) { if (layoutManager != null) {
layoutManager.setSpanCount(getResources().getInteger(R.integer.main_view_card_columns)); layoutManager.setSpanCount(getResources().getInteger(R.integer.main_view_card_columns));
} }
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.Unarchived); Cursor cardCursor = DBHelper.getLoyaltyCardCursor(mDatabase);
mAdapter = new LoyaltyCardCursorAdapter(this, cardCursor, this); final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor, this);
cardList.setAdapter(mAdapter); cardList.setAdapter(adapter);
} }
private void onClickAction(int position) { private void onClickAction(int position) {
Cursor selected = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.Unarchived); Cursor selected = DBHelper.getLoyaltyCardCursor(mDatabase);
selected.moveToPosition(position); selected.moveToPosition(position);
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected); LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
@@ -75,26 +65,6 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
finish(); finish();
} }
@Override
public boolean onCreateOptionsMenu(Menu inputMenu) {
getMenuInflater().inflate(R.menu.card_details_menu, inputMenu);
Utils.updateMenuCardDetailsButtonState(inputMenu.findItem(R.id.action_unfold), mAdapter.showingDetails());
return super.onCreateOptionsMenu(inputMenu);
}
@Override
public boolean onOptionsItemSelected(MenuItem inputItem) {
int id = inputItem.getItemId();
if (id == R.id.action_unfold) {
mAdapter.showDetails(!mAdapter.showingDetails());
invalidateOptionsMenu();
return true;
}
return super.onOptionsItemSelected(inputItem);
}
@Override @Override
public void onRowClicked(int inputPosition) { public void onRowClicked(int inputPosition) {

View File

@@ -16,13 +16,13 @@ import android.service.controls.actions.ControlAction;
import android.service.controls.templates.StatelessTemplate; import android.service.controls.templates.StatelessTemplate;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.util.List; import java.util.List;
import java.util.concurrent.Flow; import java.util.concurrent.Flow;
import java.util.function.Consumer; import java.util.function.Consumer;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@RequiresApi(Build.VERSION_CODES.R) @RequiresApi(Build.VERSION_CODES.R)
public class CardsOnPowerScreenService extends ControlsProviderService { public class CardsOnPowerScreenService extends ControlsProviderService {
@@ -40,7 +40,7 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
@NonNull @NonNull
@Override @Override
public Flow.Publisher<Control> createPublisherForAllAvailable() { public Flow.Publisher<Control> createPublisherForAllAvailable() {
Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.Unarchived); Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(mDatabase);
return subscriber -> { return subscriber -> {
while (loyaltyCardCursor.moveToNext()) { while (loyaltyCardCursor.moveToNext()) {
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(loyaltyCardCursor); LoyaltyCard card = LoyaltyCard.toLoyaltyCard(loyaltyCardCursor);

View File

@@ -1,15 +1,20 @@
package protect.card_locker; package protect.card_locker;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.content.SharedPreferences;
import android.os.Build; import android.content.res.Resources;
import android.os.Bundle; import android.util.TypedValue;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;
import java.util.HashMap;
public class CatimaAppCompatActivity extends AppCompatActivity { public class CatimaAppCompatActivity extends AppCompatActivity {
SharedPreferences pref;
HashMap<String, Integer> supportedThemes;
@Override @Override
protected void attachBaseContext(Context base) { protected void attachBaseContext(Context base) {
// Apply chosen language // Apply chosen language
@@ -17,25 +22,32 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
} }
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { public Resources.Theme getTheme() {
super.onCreate(savedInstanceState); if (supportedThemes == null) {
Utils.patchColors(this); supportedThemes = new HashMap<>();
supportedThemes.put(getString(R.string.settings_key_blue_theme), R.style.AppTheme_blue);
supportedThemes.put(getString(R.string.settings_key_brown_theme), R.style.AppTheme_brown);
supportedThemes.put(getString(R.string.settings_key_green_theme), R.style.AppTheme_green);
supportedThemes.put(getString(R.string.settings_key_grey_theme), R.style.AppTheme_grey);
supportedThemes.put(getString(R.string.settings_key_magenta_theme), R.style.AppTheme_magenta);
supportedThemes.put(getString(R.string.settings_key_pink_theme), R.style.AppTheme_pink);
supportedThemes.put(getString(R.string.settings_key_sky_blue_theme), R.style.AppTheme_sky_blue);
supportedThemes.put(getString(R.string.settings_key_violet_theme), R.style.AppTheme_violet);
}
Resources.Theme theme = super.getTheme();
pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
String themeName = pref.getString(getString(R.string.setting_key_theme_color), getString(R.string.settings_key_catima_theme));
theme.applyStyle(Utils.mapGetOrDefault(supportedThemes, themeName, R.style.AppTheme_NoActionBar), true);
return theme;
} }
@Override public int getThemeColor() {
protected void onPostCreate(@Nullable Bundle savedInstanceState) { TypedValue typedValue = new TypedValue();
super.onPostCreate(savedInstanceState); Resources.Theme theme = getTheme();
// material 3 designer does not consider status bar colors theme.resolveAttribute(R.attr.colorPrimary, typedValue, true);
// XXX changing this in onCreate causes issues with the splash screen activity, so doing this here return typedValue.data;
boolean darkMode = Utils.isDarkModeEnabled(this);
if (Build.VERSION.SDK_INT >= 23) {
getWindow().setStatusBarColor(Color.TRANSPARENT);
getWindow().getDecorView().setSystemUiVisibility(darkMode ? 0 : View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
// icons are always white back then
getWindow().setStatusBarColor(darkMode ? Color.TRANSPARENT : Color.argb(127, 0, 0, 0));
}
// XXX android 9 and below has a nasty rendering bug if the theme was patched earlier
Utils.postPatchColors(this);
} }
} }

View File

@@ -0,0 +1,92 @@
package protect.card_locker;
import com.google.zxing.BarcodeFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class CatimaBarcode {
public static final List<BarcodeFormat> barcodeFormats = Collections.unmodifiableList(Arrays.asList(
BarcodeFormat.AZTEC,
BarcodeFormat.CODE_39,
BarcodeFormat.CODE_93,
BarcodeFormat.CODE_128,
BarcodeFormat.CODABAR,
BarcodeFormat.DATA_MATRIX,
BarcodeFormat.EAN_8,
BarcodeFormat.EAN_13,
BarcodeFormat.ITF,
BarcodeFormat.PDF_417,
BarcodeFormat.QR_CODE,
BarcodeFormat.UPC_A,
BarcodeFormat.UPC_E
));
public static final List<String> barcodePrettyNames = Collections.unmodifiableList(Arrays.asList(
"Aztec",
"Code 39",
"Code 93",
"Code 128",
"Codabar",
"Data Matrix",
"EAN 8",
"EAN 13",
"ITF",
"PDF 417",
"QR Code",
"UPC A",
"UPC E"
));
private final BarcodeFormat mBarcodeFormat;
private CatimaBarcode(BarcodeFormat barcodeFormat) {
mBarcodeFormat = barcodeFormat;
}
public static CatimaBarcode fromBarcode(BarcodeFormat barcodeFormat) {
return new CatimaBarcode(barcodeFormat);
}
public static CatimaBarcode fromName(String name) {
return new CatimaBarcode(BarcodeFormat.valueOf(name));
}
public static CatimaBarcode fromPrettyName(String prettyName) {
try {
return new CatimaBarcode(barcodeFormats.get(barcodePrettyNames.indexOf(prettyName)));
} catch (IndexOutOfBoundsException e) {
throw new IllegalArgumentException("No barcode type with pretty name " + prettyName + " known!");
}
}
public boolean isSupported() {
return barcodeFormats.contains(mBarcodeFormat);
}
public boolean isSquare() {
return mBarcodeFormat == BarcodeFormat.AZTEC
|| mBarcodeFormat == BarcodeFormat.DATA_MATRIX
|| mBarcodeFormat == BarcodeFormat.MAXICODE
|| mBarcodeFormat == BarcodeFormat.QR_CODE;
}
public BarcodeFormat format() {
return mBarcodeFormat;
}
public String name() {
return mBarcodeFormat.name();
}
public String prettyName() {
int index = barcodeFormats.indexOf(mBarcodeFormat);
if (index == -1 || index >= barcodePrettyNames.size()) {
return mBarcodeFormat.name();
}
return barcodePrettyNames.get(index);
}
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker;
public class CatimaBarcodeWithValue {
private final CatimaBarcode mCatimaBarcode;
private final String mValue;
public CatimaBarcodeWithValue(CatimaBarcode catimaBarcode, String value) {
mCatimaBarcode = catimaBarcode;
mValue = value;
}
public CatimaBarcode catimaBarcode() {
return mCatimaBarcode;
}
public String value() {
return mValue;
}
}

View File

@@ -18,12 +18,10 @@ import java.util.Currency;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import protect.card_locker.barcodes.Barcode;
public class DBHelper extends SQLiteOpenHelper { public class DBHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "Catima.db"; public static final String DATABASE_NAME = "Catima.db";
public static final int ORIGINAL_DATABASE_VERSION = 1; public static final int ORIGINAL_DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 15; public static final int DATABASE_VERSION = 14;
public static class LoyaltyCardDbGroups { public static class LoyaltyCardDbGroups {
public static final String TABLE = "groups"; public static final String TABLE = "groups";
@@ -47,7 +45,6 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String STAR_STATUS = "starstatus"; public static final String STAR_STATUS = "starstatus";
public static final String LAST_USED = "lastused"; public static final String LAST_USED = "lastused";
public static final String ZOOM_LEVEL = "zoomlevel"; public static final String ZOOM_LEVEL = "zoomlevel";
public static final String ARCHIVE_STATUS = "archive";
} }
public static class LoyaltyCardDbIdsGroups { public static class LoyaltyCardDbIdsGroups {
@@ -74,12 +71,6 @@ public class DBHelper extends SQLiteOpenHelper {
Descending Descending
} }
public enum LoyaltyCardArchiveFilter {
All,
Archived,
Unarchived
}
public DBHelper(Context context) { public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); super(context, DATABASE_NAME, null, DATABASE_VERSION);
} }
@@ -106,8 +97,7 @@ public class DBHelper extends SQLiteOpenHelper {
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," + LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'," + LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'," +
LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0', " + LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0', " +
LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '100', " + LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '100' )");
LoyaltyCardDbIds.ARCHIVE_STATUS + " INTEGER DEFAULT '0' )");
// create associative table for cards in groups // create associative table for cards in groups
db.execSQL("CREATE TABLE " + LoyaltyCardDbIdsGroups.TABLE + "(" + db.execSQL("CREATE TABLE " + LoyaltyCardDbIdsGroups.TABLE + "(" +
@@ -312,10 +302,6 @@ public class DBHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '100' "); + " ADD COLUMN " + LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '100' ");
} }
if (oldVersion < 15 && newVersion >= 15) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.ARCHIVE_STATUS + " INTEGER DEFAULT '0' ");
}
} }
private static ContentValues generateFTSContentValues(final int id, final String store, final String note) { private static ContentValues generateFTSContentValues(final int id, final String store, final String note) {
@@ -362,8 +348,8 @@ public class DBHelper extends SQLiteOpenHelper {
public static long insertLoyaltyCard( public static long insertLoyaltyCard(
final SQLiteDatabase database, final String store, final String note, final Date expiry, final SQLiteDatabase database, final String store, final String note, final Date expiry,
final BigDecimal balance, final Currency balanceType, final String cardId, final BigDecimal balance, final Currency balanceType, final String cardId,
final String barcodeId, final Barcode barcodeType, final Integer headerColor, final String barcodeId, final CatimaBarcode barcodeType, final Integer headerColor,
final int starStatus, final Long lastUsed, final int archiveStatus) { final int starStatus, final Long lastUsed) {
database.beginTransaction(); database.beginTransaction();
// Card // Card
@@ -379,7 +365,6 @@ public class DBHelper extends SQLiteOpenHelper {
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor); contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus); contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime()); contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime());
contentValues.put(LoyaltyCardDbIds.ARCHIVE_STATUS, archiveStatus);
long id = database.insert(LoyaltyCardDbIds.TABLE, null, contentValues); long id = database.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
// FTS // FTS
@@ -394,8 +379,8 @@ public class DBHelper extends SQLiteOpenHelper {
public static long insertLoyaltyCard( public static long insertLoyaltyCard(
final SQLiteDatabase database, final int id, final String store, final String note, final SQLiteDatabase database, final int id, final String store, final String note,
final Date expiry, final BigDecimal balance, final Currency balanceType, final Date expiry, final BigDecimal balance, final Currency balanceType,
final String cardId, final String barcodeId, final Barcode barcodeType, final String cardId, final String barcodeId, final CatimaBarcode barcodeType,
final Integer headerColor, final int starStatus, final Long lastUsed, final int archiveStatus) { final Integer headerColor, final int starStatus, final Long lastUsed) {
database.beginTransaction(); database.beginTransaction();
// Card // Card
@@ -412,7 +397,6 @@ public class DBHelper extends SQLiteOpenHelper {
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor); contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus); contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime()); contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime());
contentValues.put(LoyaltyCardDbIds.ARCHIVE_STATUS, archiveStatus);
database.insert(LoyaltyCardDbIds.TABLE, null, contentValues); database.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
// FTS // FTS
@@ -427,8 +411,8 @@ public class DBHelper extends SQLiteOpenHelper {
public static boolean updateLoyaltyCard( public static boolean updateLoyaltyCard(
SQLiteDatabase database, final int id, final String store, final String note, SQLiteDatabase database, final int id, final String store, final String note,
final Date expiry, final BigDecimal balance, final Currency balanceType, final Date expiry, final BigDecimal balance, final Currency balanceType,
final String cardId, final String barcodeId, final Barcode barcodeType, final String cardId, final String barcodeId, final CatimaBarcode barcodeType,
final Integer headerColor, final int starStatus, final Long lastUsed, final int archiveStatus) { final Integer headerColor) {
database.beginTransaction(); database.beginTransaction();
// Card // Card
@@ -442,10 +426,6 @@ public class DBHelper extends SQLiteOpenHelper {
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId); contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null); contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null);
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor); contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
contentValues.put(LoyaltyCardDbIds.LAST_USED, lastUsed != null ? lastUsed : Utils.getUnixTime());
contentValues.put(LoyaltyCardDbIds.ARCHIVE_STATUS, archiveStatus);
int rowsUpdated = database.update(LoyaltyCardDbIds.TABLE, contentValues, int rowsUpdated = database.update(LoyaltyCardDbIds.TABLE, contentValues,
whereAttrs(LoyaltyCardDbIds.ID), withArgs(id)); whereAttrs(LoyaltyCardDbIds.ID), withArgs(id));
@@ -458,15 +438,6 @@ public class DBHelper extends SQLiteOpenHelper {
return (rowsUpdated == 1); return (rowsUpdated == 1);
} }
public static boolean updateLoyaltyCardArchiveStatus(SQLiteDatabase database, final int id, final int archiveStatus) {
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.ARCHIVE_STATUS, archiveStatus);
int rowsUpdated = database.update(LoyaltyCardDbIds.TABLE, contentValues,
whereAttrs(LoyaltyCardDbIds.ID),
withArgs(id));
return (rowsUpdated == 1);
}
public static boolean updateLoyaltyCardStarStatus(SQLiteDatabase database, final int id, final int starStatus) { public static boolean updateLoyaltyCardStarStatus(SQLiteDatabase database, final int id, final int starStatus) {
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus); contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
@@ -578,35 +549,9 @@ public class DBHelper extends SQLiteOpenHelper {
return (rowsDeleted == 1); return (rowsDeleted == 1);
} }
public static int getArchivedCardsCount(SQLiteDatabase database) {
return (int) DatabaseUtils.queryNumEntries(database, LoyaltyCardDbIds.TABLE,
whereAttrs(LoyaltyCardDbIds.ARCHIVE_STATUS), withArgs(1));
}
public static int getArchivedCardsCount(SQLiteDatabase database, final String groupName) {
Cursor data = database.rawQuery(
"select * from " + LoyaltyCardDbIds.TABLE + " c " +
" LEFT JOIN " + LoyaltyCardDbIdsGroups.TABLE + " cg " +
" ON c." + LoyaltyCardDbIds.ID + " = cg." + LoyaltyCardDbIdsGroups.cardID +
" where " + LoyaltyCardDbIds.ARCHIVE_STATUS + " = 1" +
" AND " + LoyaltyCardDbIdsGroups.groupID + "= ?",
withArgs(groupName)
);
int count = data.getCount();
data.close();
return count;
}
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database) { public static Cursor getLoyaltyCardCursor(SQLiteDatabase database) {
// An empty string will match everything // An empty string will match everything
return getLoyaltyCardCursor(database, LoyaltyCardArchiveFilter.All); return getLoyaltyCardCursor(database, "");
}
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, LoyaltyCardArchiveFilter archiveFilter) {
// An empty string will match everything
return getLoyaltyCardCursor(database, "", archiveFilter);
} }
/** /**
@@ -615,8 +560,8 @@ public class DBHelper extends SQLiteOpenHelper {
* @param filter * @param filter
* @return Cursor * @return Cursor
*/ */
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, final String filter, LoyaltyCardArchiveFilter archiveFilter) { public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, final String filter) {
return getLoyaltyCardCursor(database, filter, null, archiveFilter); return getLoyaltyCardCursor(database, filter, null);
} }
/** /**
@@ -626,8 +571,8 @@ public class DBHelper extends SQLiteOpenHelper {
* @param group * @param group
* @return Cursor * @return Cursor
*/ */
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, final String filter, Group group, LoyaltyCardArchiveFilter archiveFilter) { public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, final String filter, Group group) {
return getLoyaltyCardCursor(database, filter, group, LoyaltyCardOrder.Alpha, LoyaltyCardOrderDirection.Ascending, archiveFilter); return getLoyaltyCardCursor(database, filter, group, LoyaltyCardOrder.Alpha, LoyaltyCardOrderDirection.Ascending);
} }
/** /**
@@ -638,7 +583,7 @@ public class DBHelper extends SQLiteOpenHelper {
* @param order * @param order
* @return Cursor * @return Cursor
*/ */
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, String filter, Group group, LoyaltyCardOrder order, LoyaltyCardOrderDirection direction, LoyaltyCardArchiveFilter archiveFilter) { public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, String filter, Group group, LoyaltyCardOrder order, LoyaltyCardOrderDirection direction) {
StringBuilder groupFilter = new StringBuilder(); StringBuilder groupFilter = new StringBuilder();
String limitString = ""; String limitString = "";
@@ -661,11 +606,6 @@ public class DBHelper extends SQLiteOpenHelper {
} }
} }
String archiveFilterString = "";
if (archiveFilter != LoyaltyCardArchiveFilter.All) {
archiveFilterString = " AND " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.ARCHIVE_STATUS + " = " + (archiveFilter.equals(LoyaltyCardArchiveFilter.Unarchived) ? 0 : 1);
}
String orderField = getFieldForOrder(order); String orderField = getFieldForOrder(order);
return database.rawQuery("SELECT " + LoyaltyCardDbIds.TABLE + ".* FROM " + LoyaltyCardDbIds.TABLE + return database.rawQuery("SELECT " + LoyaltyCardDbIds.TABLE + ".* FROM " + LoyaltyCardDbIds.TABLE +
@@ -673,9 +613,7 @@ public class DBHelper extends SQLiteOpenHelper {
" ON " + LoyaltyCardDbFTS.TABLE + "." + LoyaltyCardDbFTS.ID + " = " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.ID + " ON " + LoyaltyCardDbFTS.TABLE + "." + LoyaltyCardDbFTS.ID + " = " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.ID +
(filter.trim().isEmpty() ? " " : " AND " + LoyaltyCardDbFTS.TABLE + " MATCH ? ") + (filter.trim().isEmpty() ? " " : " AND " + LoyaltyCardDbFTS.TABLE + " MATCH ? ") +
groupFilter.toString() + groupFilter.toString() +
archiveFilterString + " ORDER BY " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.STAR_STATUS + " DESC, " +
" ORDER BY " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.ARCHIVE_STATUS + " ASC, " +
LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.STAR_STATUS + " DESC, " +
" (CASE WHEN " + LoyaltyCardDbIds.TABLE + "." + orderField + " IS NULL THEN 1 ELSE 0 END), " + " (CASE WHEN " + LoyaltyCardDbIds.TABLE + "." + orderField + " IS NULL THEN 1 ELSE 0 END), " +
LoyaltyCardDbIds.TABLE + "." + orderField + " COLLATE NOCASE " + getDbDirection(order, direction) + ", " + LoyaltyCardDbIds.TABLE + "." + orderField + " COLLATE NOCASE " + getDbDirection(order, direction) + ", " +
LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC " +

View File

@@ -1,7 +1,6 @@
package protect.card_locker; package protect.card_locker;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -16,7 +15,7 @@ import protect.card_locker.preferences.Settings;
public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.GroupListItemViewHolder> { public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.GroupListItemViewHolder> {
Settings mSettings; Settings mSettings;
public final Context mContext; private final Context mContext;
private final GroupAdapterListener mListener; private final GroupAdapterListener mListener;
SQLiteDatabase mDatabase; SQLiteDatabase mDatabase;
@@ -24,7 +23,7 @@ public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.Gro
super(inputCursor, DBHelper.LoyaltyCardDbGroups.ORDER); super(inputCursor, DBHelper.LoyaltyCardDbGroups.ORDER);
setHasStableIds(true); setHasStableIds(true);
mSettings = new Settings(inputContext); mSettings = new Settings(inputContext);
mContext = inputContext; mContext = inputContext.getApplicationContext();
mListener = inputListener; mListener = inputListener;
mDatabase = new DBHelper(inputContext).getReadableDatabase(); mDatabase = new DBHelper(inputContext).getReadableDatabase();
@@ -44,18 +43,8 @@ public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.Gro
inputHolder.mName.setText(group._id); inputHolder.mName.setText(group._id);
int groupCardCount = DBHelper.getGroupCardCount(mDatabase, group._id); int groupCardCount = DBHelper.getGroupCardCount(mDatabase, group._id);
int archivedCardCount = DBHelper.getArchivedCardsCount(mDatabase, group._id); inputHolder.mCardCount.setText(mContext.getResources().getQuantityString(R.plurals.groupCardCount, groupCardCount, groupCardCount));
Resources resources = mContext.getResources();
String cardCountText;
if (archivedCardCount > 0) {
cardCountText = resources.getQuantityString(R.plurals.groupCardCountWithArchived, groupCardCount, groupCardCount, archivedCardCount);
} else {
cardCountText = resources.getQuantityString(R.plurals.groupCardCount, groupCardCount, groupCardCount);
}
inputHolder.mCardCount.setText(cardCountText);
inputHolder.mName.setTextSize(mSettings.getFontSizeMax(mSettings.getMediumFont())); inputHolder.mName.setTextSize(mSettings.getFontSizeMax(mSettings.getMediumFont()));
inputHolder.mCardCount.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont())); inputHolder.mCardCount.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));

View File

@@ -1,7 +1,10 @@
package protect.card_locker; package protect.card_locker;
import android.Manifest; import android.Manifest;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@@ -16,23 +19,26 @@ import android.widget.EditText;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.Toast; import android.widget.Toast;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts; import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import protect.card_locker.async.TaskHandler; import protect.card_locker.async.TaskHandler;
import protect.card_locker.importexport.DataFormat; import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult; import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.ImportExportResultType;
public class ImportExportActivity extends CatimaAppCompatActivity { public class ImportExportActivity extends CatimaAppCompatActivity {
private static final String TAG = "Catima"; private static final String TAG = "Catima";
@@ -52,6 +58,12 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
final private TaskHandler mTasks = new TaskHandler(); final private TaskHandler mTasks = new TaskHandler();
private NotificationManager mNotifyManager;
private NotificationCompat.Builder mBuilder;
private static final int NOTIFICATION_IMPORT = 1;
private static final int NOTIFICATION_EXPORT = 2;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -92,10 +104,10 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
try { try {
OutputStream writer = getContentResolver().openOutputStream(uri); OutputStream writer = getContentResolver().openOutputStream(uri);
Log.e(TAG, "Starting file export with: " + result.toString()); Log.e(TAG, "Starting file export with: " + result.toString());
startExport(writer, uri, exportPassword.toCharArray(), true); startExport(writer, uri, exportPassword != null ? exportPassword.toCharArray() : null, true);
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Failed to export file: " + result.toString(), e); Log.e(TAG, "Failed to export file: " + result.toString(), e);
onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), uri); onExportComplete(ImportExportResult.GenericFailure, uri);
} }
}); });
@@ -173,7 +185,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
startImport(reader, uri, importDataFormat, password, true); startImport(reader, uri, importDataFormat, password, true);
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Failed to import file: " + uri.toString(), e); Log.e(TAG, "Failed to import file: " + uri.toString(), e);
onImportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, e.toString()), uri, importDataFormat); onImportComplete(ImportExportResult.GenericFailure, uri, importDataFormat);
} }
} }
@@ -254,22 +266,59 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
builder.show(); builder.show();
} }
private void startProgressNotification(boolean importing) {
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(this, NotificationType.getImportExportChannel(this));
mBuilder.setContentTitle(getString(importing ? R.string.importing : R.string.exporting))
.setContentText(null)
.setSmallIcon(R.drawable.ic_import_export_white_24dp)
.setColor(getThemeColor())
.setProgress(0, 0, true);
mNotifyManager.notify(importing ? NOTIFICATION_IMPORT : NOTIFICATION_EXPORT, mBuilder.build());
}
private void endProgressNotification(boolean importing, ImportExportResult result, PendingIntent sendIntent) {
String notificationTitle;
String notificationMessage;
if (result.equals(ImportExportResult.Success)) {
notificationTitle = getString(importing ? R.string.importSuccessfulTitle : R.string.exportSuccessfulTitle);
notificationMessage = getString(importing ? R.string.importSuccessful : R.string.exportSuccessful);
} else {
int reason = R.string.unknown_failure;
if (result.equals(ImportExportResult.BadPassword)) {
reason = R.string.incorrect_password;
}
notificationTitle = getString(importing ? R.string.importFailedTitle : R.string.exportFailedTitle);
notificationMessage = String.format(getString(importing ? R.string.importFailed : R.string.exportFailed), getString(reason));
}
mBuilder.setContentTitle(notificationTitle)
.setContentText(notificationMessage)
.setProgress(0,0, false);
if (sendIntent != null) {
mBuilder.addAction(R.drawable.ic_share, getString(R.string.sendLabel), sendIntent);
}
mNotifyManager.notify(importing ? NOTIFICATION_IMPORT : NOTIFICATION_EXPORT, mBuilder.build());
}
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password, final boolean closeWhenDone) { private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password, final boolean closeWhenDone) {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false); mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() { ImportExportTask.TaskCompleteListener listener = (result, dataFormat1) -> {
@Override onImportComplete(result, targetUri, dataFormat1);
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) { if (closeWhenDone) {
onImportComplete(result, targetUri, dataFormat); try {
if (closeWhenDone) { target.close();
try { } catch (IOException ioException) {
target.close(); ioException.printStackTrace();
} catch (IOException ioException) {
ioException.printStackTrace();
}
} }
} }
}; };
startProgressNotification(true);
importExporter = new ImportExportTask(ImportExportActivity.this, importExporter = new ImportExportTask(ImportExportActivity.this,
dataFormat, target, password, listener); dataFormat, target, password, listener);
mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter); mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter);
@@ -277,20 +326,18 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
private void startExport(final OutputStream target, final Uri targetUri, char[] password, final boolean closeWhenDone) { private void startExport(final OutputStream target, final Uri targetUri, char[] password, final boolean closeWhenDone) {
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false); mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() { ImportExportTask.TaskCompleteListener listener = (result, dataFormat) -> {
@Override onExportComplete(result, targetUri);
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) { if (closeWhenDone) {
onExportComplete(result, targetUri); try {
if (closeWhenDone) { target.close();
try { } catch (IOException ioException) {
target.close(); ioException.printStackTrace();
} catch (IOException ioException) {
ioException.printStackTrace();
}
} }
} }
}; };
startProgressNotification(false);
importExporter = new ImportExportTask(ImportExportActivity.this, importExporter = new ImportExportTask(ImportExportActivity.this,
DataFormat.Catima, target, password, listener); DataFormat.Catima, target, password, listener);
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter); mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter);
@@ -355,68 +402,28 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
builder.show(); builder.show();
} }
private String buildResultDialogMessage(ImportExportResult result, boolean isImport) {
int messageId;
if (result.resultType() == ImportExportResultType.Success) {
messageId = isImport ? R.string.importSuccessful : R.string.exportSuccessful;
} else {
messageId = isImport ? R.string.importFailed : R.string.exportFailed;
}
StringBuilder messageBuilder = new StringBuilder(getResources().getString(messageId));
if (result.developerDetails() != null) {
messageBuilder.append("\n\n");
messageBuilder.append(getResources().getString(R.string.include_if_asking_support));
messageBuilder.append("\n\n");
messageBuilder.append(result.developerDetails());
}
return messageBuilder.toString();
}
private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) { private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) {
ImportExportResultType resultType = result.resultType(); endProgressNotification(true, result, null);
if (resultType == ImportExportResultType.BadPassword) { if (result == ImportExportResult.BadPassword) {
retryWithPassword(dataFormat, path); retryWithPassword(dataFormat, path);
return;
} }
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.importSuccessfulTitle : R.string.importFailedTitle);
builder.setMessage(buildResultDialogMessage(result, true));
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
builder.create().show();
} }
private void onExportComplete(ImportExportResult result, final Uri path) { private void onExportComplete(ImportExportResult result, final Uri path) {
ImportExportResultType resultType = result.resultType(); PendingIntent pendingIntent = null;
AlertDialog.Builder builder = new AlertDialog.Builder(this); if (result == ImportExportResult.Success) {
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.exportSuccessfulTitle : R.string.exportFailedTitle); Intent sendIntent = new Intent(Intent.ACTION_SEND);
builder.setMessage(buildResultDialogMessage(result, false)); sendIntent.putExtra(Intent.EXTRA_STREAM, path);
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss()); sendIntent.setType("text/csv");
if (resultType == ImportExportResultType.Success) { // set flag to give temporary permission to external app to use the FileProvider
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel); sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
builder.setPositiveButton(sendLabel, (dialog, which) -> { pendingIntent = PendingIntent.getActivity(this, NOTIFICATION_EXPORT, sendIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
sendIntent.setType("text/csv");
// set flag to give temporary permission to external app to use the FileProvider
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
sendLabel));
dialog.dismiss();
});
} }
builder.create().show(); endProgressNotification(false, result, pendingIntent);
} }
} }

View File

@@ -1,7 +1,7 @@
package protect.card_locker; package protect.card_locker;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog; import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
@@ -13,25 +13,23 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import androidx.core.app.NotificationCompat;
import protect.card_locker.async.CompatCallable; import protect.card_locker.async.CompatCallable;
import protect.card_locker.importexport.DataFormat; import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult; import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.ImportExportResultType;
import protect.card_locker.importexport.MultiFormatExporter; import protect.card_locker.importexport.MultiFormatExporter;
import protect.card_locker.importexport.MultiFormatImporter; import protect.card_locker.importexport.MultiFormatImporter;
public class ImportExportTask implements CompatCallable<ImportExportResult> { public class ImportExportTask implements CompatCallable<ImportExportResult> {
private static final String TAG = "Catima"; private static final String TAG = "Catima";
private Activity activity; private final Activity activity;
private boolean doImport; private final boolean doImport;
private DataFormat format; private final DataFormat format;
private OutputStream outputStream; private OutputStream outputStream;
private InputStream inputStream; private InputStream inputStream;
private char[] password; private final char[] password;
private TaskCompleteListener listener; private final TaskCompleteListener listener;
private ProgressDialog progress;
/** /**
* Constructor which will setup a task for exporting to the given file * Constructor which will setup a task for exporting to the given file
@@ -62,22 +60,21 @@ public class ImportExportTask implements CompatCallable<ImportExportResult> {
} }
private ImportExportResult performImport(Context context, InputStream stream, SQLiteDatabase database, char[] password) { private ImportExportResult performImport(Context context, InputStream stream, SQLiteDatabase database, char[] password) {
ImportExportResult importResult = MultiFormatImporter.importData(context, database, stream, format, password); ImportExportResult result = MultiFormatImporter.importData(context, database, stream, format, password);
Log.i(TAG, "Import result: " + importResult); Log.i(TAG, "Import result: " + result.name());
return importResult; return result;
} }
private ImportExportResult performExport(Context context, OutputStream stream, SQLiteDatabase database, char[] password) { private ImportExportResult performExport(Context context, OutputStream stream, SQLiteDatabase database, char[] password) {
ImportExportResult result; ImportExportResult result = ImportExportResult.GenericFailure;
try { try {
OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8); OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
result = MultiFormatExporter.exportData(context, database, stream, format, password); result = MultiFormatExporter.exportData(context, database, stream, format, password);
writer.close(); writer.close();
} catch (IOException e) { } catch (IOException e) {
result = new ImportExportResult(ImportExportResultType.GenericFailure, e.toString());
Log.e(TAG, "Unable to export file", e); Log.e(TAG, "Unable to export file", e);
} }
@@ -86,20 +83,6 @@ public class ImportExportTask implements CompatCallable<ImportExportResult> {
return result; return result;
} }
public void onPreExecute() {
progress = new ProgressDialog(activity);
progress.setTitle(doImport ? R.string.importing : R.string.exporting);
progress.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
ImportExportTask.this.stop();
}
});
progress.show();
}
protected ImportExportResult doInBackground(Void... nothing) { protected ImportExportResult doInBackground(Void... nothing) {
final SQLiteDatabase database = new DBHelper(activity).getWritableDatabase(); final SQLiteDatabase database = new DBHelper(activity).getWritableDatabase();
ImportExportResult result; ImportExportResult result;
@@ -116,15 +99,15 @@ public class ImportExportTask implements CompatCallable<ImportExportResult> {
} }
public void onPostExecute(Object castResult) { public void onPostExecute(Object castResult) {
listener.onTaskComplete((ImportExportResult) castResult, format); ImportExportResult result = (ImportExportResult) castResult;
listener.onTaskComplete(result, format);
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete"); Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
} }
protected void onCancelled() { @Override
progress.dismiss(); public void onPreExecute() {
Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled");
} }
protected void stop() { protected void stop() {

View File

@@ -15,9 +15,6 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
public class ImportURIHelper { public class ImportURIHelper {
private static final String STORE = DBHelper.LoyaltyCardDbIds.STORE; private static final String STORE = DBHelper.LoyaltyCardDbIds.STORE;
private static final String NOTE = DBHelper.LoyaltyCardDbIds.NOTE; private static final String NOTE = DBHelper.LoyaltyCardDbIds.NOTE;
@@ -64,7 +61,7 @@ public class ImportURIHelper {
try { try {
// These values are allowed to be null // These values are allowed to be null
Barcode barcodeType = null; CatimaBarcode barcodeType = null;
Date expiry = null; Date expiry = null;
BigDecimal balance = new BigDecimal("0"); BigDecimal balance = new BigDecimal("0");
Currency balanceType = null; Currency balanceType = null;
@@ -98,7 +95,7 @@ public class ImportURIHelper {
String unparsedBarcodeType = kv.get(BARCODE_TYPE); String unparsedBarcodeType = kv.get(BARCODE_TYPE);
if (unparsedBarcodeType != null && !unparsedBarcodeType.equals("")) { if (unparsedBarcodeType != null && !unparsedBarcodeType.equals("")) {
barcodeType = BarcodeFactory.fromName(unparsedBarcodeType); barcodeType = CatimaBarcode.fromName(unparsedBarcodeType);
} }
String unparsedBalance = kv.get(BALANCE); String unparsedBalance = kv.get(BALANCE);
@@ -119,7 +116,7 @@ public class ImportURIHelper {
headerColor = Integer.parseInt(unparsedHeaderColor); headerColor = Integer.parseInt(unparsedHeaderColor);
} }
return new LoyaltyCard(-1, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0, Utils.getUnixTime(), 100,0); return new LoyaltyCard(-1, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0, Utils.getUnixTime(), 100);
} catch (NullPointerException | NumberFormatException | UnsupportedEncodingException ex) { } catch (NullPointerException | NumberFormatException | UnsupportedEncodingException ex) {
throw new InvalidObjectException("Not a valid import URI"); throw new InvalidObjectException("Not a valid import URI");
} }

View File

@@ -10,9 +10,6 @@ import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.text.TextPaint; import android.text.TextPaint;
import android.util.Log;
import androidx.core.graphics.PaintCompat;
/** /**
* Original from https://github.com/andOTP/andOTP/blob/master/app/src/main/java/org/shadowice/flocke/andotp/Utilities/LetterBitmap.java * Original from https://github.com/andOTP/andOTP/blob/master/app/src/main/java/org/shadowice/flocke/andotp/Utilities/LetterBitmap.java
@@ -51,6 +48,7 @@ class LetterBitmap {
public LetterBitmap(Context context, String displayName, String key, int tileLetterFontSize, public LetterBitmap(Context context, String displayName, String key, int tileLetterFontSize,
int width, int height, Integer backgroundColor, Integer textColor) { int width, int height, Integer backgroundColor, Integer textColor) {
TextPaint paint = new TextPaint(); TextPaint paint = new TextPaint();
paint.setTypeface(Typeface.create("sans-serif-light", Typeface.BOLD));
if (textColor != null) { if (textColor != null) {
paint.setColor(textColor); paint.setColor(textColor);
@@ -60,8 +58,6 @@ class LetterBitmap {
paint.setTextAlign(Paint.Align.CENTER); paint.setTextAlign(Paint.Align.CENTER);
paint.setAntiAlias(true); paint.setAntiAlias(true);
paint.setTextSize(tileLetterFontSize);
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
if (backgroundColor == null) { if (backgroundColor == null) {
mColor = getDefaultColor(context, key); mColor = getDefaultColor(context, key);
@@ -70,31 +66,22 @@ class LetterBitmap {
} }
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
String firstChar = displayName.substring(0, 1).toUpperCase(); String firstChar = displayName.substring(0, 1);
int firstCharEnd = 2;
while (firstCharEnd <= displayName.length()) {
// Test for the longest render-able string
// But ignore containing only a-Z0-9 to not render things like ffi as a single character
String test = displayName.substring(0, firstCharEnd);
if (!isAlphabetical(test) && PaintCompat.hasGlyph(paint, test)) {
firstChar = test;
}
firstCharEnd++;
}
Log.d("LetterBitmap", "using sequence " + firstChar + " to render first char which has length " + firstChar.length());
final Canvas c = new Canvas(); final Canvas c = new Canvas();
c.setBitmap(mBitmap); c.setBitmap(mBitmap);
c.drawColor(mColor); c.drawColor(mColor);
Rect bounds = new Rect(); char[] firstCharArray = new char[1];
paint.getTextBounds(firstChar, 0, firstChar.length(), bounds); firstCharArray[0] = firstChar.toUpperCase().charAt(0);
c.drawText(firstChar, paint.setTextSize(tileLetterFontSize);
0, firstChar.length(),
width / 2.0f, (height - (bounds.bottom + bounds.top)) / 2.0f
, paint);
// The bounds that enclose the letter
Rect bounds = new Rect();
paint.getTextBounds(firstCharArray, 0, 1, bounds);
c.drawText(firstCharArray, 0, 1, width / 2.0f, height / 2.0f
+ (bounds.bottom - bounds.top) / 2.0f, paint);
} }
/** /**
@@ -125,10 +112,6 @@ class LetterBitmap {
return colors.getColor(color, Color.BLACK); return colors.getColor(color, Color.BLACK);
} }
private static boolean isAlphabetical(String string) {
return string.matches("[a-zA-Z0-9]*");
}
/** /**
* Determine the color which the letter tile will use if no default * Determine the color which the letter tile will use if no default
* color is provided. * color is provided.

View File

@@ -9,8 +9,6 @@ import java.util.Currency;
import java.util.Date; import java.util.Date;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
public class LoyaltyCard implements Parcelable { public class LoyaltyCard implements Parcelable {
public final int id; public final int id;
@@ -25,21 +23,19 @@ public class LoyaltyCard implements Parcelable {
public final String barcodeId; public final String barcodeId;
@Nullable @Nullable
public final Barcode barcodeType; public final CatimaBarcode barcodeType;
@Nullable @Nullable
public final Integer headerColor; public final Integer headerColor;
public final int starStatus; public final int starStatus;
public final int archiveStatus;
public final long lastUsed; public final long lastUsed;
public int zoomLevel; public int zoomLevel;
public LoyaltyCard(final int id, final String store, final String note, final Date expiry, public LoyaltyCard(final int id, final String store, final String note, final Date expiry,
final BigDecimal balance, final Currency balanceType, final String cardId, final BigDecimal balance, final Currency balanceType, final String cardId,
@Nullable final String barcodeId, @Nullable final Barcode barcodeType, @Nullable final String barcodeId, @Nullable final CatimaBarcode barcodeType,
@Nullable final Integer headerColor, final int starStatus, @Nullable final Integer headerColor, final int starStatus, final long lastUsed, final int zoomLevel) {
final long lastUsed, final int zoomLevel, final int archiveStatus) {
this.id = id; this.id = id;
this.store = store; this.store = store;
this.note = note; this.note = note;
@@ -53,7 +49,6 @@ public class LoyaltyCard implements Parcelable {
this.starStatus = starStatus; this.starStatus = starStatus;
this.lastUsed = lastUsed; this.lastUsed = lastUsed;
this.zoomLevel = zoomLevel; this.zoomLevel = zoomLevel;
this.archiveStatus = archiveStatus;
} }
protected LoyaltyCard(Parcel in) { protected LoyaltyCard(Parcel in) {
@@ -67,13 +62,12 @@ public class LoyaltyCard implements Parcelable {
cardId = in.readString(); cardId = in.readString();
barcodeId = in.readString(); barcodeId = in.readString();
String tmpBarcodeType = in.readString(); String tmpBarcodeType = in.readString();
barcodeType = !tmpBarcodeType.isEmpty() ? BarcodeFactory.fromName(tmpBarcodeType) : null; barcodeType = !tmpBarcodeType.isEmpty() ? CatimaBarcode.fromName(tmpBarcodeType) : null;
int tmpHeaderColor = in.readInt(); int tmpHeaderColor = in.readInt();
headerColor = tmpHeaderColor != -1 ? tmpHeaderColor : null; headerColor = tmpHeaderColor != -1 ? tmpHeaderColor : null;
starStatus = in.readInt(); starStatus = in.readInt();
lastUsed = in.readLong(); lastUsed = in.readLong();
zoomLevel = in.readInt(); zoomLevel = in.readInt();
archiveStatus = in.readInt();
} }
@Override @Override
@@ -91,7 +85,6 @@ public class LoyaltyCard implements Parcelable {
parcel.writeInt(starStatus); parcel.writeInt(starStatus);
parcel.writeLong(lastUsed); parcel.writeLong(lastUsed);
parcel.writeInt(zoomLevel); parcel.writeInt(zoomLevel);
parcel.writeInt(archiveStatus);
} }
public static LoyaltyCard toLoyaltyCard(Cursor cursor) { public static LoyaltyCard toLoyaltyCard(Cursor cursor) {
@@ -105,19 +98,18 @@ public class LoyaltyCard implements Parcelable {
int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS)); int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS));
long lastUsed = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.LAST_USED)); long lastUsed = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.LAST_USED));
int zoomLevel = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ZOOM_LEVEL)); int zoomLevel = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ZOOM_LEVEL));
int archived = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS));
int barcodeTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE); int barcodeTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
int balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE); int balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE);
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR); int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
Barcode barcodeType = null; CatimaBarcode barcodeType = null;
Currency balanceType = null; Currency balanceType = null;
Date expiry = null; Date expiry = null;
Integer headerColor = null; Integer headerColor = null;
if (cursor.isNull(barcodeTypeColumn) == false) { if (cursor.isNull(barcodeTypeColumn) == false) {
barcodeType = BarcodeFactory.fromName(cursor.getString(barcodeTypeColumn)); barcodeType = CatimaBarcode.fromName(cursor.getString(barcodeTypeColumn));
} }
if (cursor.isNull(balanceTypeColumn) == false) { if (cursor.isNull(balanceTypeColumn) == false) {
@@ -132,7 +124,7 @@ public class LoyaltyCard implements Parcelable {
headerColor = cursor.getInt(headerColorColumn); headerColor = cursor.getInt(headerColorColumn);
} }
return new LoyaltyCard(id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred, lastUsed, zoomLevel,archived); return new LoyaltyCard(id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred, lastUsed, zoomLevel);
} }
@Override @Override

View File

@@ -1,7 +1,6 @@
package protect.card_locker; package protect.card_locker;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@@ -16,6 +15,12 @@ import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.BlendModeColorFilterCompat;
import androidx.core.graphics.BlendModeCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView; import com.google.android.material.card.MaterialCardView;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -24,58 +29,36 @@ import java.util.ArrayList;
import java.util.Currency; import java.util.Currency;
import java.util.Date; import java.util.Date;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.graphics.BlendModeColorFilterCompat;
import androidx.core.graphics.BlendModeCompat;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.preferences.Settings; import protect.card_locker.preferences.Settings;
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder> { public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder> {
private int mCurrentSelectedIndex = -1; private int mCurrentSelectedIndex = -1;
Settings mSettings; Settings mSettings;
boolean mDarkModeEnabled; boolean mDarkModeEnabled;
public final Context mContext; private final Context mContext;
private final CardAdapterListener mListener; private final CardAdapterListener mListener;
protected SparseBooleanArray mSelectedItems; protected SparseBooleanArray mSelectedItems;
protected SparseBooleanArray mAnimationItemsIndex; protected SparseBooleanArray mAnimationItemsIndex;
private boolean mReverseAllAnimations = false; private boolean mReverseAllAnimations = false;
private boolean mShowDetails; private boolean mShowDetails = true;
public LoyaltyCardCursorAdapter(Context inputContext, Cursor inputCursor, CardAdapterListener inputListener) { public LoyaltyCardCursorAdapter(Context inputContext, Cursor inputCursor, CardAdapterListener inputListener) {
super(inputCursor, DBHelper.LoyaltyCardDbIds.ID); super(inputCursor, DBHelper.LoyaltyCardDbIds.ID);
setHasStableIds(true); setHasStableIds(true);
mSettings = new Settings(inputContext); mSettings = new Settings(inputContext);
mContext = inputContext; mContext = inputContext.getApplicationContext();
mListener = inputListener; mListener = inputListener;
mSelectedItems = new SparseBooleanArray(); mSelectedItems = new SparseBooleanArray();
mAnimationItemsIndex = new SparseBooleanArray(); mAnimationItemsIndex = new SparseBooleanArray();
mDarkModeEnabled = Utils.isDarkModeEnabled(inputContext); mDarkModeEnabled = Utils.isDarkModeEnabled(inputContext);
refreshState();
swapCursor(inputCursor); swapCursor(inputCursor);
} }
public void refreshState() {
// Retrieve user details preference
SharedPreferences cardDetailsPref = mContext.getSharedPreferences(
mContext.getString(R.string.sharedpreference_card_details),
Context.MODE_PRIVATE);
mShowDetails = cardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show), true);
}
public void showDetails(boolean show) { public void showDetails(boolean show) {
mShowDetails = show; mShowDetails = show;
notifyDataSetChanged(); notifyDataSetChanged();
// Store in Shared Preference to restore next adapter launch
SharedPreferences cardDetailsPref = mContext.getSharedPreferences(
mContext.getString(R.string.sharedpreference_card_details),
Context.MODE_PRIVATE);
SharedPreferences.Editor cardDetailsPrefEditor = cardDetailsPref.edit();
cardDetailsPrefEditor.putBoolean(mContext.getString(R.string.sharedpreference_card_details_show), show);
cardDetailsPrefEditor.apply();
} }
public boolean showingDetails() { public boolean showingDetails() {
@@ -118,7 +101,6 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
inputHolder.setExpiryField(null); inputHolder.setExpiryField(null);
} }
setHeaderHeight(inputHolder, mShowDetails);
Bitmap cardIcon = Utils.retrieveCardImage(mContext, loyaltyCard.id, ImageLocationType.icon); Bitmap cardIcon = Utils.retrieveCardImage(mContext, loyaltyCard.id, ImageLocationType.icon);
if (cardIcon != null) { if (cardIcon != null) {
inputHolder.mCardIcon.setImageBitmap(cardIcon); inputHolder.mCardIcon.setImageBitmap(cardIcon);
@@ -127,9 +109,9 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
inputHolder.mCardIcon.setImageBitmap(Utils.generateIcon(mContext, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile()); inputHolder.mCardIcon.setImageBitmap(Utils.generateIcon(mContext, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile());
inputHolder.mCardIcon.setScaleType(ImageView.ScaleType.FIT_CENTER); inputHolder.mCardIcon.setScaleType(ImageView.ScaleType.FIT_CENTER);
} }
inputHolder.setIconBackgroundColor(loyaltyCard.headerColor != null ? loyaltyCard.headerColor : R.attr.colorPrimary); inputHolder.setIconBackgroundColor(loyaltyCard.headerColor != null ? loyaltyCard.headerColor : ContextCompat.getColor(mContext, R.color.colorPrimary));
inputHolder.toggleCardStateIcon(loyaltyCard.starStatus != 0, loyaltyCard.archiveStatus != 0, itemSelected(inputCursor.getPosition())); inputHolder.toggleStar(loyaltyCard.starStatus != 0, itemSelected(inputCursor.getPosition()));
inputHolder.itemView.setActivated(mSelectedItems.get(inputCursor.getPosition(), false)); inputHolder.itemView.setActivated(mSelectedItems.get(inputCursor.getPosition(), false));
applyIconAnimation(inputHolder, inputCursor.getPosition()); applyIconAnimation(inputHolder, inputCursor.getPosition());
@@ -139,19 +121,6 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
inputHolder.mRow.requestLayout(); inputHolder.mRow.requestLayout();
} }
private void setHeaderHeight(LoyaltyCardListItemViewHolder inputHolder, boolean expanded) {
int iconHeight;
if (expanded) {
iconHeight = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
iconHeight = (int) mContext.getResources().getDimension(R.dimen.cardThumbnailSize);
}
inputHolder.mIconLayout.getLayoutParams().height = expanded ? 0 : iconHeight;
inputHolder.mCardIcon.getLayoutParams().height = iconHeight;
inputHolder.mTickIcon.getLayoutParams().height = iconHeight;
}
private void applyClickEvents(LoyaltyCardListItemViewHolder inputHolder, final int inputPosition) { private void applyClickEvents(LoyaltyCardListItemViewHolder inputHolder, final int inputPosition) {
inputHolder.mRow.setOnClickListener(inputView -> mListener.onRowClicked(inputPosition)); inputHolder.mRow.setOnClickListener(inputView -> mListener.onRowClicked(inputPosition));
@@ -168,12 +137,14 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
private void applyIconAnimation(LoyaltyCardListItemViewHolder inputHolder, int inputPosition) { private void applyIconAnimation(LoyaltyCardListItemViewHolder inputHolder, int inputPosition) {
if (itemSelected(inputPosition)) { if (itemSelected(inputPosition)) {
inputHolder.mCardIcon.setVisibility(View.GONE);
inputHolder.mTickIcon.setVisibility(View.VISIBLE); inputHolder.mTickIcon.setVisibility(View.VISIBLE);
if (mCurrentSelectedIndex == inputPosition) { if (mCurrentSelectedIndex == inputPosition) {
resetCurrentIndex(); resetCurrentIndex();
} }
} else { } else {
inputHolder.mTickIcon.setVisibility(View.GONE); inputHolder.mTickIcon.setVisibility(View.GONE);
inputHolder.mCardIcon.setVisibility(View.VISIBLE);
if ((mReverseAllAnimations && mAnimationItemsIndex.get(inputPosition, false)) || mCurrentSelectedIndex == inputPosition) { if ((mReverseAllAnimations && mAnimationItemsIndex.get(inputPosition, false)) || mCurrentSelectedIndex == inputPosition) {
resetCurrentIndex(); resetCurrentIndex();
} }
@@ -228,15 +199,13 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
public class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder { public class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder {
public TextView mStoreField, mNoteField, mBalanceField, mExpiryField; public TextView mStoreField, mNoteField, mBalanceField, mExpiryField;
public ImageView mCardIcon, mStarBackground, mStarBorder, mTickIcon, mArchivedBackground; public ImageView mCardIcon, mStarBackground, mStarBorder, mTickIcon;
public MaterialCardView mRow, mIconLayout; public MaterialCardView mRow;
public ConstraintLayout mStar, mArchived; public ConstraintLayout mStar;
public View mDivider; public View mDivider;
private int mIconBackgroundColor; private int mIconBackgroundColor;
protected LoyaltyCardListItemViewHolder(View inputView, CardAdapterListener inputListener) { protected LoyaltyCardListItemViewHolder(View inputView, CardAdapterListener inputListener) {
super(inputView); super(inputView);
mRow = inputView.findViewById(R.id.row); mRow = inputView.findViewById(R.id.row);
@@ -245,13 +214,11 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
mNoteField = inputView.findViewById(R.id.note); mNoteField = inputView.findViewById(R.id.note);
mBalanceField = inputView.findViewById(R.id.balance); mBalanceField = inputView.findViewById(R.id.balance);
mExpiryField = inputView.findViewById(R.id.expiry); mExpiryField = inputView.findViewById(R.id.expiry);
mIconLayout = inputView.findViewById(R.id.icon_layout);
mCardIcon = inputView.findViewById(R.id.thumbnail); mCardIcon = inputView.findViewById(R.id.thumbnail);
mStar = inputView.findViewById(R.id.star); mStar = inputView.findViewById(R.id.star);
mStarBackground = inputView.findViewById(R.id.star_background); mStarBackground = inputView.findViewById(R.id.star_background);
mStarBorder = inputView.findViewById(R.id.star_border); mStarBorder = inputView.findViewById(R.id.star_border);
mArchived = inputView.findViewById(R.id.archivedIcon);
mArchivedBackground = inputView.findViewById(R.id.archive_background);
mTickIcon = inputView.findViewById(R.id.selected_thumbnail); mTickIcon = inputView.findViewById(R.id.selected_thumbnail);
inputView.setOnLongClickListener(view -> { inputView.setOnLongClickListener(view -> {
inputListener.onRowClicked(getAdapterPosition()); inputListener.onRowClicked(getAdapterPosition());
@@ -286,12 +253,10 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
mDivider.setVisibility(View.VISIBLE); mDivider.setVisibility(View.VISIBLE);
mBalanceField.setVisibility(View.VISIBLE); mBalanceField.setVisibility(View.VISIBLE);
Drawable balanceIcon = mBalanceField.getCompoundDrawables()[0]; Drawable balanceIcon = mBalanceField.getCompoundDrawables()[0];
if (balanceIcon != null) { balanceIcon.setBounds(0, 0, drawableSize, drawableSize);
balanceIcon.setBounds(0, 0, drawableSize, drawableSize); mBalanceField.setCompoundDrawablesRelative(balanceIcon, null, null, null);
mBalanceField.setCompoundDrawablesRelative(balanceIcon, null, null, null); if (mDarkModeEnabled) {
if (mDarkModeEnabled) { balanceIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
balanceIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
}
} }
mBalanceField.setText(Utils.formatBalance(mContext, balance, balanceType)); mBalanceField.setText(Utils.formatBalance(mContext, balance, balanceType));
mBalanceField.setTextSize(size); mBalanceField.setTextSize(size);
@@ -308,25 +273,21 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
mDivider.setVisibility(View.VISIBLE); mDivider.setVisibility(View.VISIBLE);
mExpiryField.setVisibility(View.VISIBLE); mExpiryField.setVisibility(View.VISIBLE);
Drawable expiryIcon = mExpiryField.getCompoundDrawables()[0]; Drawable expiryIcon = mExpiryField.getCompoundDrawables()[0];
if (expiryIcon != null) { expiryIcon.setBounds(0, 0, drawableSize, drawableSize);
expiryIcon.setBounds(0, 0, drawableSize, drawableSize); mExpiryField.setCompoundDrawablesRelative(expiryIcon, null, null, null);
mExpiryField.setCompoundDrawablesRelative(expiryIcon, null, null, null); if (Utils.hasExpired(expiry)) {
if (Utils.hasExpired(expiry)) { expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.RED, BlendModeCompat.SRC_ATOP));
expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.RED, BlendModeCompat.SRC_ATOP)); mExpiryField.setTextColor(Color.RED);
} else if (mDarkModeEnabled) { } else if (mDarkModeEnabled) {
expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP)); expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
}
} }
mExpiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(expiry)); mExpiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(expiry));
if (Utils.hasExpired(expiry)) {
mExpiryField.setTextColor(Color.RED);
}
mExpiryField.setTextSize(size); mExpiryField.setTextSize(size);
} }
mExpiryField.requestLayout(); mExpiryField.requestLayout();
} }
public void toggleCardStateIcon(boolean enableStar, boolean enableArchive, boolean colorByTheme) { public void toggleStar(boolean enable, boolean colorByTheme) {
/* the below code does not work in android 5! hence the change of drawable instead /* the below code does not work in android 5! hence the change of drawable instead
boolean needDarkForeground = Utils.needsDarkForeground(mIconBackgroundColor); boolean needDarkForeground = Utils.needsDarkForeground(mIconBackgroundColor);
Drawable borderDrawable = mStarBorder.getDrawable().mutate(); Drawable borderDrawable = mStarBorder.getDrawable().mutate();
@@ -340,33 +301,20 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
if (colorByTheme) { if (colorByTheme) {
dark = !mDarkModeEnabled; dark = !mDarkModeEnabled;
} }
if (dark) { if (dark) {
mStarBorder.setImageResource(R.drawable.ic_unstarred_white);
mStarBackground.setImageResource(R.drawable.ic_starred_black);
mArchivedBackground.setImageResource(R.drawable.ic_baseline_archive_24_black);
} else {
mStarBorder.setImageResource(R.drawable.ic_unstarred_black); mStarBorder.setImageResource(R.drawable.ic_unstarred_black);
mStarBackground.setImageResource(R.drawable.ic_starred_black);
} else {
mStarBorder.setImageResource(R.drawable.ic_unstarred_white);
mStarBackground.setImageResource(R.drawable.ic_starred_white); mStarBackground.setImageResource(R.drawable.ic_starred_white);
mArchivedBackground.setImageResource(R.drawable.ic_baseline_archive_24);
} }
if (enable) {
if (enableStar) {
mStar.setVisibility(View.VISIBLE); mStar.setVisibility(View.VISIBLE);
} else{ } else {
mStar.setVisibility(View.GONE); mStar.setVisibility(View.GONE);
} }
if (enableArchive) {
mArchived.setVisibility(View.VISIBLE);
} else{
mArchived.setVisibility(View.GONE);
}
mStarBorder.invalidate(); mStarBorder.invalidate();
mStarBackground.invalidate(); mStarBackground.invalidate();
mArchivedBackground.invalidate();
} }
public void setIconBackgroundColor(int color) { public void setIconBackgroundColor(int color) {

View File

@@ -5,10 +5,10 @@ import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.DatePickerDialog; import android.app.DatePickerDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
@@ -36,7 +36,6 @@ import android.widget.Toast;
import com.google.android.material.chip.Chip; import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup; import com.google.android.material.chip.ChipGroup;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
@@ -51,7 +50,6 @@ import java.io.IOException;
import java.io.InvalidObjectException; import java.io.InvalidObjectException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
@@ -70,15 +68,14 @@ import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import androidx.exifinterface.media.ExifInterface; import androidx.exifinterface.media.ExifInterface;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.palette.graphics.Palette;
import protect.card_locker.async.TaskHandler; import protect.card_locker.async.TaskHandler;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
public class LoyaltyCardEditActivity extends CatimaAppCompatActivity { public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
private static final String TAG = "Catima"; private static final String TAG = "Catima";
@@ -109,7 +106,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_ICON = 102; private static final int PERMISSION_REQUEST_CAMERA_IMAGE_ICON = 102;
public static final String BUNDLE_ID = "id"; public static final String BUNDLE_ID = "id";
public static final String BUNDLE_DUPLICATE_ID = "duplicateId";
public static final String BUNDLE_UPDATE = "update"; public static final String BUNDLE_UPDATE = "update";
public static final String BUNDLE_CARDID = "cardId"; public static final String BUNDLE_CARDID = "cardId";
public static final String BUNDLE_BARCODEID = "barcodeId"; public static final String BUNDLE_BARCODEID = "barcodeId";
@@ -119,7 +115,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
TabLayout tabs; TabLayout tabs;
ImageView thumbnail; ImageView thumbnail;
ImageView thumbnailEditIcon;
EditText storeFieldEdit; EditText storeFieldEdit;
EditText noteFieldEdit; EditText noteFieldEdit;
ChipGroup groupsChips; ChipGroup groupsChips;
@@ -139,11 +134,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
Button enterButton; Button enterButton;
Toolbar toolbar;
int loyaltyCardId; int loyaltyCardId;
boolean updateLoyaltyCard; boolean updateLoyaltyCard;
boolean duplicateFromLoyaltyCardId;
String cardId; String cardId;
String barcodeId; String barcodeId;
String barcodeType; String barcodeType;
@@ -161,7 +153,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
AlertDialog confirmExitDialog = null; AlertDialog confirmExitDialog = null;
boolean validBalance = true; boolean validBalance = true;
Runnable barcodeImageGenerationFinishedCallback; Runnable warnOnInvalidBarcodeType;
HashMap<String, Currency> currencies = new HashMap<>(); HashMap<String, Currency> currencies = new HashMap<>();
@@ -206,22 +198,16 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
(Currency) (fieldName == LoyaltyCardField.balanceType ? value : loyaltyCard.balanceType), (Currency) (fieldName == LoyaltyCardField.balanceType ? value : loyaltyCard.balanceType),
(String) (fieldName == LoyaltyCardField.cardId ? value : loyaltyCard.cardId), (String) (fieldName == LoyaltyCardField.cardId ? value : loyaltyCard.cardId),
(String) (fieldName == LoyaltyCardField.barcodeId ? value : loyaltyCard.barcodeId), (String) (fieldName == LoyaltyCardField.barcodeId ? value : loyaltyCard.barcodeId),
(Barcode) (fieldName == LoyaltyCardField.barcodeType ? value : loyaltyCard.barcodeType), (CatimaBarcode) (fieldName == LoyaltyCardField.barcodeType ? value : loyaltyCard.barcodeType),
(Integer) (fieldName == LoyaltyCardField.headerColor ? value : loyaltyCard.headerColor), (Integer) (fieldName == LoyaltyCardField.headerColor ? value : loyaltyCard.headerColor),
(int) (fieldName == LoyaltyCardField.starStatus ? value : loyaltyCard.starStatus), (int) (fieldName == LoyaltyCardField.starStatus ? value : loyaltyCard.starStatus),
0, // Unimportant, always set to null in doSave so the DB updates it to the current timestamp Utils.getUnixTime(), 100
100, // Unimportant, not updated in doSave, defaults to 100 for new cards
(int) (fieldName == LoyaltyCardField.archiveStatus ? value : loyaltyCard.archiveStatus)
); );
} }
private void updateTempState(LoyaltyCardField fieldName, Object value) { private void updateTempState(LoyaltyCardField fieldName, Object value) {
tempLoyaltyCard = updateTempState(tempLoyaltyCard, fieldName, value); tempLoyaltyCard = updateTempState(tempLoyaltyCard, fieldName, value);
if (initDone && (fieldName == LoyaltyCardField.cardId || fieldName == LoyaltyCardField.barcodeId || fieldName == LoyaltyCardField.barcodeType)) {
generateBarcode();
}
hasChanged = true; hasChanged = true;
} }
@@ -229,7 +215,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
final Bundle b = intent.getExtras(); final Bundle b = intent.getExtras();
loyaltyCardId = b != null ? b.getInt(BUNDLE_ID) : 0; loyaltyCardId = b != null ? b.getInt(BUNDLE_ID) : 0;
updateLoyaltyCard = b != null && b.getBoolean(BUNDLE_UPDATE, false); updateLoyaltyCard = b != null && b.getBoolean(BUNDLE_UPDATE, false);
duplicateFromLoyaltyCardId = b != null && b.getBoolean(BUNDLE_DUPLICATE_ID, false);
cardId = b != null ? b.getString(BUNDLE_CARDID) : null; cardId = b != null ? b.getString(BUNDLE_CARDID) : null;
barcodeId = b != null ? b.getString(BUNDLE_BARCODEID) : null; barcodeId = b != null ? b.getString(BUNDLE_BARCODEID) : null;
@@ -238,7 +223,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
importLoyaltyCardUri = intent.getData(); importLoyaltyCardUri = intent.getData();
Log.d(TAG, "Edit activity: id=" + loyaltyCardId Log.d(TAG, "View activity: id=" + loyaltyCardId
+ ", updateLoyaltyCard=" + updateLoyaltyCard); + ", updateLoyaltyCard=" + updateLoyaltyCard);
} }
@@ -301,7 +286,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.loyalty_card_edit_activity); setContentView(R.layout.loyalty_card_edit_activity);
toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
@@ -320,7 +305,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
tabs = findViewById(R.id.tabs); tabs = findViewById(R.id.tabs);
thumbnail = findViewById(R.id.thumbnail); thumbnail = findViewById(R.id.thumbnail);
thumbnailEditIcon = findViewById(R.id.thumbnailEditIcon);
storeFieldEdit = findViewById(R.id.storeNameEdit); storeFieldEdit = findViewById(R.id.storeNameEdit);
noteFieldEdit = findViewById(R.id.noteEdit); noteFieldEdit = findViewById(R.id.noteEdit);
groupsChips = findViewById(R.id.groupChips); groupsChips = findViewById(R.id.groupChips);
@@ -331,7 +315,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
barcodeIdField = findViewById(R.id.barcodeIdField); barcodeIdField = findViewById(R.id.barcodeIdField);
barcodeTypeField = findViewById(R.id.barcodeTypeField); barcodeTypeField = findViewById(R.id.barcodeTypeField);
barcodeImage = findViewById(R.id.barcode); barcodeImage = findViewById(R.id.barcode);
barcodeImage.setClipToOutline(true);
barcodeImageLayout = findViewById(R.id.barcodeLayout); barcodeImageLayout = findViewById(R.id.barcodeLayout);
barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout); barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
cardImageFrontHolder = findViewById(R.id.frontImageHolder); cardImageFrontHolder = findViewById(R.id.frontImageHolder);
@@ -340,10 +323,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
cardImageBack = findViewById(R.id.backImage); cardImageBack = findViewById(R.id.backImage);
enterButton = findViewById(R.id.enterButton); enterButton = findViewById(R.id.enterButton);
cardImageFront.setBackgroundColor(getThemeColor());
cardImageBack.setBackgroundColor(getThemeColor());
barcodeImageGenerationFinishedCallback = () -> { warnOnInvalidBarcodeType = () -> {
if (!(boolean) barcodeImage.getTag()) { if (!(boolean) barcodeImage.getTag()) {
barcodeImageLayout.setVisibility(View.GONE);
Toast.makeText(LoyaltyCardEditActivity.this, getString(R.string.wrongValueForBarcodeType), Toast.LENGTH_LONG).show(); Toast.makeText(LoyaltyCardEditActivity.this, getString(R.string.wrongValueForBarcodeType), Toast.LENGTH_LONG).show();
} }
}; };
@@ -406,10 +390,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, int count) { public void onTextChanged(CharSequence s, int start, int before, int count) {
try { try {
BigDecimal balance = Utils.parseBalance(s.toString(), tempLoyaltyCard.balanceType); BigDecimal balance = Utils.parseCurrency(s.toString(), Utils.currencyHasDecimals(tempLoyaltyCard.balanceType));
updateTempState(LoyaltyCardField.balance, balance); updateTempState(LoyaltyCardField.balance, balance);
validBalance = true; validBalance = true;
} catch (ParseException e) {
} catch (NumberFormatException e) {
validBalance = false; validBalance = false;
e.printStackTrace(); e.printStackTrace();
} }
@@ -456,10 +441,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
for (int i = locales.size() - 1; i >= 0; i--) { for (int i = locales.size() - 1; i >= 0; i--) {
Locale locale = locales.get(i); Locale locale = locales.get(i);
currencyPrioritizeLocaleSymbols(currencyList, locale); String currencySymbol = Currency.getInstance(locale).getSymbol();
currencyList.remove(currencySymbol);
currencyList.add(0, currencySymbol);
} }
} else { } else {
currencyPrioritizeLocaleSymbols(currencyList, mSystemLocale); String currencySymbol = Currency.getInstance(mSystemLocale).getSymbol();
currencyList.remove(currencySymbol);
currencyList.add(0, currencySymbol);
} }
currencyList.add(0, getString(R.string.points)); currencyList.add(0, getString(R.string.points));
@@ -535,6 +524,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
} else { } else {
updateTempState(LoyaltyCardField.barcodeId, s.toString()); updateTempState(LoyaltyCardField.barcodeId, s.toString());
} }
generateOrHideBarcode();
} }
@Override @Override
@@ -555,10 +546,18 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
updateTempState(LoyaltyCardField.barcodeType, null); updateTempState(LoyaltyCardField.barcodeType, null);
} else { } else {
try { try {
updateTempState(LoyaltyCardField.barcodeType, barcodeTypeField.getTag()); CatimaBarcode barcodeFormat = CatimaBarcode.fromPrettyName(s.toString());
updateTempState(LoyaltyCardField.barcodeType, barcodeFormat);
if (!barcodeFormat.isSupported()) {
Toast.makeText(LoyaltyCardEditActivity.this, getString(R.string.unsupportedBarcodeType), Toast.LENGTH_LONG).show();
}
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
} }
} }
generateOrHideBarcode();
} }
} }
@@ -670,6 +669,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
}); });
mCropperOptions = new UCrop.Options(); mCropperOptions = new UCrop.Options();
setCropperTheme();
} }
// ucrop 2.2.6 initial aspect ratio is glitched when 0x0 is used as the initial ratio option // ucrop 2.2.6 initial aspect ratio is glitched when 0x0 is used as the initial ratio option
@@ -689,24 +689,13 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
new AspectRatio(getResources().getString(R.string.ucrop_label_original).toUpperCase(), sourceWidth, sourceHeight), new AspectRatio(getResources().getString(R.string.ucrop_label_original).toUpperCase(), sourceWidth, sourceHeight),
new AspectRatio(getResources().getString(R.string.card).toUpperCase(), 85.6f, 53.98f) new AspectRatio(getResources().getString(R.string.card).toUpperCase(), 85.6f, 53.98f)
); );
}
// Fix theming private void setCropperTheme() {
mCropperOptions.setToolbarColor(getThemeColor());
int colorPrimary = MaterialColors.getColor(this, R.attr.colorPrimary, ContextCompat.getColor(this, R.color.md_theme_light_primary)); mCropperOptions.setStatusBarColor(getThemeColor());
int colorOnPrimary = MaterialColors.getColor(this, R.attr.colorOnPrimary, ContextCompat.getColor(this, R.color.md_theme_light_onPrimary)); mCropperOptions.setToolbarWidgetColor(Color.WHITE);
int colorSurface = MaterialColors.getColor(this, R.attr.colorSurface, ContextCompat.getColor(this, R.color.md_theme_light_surface)); mCropperOptions.setActiveControlsWidgetColor(getThemeColor());
int colorOnSurface = MaterialColors.getColor(this, R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
int colorBackground = MaterialColors.getColor(this, android.R.attr.colorBackground, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
mCropperOptions.setToolbarColor(colorSurface);
mCropperOptions.setStatusBarColor(colorSurface);
mCropperOptions.setToolbarWidgetColor(colorOnSurface);
mCropperOptions.setRootViewBackgroundColor(colorBackground);
// set tool tip to be the darker of primary color
if (Utils.isDarkModeEnabled(this)) {
mCropperOptions.setActiveControlsWidgetColor(colorOnPrimary);
} else {
mCropperOptions.setActiveControlsWidgetColor(colorPrimary);
}
} }
@Override @Override
@@ -750,8 +739,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
onResuming = true; onResuming = true;
if (tempLoyaltyCard == null) { if (tempLoyaltyCard == null) {
if (updateLoyaltyCard || duplicateFromLoyaltyCardId) { if (updateLoyaltyCard) {
tempLoyaltyCard = DBHelper.getLoyaltyCard(mDatabase, loyaltyCardId); tempLoyaltyCard = DBHelper.getLoyaltyCard(mDatabase, loyaltyCardId);
if (tempLoyaltyCard == null) { if (tempLoyaltyCard == null) {
Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId); Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId);
@@ -769,7 +759,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
} }
} else { } else {
// New card, use default values // New card, use default values
tempLoyaltyCard = new LoyaltyCard(-1, "", "", null, new BigDecimal("0"), null, "", null, null, null, 0, Utils.getUnixTime(), 100,0); tempLoyaltyCard = new LoyaltyCard(-1, "", "", null, new BigDecimal("0"), null, "", null, null, null, 0, Utils.getUnixTime(), 100);
} }
} }
@@ -777,11 +767,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
if (!initDone) { if (!initDone) {
if (updateLoyaltyCard) { if (updateLoyaltyCard) {
setTitle(R.string.editCardTitle); setTitle(R.string.editCardTitle);
} else {
setTitle(R.string.addCardTitle);
}
if (updateLoyaltyCard || duplicateFromLoyaltyCardId) {
if (!mFrontImageUnsaved && !croppedFrontImage() && !mFrontImageRemoved) { if (!mFrontImageUnsaved && !croppedFrontImage() && !mFrontImageRemoved) {
setCardImage(cardImageFront, Utils.retrieveCardImage(this, tempLoyaltyCard.id, ImageLocationType.front), true); setCardImage(cardImageFront, Utils.retrieveCardImage(this, tempLoyaltyCard.id, ImageLocationType.front), true);
} }
@@ -816,7 +802,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
formatBalanceCurrencyField(tempLoyaltyCard.balanceType); formatBalanceCurrencyField(tempLoyaltyCard.balanceType);
cardIdFieldView.setText(tempLoyaltyCard.cardId); cardIdFieldView.setText(tempLoyaltyCard.cardId);
barcodeIdField.setText(tempLoyaltyCard.barcodeId != null ? tempLoyaltyCard.barcodeId : getString(R.string.sameAsCardId)); barcodeIdField.setText(tempLoyaltyCard.barcodeId != null ? tempLoyaltyCard.barcodeId : getString(R.string.sameAsCardId));
setbarcodeTypeField(tempLoyaltyCard.barcodeType); barcodeTypeField.setText(tempLoyaltyCard.barcodeType != null ? tempLoyaltyCard.barcodeType.prettyName() : getString(R.string.noBarcode));
if (groupsChips.getChildCount() == 0) { if (groupsChips.getChildCount() == 0) {
List<Group> existingGroups = DBHelper.getGroups(mDatabase); List<Group> existingGroups = DBHelper.getGroups(mDatabase);
@@ -859,16 +845,24 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
// Generate random header color // Generate random header color
if (tempLoyaltyCard.headerColor == null) { if (tempLoyaltyCard.headerColor == null) {
// Select a random color to start out with. // Select a random color to start out with.
updateTempState(LoyaltyCardField.headerColor, Utils.getRandomHeaderColor(this)); TypedArray colors = getResources().obtainTypedArray(R.array.letter_tile_colors);
final int color = (int) (Math.random() * colors.length());
updateTempState(LoyaltyCardField.headerColor, colors.getColor(color, Color.BLACK));
colors.recycle();
}
// It can't be null because we set it in updateTempState but SpotBugs insists it can be
// NP_NULL_ON_SOME_PATH: Possible null pointer dereference
if (tempLoyaltyCard.headerColor != null) {
thumbnail.setOnClickListener(new ChooseCardImage());
} }
// Update from intent // Update from intent
if (barcodeType != null) { if (barcodeType != null) {
try { try {
Barcode barcode = BarcodeFactory.fromName(barcodeType); barcodeTypeField.setText(CatimaBarcode.fromName(barcodeType).prettyName());
setbarcodeTypeField(barcode);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
setbarcodeTypeField(null); barcodeTypeField.setText(getString(R.string.noBarcode));
} }
} }
@@ -895,7 +889,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
hasChanged = hadChanges; hasChanged = hadChanges;
} }
generateBarcode(); generateOrHideBarcode();
enterButton.setOnClickListener(new EditCardIdAndBarcode()); enterButton.setOnClickListener(new EditCardIdAndBarcode());
barcodeImage.setOnClickListener(new EditCardIdAndBarcode()); barcodeImage.setOnClickListener(new EditCardIdAndBarcode());
@@ -909,28 +903,13 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
generateIcon(storeFieldEdit.getText().toString()); generateIcon(storeFieldEdit.getText().toString());
// It can't be null because we set it in updateTempState but SpotBugs insists it can be
// NP_NULL_ON_SOME_PATH: Possible null pointer dereference and
// NP_NULL_PARAM_DEREF: Method call passes null for non-null parameter
Integer headerColor = tempLoyaltyCard.headerColor;
if (headerColor != null) {
thumbnail.setOnClickListener(new ChooseCardImage());
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
}
onResuming = false; onResuming = false;
} }
protected void setColorFromIcon() { protected void setColorFromIcon() {
Object icon = thumbnail.getTag(); Object icon = thumbnail.getTag();
if (icon != null && (icon instanceof Bitmap)) { if (icon != null && (icon instanceof Bitmap)) {
int headerColor = Utils.getHeaderColorFromImage((Bitmap) icon, tempLoyaltyCard.headerColor != null ? tempLoyaltyCard.headerColor : R.attr.colorPrimary); updateTempState(LoyaltyCardField.headerColor, new Palette.Builder((Bitmap) icon).generate().getDominantColor(tempLoyaltyCard.headerColor != null ? tempLoyaltyCard.headerColor : ContextCompat.getColor(this, R.color.colorPrimary)));
updateTempState(LoyaltyCardField.headerColor, headerColor);
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
} else { } else {
Log.d("setColorFromIcon", "attempting header color change from icon but icon does not exist"); Log.d("setColorFromIcon", "attempting header color change from icon but icon does not exist");
} }
@@ -946,11 +925,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
} }
} }
private void setbarcodeTypeField(Barcode barcode) {
barcodeTypeField.setTag(barcode);
barcodeTypeField.setText(barcode != null ? barcode.prettyName() : getString(R.string.noBarcode));
}
protected static void formatExpiryField(Context context, EditText expiryField, Date expiry) { protected static void formatExpiryField(Context context, EditText expiryField, Date expiry) {
expiryField.setTag(expiry); expiryField.setTag(expiry);
@@ -979,12 +953,16 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
super.onRequestPermissionsResult(requestCode, permissions, grantResults); super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_FRONT) { try {
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_FRONT); if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_FRONT) {
} else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_BACK) { takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_FRONT);
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_BACK); } else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_BACK) {
} else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_ICON) { takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_BACK);
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_ICON); } else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_ICON) {
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_ICON);
}
} catch (Exception e) {
e.printStackTrace();
} }
} }
} }
@@ -1113,9 +1091,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
public void onColorSelected(int dialogId, int color) { public void onColorSelected(int dialogId, int color) {
updateTempState(LoyaltyCardField.headerColor, color); updateTempState(LoyaltyCardField.headerColor, color);
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(color) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(color) ? Color.WHITE : Color.BLACK);
// Unset image if set // Unset image if set
thumbnail.setTag(null); thumbnail.setTag(null);
@@ -1183,14 +1158,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
Intent i = new Intent(Intent.ACTION_PICK); Intent i = new Intent(Intent.ACTION_PICK);
i.setType("image/*"); i.setType("image/*");
mPhotoPickerLauncher.launch(i);
try {
mPhotoPickerLauncher.launch(i);
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
return null; return null;
}); });
@@ -1220,10 +1188,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
callable.call(); callable.call();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
// Rethrow as NoSuchElementException
// This isn't really true, but a View.OnClickListener doesn't allow throwing other types
throw new NoSuchElementException(e.getMessage());
} }
}) })
.show(); .show();
@@ -1257,16 +1221,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
int day = c.get(Calendar.DAY_OF_MONTH); int day = c.get(Calendar.DAY_OF_MONTH);
// Create a new instance of DatePickerDialog and return it // Create a new instance of DatePickerDialog and return it
DatePickerDialog datePickerDialog = new DatePickerDialog(getActivity(), this, year, month, day); return new DatePickerDialog(getActivity(), this, year, month, day);
datePickerDialog.getDatePicker().setMinDate(getMinDateOfDatePicker());
return datePickerDialog;
}
private long getMinDateOfDatePicker()
{
Calendar minDateCalendar = Calendar.getInstance();
minDateCalendar.set(1970, 0, 1);
return minDateCalendar.getTimeInMillis();
} }
public void onDateSet(DatePicker view, int year, int month, int day) { public void onDateSet(DatePicker view, int year, int month, int day) {
@@ -1288,11 +1243,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
} }
private void doSave() { private void doSave() {
if (isFinishing()) {
// If we are done saving, ignore any queued up save button presses
return;
}
if (tempStoredOldBarcodeValue != null) { if (tempStoredOldBarcodeValue != null) {
askBarcodeChange(this::doSave); askBarcodeChange(this::doSave);
return; return;
@@ -1320,40 +1270,41 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
selectedGroups.add((Group) chip.getTag()); selectedGroups.add((Group) chip.getTag());
} }
// Both update and new card save with lastUsed set to null if (updateLoyaltyCard) { //update of "starStatus" not necessary, since it cannot be changed in this activity (only in ViewActivity)
// This makes the DBHelper set it to the current date DBHelper.updateLoyaltyCard(mDatabase, loyaltyCardId, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor);
// So that new and edited card are always on top when sorting by recently used try {
if (updateLoyaltyCard) { Utils.saveCardImage(this, (Bitmap) cardImageFront.getTag(), loyaltyCardId, ImageLocationType.front);
DBHelper.updateLoyaltyCard(mDatabase, loyaltyCardId, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, tempLoyaltyCard.starStatus, null, tempLoyaltyCard.archiveStatus); Utils.saveCardImage(this, (Bitmap) cardImageBack.getTag(), loyaltyCardId, ImageLocationType.back);
Utils.saveCardImage(this, (Bitmap) thumbnail.getTag(), loyaltyCardId, ImageLocationType.icon);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
} else { } else {
loyaltyCardId = (int) DBHelper.insertLoyaltyCard(mDatabase, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, 0, null, 0); loyaltyCardId = (int) DBHelper.insertLoyaltyCard(mDatabase, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, 0, tempLoyaltyCard.lastUsed);
try {
Utils.saveCardImage(this, (Bitmap) cardImageFront.getTag(), loyaltyCardId, ImageLocationType.front);
Utils.saveCardImage(this, (Bitmap) cardImageBack.getTag(), loyaltyCardId, ImageLocationType.back);
Utils.saveCardImage(this, (Bitmap) thumbnail.getTag(), loyaltyCardId, ImageLocationType.icon);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} }
try {
Utils.saveCardImage(this, (Bitmap) cardImageFront.getTag(), loyaltyCardId, ImageLocationType.front);
Utils.saveCardImage(this, (Bitmap) cardImageBack.getTag(), loyaltyCardId, ImageLocationType.back);
Utils.saveCardImage(this, (Bitmap) thumbnail.getTag(), loyaltyCardId, ImageLocationType.icon);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Log.i(TAG, "Set " + loyaltyCardId + " to " + cardId + " (update: " + updateLoyaltyCard + ")");
DBHelper.setLoyaltyCardGroups(mDatabase, loyaltyCardId, selectedGroups); DBHelper.setLoyaltyCardGroups(mDatabase, loyaltyCardId, selectedGroups);
ShortcutHelper.updateShortcuts(this, DBHelper.getLoyaltyCard(mDatabase, loyaltyCardId)); ShortcutHelper.updateShortcuts(this, DBHelper.getLoyaltyCard(mDatabase, loyaltyCardId));
if (duplicateFromLoyaltyCardId) {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
startActivity(intent);
}
finish(); finish();
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.card_add_menu, menu); if (updateLoyaltyCard) {
getMenuInflater().inflate(R.menu.card_update_menu, menu);
} else {
getMenuInflater().inflate(R.menu.card_add_menu, menu);
}
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
@@ -1362,9 +1313,30 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId(); int id = item.getItemId();
if (id == android.R.id.home) { switch (id) {
askBeforeQuitIfChanged(); case android.R.id.home:
return true; askBeforeQuitIfChanged();
break;
case R.id.action_delete:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.deleteTitle);
builder.setMessage(R.string.deleteConfirmation);
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
Log.e(TAG, "Deleting card: " + loyaltyCardId);
DBHelper.deleteLoyaltyCard(mDatabase, LoyaltyCardEditActivity.this, loyaltyCardId);
ShortcutHelper.removeShortcut(LoyaltyCardEditActivity.this, loyaltyCardId);
finish();
dialog.dismiss();
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
AlertDialog dialog = builder.create();
dialog.show();
return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
@@ -1417,40 +1389,36 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
} }
} }
} }
Intent ucropIntent = UCrop.of( mCropperLauncher.launch(
sourceUri, UCrop.of(
destUri sourceUri,
).withOptions(mCropperOptions) destUri
.getIntent(this); ).withOptions(mCropperOptions)
ucropIntent.setClass(this, UCropWrapper.class); .getIntent(this)
for (int i = 0; i < toolbar.getChildCount(); i++) { );
// send toolbar font details to ucrop wrapper
View child = toolbar.getChildAt(i);
if (child instanceof AppCompatTextView) {
AppCompatTextView childTextView = (AppCompatTextView) child;
ucropIntent.putExtra(UCropWrapper.UCROP_TOOLBAR_TYPEFACE_STYLE, childTextView.getTypeface().getStyle());
break;
}
}
mCropperLauncher.launch(ucropIntent);
} }
private void generateBarcode() { private void showBarcode() {
if (tempLoyaltyCard == null) { barcodeImageLayout.setVisibility(View.VISIBLE);
return; }
}
mTasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false); private void hideBarcode() {
barcodeImageLayout.setVisibility(View.GONE);
}
private void generateOrHideBarcode() {
String cardIdString = tempLoyaltyCard.barcodeId != null ? tempLoyaltyCard.barcodeId : tempLoyaltyCard.cardId; String cardIdString = tempLoyaltyCard.barcodeId != null ? tempLoyaltyCard.barcodeId : tempLoyaltyCard.cardId;
CatimaBarcode barcodeFormat = tempLoyaltyCard.barcodeType; CatimaBarcode barcodeFormat = tempLoyaltyCard.barcodeType;
if (cardIdString == null || barcodeFormat == null) { if (barcodeFormat == null || cardIdString.isEmpty() || !barcodeFormat.isSupported()) {
barcodeImageLayout.setVisibility(View.GONE); hideBarcode();
return; } else {
generateBarcode(cardIdString, barcodeFormat);
} }
}
barcodeImageLayout.setVisibility(View.VISIBLE); private void generateBarcode(String cardIdString, CatimaBarcode barcodeFormat) {
mTasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false);
if (barcodeImage.getHeight() == 0) { if (barcodeImage.getHeight() == 0) {
Log.d(TAG, "ImageView size is not known known at start, waiting for load"); Log.d(TAG, "ImageView size is not known known at start, waiting for load");
@@ -1463,15 +1431,17 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this); barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Log.d(TAG, "ImageView size now known"); Log.d(TAG, "ImageView size now known");
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, barcodeImageGenerationFinishedCallback, true); BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, warnOnInvalidBarcodeType);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter); mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
} }
}); });
} else { } else {
Log.d(TAG, "ImageView size known known, creating barcode"); Log.d(TAG, "ImageView size known known, creating barcode");
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, barcodeImageGenerationFinishedCallback, true); BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, warnOnInvalidBarcodeType);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter); mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
} }
showBarcode();
} }
private void generateIcon(String store) { private void generateIcon(String store) {
@@ -1501,36 +1471,32 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
} }
View cardPart = findViewById(R.id.cardPart); View cardPart = findViewById(R.id.cardPart);
View optionsPart = findViewById(R.id.optionsPart); View barcodePart = findViewById(R.id.barcodePart);
View picturesPart = findViewById(R.id.picturesPart); View picturesPart = findViewById(R.id.picturesPart);
if (getString(R.string.card).equals(part)) { if (getString(R.string.card).equals(part)) {
cardPart.setVisibility(View.VISIBLE); cardPart.setVisibility(View.VISIBLE);
optionsPart.setVisibility(View.GONE); barcodePart.setVisibility(View.GONE);
picturesPart.setVisibility(View.GONE);
// Explicitly hide barcode (fixes blurriness on redraw)
hideBarcode();
} else if (getString(R.string.barcode).equals(part)) {
cardPart.setVisibility(View.GONE);
barcodePart.setVisibility(View.VISIBLE);
picturesPart.setVisibility(View.GONE); picturesPart.setVisibility(View.GONE);
// Redraw barcode due to size change (Visibility.GONE sets it to 0) // Redraw barcode due to size change (Visibility.GONE sets it to 0)
generateBarcode(); generateOrHideBarcode();
} else if (getString(R.string.options).equals(part)) {
cardPart.setVisibility(View.GONE);
optionsPart.setVisibility(View.VISIBLE);
picturesPart.setVisibility(View.GONE);
} else if (getString(R.string.photos).equals(part)) { } else if (getString(R.string.photos).equals(part)) {
cardPart.setVisibility(View.GONE); cardPart.setVisibility(View.GONE);
optionsPart.setVisibility(View.GONE); barcodePart.setVisibility(View.GONE);
picturesPart.setVisibility(View.VISIBLE); picturesPart.setVisibility(View.VISIBLE);
// Explicitly hide barcode (fixes blurriness on redraw)
hideBarcode();
} else { } else {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }
private void currencyPrioritizeLocaleSymbols(ArrayList<String> currencyList, Locale locale) {
try {
String currencySymbol = Currency.getInstance(locale).getSymbol();
currencyList.remove(currencySymbol);
currencyList.add(0, currencySymbol);
} catch (IllegalArgumentException e) {
Log.d(TAG, "Could not get currency data for locale info: " + e);
}
}
} }

View File

@@ -11,6 +11,5 @@ public enum LoyaltyCardField {
barcodeId, barcodeId,
barcodeType, barcodeType,
headerColor, headerColor,
starStatus, starStatus
archiveStatus
} }

View File

File diff suppressed because it is too large Load Diff

View File

@@ -17,20 +17,10 @@ import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts; import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ActionMode; import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
@@ -42,7 +32,6 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -58,20 +47,16 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
private ActionMode mCurrentActionMode; private ActionMode mCurrentActionMode;
private SearchView mSearchView; private SearchView mSearchView;
private GestureDetector mGestureDetector; private GestureDetector mGestureDetector;
private int mLoyaltyCardCount = 0;
protected String mFilter = ""; protected String mFilter = "";
protected Object mGroup = null; protected Object mGroup = null;
protected DBHelper.LoyaltyCardOrder mOrder = DBHelper.LoyaltyCardOrder.Alpha; protected DBHelper.LoyaltyCardOrder mOrder = DBHelper.LoyaltyCardOrder.Alpha;
protected DBHelper.LoyaltyCardOrderDirection mOrderDirection = DBHelper.LoyaltyCardOrderDirection.Ascending; protected DBHelper.LoyaltyCardOrderDirection mOrderDirection = DBHelper.LoyaltyCardOrderDirection.Ascending;
protected int selectedTab = 0; protected int selectedTab = 0;
private RecyclerView mCardList; private RecyclerView mCardList;
private View mHelpSection; private View mHelpText;
private View mNoMatchingCardsText; private View mNoMatchingCardsText;
private View mNoGroupCardsText; private View mNoGroupCardsText;
private boolean mArchiveMode;
public static final String BUNDLE_ARCHIVE_MODE = "archiveMode";
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher; private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
private ActivityResultLauncher<Intent> mSettingsLauncher; private ActivityResultLauncher<Intent> mSettingsLauncher;
@@ -155,8 +140,10 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
} }
builder.setPositiveButton(R.string.confirm, (dialog, which) -> { builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
DBHelper db = new DBHelper(MainActivity.this);
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) { for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Deleting card: " + loyaltyCard.id); Log.e(TAG, "Deleting card: " + loyaltyCard.id);
DBHelper.deleteLoyaltyCard(mDatabase, MainActivity.this, loyaltyCard.id); DBHelper.deleteLoyaltyCard(mDatabase, MainActivity.this, loyaltyCard.id);
@@ -166,7 +153,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
TabLayout.Tab tab = ((TabLayout) findViewById(R.id.groups)).getTabAt(selectedTab); TabLayout.Tab tab = ((TabLayout) findViewById(R.id.groups)).getTabAt(selectedTab);
mGroup = tab != null ? tab.getTag() : null; mGroup = tab != null ? tab.getTag() : null;
updateLoyaltyCardList(true); updateLoyaltyCardList();
dialog.dismiss(); dialog.dismiss();
}); });
@@ -176,44 +163,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
return true; return true;
} }
else if(inputItem.getItemId() == R.id.action_archive){
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Archiving card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id,1);
updateLoyaltyCardList(false);
inputMode.finish();
invalidateOptionsMenu();
}
return true;
}
else if(inputItem.getItemId() == R.id.action_unarchive){
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Unarchiving card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id,0);
updateLoyaltyCardList(false);
inputMode.finish();
invalidateOptionsMenu();
}
return true;
}
else if(inputItem.getItemId() == R.id.action_star){
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Starring card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 1);
updateLoyaltyCardList(false);
inputMode.finish();
}
return true;
}
else if(inputItem.getItemId() == R.id.action_unstar){
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Unstarring card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 0);
updateLoyaltyCardList(false);
inputMode.finish();
}
return true;
}
return false; return false;
} }
@@ -227,28 +176,13 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
@Override @Override
protected void onCreate(Bundle inputSavedInstanceState) { protected void onCreate(Bundle inputSavedInstanceState) {
extractIntentFields(getIntent());
SplashScreen.installSplashScreen(this);
super.onCreate(inputSavedInstanceState); super.onCreate(inputSavedInstanceState);
if(!mArchiveMode) { SplashScreen.installSplashScreen(this);
setTitle(R.string.app_name); setTitle(R.string.app_name);
setContentView(R.layout.main_activity); setContentView(R.layout.main_activity);
}
else{
setTitle(R.string.archiveList);
setContentView(R.layout.archive_activity);
}
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
if(mArchiveMode){
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
mDatabase = new DBHelper(this).getWritableDatabase(); mDatabase = new DBHelper(this).getWritableDatabase();
TabLayout groupsTabLayout = findViewById(R.id.groups); TabLayout groupsTabLayout = findViewById(R.id.groups);
@@ -258,7 +192,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
selectedTab = tab.getPosition(); selectedTab = tab.getPosition();
Log.d("onTabSelected", "Tab Position " + tab.getPosition()); Log.d("onTabSelected", "Tab Position " + tab.getPosition());
mGroup = tab.getTag(); mGroup = tab.getTag();
updateLoyaltyCardList(false); updateLoyaltyCardList();
// Store active tab in Shared Preference to restore next app launch // Store active tab in Shared Preference to restore next app launch
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences( SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab), getString(R.string.sharedpreference_active_tab),
@@ -283,11 +217,12 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
View.OnTouchListener gestureTouchListener = (v, event) -> mGestureDetector.onTouchEvent(event); View.OnTouchListener gestureTouchListener = (v, event) -> mGestureDetector.onTouchEvent(event);
mHelpSection = findViewById(R.id.helpSection); mHelpText = findViewById(R.id.helpText);
mNoMatchingCardsText = findViewById(R.id.noMatchingCardsText); mNoMatchingCardsText = findViewById(R.id.noMatchingCardsText);
mNoGroupCardsText = findViewById(R.id.noGroupCardsText); mNoGroupCardsText = findViewById(R.id.noGroupCardsText);
mCardList = findViewById(R.id.list); mCardList = findViewById(R.id.list);
mHelpText.setOnTouchListener(gestureTouchListener);
mNoMatchingCardsText.setOnTouchListener(gestureTouchListener); mNoMatchingCardsText.setOnTouchListener(gestureTouchListener);
mCardList.setOnTouchListener(gestureTouchListener); mCardList.setOnTouchListener(gestureTouchListener);
mNoGroupCardsText.setOnTouchListener(gestureTouchListener); mNoGroupCardsText.setOnTouchListener(gestureTouchListener);
@@ -297,7 +232,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
registerForContextMenu(mCardList); registerForContextMenu(mCardList);
mGroup = null; mGroup = null;
updateLoyaltyCardList(true); updateLoyaltyCardList();
/* /*
* This was added for Huawei, but Huawei is just too much of a fucking pain. * This was added for Huawei, but Huawei is just too much of a fucking pain.
@@ -362,8 +297,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
mAdapter.refreshState();
if (mCurrentActionMode != null) { if (mCurrentActionMode != null) {
mAdapter.clearSelections(); mAdapter.clearSelections();
mCurrentActionMode.finish(); mCurrentActionMode.finish();
@@ -403,28 +336,24 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
assert tab != null; assert tab != null;
mGroup = tab.getTag(); mGroup = tab.getTag();
} }
updateLoyaltyCardList(true); updateLoyaltyCardList();
// End of active tab logic // End of active tab logic
if (!mArchiveMode) { FloatingActionButton addButton = findViewById(R.id.fabAdd);
FloatingActionButton addButton = findViewById(R.id.fabAdd); addButton.setOnClickListener(v -> {
Intent intent = new Intent(getApplicationContext(), ScanActivity.class);
addButton.setOnClickListener(v -> { Bundle bundle = new Bundle();
Intent intent = new Intent(getApplicationContext(), ScanActivity.class); if (selectedTab != 0) {
Bundle bundle = new Bundle(); bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, groupsTabLayout.getTabAt(selectedTab).getText().toString());
if (selectedTab != 0) { }
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, groupsTabLayout.getTabAt(selectedTab).getText().toString()); intent.putExtras(bundle);
} mBarcodeScannerLauncher.launch(intent);
intent.putExtras(bundle); });
mBarcodeScannerLauncher.launch(intent); addButton.bringToFront();
});
addButton.bringToFront();
}
} }
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (!mSearchView.isIconified()) { if (!mSearchView.isIconified()) {
mSearchView.setIconified(true); mSearchView.setIconified(true);
return; return;
@@ -433,37 +362,20 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
super.onBackPressed(); super.onBackPressed();
} }
private void displayCardSetupOptions(Menu menu, boolean shouldShow) { private void updateLoyaltyCardList() {
for (int id : new int[]{R.id.action_search, R.id.action_unfold, R.id.action_sort}) {
menu.findItem(id).setVisible(shouldShow);
}
}
private void updateLoyaltyCardCount() {
mLoyaltyCardCount = DBHelper.getLoyaltyCardCount(mDatabase);
}
private void updateLoyaltyCardList(boolean updateCount) {
Group group = null; Group group = null;
if (mGroup != null) { if (mGroup != null) {
group = (Group) mGroup; group = (Group) mGroup;
} }
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase, mFilter, group, mOrder, mOrderDirection, mArchiveMode ? DBHelper.LoyaltyCardArchiveFilter.Archived : DBHelper.LoyaltyCardArchiveFilter.Unarchived)); mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase, mFilter, group, mOrder, mOrderDirection));
if (updateCount) { if (DBHelper.getLoyaltyCardCount(mDatabase) > 0) {
updateLoyaltyCardCount();
// Update menu icons if necessary
invalidateOptionsMenu();
}
if (mLoyaltyCardCount > 0) {
// We want the cardList to be visible regardless of the filtered match count // We want the cardList to be visible regardless of the filtered match count
// to ensure that the noMatchingCardsText doesn't end up being shown below // to ensure that the noMatchingCardsText doesn't end up being shown below
// the keyboard // the keyboard
mHelpSection.setVisibility(View.GONE); mHelpText.setVisibility(View.GONE);
mNoGroupCardsText.setVisibility(View.GONE); mNoGroupCardsText.setVisibility(View.GONE);
if (mAdapter.getItemCount() > 0) { if (mAdapter.getItemCount() > 0) {
mCardList.setVisibility(View.VISIBLE); mCardList.setVisibility(View.VISIBLE);
mNoMatchingCardsText.setVisibility(View.GONE); mNoMatchingCardsText.setVisibility(View.GONE);
@@ -480,15 +392,8 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
} }
} }
} else { } else {
if (mArchiveMode) {
// If an user deletes the last card in archive mode, we should close the activity
// This will move us back to the main view
finish();
}
mCardList.setVisibility(View.GONE); mCardList.setVisibility(View.GONE);
mHelpSection.setVisibility(View.VISIBLE); mHelpText.setVisibility(View.VISIBLE);
mNoMatchingCardsText.setVisibility(View.GONE); mNoMatchingCardsText.setVisibility(View.GONE);
mNoGroupCardsText.setVisibility(View.GONE); mNoGroupCardsText.setVisibility(View.GONE);
} }
@@ -498,11 +403,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
} }
} }
private void extractIntentFields(Intent intent) {
final Bundle b = intent.getExtras();
mArchiveMode = b != null && b.getBoolean(BUNDLE_ARCHIVE_MODE, false);
}
public void updateTabGroups(TabLayout groupsTabLayout) { public void updateTabGroups(TabLayout groupsTabLayout) {
List<Group> newGroups = DBHelper.getGroups(mDatabase); List<Group> newGroups = DBHelper.getGroups(mDatabase);
@@ -527,19 +427,11 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
} }
groupsTabLayout.setVisibility(View.VISIBLE); groupsTabLayout.setVisibility(View.VISIBLE);
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu inputMenu) { public boolean onCreateOptionsMenu(Menu inputMenu) {
if(!mArchiveMode) getMenuInflater().inflate(R.menu.main_menu, inputMenu);
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
else{
getMenuInflater().inflate(R.menu.archive_menu, inputMenu);
}
Utils.updateMenuCardDetailsButtonState(inputMenu.findItem(R.id.action_unfold), mAdapter.showingDetails());
displayCardSetupOptions(inputMenu, mLoyaltyCardCount > 0);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
if (searchManager != null) { if (searchManager != null) {
@@ -566,21 +458,12 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition()); TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition());
mGroup = currentTab != null ? currentTab.getTag() : null; mGroup = currentTab != null ? currentTab.getTag() : null;
updateLoyaltyCardList(false); updateLoyaltyCardList();
return true; return true;
} }
}); });
} }
if(!mArchiveMode) {
if (DBHelper.getArchivedCardsCount(mDatabase) == 0) {
inputMenu.findItem(R.id.action_archived).setVisible(false);
} else {
inputMenu.findItem(R.id.action_archived).setVisible(true);
}
}
return super.onCreateOptionsMenu(inputMenu); return super.onCreateOptionsMenu(inputMenu);
} }
@@ -588,18 +471,24 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
public boolean onOptionsItemSelected(MenuItem inputItem) { public boolean onOptionsItemSelected(MenuItem inputItem) {
int id = inputItem.getItemId(); int id = inputItem.getItemId();
if (id == android.R.id.home) {
onBackPressed();
}
if (id == R.id.action_unfold) { if (id == R.id.action_unfold) {
mAdapter.showDetails(!mAdapter.showingDetails()); boolean shouldShow = !mAdapter.showingDetails();
invalidateOptionsMenu();
if (shouldShow) {
inputItem.setIcon(R.drawable.ic_baseline_unfold_less_24);
inputItem.setTitle(R.string.action_hide_details);
} else {
inputItem.setIcon(R.drawable.ic_baseline_unfold_more_24);
inputItem.setTitle(R.string.action_show_details);
}
mAdapter.showDetails(shouldShow);
return true; return true;
} }
if (id == R.id.action_sort) { if (id == R.id.action_sort) {
TabLayout.Tab tab = ((TabLayout) findViewById(R.id.groups)).getTabAt(selectedTab);
AtomicInteger currentIndex = new AtomicInteger(); AtomicInteger currentIndex = new AtomicInteger();
List<DBHelper.LoyaltyCardOrder> loyaltyCardOrders = Arrays.asList(DBHelper.LoyaltyCardOrder.values()); List<DBHelper.LoyaltyCardOrder> loyaltyCardOrders = Arrays.asList(DBHelper.LoyaltyCardOrder.values());
for (int i = 0; i < loyaltyCardOrders.size(); i++) { for (int i = 0; i < loyaltyCardOrders.size(); i++) {
@@ -615,21 +504,17 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
final View customLayout = getLayoutInflater().inflate(R.layout.sorting_option, null); final View customLayout = getLayoutInflater().inflate(R.layout.sorting_option, null);
builder.setView(customLayout); builder.setView(customLayout);
CheckBox showReversed = (CheckBox) customLayout.findViewById(R.id.checkBox_reverse); CheckBox ch = (CheckBox) customLayout.findViewById(R.id.checkBox_reverse);
ch.setChecked(mOrderDirection == DBHelper.LoyaltyCardOrderDirection.Descending);
showReversed.setChecked(mOrderDirection == DBHelper.LoyaltyCardOrderDirection.Descending);
builder.setSingleChoiceItems(R.array.sort_types_array, currentIndex.get(), (dialog, which) -> currentIndex.set(which)); builder.setSingleChoiceItems(R.array.sort_types_array, currentIndex.get(), (dialog, which) -> currentIndex.set(which));
builder.setPositiveButton(R.string.sort, (dialog, which) -> { builder.setPositiveButton(R.string.sort, (dialog, which) -> {
if (ch.isChecked()) {
setSort( setSort(loyaltyCardOrders.get(currentIndex.get()), DBHelper.LoyaltyCardOrderDirection.Descending);
loyaltyCardOrders.get(currentIndex.get()), } else {
showReversed.isChecked() ? DBHelper.LoyaltyCardOrderDirection.Descending : DBHelper.LoyaltyCardOrderDirection.Ascending setSort(loyaltyCardOrders.get(currentIndex.get()), DBHelper.LoyaltyCardOrderDirection.Ascending);
); }
dialog.dismiss(); dialog.dismiss();
}); });
@@ -647,15 +532,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
return true; return true;
} }
if (id == R.id.action_archived) {
Intent i = new Intent(getApplicationContext(), MainActivity.class);
Bundle bundle = new Bundle();
bundle.putBoolean("archiveMode", true);
i.putExtras(bundle);
startActivity(i);
return true;
}
if (id == R.id.action_import_export) { if (id == R.id.action_import_export) {
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class); Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
startActivity(i); startActivity(i);
@@ -674,7 +550,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
return true; return true;
} }
return super.onOptionsItemSelected(inputItem); return super.onOptionsItemSelected(inputItem);
} }
@@ -693,7 +568,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
sortPrefEditor.apply(); sortPrefEditor.apply();
// Update card list // Update card list
updateLoyaltyCardList(false); updateLoyaltyCardList();
} }
@Override @Override
@@ -796,47 +671,10 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
mCurrentActionMode.setTitle(getResources().getQuantityString(R.plurals.selectedCardCount, count, count)); mCurrentActionMode.setTitle(getResources().getQuantityString(R.plurals.selectedCardCount, count, count));
MenuItem editItem = mCurrentActionMode.getMenu().findItem(R.id.action_edit); MenuItem editItem = mCurrentActionMode.getMenu().findItem(R.id.action_edit);
MenuItem archiveItem = mCurrentActionMode.getMenu().findItem(R.id.action_archive);
MenuItem unarchiveItem = mCurrentActionMode.getMenu().findItem(R.id.action_unarchive);
MenuItem starItem = mCurrentActionMode.getMenu().findItem(R.id.action_star);
MenuItem unstarItem = mCurrentActionMode.getMenu().findItem(R.id.action_unstar);
boolean hasStarred = false;
boolean hasUnstarred = false;
if(!mArchiveMode) {
unarchiveItem.setVisible(false);
archiveItem.setVisible(true);
}
else{
unarchiveItem.setVisible(true);
archiveItem.setVisible(false);
}
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
if (loyaltyCard.starStatus == 1) {
hasStarred = true;
} else {
hasUnstarred = true;
}
if (hasStarred && hasUnstarred) {
hasStarred = true;
hasUnstarred = true;
break;
}
}
if (count == 1) { if (count == 1) {
starItem.setVisible(!hasStarred);
unstarItem.setVisible(!hasUnstarred);
editItem.setVisible(true); editItem.setVisible(true);
editItem.setEnabled(true); editItem.setEnabled(true);
} else { } else {
starItem.setVisible(hasUnstarred);
unstarItem.setVisible(hasStarred);
editItem.setVisible(false); editItem.setVisible(false);
editItem.setEnabled(false); editItem.setEnabled(false);
} }
@@ -845,7 +683,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
} }
} }
@Override @Override
public void onRowClicked(int inputPosition) { public void onRowClicked(int inputPosition) {
if (mAdapter.getSelectedItemCount() > 0) { if (mAdapter.getSelectedItemCount() > 0) {
@@ -867,22 +704,15 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
return; return;
} }
Intent intent = new Intent(this, LoyaltyCardViewActivity.class); Intent i = new Intent(this, LoyaltyCardViewActivity.class);
intent.setAction(""); i.setAction("");
final Bundle b = new Bundle(); final Bundle b = new Bundle();
b.putInt("id", loyaltyCard.id); b.putInt("id", loyaltyCard.id);
i.putExtras(b);
ArrayList<Integer> cardList = new ArrayList<>();
for (int i = 0; i < mAdapter.getItemCount(); i++) {
cardList.add(mAdapter.getCard(i).id);
}
b.putIntegerArrayList("cardList", cardList);
intent.putExtras(b);
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard); ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard);
startActivity(intent); startActivity(i);
} }
} }
} }

View File

@@ -1,13 +1,12 @@
package protect.card_locker; package protect.card_locker;
import android.content.Intent; import android.content.Intent;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Log; import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
@@ -35,7 +34,7 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
protected Group mGroup = null; protected Group mGroup = null;
private RecyclerView mCardList; private RecyclerView mCardList;
private TextView noGroupCardsText; private TextView mHelpText;
private EditText mGroupNameText; private EditText mGroupNameText;
private boolean mGroupNameNotInUse; private boolean mGroupNameNotInUse;
@@ -49,7 +48,7 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
mDatabase = new DBHelper(this).getWritableDatabase(); mDatabase = new DBHelper(this).getWritableDatabase();
noGroupCardsText = findViewById(R.id.noGroupCardsText); mHelpText = findViewById(R.id.helpText);
mCardList = findViewById(R.id.list); mCardList = findViewById(R.id.list);
FloatingActionButton saveButton = findViewById(R.id.fabSave); FloatingActionButton saveButton = findViewById(R.id.fabSave);
@@ -133,7 +132,7 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
finish(); finish();
}); });
// this setText is here because content_main.xml is reused from main activity // this setText is here because content_main.xml is reused from main activity
noGroupCardsText.setText(getResources().getText(R.string.noGiftCardsGroup)); mHelpText.setText(getResources().getText(R.string.noGiftCardsGroup));
updateLoyaltyCardList(); updateLoyaltyCardList();
} }
@@ -157,26 +156,6 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
return ret; return ret;
} }
@Override
public boolean onCreateOptionsMenu(Menu inputMenu) {
getMenuInflater().inflate(R.menu.card_details_menu, inputMenu);
Utils.updateMenuCardDetailsButtonState(inputMenu.findItem(R.id.action_unfold), mAdapter.showingDetails());
return super.onCreateOptionsMenu(inputMenu);
}
@Override
public boolean onOptionsItemSelected(MenuItem inputItem) {
int id = inputItem.getItemId();
if (id == R.id.action_unfold) {
mAdapter.showDetails(!mAdapter.showingDetails());
invalidateOptionsMenu();
return true;
}
return super.onOptionsItemSelected(inputItem);
}
@Override @Override
protected void onSaveInstanceState(@NonNull Bundle outState) { protected void onSaveInstanceState(@NonNull Bundle outState) {
@@ -191,10 +170,10 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
if (mAdapter.getItemCount() == 0) { if (mAdapter.getItemCount() == 0) {
mCardList.setVisibility(View.GONE); mCardList.setVisibility(View.GONE);
noGroupCardsText.setVisibility(View.VISIBLE); mHelpText.setVisibility(View.VISIBLE);
} else { } else {
mCardList.setVisibility(View.VISIBLE); mCardList.setVisibility(View.VISIBLE);
noGroupCardsText.setVisibility(View.GONE); mHelpText.setVisibility(View.GONE);
} }
} }

View File

@@ -109,7 +109,7 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
} }
private void createGroup() { private void createGroup() {
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AlertDialogTheme);
builder.setTitle(R.string.enter_group_name); builder.setTitle(R.string.enter_group_name);
final EditText input = new EditText(this); final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT); input.setInputType(InputType.TYPE_CLASS_TEXT);

View File

@@ -0,0 +1,29 @@
package protect.card_locker;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
public class NotificationType {
private final static String NOTIFICATION_IMPORT_EXPORT = "NOTIFICATION_IMPORT_EXPORT";
public static String getImportExportChannel(Context context) {
createNotificationChannel(context, NOTIFICATION_IMPORT_EXPORT, context.getString(R.string.importExport), context.getString(R.string.importExportDescription), NotificationManager.IMPORTANCE_HIGH);
return NOTIFICATION_IMPORT_EXPORT;
}
private static void createNotificationChannel(Context context, String channelId, CharSequence name, String description, int importance) {
// Create the NotificationType, but only on API 26+ because
// the NotificationType class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(channelId, name, importance);
channel.setDescription(description);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
}

View File

@@ -1,7 +1,6 @@
package protect.card_locker; package protect.card_locker;
import android.app.Activity; import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
@@ -207,11 +206,6 @@ public class ScanActivity extends CatimaAppCompatActivity {
public void addFromImage(View view) { public void addFromImage(View view) {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK); Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("image/*"); photoPickerIntent.setType("image/*");
try { photoPickerLauncher.launch(photoPickerIntent);
photoPickerLauncher.launch(photoPickerIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
} }
} }

View File

@@ -1,79 +0,0 @@
package protect.card_locker;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.textview.MaterialTextView;
import com.yalantis.ucrop.UCropActivity;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
public class UCropWrapper extends UCropActivity {
public static final String UCROP_TOOLBAR_TYPEFACE_STYLE = "ucop_toolbar_typeface_style";
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
boolean darkMode = Utils.isDarkModeEnabled(this);
// setup status bar to look like the rest of the app
if (Build.VERSION.SDK_INT >= 23) {
getWindow().getDecorView().setSystemUiVisibility(darkMode ? 0 : View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
// icons are always white back then
if (!darkMode) {
getWindow().setStatusBarColor(ColorUtils.compositeColors(Color.argb(127, 0, 0, 0), getWindow().getStatusBarColor()));
}
}
// find and check views that we wish to color modify
// for when we update ucrop or switch to another cropper
View check = findViewById(com.yalantis.ucrop.R.id.wrapper_controls);
if (check instanceof FrameLayout) {
FrameLayout controls = (FrameLayout) check;
check = findViewById(com.yalantis.ucrop.R.id.wrapper_states);
if (check instanceof LinearLayout) {
LinearLayout states = (LinearLayout) check;
for (int i = 0; i < controls.getChildCount(); i++) {
check = controls.getChildAt(i);
if (check instanceof AppCompatImageView) {
AppCompatImageView controlsBackgroundImage = (AppCompatImageView) check;
// everything gathered and are as expected, now perform color patching
Utils.patchColors(this);
int colorSurface = MaterialColors.getColor(this, R.attr.colorSurface, ContextCompat.getColor(this, R.color.md_theme_light_surface));
int colorOnSurface = MaterialColors.getColor(this, R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
Drawable controlsBackgroundImageDrawable = controlsBackgroundImage.getBackground();
controlsBackgroundImageDrawable.mutate();
controlsBackgroundImageDrawable.setTint(darkMode ? colorOnSurface : colorSurface);
controlsBackgroundImage.setBackgroundDrawable(controlsBackgroundImageDrawable);
states.setBackgroundColor(darkMode ? colorSurface : colorOnSurface);
break;
}
}
}
}
// change toolbar font
check = findViewById(com.yalantis.ucrop.R.id.toolbar_title);
if (check instanceof MaterialTextView) {
MaterialTextView toolbarTextview = (MaterialTextView) check;
Intent intent = getIntent();
int style = intent.getIntExtra(UCROP_TOOLBAR_TYPEFACE_STYLE, -1);
if (style != -1) {
toolbarTextview.setTypeface(Typeface.defaultFromStyle(style));
}
}
}
}

View File

@@ -5,7 +5,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Color; import android.graphics.Color;
@@ -15,11 +14,8 @@ import android.os.Build;
import android.os.LocaleList; import android.os.LocaleList;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.util.Log; import android.util.Log;
import android.util.TypedValue;
import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.material.color.DynamicColors;
import com.google.zxing.BinaryBitmap; import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource; import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader; import com.google.zxing.MultiFormatReader;
@@ -36,7 +32,6 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Calendar; import java.util.Calendar;
import java.util.Currency; import java.util.Currency;
import java.util.Date; import java.util.Date;
@@ -44,11 +39,9 @@ import java.util.GregorianCalendar;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.graphics.ColorUtils; import androidx.core.graphics.ColorUtils;
import androidx.exifinterface.media.ExifInterface; import androidx.exifinterface.media.ExifInterface;
import androidx.palette.graphics.Palette;
import protect.card_locker.preferences.Settings; import protect.card_locker.preferences.Settings;
public class Utils { public class Utils {
@@ -213,7 +206,7 @@ public class Utils {
if (currency == null) { if (currency == null) {
numberFormat.setMaximumFractionDigits(0); numberFormat.setMaximumFractionDigits(0);
return context.getResources().getQuantityString(R.plurals.balancePoints, value.intValue(), numberFormat.format(value)); return context.getString(R.string.balancePoints, numberFormat.format(value));
} }
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(); NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
@@ -238,19 +231,32 @@ public class Utils {
return numberFormat.format(value); return numberFormat.format(value);
} }
static public BigDecimal parseBalance(String value, Currency currency) throws ParseException { static public Boolean currencyHasDecimals(Currency currency) {
NumberFormat numberFormat = NumberFormat.getInstance();
if (currency == null) { if (currency == null) {
numberFormat.setMaximumFractionDigits(0); return false;
} else {
numberFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
numberFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
} }
Log.d(TAG, numberFormat.parse(value).toString()); return currency.getDefaultFractionDigits() != 0;
}
return new BigDecimal(numberFormat.parse(value).toString()); static public BigDecimal parseCurrency(String value, Boolean hasDecimals) throws NumberFormatException {
// If there are no decimals expected, remove all separators before parsing
if (!hasDecimals) {
value = value.replaceAll("[^0-9]", "");
return new BigDecimal(value);
}
// There are many ways users can write a currency, so we fix it up a bit
// 1. Replace all non-numbers with dots
value = value.replaceAll("[^0-9]", ".");
// 2. Remove all but the last dot
while (value.split("\\.").length > 2) {
value = value.replaceFirst("\\.", "");
}
// Parse as BigDecimal
return new BigDecimal(value);
} }
static public byte[] bitmapToByteArray(Bitmap bitmap) { static public byte[] bitmapToByteArray(Bitmap bitmap) {
@@ -345,14 +351,6 @@ public class Utils {
saveCardImage(context, bitmap, getCardImageFileName(loyaltyCardId, type)); saveCardImage(context, bitmap, getCardImageFileName(loyaltyCardId, type));
} }
public static File retrieveCardImageAsFile(Context context, String fileName) {
return context.getFileStreamPath(fileName);
}
public static File retrieveCardImageAsFile(Context context, int loyaltyCardId, ImageLocationType type) {
return retrieveCardImageAsFile(context, getCardImageFileName(loyaltyCardId, type));
}
static public Bitmap retrieveCardImage(Context context, String fileName) { static public Bitmap retrieveCardImage(Context context, String fileName) {
FileInputStream in; FileInputStream in;
try { try {
@@ -450,82 +448,4 @@ public class Utils {
return loadImage(context.getCacheDir() + "/" + name); return loadImage(context.getCacheDir() + "/" + name);
} }
// https://stackoverflow.com/a/59324801/8378787
public static int getComplementaryColor(int color) {
int R = color & 255;
int G = (color >> 8) & 255;
int B = (color >> 16) & 255;
int A = (color >> 24) & 255;
R = 255 - R;
G = 255 - G;
B = 255 - B;
return R + (G << 8) + (B << 16) + (A << 24);
}
// replace colors in the current theme
public static void patchColors(AppCompatActivity activity) {
Settings settings = new Settings(activity);
String color = settings.getColor();
Resources.Theme theme = activity.getTheme();
Resources resources = activity.getResources();
if (color.equals(resources.getString(R.string.settings_key_pink_theme))) {
theme.applyStyle(R.style.pink, true);
} else if (color.equals(resources.getString(R.string.settings_key_magenta_theme))) {
theme.applyStyle(R.style.magenta, true);
} else if (color.equals(resources.getString(R.string.settings_key_violet_theme))) {
theme.applyStyle(R.style.violet, true);
} else if (color.equals(resources.getString(R.string.settings_key_blue_theme))) {
theme.applyStyle(R.style.blue, true);
} else if (color.equals(resources.getString(R.string.settings_key_sky_blue_theme))) {
theme.applyStyle(R.style.skyblue, true);
} else if (color.equals(resources.getString(R.string.settings_key_green_theme))) {
theme.applyStyle(R.style.green, true);
} else if (color.equals(resources.getString(R.string.settings_key_brown_theme))) {
theme.applyStyle(R.style.brown, true);
} else if (color.equals(resources.getString(R.string.settings_key_catima_theme))) {
// catima theme is AppTheme itself, no dynamic colors nor applyStyle
} else {
// final catch all in case of invalid theme value from older versions
// also handles R.string.settings_key_system_theme
DynamicColors.applyIfAvailable(activity);
}
if (isDarkModeEnabled(activity) && settings.getOledDark()) {
theme.applyStyle(R.style.DarkBackground, true);
}
}
// XXX android 9 and below has issues with patched theme where the background becomes a
// rendering mess
// use after views are inflated
public static void postPatchColors(AppCompatActivity activity) {
TypedValue typedValue = new TypedValue();
activity.getTheme().resolveAttribute(android.R.attr.colorBackground, typedValue, true);
activity.findViewById(android.R.id.content).setBackgroundColor(typedValue.data);
}
public static void updateMenuCardDetailsButtonState(MenuItem item, boolean currentlyExpanded) {
if (currentlyExpanded) {
item.setIcon(R.drawable.ic_baseline_unfold_less_24);
item.setTitle(R.string.action_hide_details);
} else {
item.setIcon(R.drawable.ic_baseline_unfold_more_24);
item.setTitle(R.string.action_show_details);
}
}
public static int getHeaderColorFromImage(Bitmap image, int fallback) {
if (image == null) {
return fallback;
}
return new Palette.Builder(image).generate().getDominantColor(R.attr.colorPrimary);
}
public static int getRandomHeaderColor(Context context) {
TypedArray colors = context.getResources().obtainTypedArray(R.array.letter_tile_colors);
final int color = (int) (Math.random() * colors.length());
return colors.getColor(color, Color.BLACK);
}
} }

View File

@@ -1,35 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class AztecBarcode extends Barcode {
@Override
public String prettyName() {
return "Aztec";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.AZTEC;
}
@Override
public String exampleValue() {
return "AZTEC";
}
@Override
public boolean isSquare() {
return true;
}
@Override
public boolean is2D() {
return true;
}
@Override
public boolean hasInternalPadding() {
return false;
}
}

View File

@@ -1,22 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
/**
* Abstract barcode class
*/
public abstract class Barcode {
public String name() {
return format().name();
};
abstract public String prettyName();
abstract public BarcodeFormat format();
abstract public String exampleValue();
abstract public boolean isSquare();
abstract public boolean is2D();
public boolean hasInternalPadding() {
return false;
};
public boolean isSupported() { return true; };
}

View File

@@ -1,62 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class BarcodeFactory {
public static final Map<String, BarcodeFormat> barcodeNames = new HashMap<>() {{
put(BarcodeFormat.AZTEC.name(), BarcodeFormat.AZTEC);
put(BarcodeFormat.CODE_39.name(), BarcodeFormat.CODE_39);
put(BarcodeFormat.CODE_93.name(), BarcodeFormat.CODE_93);
put(BarcodeFormat.CODE_128.name(), BarcodeFormat.CODE_128);
put(BarcodeFormat.CODABAR.name(), BarcodeFormat.CODABAR);
put(BarcodeFormat.DATA_MATRIX.name(), BarcodeFormat.DATA_MATRIX);
put(BarcodeFormat.EAN_8.name(), BarcodeFormat.EAN_8);
put(BarcodeFormat.EAN_13.name(), BarcodeFormat.EAN_13);
put(BarcodeFormat.ITF.name(), BarcodeFormat.ITF);
put(BarcodeFormat.PDF_417.name(), BarcodeFormat.PDF_417);
put(BarcodeFormat.QR_CODE.name(), BarcodeFormat.QR_CODE);
put(BarcodeFormat.UPC_A.name(), BarcodeFormat.UPC_A);
put(BarcodeFormat.UPC_E.name(), BarcodeFormat.UPC_E);
}};
public static Barcode fromBarcode(BarcodeFormat barcodeFormat) {
switch (barcodeFormat) {
case AZTEC: return new AztecBarcode();
case CODE_39: return new Code39Barcode();
case CODE_93: return new Code93Barcode();
case CODE_128: return new Code128Barcode();
case CODABAR: return new CodabarBarcode();
case DATA_MATRIX: return new DataMatrixBarcode();
case EAN_8: return new Ean8Barcode();
case EAN_13: return new Ean13Barcode();
case ITF: return new ItfBarcode();
case PDF_417: return new Pdf417Barcode();
case QR_CODE: return new QrCodeBarcode();
case UPC_A: return new UpcABarcode();
case UPC_E: return new UpcEBarcode();
default: throw new IllegalArgumentException();
}
}
public static Barcode fromName(String name) {
return fromBarcode(Objects.requireNonNull(barcodeNames.get(name)));
}
public static boolean isSupported(BarcodeFormat barcodeFormat) {
return barcodeNames.containsValue(barcodeFormat);
}
public static boolean isSupported(String name) {
return barcodeNames.containsKey(name);
}
public static Collection<BarcodeFormat> getAllFormats() {
return barcodeNames.values();
}
}

View File

@@ -1,19 +0,0 @@
package protect.card_locker.barcodes;
public class BarcodeWithValue {
private final Barcode mBarcode;
private final String mValue;
public BarcodeWithValue(Barcode barcode, String value) {
mBarcode = barcode;
mValue = value;
}
public Barcode barcode() {
return mBarcode;
}
public String value() {
return mValue;
}
}

View File

@@ -1,35 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class CodabarBarcode extends Barcode {
@Override
public String prettyName() {
return "Codabar";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.CODABAR;
}
@Override
public String exampleValue() {
return "C0C";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
@Override
public boolean hasInternalPadding() {
return false;
}
}

View File

@@ -1,30 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class Code128Barcode extends Barcode {
@Override
public String prettyName() {
return "Code 128";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.CODE_128;
}
@Override
public String exampleValue() {
return "CODE_128";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -1,35 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class Code39Barcode extends Barcode {
@Override
public String prettyName() {
return "Code 39";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.CODE_39;
}
@Override
public String exampleValue() {
return "CODE_39";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
@Override
public boolean hasInternalPadding() {
return false;
}
}

View File

@@ -1,30 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class Code93Barcode extends Barcode {
@Override
public String prettyName() {
return "Code 93";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.CODE_93;
}
@Override
public String exampleValue() {
return "CODE_93";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -1,30 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class DataMatrixBarcode extends Barcode {
@Override
public String prettyName() {
return "Data Matrix";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.DATA_MATRIX;
}
@Override
public String exampleValue() {
return "DATA_MATRIX";
}
@Override
public boolean isSquare() {
return true;
}
@Override
public boolean is2D() {
return true;
}
}

View File

@@ -1,30 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class Ean13Barcode extends Barcode {
@Override
public String prettyName() {
return "EAN 13";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.EAN_13;
}
@Override
public String exampleValue() {
return "5901234123457";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -1,30 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class Ean8Barcode extends Barcode {
@Override
public String prettyName() {
return "EAN 8";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.EAN_8;
}
@Override
public String exampleValue() {
return "32123456";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -1,30 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class ItfBarcode extends Barcode {
@Override
public String prettyName() {
return "ITF";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.ITF;
}
@Override
public String exampleValue() {
return "1003";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -1,35 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class Pdf417Barcode extends Barcode {
@Override
public String prettyName() {
return "PDF 417";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.PDF_417;
}
@Override
public String exampleValue() {
return "PDF_417";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return true;
}
@Override
public boolean hasInternalPadding() {
return true;
}
}

View File

@@ -1,35 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class QrCodeBarcode extends Barcode {
@Override
public String prettyName() {
return "QR Code";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.QR_CODE;
}
@Override
public String exampleValue() {
return "QR_CODE";
}
@Override
public boolean isSquare() {
return true;
}
@Override
public boolean is2D() {
return true;
}
@Override
public boolean hasInternalPadding() {
return true;
}
}

View File

@@ -1,30 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class UpcABarcode extends Barcode {
@Override
public String prettyName() {
return "UPC A";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.UPC_A;
}
@Override
public String exampleValue() {
return "123456789012";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -1,30 +0,0 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class UpcEBarcode extends Barcode {
@Override
public String prettyName() {
return "UPC E";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.UPC_E;
}
@Override
public String exampleValue() {
return "0123456";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -135,8 +135,7 @@ public class CatimaExporter implements Exporter {
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
DBHelper.LoyaltyCardDbIds.HEADER_COLOR, DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
DBHelper.LoyaltyCardDbIds.STAR_STATUS, DBHelper.LoyaltyCardDbIds.STAR_STATUS,
DBHelper.LoyaltyCardDbIds.LAST_USED, DBHelper.LoyaltyCardDbIds.LAST_USED);
DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS);
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(database); Cursor cardCursor = DBHelper.getLoyaltyCardCursor(database);
@@ -154,8 +153,7 @@ public class CatimaExporter implements Exporter {
card.barcodeType != null ? card.barcodeType.name() : "", card.barcodeType != null ? card.barcodeType.name() : "",
card.headerColor, card.headerColor,
card.starStatus, card.starStatus,
card.lastUsed, card.lastUsed);
card.archiveStatus);
if (Thread.currentThread().isInterrupted()) { if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException(); throw new InterruptedException();

View File

@@ -25,6 +25,7 @@ import java.util.Currency;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper; import protect.card_locker.DBHelper;
import protect.card_locker.FormatException; import protect.card_locker.FormatException;
import protect.card_locker.Group; import protect.card_locker.Group;
@@ -320,15 +321,6 @@ public class CatimaImporter implements Importer {
} }
if (starStatus != 1) starStatus = 0; if (starStatus != 1) starStatus = 0;
int archiveStatus = 0;
try {
archiveStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS, record, false);
} catch (FormatException _e) {
// This field did not exist in versions 2.16.3 and before
// We catch this exception so we can still import old backups
}
if (archiveStatus != 1) archiveStatus = 0;
Long lastUsed = 0L; Long lastUsed = 0L;
try { try {
lastUsed = CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.LAST_USED, record, false); lastUsed = CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.LAST_USED, record, false);
@@ -337,7 +329,7 @@ public class CatimaImporter implements Importer {
// We catch this exception so we can still import old backups // We catch this exception so we can still import old backups
} }
DBHelper.insertLoyaltyCard(database, id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus, lastUsed,archiveStatus); DBHelper.insertLoyaltyCard(database, id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus, lastUsed);
} }
/** /**

View File

@@ -6,6 +6,8 @@ import android.database.sqlite.SQLiteDatabase;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import protect.card_locker.DBHelper;
/** /**
* Interface for a class which can export the contents of the database * Interface for a class which can export the contents of the database
* in a given format. * in a given format.

View File

@@ -18,9 +18,9 @@ import java.math.BigDecimal;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.ParseException; import java.text.ParseException;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper; import protect.card_locker.DBHelper;
import protect.card_locker.FormatException; import protect.card_locker.FormatException;
import protect.card_locker.Utils;
/** /**
* Class for importing a database from CSV (Comma Separate Values) * Class for importing a database from CSV (Comma Separate Values)
@@ -56,7 +56,7 @@ public class FidmeImporter implements Importer {
try { try {
for (CSVRecord record : fidmeParser) { for (CSVRecord record : fidmeParser) {
importLoyaltyCard(context, database, record); importLoyaltyCard(database, record);
if (Thread.currentThread().isInterrupted()) { if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException(); throw new InterruptedException();
@@ -75,7 +75,7 @@ public class FidmeImporter implements Importer {
* Import a single loyalty card into the database using the given * Import a single loyalty card into the database using the given
* session. * session.
*/ */
private void importLoyaltyCard(Context context, SQLiteDatabase database, CSVRecord record) private void importLoyaltyCard(SQLiteDatabase database, CSVRecord record)
throws FormatException { throws FormatException {
// A loyalty card export from Fidme contains the following fields: // A loyalty card export from Fidme contains the following fields:
// Retailer (store name) // Retailer (store name)
@@ -109,10 +109,7 @@ public class FidmeImporter implements Importer {
// The ID is called reference // The ID is called reference
String cardId = CSVHelpers.extractString("Reference", record, ""); String cardId = CSVHelpers.extractString("Reference", record, "");
if (cardId.isEmpty()) { if (cardId.isEmpty()) {
// Fidme deletes the card id if a card is expired throw new FormatException("No card ID listed, but is required");
// Because Catima considers the card id a required field, we ignore these expired cards
// https://github.com/CatimaLoyalty/Android/issues/1005
return;
} }
// Sadly, Fidme exports don't contain the card type // Sadly, Fidme exports don't contain the card type
@@ -120,13 +117,11 @@ public class FidmeImporter implements Importer {
// TODO: Hook this into our own loyalty card DB if we ever get one // TODO: Hook this into our own loyalty card DB if we ever get one
CatimaBarcode barcodeType = null; CatimaBarcode barcodeType = null;
// No favourite data or colour in the export either // No favourite data in the export either
int starStatus = 0; int starStatus = 0;
int archiveStatus = 0;
int headerColor = Utils.getRandomHeaderColor(context);
// TODO: Front and back image // TODO: Front and back image
DBHelper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, starStatus, null,archiveStatus); DBHelper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, starStatus, null);
} }
} }

View File

@@ -1,23 +1,7 @@
package protect.card_locker.importexport; package protect.card_locker.importexport;
public class ImportExportResult { public enum ImportExportResult {
private ImportExportResultType resultType; Success,
private String developerDetails; GenericFailure,
BadPassword;
public ImportExportResult(ImportExportResultType resultType) {
this(resultType, null);
}
public ImportExportResult(ImportExportResultType resultType, String developerDetails) {
this.resultType = resultType;
this.developerDetails = developerDetails;
}
public ImportExportResultType resultType() {
return resultType;
}
public String developerDetails() {
return developerDetails;
}
} }

View File

@@ -1,7 +0,0 @@
package protect.card_locker.importexport;
public enum ImportExportResultType {
Success,
GenericFailure,
BadPassword;
}

View File

@@ -4,8 +4,11 @@ import android.content.Context;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.util.Log; import android.util.Log;
import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import protect.card_locker.DBHelper;
public class MultiFormatExporter { public class MultiFormatExporter {
private static final String TAG = "Catima"; private static final String TAG = "Catima";
@@ -31,20 +34,20 @@ public class MultiFormatExporter {
break; break;
} }
String error;
if (exporter != null) { if (exporter != null) {
try { try {
exporter.exportData(context, database, output, password); exporter.exportData(context, database, output, password);
return new ImportExportResult(ImportExportResultType.Success); return ImportExportResult.Success;
} catch (Exception e) { } catch (IOException e) {
Log.e(TAG, "Failed to export data", e);
} catch (InterruptedException e) {
Log.e(TAG, "Failed to export data", e); Log.e(TAG, "Failed to export data", e);
error = e.toString();
} }
} else {
error = "Unsupported data format exported: " + format.name();
Log.e(TAG, error);
}
return new ImportExportResult(ImportExportResultType.GenericFailure, error); return ImportExportResult.GenericFailure;
} else {
Log.e(TAG, "Unsupported data format exported: " + format.name());
return ImportExportResult.GenericFailure;
}
} }
} }

View File

@@ -6,7 +6,14 @@ import android.util.Log;
import net.lingala.zip4j.exception.ZipException; import net.lingala.zip4j.exception.ZipException;
import org.json.JSONException;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.ParseException;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
public class MultiFormatImporter { public class MultiFormatImporter {
private static final String TAG = "Catima"; private static final String TAG = "Catima";
@@ -40,31 +47,23 @@ public class MultiFormatImporter {
break; break;
} }
String error = null;
if (importer != null) { if (importer != null) {
database.beginTransaction(); database.beginTransaction();
try { try {
importer.importData(context, database, input, password); importer.importData(context, database, input, password);
database.setTransactionSuccessful(); database.setTransactionSuccessful();
return new ImportExportResult(ImportExportResultType.Success); return ImportExportResult.Success;
} catch (ZipException e) { } catch (ZipException e) {
if (e.getType().equals(ZipException.Type.WRONG_PASSWORD)) { return ImportExportResult.BadPassword;
return new ImportExportResult(ImportExportResultType.BadPassword); } catch (IOException | FormatException | InterruptedException | JSONException | ParseException | NullPointerException e) {
} else {
Log.e(TAG, "Failed to import data", e);
error = e.toString();
}
} catch (Exception e) {
Log.e(TAG, "Failed to import data", e); Log.e(TAG, "Failed to import data", e);
error = e.toString();
} finally { } finally {
database.endTransaction(); database.endTransaction();
} }
} else { } else {
error = "Unsupported data format imported: " + format.name(); Log.e(TAG, "Unsupported data format imported: " + format.name());
Log.e(TAG, error);
} }
return new ImportExportResult(ImportExportResultType.GenericFailure, error); return ImportExportResult.GenericFailure;
} }
} }

View File

@@ -3,7 +3,6 @@ package protect.card_locker.importexport;
import android.content.Context; import android.content.Context;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.util.Log;
import com.google.zxing.BarcodeFormat; import com.google.zxing.BarcodeFormat;
@@ -24,6 +23,7 @@ import java.nio.charset.StandardCharsets;
import java.text.ParseException; import java.text.ParseException;
import java.util.HashMap; import java.util.HashMap;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper; import protect.card_locker.DBHelper;
import protect.card_locker.FormatException; import protect.card_locker.FormatException;
import protect.card_locker.ImageLocationType; import protect.card_locker.ImageLocationType;
@@ -39,17 +39,15 @@ import protect.card_locker.ZipUtils;
* A header is expected for the each table showing the names of the columns. * A header is expected for the each table showing the names of the columns.
*/ */
public class StocardImporter implements Importer { public class StocardImporter implements Importer {
private static final String TAG = "Catima";
public void importData(Context context, SQLiteDatabase database, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException { public void importData(Context context, SQLiteDatabase database, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
HashMap<String, HashMap<String, Object>> loyaltyCardHashMap = new HashMap<>(); HashMap<String, HashMap<String, Object>> loyaltyCardHashMap = new HashMap<>();
HashMap<String, HashMap<String, Object>> providers = new HashMap<>(); HashMap<String, HashMap<String, String>> providers = new HashMap<>();
final CSVParser parser = new CSVParser(new InputStreamReader(context.getResources().openRawResource(R.raw.stocard_stores), StandardCharsets.UTF_8), CSVFormat.RFC4180.builder().setHeader().build()); final CSVParser parser = new CSVParser(new InputStreamReader(context.getResources().openRawResource(R.raw.stocard_stores), StandardCharsets.UTF_8), CSVFormat.RFC4180.builder().setHeader().build());
try { try {
for (CSVRecord record : parser) { for (CSVRecord record : parser) {
HashMap<String, Object> recordData = new HashMap<>(); HashMap<String, String> recordData = new HashMap<>();
recordData.put("name", record.get("name")); recordData.put("name", record.get("name"));
recordData.put("barcodeFormat", record.get("barcodeFormat")); recordData.put("barcodeFormat", record.get("barcodeFormat"));
@@ -64,8 +62,6 @@ public class StocardImporter implements Importer {
ZipInputStream zipInputStream = new ZipInputStream(input, password); ZipInputStream zipInputStream = new ZipInputStream(input, password);
String[] providersFileName = null; String[] providersFileName = null;
String[] customProvidersBaseName = null;
String customProviderId = "";
String[] cardBaseName = null; String[] cardBaseName = null;
String cardName = ""; String cardName = "";
LocalFileHeader localFileHeader; LocalFileHeader localFileHeader;
@@ -82,14 +78,6 @@ public class StocardImporter implements Importer {
nameParts[0], nameParts[0],
"analytics-properties.json" "analytics-properties.json"
}; };
customProvidersBaseName = new String[]{
nameParts[0],
"sync",
"data",
"users",
nameParts[0],
"loyalty-card-custom-providers"
};
cardBaseName = new String[]{ cardBaseName = new String[]{
nameParts[0], nameParts[0],
"sync", "sync",
@@ -100,33 +88,6 @@ public class StocardImporter implements Importer {
}; };
} }
if (startsWith(nameParts, customProvidersBaseName, 1)) {
// Extract providerId
customProviderId = nameParts[customProvidersBaseName.length].split("\\.", 2)[0];
// Name file
if (nameParts.length == customProvidersBaseName.length + 1) {
// Ignore the .txt file
if (fileName.endsWith(".json")) {
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
providers = appendToHashMap(
providers,
customProviderId,
"name",
jsonObject.getString("name")
);
}
} else if (fileName.endsWith("logo.png")) {
providers = appendToHashMap(
providers,
customProviderId,
"logo",
ZipUtils.readImage(zipInputStream)
);
}
}
if (startsWith(nameParts, cardBaseName, 1)) { if (startsWith(nameParts, cardBaseName, 1)) {
// Extract cardName // Extract cardName
cardName = nameParts[cardBaseName.length].split("\\.", 2)[0]; cardName = nameParts[cardBaseName.length].split("\\.", 2)[0];
@@ -137,33 +98,24 @@ public class StocardImporter implements Importer {
if (fileName.endsWith(".json")) { if (fileName.endsWith(".json")) {
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream); JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
loyaltyCardHashMap = appendToHashMap( loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap, loyaltyCardHashMap,
cardName, cardName,
"cardId", "cardId",
jsonObject.getString("input_id") jsonObject.getString("input_id")
); );
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
// Provider ID can be either custom or not, extract whatever version is relevant
String customProviderPrefix = "/users/" + nameParts[0] + "/loyalty-card-custom-providers/";
String providerId = jsonObject
.getJSONObject("input_provider_reference")
.getString("identifier");
if (providerId.startsWith(customProviderPrefix)) {
providerId = providerId.substring(customProviderPrefix.length());
} else {
providerId = providerId.substring("/loyalty-card-providers/".length());
}
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap, loyaltyCardHashMap,
cardName, cardName,
"_providerId", "_providerId",
providerId jsonObject
.getJSONObject("input_provider_reference")
.getString("identifier")
.substring("/loyalty-card-providers/".length())
); );
if (jsonObject.has("input_barcode_format")) { if (jsonObject.has("input_barcode_format")) {
loyaltyCardHashMap = appendToHashMap( loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap, loyaltyCardHashMap,
cardName, cardName,
"barcodeType", "barcodeType",
@@ -172,7 +124,7 @@ public class StocardImporter implements Importer {
} }
} }
} else if (fileName.endsWith("notes/default.json")) { } else if (fileName.endsWith("notes/default.json")) {
loyaltyCardHashMap = appendToHashMap( loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap, loyaltyCardHashMap,
cardName, cardName,
"note", "note",
@@ -180,14 +132,14 @@ public class StocardImporter implements Importer {
.getString("content") .getString("content")
); );
} else if (fileName.endsWith("/images/front.png")) { } else if (fileName.endsWith("/images/front.png")) {
loyaltyCardHashMap = appendToHashMap( loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap, loyaltyCardHashMap,
cardName, cardName,
"frontImage", "frontImage",
ZipUtils.readImage(zipInputStream) ZipUtils.readImage(zipInputStream)
); );
} else if (fileName.endsWith("/images/back.png")) { } else if (fileName.endsWith("/images/back.png")) {
loyaltyCardHashMap = appendToHashMap( loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap, loyaltyCardHashMap,
cardName, cardName,
"backImage", "backImage",
@@ -203,41 +155,22 @@ public class StocardImporter implements Importer {
for (HashMap<String, Object> loyaltyCardData : loyaltyCardHashMap.values()) { for (HashMap<String, Object> loyaltyCardData : loyaltyCardHashMap.values()) {
String providerId = (String) loyaltyCardData.get("_providerId"); String providerId = (String) loyaltyCardData.get("_providerId");
HashMap<String, String> providerData = providers.get(providerId);
if (providerId == null) { String store = providerData != null ? providerData.get("name") : providerId;
Log.d(TAG, "Missing providerId for card " + loyaltyCardData + ", ignoring...");
continue;
}
HashMap<String, Object> providerData = providers.get(providerId);
String store = providerData != null ? providerData.get("name").toString() : providerId;
String note = (String) Utils.mapGetOrDefault(loyaltyCardData, "note", ""); String note = (String) Utils.mapGetOrDefault(loyaltyCardData, "note", "");
String cardId = (String) loyaltyCardData.get("cardId"); String cardId = (String) loyaltyCardData.get("cardId");
String barcodeTypeString = (String) Utils.mapGetOrDefault(loyaltyCardData, "barcodeType", providerData != null ? providerData.get("barcodeFormat") : null); String barcodeTypeString = (String) Utils.mapGetOrDefault(loyaltyCardData, "barcodeType", providerData != null ? providerData.get("barcodeFormat") : null);
CatimaBarcode barcodeType = null; CatimaBarcode barcodeType = null;
if (barcodeTypeString != null && !barcodeTypeString.isEmpty()) { if (barcodeTypeString != null) {
if (barcodeTypeString.equals("RSS_DATABAR_EXPANDED")) { if (barcodeTypeString.equals("RSS_DATABAR_EXPANDED")) {
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.RSS_EXPANDED); barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.RSS_EXPANDED);
} else if (barcodeTypeString.equals("GS1_128")) {
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.CODE_128);
} else { } else {
barcodeType = CatimaBarcode.fromName(barcodeTypeString); barcodeType = CatimaBarcode.fromName(barcodeTypeString);
} }
} }
int headerColor = Utils.getRandomHeaderColor(context); long loyaltyCardInternalId = DBHelper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, 0, null);
Bitmap cardIcon = null;
if (providerData != null && providerData.containsKey("logo")) {
cardIcon = (Bitmap) providerData.get("logo");
headerColor = Utils.getHeaderColorFromImage(cardIcon, headerColor);
}
long loyaltyCardInternalId = DBHelper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, 0, null,0);
if (cardIcon != null) {
Utils.saveCardImage(context, cardIcon, (int) loyaltyCardInternalId, ImageLocationType.icon);
}
if (loyaltyCardData.containsKey("frontImage")) { if (loyaltyCardData.containsKey("frontImage")) {
Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("frontImage"), (int) loyaltyCardInternalId, ImageLocationType.front); Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("frontImage"), (int) loyaltyCardInternalId, ImageLocationType.front);
@@ -264,7 +197,7 @@ public class StocardImporter implements Importer {
return true; return true;
} }
private HashMap<String, HashMap<String, Object>> appendToHashMap(HashMap<String, HashMap<String, Object>> loyaltyCardHashMap, String cardID, String key, Object value) { private HashMap<String, HashMap<String, Object>> appendToLoyaltyCardHashMap(HashMap<String, HashMap<String, Object>> loyaltyCardHashMap, String cardID, String key, Object value) {
HashMap<String, Object> loyaltyCardData = loyaltyCardHashMap.get(cardID); HashMap<String, Object> loyaltyCardData = loyaltyCardHashMap.get(cardID);
if (loyaltyCardData == null) { if (loyaltyCardData == null) {
loyaltyCardData = new HashMap<>(); loyaltyCardData = new HashMap<>();

View File

@@ -23,6 +23,7 @@ import java.util.Currency;
import java.util.Date; import java.util.Date;
import java.util.TimeZone; import java.util.TimeZone;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper; import protect.card_locker.DBHelper;
import protect.card_locker.FormatException; import protect.card_locker.FormatException;
import protect.card_locker.Utils; import protect.card_locker.Utils;
@@ -125,7 +126,7 @@ public class VoucherVaultImporter implements Importer {
throw new FormatException("Unknown colour type found: " + colorFromJSON); throw new FormatException("Unknown colour type found: " + colorFromJSON);
} }
DBHelper.insertLoyaltyCard(database, store, "", expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0, Utils.getUnixTime(),0); DBHelper.insertLoyaltyCard(database, store, "", expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0, Utils.getUnixTime());
} }
bufferedReader.close(); bufferedReader.close();

View File

@@ -91,8 +91,8 @@ public class Settings {
return getBoolean(R.string.settings_key_display_barcode_max_brightness, true); return getBoolean(R.string.settings_key_display_barcode_max_brightness, true);
} }
public String getCardViewOrientation() { public boolean getLockBarcodeScreenOrientation() {
return getString(R.string.settings_key_card_orientation, getResString(R.string.settings_key_follow_system_orientation)); return getBoolean(R.string.settings_key_lock_barcode_orientation, false);
} }
public boolean getKeepScreenOn() { public boolean getKeepScreenOn() {
@@ -102,12 +102,4 @@ public class Settings {
public boolean getDisableLockscreenWhileViewingCard() { public boolean getDisableLockscreenWhileViewingCard() {
return getBoolean(R.string.settings_key_disable_lockscreen_while_viewing_card, true); return getBoolean(R.string.settings_key_disable_lockscreen_while_viewing_card, true);
} }
public boolean getOledDark() {
return getBoolean(R.string.settings_key_oled_dark, false);
}
public String getColor() {
return getString(R.string.setting_key_theme_color, mContext.getResources().getString(R.string.settings_key_system_theme));
}
} }

View File

@@ -6,12 +6,6 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import com.google.android.material.color.DynamicColors;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
@@ -20,6 +14,11 @@ import androidx.fragment.app.DialogFragment;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import nl.invissvenska.numberpickerpreference.NumberDialogPreference; import nl.invissvenska.numberpickerpreference.NumberDialogPreference;
import nl.invissvenska.numberpickerpreference.NumberPickerPreferenceDialogFragment; import nl.invissvenska.numberpickerpreference.NumberPickerPreferenceDialogFragment;
import protect.card_locker.CatimaAppCompatActivity; import protect.card_locker.CatimaAppCompatActivity;
@@ -130,28 +129,16 @@ public class SettingsActivity extends CatimaAppCompatActivity {
return true; return true;
}); });
localePreference.setOnPreferenceChangeListener((preference, newValue) -> { Preference colorPreference = findPreference(getResources().getString(R.string.setting_key_theme_color));
refreshActivity(true);
return true;
});
Preference oledDarkPreference = findPreference(getResources().getString(R.string.settings_key_oled_dark));
assert oledDarkPreference != null;
oledDarkPreference.setOnPreferenceChangeListener((preference, newValue) -> {
refreshActivity(true);
return true;
});
ListPreference colorPreference = findPreference(getResources().getString(R.string.setting_key_theme_color));
assert colorPreference != null; assert colorPreference != null;
colorPreference.setOnPreferenceChangeListener((preference, o) -> { colorPreference.setOnPreferenceChangeListener((preference, o) -> {
refreshActivity(true); refreshActivity(true);
return true; return true;
}); });
if (!DynamicColors.isDynamicColorAvailable()) { localePreference.setOnPreferenceChangeListener((preference, newValue) -> {
colorPreference.setEntryValues(R.array.color_values_no_dynamic); refreshActivity(true);
colorPreference.setEntries(R.array.color_value_strings_no_dynamic); return true;
} });
} }
private void refreshActivity(boolean reloadMain) { private void refreshActivity(boolean reloadMain) {

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- https://stackoverflow.com/questions/5151591/android-left-to-right-slide-animation/5151774#5151774 -->
<translate
android:duration="200"
android:fromXDelta="-100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- https://stackoverflow.com/questions/5151591/android-left-to-right-slide-animation/5151774#5151774 -->
<translate
android:duration="200"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- https://stackoverflow.com/questions/5151591/android-left-to-right-slide-animation/5151774#5151774 -->
<translate
android:duration="200"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="-100%"
android:toYDelta="0%" />
</set>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- https://stackoverflow.com/questions/5151591/android-left-to-right-slide-animation/5151774#5151774 -->
<translate
android:duration="200"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="100%"
android:toYDelta="0%" />
</set>

View File

@@ -1,5 +0,0 @@
<vector android:autoMirrored="true" android:height="24dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> <path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>

View File

@@ -1,5 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/> <path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector> </vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@android:color/white">
<path
android:fillColor="@android:color/white"
android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"/>
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@android:color/black">
<path
android:fillColor="@android:color/black"
android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"/>
</vector>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 5,-5z"/> <path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 5,-5z"/>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7,14l5,-5 5,5z"/> <path android:fillColor="@android:color/white" android:pathData="M7,14l5,-5 5,5z"/>

View File

@@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
</vector>

View File

@@ -1,5 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp" <vector android:autoMirrored="true" android:height="24dp"
android:tint="?attr/colorControlNormal" android:viewportHeight="24" android:tint="#FFFFFF" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/> <path android:fillColor="@android:color/white" android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
</vector> </vector>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7.41,18.59L8.83,20 12,16.83 15.17,20l1.41,-1.41L12,14l-4.59,4.59zM16.59,5.41L15.17,4 12,7.17 8.83,4 7.41,5.41 12,10l4.59,-4.59z"/> <path android:fillColor="@android:color/white" android:pathData="M7.41,18.59L8.83,20 12,16.83 15.17,20l1.41,-1.41L12,14l-4.59,4.59zM16.59,5.41L15.17,4 12,7.17 8.83,4 7.41,5.41 12,10l4.59,-4.59z"/>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,5.83L15.17,9l1.41,-1.41L12,3 7.41,7.59 8.83,9 12,5.83zM12,18.17L8.83,15l-1.41,1.41L12,21l4.59,-4.59L15.17,15 12,18.17z"/> <path android:fillColor="@android:color/white" android:pathData="M12,5.83L15.17,9l1.41,-1.41L12,3 7.41,7.59 8.83,9 12,5.83zM12,18.17L8.83,15l-1.41,1.41L12,21l4.59,-4.59L15.17,15 12,18.17z"/>

View File

@@ -2,8 +2,7 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24" android:viewportHeight="24">
android:tint="?attr/colorControlNormal">
<path <path
android:fillColor="#FFFFFFFF" android:fillColor="#FFFFFFFF"
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/> android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/> <path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>

View File

@@ -1,6 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="@color/colorPrimary"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path

View File

@@ -2,8 +2,7 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24" android:viewportHeight="24">
android:tint="?attr/colorControlNormal">
<path <path
android:pathData="M18,5l0,-3l-12,0l0,1.17l1.83,1.83z" android:pathData="M18,5l0,-3l-12,0l0,1.17l1.83,1.83z"
android:fillColor="#FFFFFF"/> android:fillColor="#FFFFFF"/>

View File

@@ -2,8 +2,7 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24" android:viewportHeight="24">
android:tint="?attr/colorControlNormal">
<path <path
android:pathData="M6,2h12v3h-12z" android:pathData="M6,2h12v3h-12z"
android:fillColor="#FFFFFF"/> android:fillColor="#FFFFFF"/>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/> <path android:fillColor="@android:color/white" android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z"/> <path android:fillColor="@android:color/white" android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z"/>

View File

@@ -1,27 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#FF000000"
android:pathData="M53.26,40.92l14.35,-6.39l2.86,6.42"
android:strokeAlpha="0.4"
android:fillAlpha="0.4"/>
<path
android:fillColor="#FF000000"
android:pathData="M36.14,40.95l2.86,-6.42l14.24,6.34"
android:strokeAlpha="0.4"
android:fillAlpha="0.4"/>
<path
android:fillColor="#FF000000"
android:pathData="M40.01,37.17l7.73,3.44H38.48l1.53,-3.44m26.58,0 l1.53,3.44H58.86l7.73,-3.44M39,34.53l-2.86,6.42v1.66H70.47V40.95L67.61,34.53 53.27,40.92l-0.02,-0.05L39,34.53Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M74.07,51.23l4.93,1.41l-6.44,22.48l-37.61,-10.79l39.13,0l0,-13.11z"
android:strokeAlpha="0.7"
android:fillAlpha="0.7"/>
<path
android:fillColor="#FF000000"
android:pathData="M34.94,40.95C31.66,40.95 29,46.19 29,52.64s2.66,11.69 5.94,11.69L74.07,64.34L74.07,40.95ZM41.21,51.08 L40.15,50.02 44.43,45.74 48.71,50.02 47.65,51.08 44.43,47.86ZM58.02,56.56a3.11,3.11 0,0 1,-2.93 2.05,3.15 3.15,0 0,1 -0.55,-0.05 3.11,3.11 0,0 1,-1.83 -1.04,3.12 3.12,0 0,1 -5.3,-0.96 0.75,0.75 0,0 1,1.41 -0.51,1.62 1.62,0 0,0 3.14,-0.55 0.75,0.75 0,0 1,1.5 0,1.62 1.62,0 0,0 3.14,0.55 0.75,0.75 0,0 1,1.41 0.51ZM64.14,51.08 L60.92,47.86L57.71,51.08l-1.06,-1.06 4.28,-4.28 4.28,4.28Z"/>
</vector>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z"/> <path android:fillColor="@android:color/white" android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z"/>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/> <path android:fillColor="@android:color/white" android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/> <path android:fillColor="@android:color/white" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/> <path android:fillColor="@android:color/white" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/listItemHighlight" android:state_activated="true" />
</selector>

View File

@@ -1,5 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF" />
<corners android:radius="10dp" />
</shape>

View File

@@ -1,5 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/> <path android:fillColor="@android:color/white" android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
</vector> </vector>

View File

@@ -10,13 +10,15 @@
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<com.google.android.material.appbar.MaterialToolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
style="?attr/toolbarStyle" /> android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
@@ -44,6 +46,7 @@
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:padding="2dp" android:padding="2dp"
android:text="@string/version_history" android:text="@string/version_history"
android:textColor="@color/colorSecondaryText"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@@ -58,12 +61,12 @@
app:layout_constraintTop_toBottomOf="@id/version_history_main" /> app:layout_constraintTop_toBottomOf="@id/version_history_main" />
<TextView <TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:text="@string/arrow" android:text="@string/arrow"
android:textColor="@color/colorSecondaryText"
android:textSize="20sp" android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -83,6 +86,7 @@
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:padding="2dp" android:padding="2dp"
android:text="@string/credits" android:text="@string/credits"
android:textColor="@color/colorSecondaryText"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@@ -97,12 +101,12 @@
app:layout_constraintTop_toBottomOf="@id/credits_main" /> app:layout_constraintTop_toBottomOf="@id/credits_main" />
<TextView <TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:text="@string/arrow" android:text="@string/arrow"
android:textColor="@color/colorSecondaryText"
android:textSize="20sp" android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -122,6 +126,7 @@
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:padding="2dp" android:padding="2dp"
android:text="@string/help_translate_this_app" android:text="@string/help_translate_this_app"
android:textColor="@color/colorSecondaryText"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@@ -138,12 +143,12 @@
app:layout_constraintTop_toBottomOf="@id/translate_main"/> app:layout_constraintTop_toBottomOf="@id/translate_main"/>
<TextView <TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:text="@string/arrow" android:text="@string/arrow"
android:textColor="@color/colorSecondaryText"
android:textSize="20sp" android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -163,6 +168,7 @@
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:padding="2dp" android:padding="2dp"
android:text="@string/license" android:text="@string/license"
android:textColor="@color/colorSecondaryText"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@@ -179,12 +185,12 @@
app:layout_constraintTop_toBottomOf="@id/license_main"/> app:layout_constraintTop_toBottomOf="@id/license_main"/>
<TextView <TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:text="@string/arrow" android:text="@string/arrow"
android:textColor="@color/colorSecondaryText"
android:textSize="20sp" android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -204,6 +210,7 @@
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:padding="2dp" android:padding="2dp"
android:text="@string/source_repository" android:text="@string/source_repository"
android:textColor="@color/colorSecondaryText"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@@ -219,12 +226,12 @@
app:layout_constraintTop_toBottomOf="@id/repo_main" /> app:layout_constraintTop_toBottomOf="@id/repo_main" />
<TextView <TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:text="@string/arrow" android:text="@string/arrow"
android:textColor="@color/colorSecondaryText"
android:textSize="20sp" android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -244,6 +251,7 @@
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:padding="2dp" android:padding="2dp"
android:text="@string/privacy_policy" android:text="@string/privacy_policy"
android:textColor="@color/colorSecondaryText"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@@ -259,12 +267,12 @@
app:layout_constraintTop_toBottomOf="@id/privacy_main" /> app:layout_constraintTop_toBottomOf="@id/privacy_main" />
<TextView <TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:text="@string/arrow" android:text="@string/arrow"
android:textColor="@color/colorSecondaryText"
android:textSize="20sp" android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -284,6 +292,7 @@
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:padding="2dp" android:padding="2dp"
android:text="@string/rate_this_app" android:text="@string/rate_this_app"
android:textColor="@color/colorSecondaryText"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@@ -299,12 +308,12 @@
app:layout_constraintTop_toBottomOf="@id/rate_main" /> app:layout_constraintTop_toBottomOf="@id/rate_main" />
<TextView <TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:text="@string/arrow" android:text="@string/arrow"
android:textColor="@color/colorSecondaryText"
android:textSize="20sp" android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -324,6 +333,7 @@
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:padding="2dp" android:padding="2dp"
android:text="@string/report_error" android:text="@string/report_error"
android:textColor="@color/colorSecondaryText"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@@ -339,12 +349,12 @@
android:padding="2dp"/> android:padding="2dp"/>
<TextView <TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:text="@string/arrow" android:text="@string/arrow"
android:textColor="@color/colorSecondaryText"
android:textSize="20sp" android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@@ -7,15 +7,14 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context="protect.card_locker.ManageGroupActivity"> tools:context="protect.card_locker.ManageGroupActivity">
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabSave" android:id="@+id/fabSave"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end|bottom" android:layout_gravity="end|bottom"
android:layout_margin="16dp" app:srcCompat="@drawable/save_24dp"
android:contentDescription="@string/save" android:contentDescription="@string/save"
app:srcCompat="@drawable/ic_done" /> android:layout_margin="16dp" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@@ -23,13 +22,15 @@
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<com.google.android.material.appbar.MaterialToolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
style="?attr/toolbarStyle" /> android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
<com.google.android.material.tabs.TabLayout <com.google.android.material.tabs.TabLayout
android:id="@+id/groups" android:id="@+id/groups"

View File

@@ -1,32 +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.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
style="?attr/toolbarStyle" />
<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"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -5,8 +5,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView <ImageView
android:background="@drawable/round_outline"
android:importantForAccessibility="no"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/barcode_disp_height" android:layout_height="@dimen/barcode_disp_height"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"

View File

@@ -2,19 +2,19 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent"> android:layout_height="fill_parent">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<com.google.android.material.appbar.MaterialToolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
style="?attr/toolbarStyle" /> android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<LinearLayout <LinearLayout
@@ -62,8 +62,7 @@
android:hint="AB1234" android:hint="AB1234"
android:importantForAutofill="no" android:importantForAutofill="no"
android:inputType="text" android:inputType="text"
android:minHeight="48dp" android:minHeight="48dp" />
tools:ignore="ContentDescription" />
</LinearLayout> </LinearLayout>
<Button <Button
android:id="@+id/noBarcode" android:id="@+id/noBarcode"
@@ -74,6 +73,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
android:text="@string/barcodeNoBarcode" android:text="@string/barcodeNoBarcode"
android:textColor="#FFFFFF"
android:enabled="false" /> android:enabled="false" />
<ListView <ListView
android:id="@+id/barcodes" android:id="@+id/barcodes"

View File

@@ -9,33 +9,14 @@
tools:context="protect.card_locker.MainActivity" tools:context="protect.card_locker.MainActivity"
tools:showIn="@layout/main_activity"> tools:showIn="@layout/main_activity">
<LinearLayout <TextView
android:id="@+id/helpSection" style="@style/AppTheme.TextView.NoData"
android:layout_width="match_parent" android:id="@+id/helpText"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:orientation="vertical" android:layout_height="match_parent"
android:visibility="gone"> android:gravity="center"
android:text="@string/noGiftCards"
<ImageView android:visibility="gone"/>
android:layout_width="match_parent"
android:layout_height="184dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_launcher_foreground" />
<TextView
style="@style/TextAppearance.Material3.HeadlineLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/welcome" />
<TextView
style="@style/AppTheme.TextView.NoData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/noGiftCards"/>
</LinearLayout>
<TextView <TextView
style="@style/AppTheme.TextView.NoData" style="@style/AppTheme.TextView.NoData"
@@ -45,7 +26,7 @@
android:gravity="center" android:gravity="center"
android:text="@string/noMatchingGiftCards" android:text="@string/noMatchingGiftCards"
android:visibility="gone"/> android:visibility="gone"/>
<TextView <TextView
style="@style/AppTheme.TextView.NoData" style="@style/AppTheme.TextView.NoData"
android:id="@+id/noGroupCardsText" android:id="@+id/noGroupCardsText"
@@ -61,9 +42,8 @@
app:spanCount="@integer/main_view_card_columns" app:spanCount="@integer/main_view_card_columns"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingBottom="80dp"
android:clipToPadding="false"
android:scrollbars="vertical" android:scrollbars="vertical"
android:background="@color/mainLoyaltyCardBackground"
android:visibility="gone"/> android:visibility="gone"/>
</RelativeLayout> </RelativeLayout>

View File

@@ -43,7 +43,8 @@
android:layout_height="@dimen/cardThumbnailSize" android:layout_height="@dimen/cardThumbnailSize"
android:layout_weight="1" android:layout_weight="1"
app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24" app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24"
android:contentDescription="@string/moveUp"/> android:contentDescription="@string/moveUp"
app:tint="@color/iconColor"/>
<ImageButton <ImageButton
android:id="@+id/moveDown" android:id="@+id/moveDown"
@@ -51,7 +52,8 @@
android:layout_height="@dimen/cardThumbnailSize" android:layout_height="@dimen/cardThumbnailSize"
android:layout_weight="1" android:layout_weight="1"
app:srcCompat="@drawable/ic_baseline_arrow_drop_down_24" app:srcCompat="@drawable/ic_baseline_arrow_drop_down_24"
android:contentDescription="@string/moveDown"/> android:contentDescription="@string/moveDown"
app:tint="@color/iconColor"/>
<ImageButton <ImageButton
android:id="@+id/edit" android:id="@+id/edit"
@@ -59,7 +61,8 @@
android:layout_height="@dimen/cardThumbnailSize" android:layout_height="@dimen/cardThumbnailSize"
android:layout_weight="1" android:layout_weight="1"
app:srcCompat="@drawable/ic_mode_edit_white_24dp" app:srcCompat="@drawable/ic_mode_edit_white_24dp"
android:contentDescription="@string/edit"/> android:contentDescription="@string/edit"
app:tint="@color/iconColor"/>
<ImageButton <ImageButton
android:id="@+id/delete" android:id="@+id/delete"
@@ -67,7 +70,8 @@
android:layout_height="@dimen/cardThumbnailSize" android:layout_height="@dimen/cardThumbnailSize"
android:layout_weight="1" android:layout_weight="1"
app:srcCompat="@drawable/ic_delete_white_24dp" app:srcCompat="@drawable/ic_delete_white_24dp"
android:contentDescription="@string/delete"/> android:contentDescription="@string/delete"
app:tint="@color/iconColor"/>
</LinearLayout> </LinearLayout>

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