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
1715 changed files with 8773 additions and 22077 deletions

View File

@@ -3,15 +3,12 @@ name: Android CI
on:
push:
branches:
- main
- master
- staging
- trying
pull_request:
branches:
- main
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
- master
jobs:
build:
@@ -23,17 +20,17 @@ jobs:
- name: Fail on bad translations
run: if grep -ri "<xliff" app/src/main/res/values*/strings.xml; then echo "Invalidly escaped translations found"; exit 1; fi
- uses: gradle/wrapper-validation-action@v1
- name: set up OpenJDK 17
run: |
sudo apt-get update
sudo apt-get install -y openjdk-17-jdk-headless
sudo update-alternatives --auto java
- name: set up JDK 11
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
- name: Build
run: ./gradlew assembleRelease
- name: Check lint
run: ./gradlew lintRelease
- name: Run unit tests
run: timeout 5m ./gradlew testReleaseUnitTest || { ./gradlew --stop && timeout 5m ./gradlew testReleaseUnitTest; }
run: ./gradlew testReleaseUnitTest || ./gradlew testReleaseUnitTest
- name: SpotBugs
run: ./gradlew spotbugsRelease
- name: Archive test results

View File

@@ -13,12 +13,11 @@ jobs:
steps:
- uses: actions/stale@v4
with:
days-before-stale: -1
days-before-close: 90
stale-issue-message: ""
stale-pr-message: ""
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.'
only-labels: 'state: needs info'
stale-issue-label: 'state: needs info'
stale-pr-label: 'state: needs info'
enable-statistics: true
only-labels: 'needs info'
stale-issue-label: 'needs info'
stale-pr-label: 'needs info'
remove-stale-when-updated: false

View File

@@ -1,8 +1,8 @@
name: Compress Images on Push to Main
name: Compress Images on Push to Master
on:
push:
branches:
- main
- master
paths:
- '**.jpg'
- '**.jpeg'

View File

@@ -2,7 +2,7 @@ name: Convert CHANGELOG to Fastlane
on:
push:
branches:
- main
- master
jobs:
convert_changelog_to_fastlane:

73
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches:
- master
pull_request:
# The branches below must be a subset of the branches above
branches:
- master
schedule:
- cron: '33 1 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -6,7 +6,7 @@ on:
jobs:
contributors_to_file:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/master'
name: Write contributors to file
steps:
- name: Checkout repo

View File

@@ -1,10 +0,0 @@
name: "Validate Gradle Wrapper"
on: [push, pull_request]
jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1

5
.gitignore vendored
View File

@@ -8,8 +8,3 @@ captures/
**/release
**/debug
app/*.log
# Bundle
/.bundle/
/vendor/bundle
/lib/bundler/man/

View File

@@ -1,167 +1,8 @@
# Changelog
## v2.25.0 - 127 (2023-07-09)
- Barcode rendering improvements
- Basic interoperability with external apps (Android 6.0+)
- Reorganized settings screen
- Fix importing from some browsers that add a trailing / to the share URL
## v2.24.2 - 126 (2023-06-18)
- Various RTL fixes
## v2.24.1 - 125 (2023-06-11)
- Deal more gracefully with missing header colours
## v2.24.0 - 124 (2023-06-10)
- Support selecting exactly which details to view in card overview
## v2.23.3 - 123 (2023-06-03)
- Minor UI improvements
- Fix new design not being usable on devices with square screens
## v2.23.2 - 122 (2023-05-30)
- Long-press card icon in view activity to change it
- Improve button styling in Groups screen
- Fix long barcode values causing barcode to scale down to nothing
## v2.23.1 - 121 (2023-05-27)
- Update used libraries
## v2.23.0 - 120 (2023-05-25)
- Complete redesign of main and loyalty card view screens
- Material You design for the settings screen
- Fix crash when using "Take a photo" with disabled camera app
## v2.22.1 - 119 (2023-04-14)
- Use Material You colours on more devices (Google library update)
## v2.22.0 - 118 (2023-03-18)
- Support setting start of card validity
- Fix Stocard import (Stocard's export format changed)
## v2.21.2 - 117 (2023-01-27)
- Remove unnecessary permissions
- Target Android 13
## v2.21.1 - 116 (2022-12-06)
- Fix quick spend dialog not allowing , separator
- Support loading image from file manager
## v2.21.0 - 115 (2022-11-06)
- Open image in gallery on long-press
- Apply Material style to dialogs
- Support creating card by sharing an image to Catima
- Add quick spend button to card screen
## v2.20.0 - 114 (2022-09-21)
- Add Monochrome icon for Android 13
- Improve first launch screen
- Fidme import fixes
## v2.19.0 - 113 (2022-08-14)
- 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 (2022-07-29)
- Make the possibility to set a custom header more visible
## v2.18.1 - 111 (2022-07-24)
- 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 (2022-06-28)
- Fix incorrect text colour on "No barcode" button
## v2.17.0 - 108 (2022-06-24)
- 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)
## Unreleased - 96
- Add CODE 93 support
- Various minor bugfixes and improvements
## v2.11.2 - 95 (2021-12-04)
@@ -658,7 +499,7 @@ Additional features/improvements:
## v0.7 - 7 (2016-07-14)
- Long-click of a card brings up option to copy card ID to the clipboard. ([pull #49](https://github.com/brarcher/loyalty-card-locker/issues/49))
- Back button on Import/Export view now works, moving user to main view
- Back button on Input/Export view now works, moving user to main view
## v0.6 - 6 (2016-05-23)

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
project.
## Translation Changes
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
## Test Your Code
There are four possible tests you can run to verify your code. The first
is unit tests, which check the basic functionality of the application, and
@@ -41,14 +28,14 @@ and SpotBugs, run using:
The final check is by testing the application on a live device and verifying
the basic functionality works as expected.
### Make Sure Your Code is Tested
## Make Sure Your Code is Tested
The Catima code uses a fair number of unit tests to verify that
the basic functionality is working. Submissions which add functionality
or significantly change the existing code should include additional tests
to verify the proper operation of the proposed changes.
### Explain Your Work
## Explain Your Work
At the top of every patch you should include a description of the problem you
are trying to solve, how you solved it, and why you chose the solution you
@@ -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
fixed.
### Sign Your Work
## Sign Your Work
The sign-off is a simple line at the end of the patch description, which
certifies that you wrote it or otherwise have the right to pass it on as an
@@ -95,10 +82,10 @@ your real name, saying:
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
and potentially merged into the main Catima repository. The preferred
way to do this is to submit a Pull Request to the Catima project.
Changes need to apply cleanly onto the main branch and pass all
Changes need to apply cleanly onto the master branch and pass all
unit tests and produce no errors during static analysis.

View File

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

View File

@@ -1,30 +1,29 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.6)
rexml
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
CFPropertyList (3.0.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.771.0)
aws-sdk-core (3.173.1)
aws-partitions (1.501.0)
aws-sdk-core (3.121.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.64.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.122.0)
aws-sdk-core (~> 3, >= 3.165.0)
jmespath (~> 1.0)
aws-sdk-kms (1.48.0)
aws-sdk-core (~> 3, >= 3.120.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.102.0)
aws-sdk-core (~> 3, >= 3.120.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.5.2)
aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
@@ -34,20 +33,19 @@ GEM
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.99.0)
faraday (1.10.3)
dotenv (2.7.6)
emoji_regex (3.2.2)
excon (0.85.0)
faraday (1.7.2)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-httpclient (~> 1.0.1)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-net_http_persistent (~> 1.1)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
@@ -56,17 +54,14 @@ GEM
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday_middleware (1.1.0)
faraday (~> 1.0)
fastimage (2.2.7)
fastlane (2.213.0)
fastimage (2.2.5)
fastlane (2.193.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -90,7 +85,7 @@ GEM
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
multipart-post (~> 2.0.0)
naturally (~> 2.2)
optparse (~> 0.1.1)
plist (>= 3.1.0, < 4.0.0)
@@ -106,9 +101,9 @@ GEM
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.42.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.0)
google-apis-androidpublisher_v3 (0.11.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-core (0.4.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@@ -117,53 +112,53 @@ GEM
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.19.0)
google-apis-core (>= 0.9.0, < 2.a)
google-apis-iamcredentials_v1 (0.7.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-playcustomapp_v1 (0.5.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.6.0)
google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.3.1)
google-cloud-storage (1.44.0)
addressable (~> 2.8)
google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.1.0)
google-cloud-storage (1.34.1)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.19.0)
google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.5.2)
faraday (>= 0.17.3, < 3.a)
googleauth (0.17.1)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
signet (~> 0.15)
highline (2.0.3)
http-cookie (1.0.5)
http-cookie (1.0.4)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.6.3)
jwt (2.7.0)
jmespath (1.4.0)
json (2.5.1)
jwt (2.2.3)
memoist (0.16.2)
mini_magick (4.12.0)
mini_mime (1.1.2)
mini_magick (4.11.0)
mini_mime (1.1.1)
multi_json (1.15.0)
multipart-post (2.3.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.1)
optparse (0.1.1)
os (1.1.4)
plist (3.7.0)
public_suffix (5.0.1)
os (1.1.1)
plist (3.6.0)
public_suffix (4.0.6)
rake (13.0.6)
representable (3.2.0)
representable (3.1.1)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
@@ -173,18 +168,18 @@ GEM
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.17.0)
signet (0.16.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.10)
simctl (1.6.8)
CFPropertyList
naturally
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
trailblazer-option (0.1.2)
trailblazer-option (0.1.1)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
@@ -192,11 +187,11 @@ GEM
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.8.1)
unf_ext (0.0.8)
unicode-display_width (1.7.0)
webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.22.0)
xcodeproj (1.21.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
@@ -215,4 +210,4 @@ DEPENDENCIES
fastlane
BUNDLED WITH
2.3.26
2.1.4

View File

@@ -1,9 +1,7 @@
import com.github.spotbugs.snom.SpotBugsTask
plugins {
id 'com.android.application'
id 'com.github.spotbugs'
}
apply plugin: 'com.android.application'
apply plugin: 'com.github.spotbugs'
spotbugs {
ignoreFailures = false
@@ -13,14 +11,15 @@ spotbugs {
}
android {
compileSdk 33
compileSdkVersion 31
buildToolsVersion "31.0.0"
defaultConfig {
applicationId "me.hackerchick.catima"
minSdk 21
targetSdk 33
versionCode 127
versionName "2.25.0"
minSdkVersion 21
targetSdkVersion 31
versionCode 95
versionName "2.11.2"
vectorDrawables.useSupportLibrary true
multiDexEnabled true
@@ -38,10 +37,6 @@ android {
}
}
buildFeatures {
viewBinding true
}
bundle {
language {
enableSplit = false
@@ -58,6 +53,10 @@ android {
targetCompatibility JavaVersion.VERSION_11
}
lintOptions {
disable "GoogleAppIndexingWarning", "ButtonStyle", "AlwaysShowAction",
"MissingTranslation", "MissingPrefix"
}
sourceSets {
test {
@@ -77,40 +76,37 @@ android {
includeAndroidResources true
}
}
lint {
lintConfig file('lint.xml')
}
namespace 'protect.card_locker'
}
dependencies {
// AndroidX
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.exifinterface:exifinterface:1.3.6'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.exifinterface:exifinterface:1.3.3'
implementation 'androidx.palette:palette:1.0.0'
implementation 'androidx.preference:preference:1.2.0'
implementation 'com.google.android.material:material:1.9.0'
implementation 'com.github.yalantis:ucrop:2.2.8'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
implementation 'androidx.preference:preference:1.1.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'com.github.yalantis:ucrop:2.2.7'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
// Splash Screen
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
// Third-party
implementation 'com.journeyapps:zxing-android-embedded:4.3.0@aar'
implementation 'com.google.zxing:core:3.5.1'
implementation 'com.google.zxing:core:3.4.1'
implementation 'org.apache.commons:commons-csv:1.9.0'
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'net.lingala.zip4j:zip4j:2.11.5'
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.4'
implementation 'net.lingala.zip4j:zip4j:2.9.1'
// SpotBugs
implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0'
// Testing
testImplementation 'androidx.test:core:1.5.0'
testImplementation 'androidx.test:core:1.4.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.10.3'
testImplementation 'org.robolectric:robolectric:4.7.3'
}
tasks.withType(SpotBugsTask) {

View File

@@ -6,8 +6,5 @@
<Match>
<Class name="~.*Manifest\$.*"/>
</Match>
<Match>
<Class name="~.*Binding" />
</Match>
</FindBugsFilter>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="AlwaysShowAction" severity="ignore" />
<issue id="ButtonStyle" severity="ignore" />
<issue id="GoogleAppIndexingWarning" severity="ignore" />
<issue id="MissingTranslation" severity="ignore" />
<issue id="MissingPrefix" severity="ignore" />
</lint>

View File

@@ -1,18 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<permission
android:description="@string/permissionReadCardsDescription"
android:icon="@drawable/ic_launcher_foreground"
android:label="@string/permissionReadCardsLabel"
android:name="${applicationId}.READ_CARDS"
android:protectionLevel="dangerous" />
xmlns:tools="http://schemas.android.com/tools"
package="protect.card_locker">
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="23" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature
android:name="android.hardware.camera"
@@ -38,12 +33,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity
android:name=".AboutActivity"
@@ -113,39 +102,7 @@
<activity
android:name=".ImportExportActivity"
android:label="@string/importExport"
android:exported="true"
android:theme="@style/AppTheme.NoActionBar">
<!-- ZIP Intent Filter -->
<intent-filter
android:label="@string/importCards">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/zip" />
<data android:scheme="content"/>
<data android:host="*"/>
</intent-filter>
<!-- JSON Intent Filter -->
<intent-filter
android:label="@string/importCards">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/json" />
<data android:scheme="content"/>
<data android:host="*"/>
</intent-filter>
<!-- CSV Intent Filter -->
<intent-filter
android:label="@string/importCards">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content"/>
<data android:host="*" />
<data android:mimeType="text/comma-separated-values" />
</intent-filter>
</activity>
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".CardShortcutConfigure"
android:exported="true"
@@ -159,15 +116,9 @@
</activity>
<activity
android:name=".UCropWrapper"
android:name="com.yalantis.ucrop.UCropActivity"
android:theme="@style/AppTheme.NoActionBar" />
<provider
android:name=".contentprovider.CardsContentProvider"
android:authorities="${applicationId}.contentprovider.cards"
android:exported="true"
android:readPermission="${applicationId}.READ_CARDS"/>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}"
@@ -183,5 +134,7 @@
<action android:name="android.service.controls.ControlsProviderService" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -1,53 +1,135 @@
package protect.card_locker;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.text.HtmlCompat;
import protect.card_locker.databinding.AboutActivityBinding;
public class AboutActivity extends CatimaAppCompatActivity {
public class AboutActivity extends CatimaAppCompatActivity implements View.OnClickListener {
private static final String TAG = "Catima";
private AboutActivityBinding binding;
private AboutContent content;
ConstraintLayout version_history, translate, license, repo, privacy, error, credits, rate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = AboutActivityBinding.inflate(getLayoutInflater());
content = new AboutContent(this);
setTitle(content.getPageTitle());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
enableToolbarBackButton();
setTitle(R.string.about);
setContentView(R.layout.about_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
TextView copyright = binding.creditsSub;
copyright.setText(content.getCopyright());
TextView versionHistory = binding.versionHistorySub;
versionHistory.setText(content.getVersionHistory());
StringBuilder contributors = new StringBuilder().append("<br/>");
binding.versionHistory.setTag("https://catima.app/changelog/");
binding.translate.setTag("https://hosted.weblate.org/engage/catima/");
binding.license.setTag("https://github.com/CatimaLoyalty/Android/blob/main/LICENSE");
binding.repo.setTag("https://github.com/CatimaLoyalty/Android/");
binding.privacy.setTag("https://catima.app/privacy-policy/");
binding.reportError.setTag("https://github.com/CatimaLoyalty/Android/issues");
binding.rate.setTag("https://play.google.com/store/apps/details?id=me.hackerchick.catima");
binding.donate.setTag("https://catima.app/contribute/#donating");
BufferedReader reader = new BufferedReader(new InputStreamReader(getResources().openRawResource(R.raw.contributors), StandardCharsets.UTF_8));
boolean installedFromGooglePlay = Utils.installedFromGooglePlay(this);
// Hide Google Play rate button if not on Google Play
binding.rate.setVisibility(installedFromGooglePlay ? View.VISIBLE : View.GONE);
// Hide donate button on Google Play (Google Play doesn't allow donation links)
binding.donate.setVisibility(installedFromGooglePlay ? View.GONE : View.VISIBLE);
try {
while (true) {
String tmp = reader.readLine();
bindClickListeners();
if (tmp == null || tmp.isEmpty()) {
reader.close();
break;
}
contributors.append("<br/>");
contributors.append(tmp);
}
} catch (IOException ignored) {
}
final List<ThirdPartyInfo> USED_LIBRARIES = new ArrayList<>();
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("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference", "GNU LGPL 3.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 Android Embedded", "https://github.com/journeyapps/zxing-android-embedded", "Apache 2.0"));
final List<ThirdPartyInfo> USED_ASSETS = new ArrayList<>();
USED_ASSETS.add(new ThirdPartyInfo("Android icons", "https://fonts.google.com/icons?selected=Material+Icons", "Apache 2.0"));
StringBuilder libs = new StringBuilder().append("<br/>");
for (ThirdPartyInfo entry : USED_LIBRARIES) {
libs.append("<br/><a href=\"").append(entry.url()).append("\">").append(entry.name()).append("</a> (").append(entry.license()).append(")");
}
StringBuilder resources = new StringBuilder().append("<br/>");
for (ThirdPartyInfo entry : USED_ASSETS) {
resources.append("<br/><a href=\"").append(entry.url()).append("\">").append(entry.name()).append("</a> (").append(entry.license()).append(")");
}
String appName = getString(R.string.app_name);
int year = Calendar.getInstance().get(Calendar.YEAR);
String version = "?";
try {
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
version = pi.versionName;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Package name not found", e);
}
TextView copyright = findViewById(R.id.credits_sub);
copyright.setText(String.format(getString(R.string.app_copyright_fmt), year));
TextView vHistory = findViewById(R.id.version_history_sub);
vHistory.setText(String.format(getString(R.string.debug_version_fmt), version));
setTitle(String.format(getString(R.string.about_title_fmt), appName));
version_history = findViewById(R.id.version_history);
translate = findViewById(R.id.translate);
license = findViewById(R.id.license);
repo = findViewById(R.id.repo);
privacy = findViewById(R.id.privacy);
error = findViewById(R.id.report_error);
credits = findViewById(R.id.credits);
rate = findViewById(R.id.rate);
version_history.setOnClickListener(this);
translate.setOnClickListener(this);
license.setOnClickListener(this);
repo.setOnClickListener(this);
privacy.setOnClickListener(this);
error.setOnClickListener(this);
rate.setOnClickListener(this);
StringBuilder contributorInfo = new StringBuilder();
contributorInfo.append(HtmlCompat.fromHtml(String.format(getString(R.string.app_contributors), contributors.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
contributorInfo.append("\n\n");
contributorInfo.append(getString(R.string.app_copyright_old));
contributorInfo.append("\n\n");
contributorInfo.append(HtmlCompat.fromHtml(String.format(getString(R.string.app_libraries), libs.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
contributorInfo.append("\n\n");
contributorInfo.append(HtmlCompat.fromHtml(String.format(getString(R.string.app_resources), resources.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
credits.setOnClickListener(view -> new AlertDialog.Builder(this)
.setTitle(R.string.credits)
.setMessage(contributorInfo.toString())
.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
})
.show());
}
@Override
@@ -60,50 +142,31 @@ public class AboutActivity extends CatimaAppCompatActivity {
}
@Override
protected void onDestroy() {
super.onDestroy();
content.destroy();
clearClickListeners();
binding = null;
public void onClick(View view) {
int id = view.getId();
String url;
if (id == R.id.version_history) {
url = "https://catima.app/changelog/";
} else if (id == R.id.translate) {
url = "https://hosted.weblate.org/engage/catima/";
} else if (id == R.id.license) {
url = "https://github.com/TheLastProject/Catima/blob/master/LICENSE";
} else if (id == R.id.repo) {
url = "https://github.com/TheLastProject/Catima/";
} else if (id == R.id.privacy) {
url = "https://catima.app/privacy-policy/";
} else if (id == R.id.report_error) {
url = "https://github.com/TheLastProject/Catima/issues";
} else if (id == R.id.rate) {
url = "https://play.google.com/store/apps/details?id=me.hackerchick.catima";
} else {
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
startActivity(intent);
}
private void bindClickListeners() {
View.OnClickListener openExternalBrowser = view -> {
Object tag = view.getTag();
if (tag instanceof String && ((String) tag).startsWith("https://")) {
(new OpenWebLinkHandler()).openBrowser(this, (String) tag);
}
};
binding.versionHistory.setOnClickListener(openExternalBrowser);
binding.translate.setOnClickListener(openExternalBrowser);
binding.license.setOnClickListener(openExternalBrowser);
binding.repo.setOnClickListener(openExternalBrowser);
binding.privacy.setOnClickListener(openExternalBrowser);
binding.reportError.setOnClickListener(openExternalBrowser);
binding.rate.setOnClickListener(openExternalBrowser);
binding.donate.setOnClickListener(openExternalBrowser);
binding.credits.setOnClickListener(view -> showCredits());
}
private void clearClickListeners() {
binding.versionHistory.setOnClickListener(null);
binding.translate.setOnClickListener(null);
binding.license.setOnClickListener(null);
binding.repo.setOnClickListener(null);
binding.privacy.setOnClickListener(null);
binding.reportError.setOnClickListener(null);
binding.rate.setOnClickListener(null);
binding.donate.setOnClickListener(null);
binding.credits.setOnClickListener(null);
}
private void showCredits() {
new MaterialAlertDialogBuilder(this)
.setTitle(R.string.credits)
.setMessage(content.getContributorInfo())
.setPositiveButton(R.string.ok, null)
.show();
}
}

View File

@@ -1,111 +0,0 @@
package protect.card_locker;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import androidx.core.text.HtmlCompat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
public class AboutContent {
public static final String TAG = "Catima";
public Context context;
public AboutContent(Context context) {
this.context = context;
}
public void destroy() {
this.context = null;
}
public String getPageTitle() {
return String.format(context.getString(R.string.about_title_fmt), context.getString(R.string.app_name));
}
public String getAppVersion() {
String version = "?";
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
version = pi.versionName;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Package name not found", e);
}
return version;
}
public int getCurrentYear() {
return Calendar.getInstance().get(Calendar.YEAR);
}
public String getCopyright() {
return String.format(context.getString(R.string.app_copyright_fmt), getCurrentYear());
}
public String getContributors() {
String contributors;
try {
contributors = "<br/>" + Utils.readTextFile(context, R.raw.contributors);
} catch (IOException ignored) {
return "";
}
return contributors.replace("\n", "<br />");
}
public String getThirdPartyLibraries() {
final List<ThirdPartyInfo> usedLibraries = new ArrayList<>();
usedLibraries.add(new ThirdPartyInfo("Color Picker", "https://github.com/jaredrummler/ColorPicker", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("Commons CSV", "https://commons.apache.org/proper/commons-csv/", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference", "GNU LGPL 3.0"));
usedLibraries.add(new ThirdPartyInfo("uCrop", "https://github.com/Yalantis/uCrop", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("Zip4j", "https://github.com/srikanth-lingala/zip4j", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("ZXing", "https://github.com/zxing/zxing", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded", "Apache 2.0"));
StringBuilder result = new StringBuilder("<br/>");
for (ThirdPartyInfo entry : usedLibraries) {
result.append("<br/>")
.append(entry.toHtml());
}
return result.toString();
}
public String getUsedThirdPartyAssets() {
final List<ThirdPartyInfo> usedAssets = new ArrayList<>();
usedAssets.add(new ThirdPartyInfo("Android icons", "https://fonts.google.com/icons?selected=Material+Icons", "Apache 2.0"));
StringBuilder result = new StringBuilder().append("<br/>");
for (ThirdPartyInfo entry : usedAssets) {
result.append("<br/>")
.append(entry.toHtml());
}
return result.toString();
}
public String getContributorInfo() {
StringBuilder contributorInfo = new StringBuilder();
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_contributors), getContributors()), HtmlCompat.FROM_HTML_MODE_COMPACT));
contributorInfo.append("\n\n");
contributorInfo.append(context.getString(R.string.app_copyright_old));
contributorInfo.append("\n\n");
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_libraries), getThirdPartyLibraries()), HtmlCompat.FROM_HTML_MODE_COMPACT));
contributorInfo.append("\n\n");
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_resources), getUsedThirdPartyAssets()), HtmlCompat.FROM_HTML_MODE_COMPACT));
return contributorInfo.toString();
}
public String getVersionHistory() {
return String.format(context.getString(R.string.debug_version_fmt), getAppVersion());
}
}

View File

@@ -1,5 +0,0 @@
package protect.card_locker;
public interface BarcodeImageWriterResultCallback {
void onBarcodeImageWriterResult(boolean success);
}

View File

@@ -5,7 +5,6 @@ import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
@@ -41,15 +40,13 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
private final CatimaBarcode format;
private final int imageHeight;
private final int imageWidth;
private final int imagePadding;
private final boolean widthPadding;
private final boolean showFallback;
private final BarcodeImageWriterResultCallback callback;
private final Runnable callback;
BarcodeImageWriterTask(
Context context, ImageView imageView, String cardIdString,
CatimaBarcode barcodeFormat, TextView textView,
boolean showFallback, BarcodeImageWriterResultCallback callback, boolean roundCornerPadding
boolean showFallback, Runnable callback
) {
mContext = context;
@@ -63,37 +60,16 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
cardId = cardIdString;
format = barcodeFormat;
int imageViewHeight = imageView.getHeight();
int imageViewWidth = imageView.getWidth();
// 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()) {
imagePadding = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, context.getResources().getDisplayMetrics()));
} else {
imagePadding = 0;
}
if (format.isSquare() && imageViewWidth > imageViewHeight) {
imageViewWidth -= imagePadding;
widthPadding = true;
} else {
imageViewHeight -= imagePadding;
widthPadding = false;
}
final int MAX_WIDTH = getMaxWidth(format);
if (format.isSquare()) {
imageHeight = imageWidth = Math.min(imageViewHeight, Math.min(MAX_WIDTH, imageViewWidth));
} else if (imageView.getWidth() < MAX_WIDTH) {
imageHeight = imageViewHeight;
imageWidth = imageViewWidth;
if (imageView.getWidth() < MAX_WIDTH) {
imageHeight = imageView.getHeight();
imageWidth = imageView.getWidth();
} else {
// Scale down the image to reduce the memory needed to produce it
imageWidth = MAX_WIDTH;
double ratio = (double) MAX_WIDTH / (double) imageViewWidth;
imageHeight = (int) (imageViewHeight * ratio);
double ratio = (double) MAX_WIDTH / (double) imageView.getWidth();
imageHeight = (int) (imageView.getHeight() * ratio);
}
this.showFallback = showFallback;
@@ -103,15 +79,12 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
switch (format.format()) {
// 2D barcodes
case AZTEC:
case DATA_MATRIX:
case MAXICODE:
case PDF_417:
case QR_CODE:
return MAX_WIDTH_2D;
// 2D but rectangular versions get blurry otherwise
case DATA_MATRIX:
return MAX_WIDTH_1D;
// 1D barcodes:
case CODABAR:
case CODE_39:
@@ -273,11 +246,6 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
if (result != null) {
Log.i(TAG, "Displaying barcode");
if (widthPadding) {
imageView.setPadding(imagePadding / 2, 0, imagePadding / 2, 0);
} else {
imageView.setPadding(0, imagePadding / 2, 0, imagePadding / 2);
}
imageView.setVisibility(View.VISIBLE);
if (isSuccesful) {
@@ -299,7 +267,7 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
}
if (callback != null) {
callback.onBarcodeImageWriterResult(isSuccesful);
callback.run();
}
}

View File

@@ -19,8 +19,6 @@ import java.util.ArrayList;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import protect.card_locker.databinding.BarcodeSelectorActivityBinding;
/**
* This activity is callable and will allow a user to enter
* barcode data and generate all barcodes possible for
@@ -28,7 +26,6 @@ import protect.card_locker.databinding.BarcodeSelectorActivityBinding;
* data and type will be returned to the caller.
*/
public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements BarcodeSelectorAdapter.BarcodeSelectorListener {
private BarcodeSelectorActivityBinding binding;
private static final String TAG = "Catima";
// Result this activity will return
@@ -43,15 +40,17 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = BarcodeSelectorActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.selectBarcodeTitle);
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.barcode_selector_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
EditText cardId = binding.cardId;
ListView mBarcodeList = binding.barcodes;
EditText cardId = findViewById(R.id.cardId);
ListView mBarcodeList = findViewById(R.id.barcodes);
mAdapter = new BarcodeSelectorAdapter(this, new ArrayList<>(), this);
mBarcodeList.setAdapter(mAdapter);
@@ -67,7 +66,7 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements
runOnUiThread(() -> {
generateBarcodes(s.toString());
View noBarcodeButtonView = binding.noBarcode;
View noBarcodeButtonView = findViewById(R.id.noBarcode);
setButtonListener(noBarcodeButtonView, s.toString());
noBarcodeButtonView.setEnabled(s.length() > 0);
});

View File

@@ -13,7 +13,6 @@ import android.widget.TextView;
import java.util.ArrayList;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.BarcodeLayoutBinding;
public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue> {
private static final String TAG = "Catima";
@@ -52,10 +51,9 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue>
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
BarcodeLayoutBinding barcodeLayoutBinding = BarcodeLayoutBinding.inflate(inflater, parent, false);
convertView = barcodeLayoutBinding.getRoot();
viewHolder.image = barcodeLayoutBinding.barcodeImage;
viewHolder.text = barcodeLayoutBinding.barcodeName;
convertView = inflater.inflate(R.layout.barcode_layout, parent, false);
viewHolder.image = convertView.findViewById(R.id.barcodeImage);
viewHolder.text = convertView.findViewById(R.id.barcodeName);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
@@ -78,7 +76,6 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue>
final CatimaBarcode format = CatimaBarcode.fromName(formatType);
image.setImageBitmap(null);
image.setClipToOutline(true);
if (image.getHeight() == 0) {
// The size of the ImageView is not yet available as it has not
@@ -92,13 +89,13 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue>
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);
}
});
} else {
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);
}
}

View File

@@ -4,68 +4,55 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.databinding.SimpleToolbarListActivityBinding;
/**
* The configuration screen for creating a shortcut.
*/
public class CardShortcutConfigure extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
private SimpleToolbarListActivityBinding binding;
public class CardShortcutConfigure extends AppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
static final String TAG = "Catima";
private SQLiteDatabase mDatabase;
private LoyaltyCardCursorAdapter mAdapter;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
binding = SimpleToolbarListActivityBinding.inflate(getLayoutInflater());
mDatabase = new DBHelper(this).getReadableDatabase();
// Set the result to CANCELED. This will cause nothing to happen if the
// aback button is pressed.
setResult(RESULT_CANCELED);
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.simple_toolbar_list_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(R.string.shortcutSelectCard);
setSupportActionBar(toolbar);
// If there are no cards, bail
int cardCount = DBHelper.getLoyaltyCardCount(mDatabase);
if (cardCount == 0) {
if (DBHelper.getLoyaltyCardCount(mDatabase) == 0) {
Toast.makeText(this, R.string.noCardsMessage, Toast.LENGTH_LONG).show();
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 = binding.list;
final RecyclerView cardList = findViewById(R.id.list);
GridLayoutManager layoutManager = (GridLayoutManager) cardList.getLayoutManager();
if (layoutManager != null) {
layoutManager.setSpanCount(getResources().getInteger(R.integer.main_view_card_columns));
}
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.Unarchived);
mAdapter = new LoyaltyCardCursorAdapter(this, cardCursor, this);
cardList.setAdapter(mAdapter);
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(mDatabase);
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor, this);
cardList.setAdapter(adapter);
}
private void onClickAction(int position) {
Cursor selected = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.Unarchived);
Cursor selected = DBHelper.getLoyaltyCardCursor(mDatabase);
selected.moveToPosition(position);
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
@@ -78,26 +65,6 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
finish();
}
@Override
public boolean onCreateOptionsMenu(Menu inputMenu) {
getMenuInflater().inflate(R.menu.card_details_menu, inputMenu);
return super.onCreateOptionsMenu(inputMenu);
}
@Override
public boolean onOptionsItemSelected(MenuItem inputItem) {
int id = inputItem.getItemId();
if (id == R.id.action_shown_details) {
mAdapter.showSelectDetailDisplayDialog();
invalidateOptionsMenu();
return true;
}
return super.onOptionsItemSelected(inputItem);
}
@Override
public void onRowClicked(int inputPosition) {

View File

@@ -1,5 +1,6 @@
package protect.card_locker;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -15,13 +16,13 @@ import android.service.controls.actions.ControlAction;
import android.service.controls.templates.StatelessTemplate;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.util.List;
import java.util.concurrent.Flow;
import java.util.function.Consumer;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@RequiresApi(Build.VERSION_CODES.R)
public class CardsOnPowerScreenService extends ControlsProviderService {
@@ -39,7 +40,7 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
@NonNull
@Override
public Flow.Publisher<Control> createPublisherForAllAvailable() {
Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.Unarchived);
Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(mDatabase);
return subscriber -> {
while (loyaltyCardCursor.moveToNext()) {
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(loyaltyCardCursor);
@@ -68,9 +69,10 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
subscriber.onSubscribe(new NoOpSubscription());
for (String controlId : controlIds) {
Control control;
Integer cardId = this.controlIdToCardId(controlId);
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, cardId);
if (card != null) {
try {
Integer cardId = this.controlIdToCardId(controlId);
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, cardId);
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra("id", card.id);
@@ -83,7 +85,7 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
.setControlTemplate(new StatelessTemplate(controlId))
.setCustomIcon(Icon.createWithBitmap(getIcon(this, card)))
.build();
} else {
} catch (NullPointerException ignored) {
Intent mainScreenIntent = new Intent(this, MainActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), -1, mainScreenIntent, PendingIntent.FLAG_IMMUTABLE);
@@ -135,7 +137,7 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
closePowerScreenOnAndroid11();
}
@SuppressWarnings({"MissingPermission", "deprecation"})
@SuppressLint({"MissingPermission", "deprecation"})
private void closePowerScreenOnAndroid11() {
// Android 12 will auto-close the power screen, but earlier versions won't
// Lint complains about this but on Android 11 the permission is not needed

View File

@@ -1,18 +1,20 @@
package protect.card_locker;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.util.TypedValue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.preference.PreferenceManager;
import java.util.HashMap;
public class CatimaAppCompatActivity extends AppCompatActivity {
SharedPreferences pref;
HashMap<String, Integer> supportedThemes;
@Override
protected void attachBaseContext(Context base) {
// Apply chosen language
@@ -20,37 +22,32 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Utils.patchColors(this);
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// material 3 designer does not consider status bar colors
// XXX changing this in onCreate causes issues with the splash screen activity, so doing this here
boolean darkMode = Utils.isDarkModeEnabled(this);
if (Build.VERSION.SDK_INT >= 23) {
View decorView = getWindow().getDecorView();
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(getWindow(), decorView);
wic.setAppearanceLightStatusBars(!darkMode);
getWindow().setStatusBarColor(Color.TRANSPARENT);
} else {
// icons are always white back then
getWindow().setStatusBarColor(darkMode ? Color.TRANSPARENT : Color.argb(127, 0, 0, 0));
public Resources.Theme getTheme() {
if (supportedThemes == null) {
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);
}
// XXX android 9 and below has a nasty rendering bug if the theme was patched earlier
Utils.postPatchColors(this);
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;
}
protected void enableToolbarBackButton() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
public int getThemeColor() {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = getTheme();
theme.resolveAttribute(R.attr.colorPrimary, typedValue, true);
return typedValue.data;
}
}

View File

@@ -67,15 +67,11 @@ public class CatimaBarcode {
public boolean isSquare() {
return mBarcodeFormat == BarcodeFormat.AZTEC
|| mBarcodeFormat == BarcodeFormat.DATA_MATRIX
|| mBarcodeFormat == BarcodeFormat.MAXICODE
|| mBarcodeFormat == BarcodeFormat.QR_CODE;
}
public boolean hasInternalPadding() {
return mBarcodeFormat == BarcodeFormat.PDF_417
|| mBarcodeFormat == BarcodeFormat.QR_CODE;
}
public BarcodeFormat format() {
return mBarcodeFormat;
}

View File

@@ -21,7 +21,7 @@ import java.util.List;
public class DBHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "Catima.db";
public static final int ORIGINAL_DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 16;
public static final int DATABASE_VERSION = 14;
public static class LoyaltyCardDbGroups {
public static final String TABLE = "groups";
@@ -33,7 +33,6 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String TABLE = "cards";
public static final String ID = "_id";
public static final String STORE = "store";
public static final String VALID_FROM = "validfrom";
public static final String EXPIRY = "expiry";
public static final String BALANCE = "balance";
public static final String BALANCE_TYPE = "balancetype";
@@ -46,7 +45,6 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String STAR_STATUS = "starstatus";
public static final String LAST_USED = "lastused";
public static final String ZOOM_LEVEL = "zoomlevel";
public static final String ARCHIVE_STATUS = "archive";
}
public static class LoyaltyCardDbIdsGroups {
@@ -73,12 +71,6 @@ public class DBHelper extends SQLiteOpenHelper {
Descending
}
public enum LoyaltyCardArchiveFilter {
All,
Archived,
Unarchived
}
public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@@ -96,7 +88,6 @@ public class DBHelper extends SQLiteOpenHelper {
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
LoyaltyCardDbIds.STORE + " TEXT not null," +
LoyaltyCardDbIds.NOTE + " TEXT not null," +
LoyaltyCardDbIds.VALID_FROM + " INTEGER," +
LoyaltyCardDbIds.EXPIRY + " INTEGER," +
LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'," +
LoyaltyCardDbIds.BALANCE_TYPE + " TEXT," +
@@ -106,8 +97,7 @@ public class DBHelper extends SQLiteOpenHelper {
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT," +
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'," +
LoyaltyCardDbIds.LAST_USED + " INTEGER DEFAULT '0', " +
LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '100', " +
LoyaltyCardDbIds.ARCHIVE_STATUS + " INTEGER DEFAULT '0' )");
LoyaltyCardDbIds.ZOOM_LEVEL + " INTEGER DEFAULT '100' )");
// create associative table for cards in groups
db.execSQL("CREATE TABLE " + LoyaltyCardDbIdsGroups.TABLE + "(" +
@@ -312,15 +302,6 @@ public class DBHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " 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' ");
}
if (oldVersion < 16 && newVersion >= 16) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.VALID_FROM + " INTEGER");
}
}
private static ContentValues generateFTSContentValues(final int id, final String store, final String note) {
@@ -365,17 +346,16 @@ public class DBHelper extends SQLiteOpenHelper {
}
public static long insertLoyaltyCard(
final SQLiteDatabase database, final String store, final String note, final Date validFrom,
final Date expiry, final BigDecimal balance, final Currency balanceType, final String cardId,
final SQLiteDatabase database, final String store, final String note, final Date expiry,
final BigDecimal balance, final Currency balanceType, final String cardId,
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();
// Card
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.VALID_FROM, validFrom != null ? validFrom.getTime() : null);
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString());
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
@@ -385,7 +365,6 @@ public class DBHelper extends SQLiteOpenHelper {
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);
long id = database.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
// FTS
@@ -399,10 +378,9 @@ public class DBHelper extends SQLiteOpenHelper {
public static long insertLoyaltyCard(
final SQLiteDatabase database, final int id, final String store, final String note,
final Date validFrom, final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId, final String barcodeId,
final CatimaBarcode barcodeType, final Integer headerColor, final int starStatus,
final Long lastUsed, final int archiveStatus) {
final Date expiry, final BigDecimal balance, final Currency balanceType,
final String cardId, final String barcodeId, final CatimaBarcode barcodeType,
final Integer headerColor, final int starStatus, final Long lastUsed) {
database.beginTransaction();
// Card
@@ -410,7 +388,6 @@ public class DBHelper extends SQLiteOpenHelper {
contentValues.put(LoyaltyCardDbIds.ID, id);
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.VALID_FROM, validFrom != null ? validFrom.getTime() : null);
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString());
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
@@ -420,7 +397,6 @@ public class DBHelper extends SQLiteOpenHelper {
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);
database.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
// FTS
@@ -434,17 +410,15 @@ public class DBHelper extends SQLiteOpenHelper {
public static boolean updateLoyaltyCard(
SQLiteDatabase database, final int id, final String store, final String note,
final Date validFrom, final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId, final String barcodeId,
final CatimaBarcode barcodeType, final Integer headerColor, final int starStatus,
final Long lastUsed, final int archiveStatus) {
final Date expiry, final BigDecimal balance, final Currency balanceType,
final String cardId, final String barcodeId, final CatimaBarcode barcodeType,
final Integer headerColor) {
database.beginTransaction();
// Card
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.VALID_FROM, validFrom != null ? validFrom.getTime() : null);
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString());
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
@@ -452,10 +426,6 @@ public class DBHelper extends SQLiteOpenHelper {
contentValues.put(LoyaltyCardDbIds.BARCODE_ID, barcodeId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType != null ? barcodeType.name() : null);
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,
whereAttrs(LoyaltyCardDbIds.ID), withArgs(id));
@@ -468,15 +438,6 @@ public class DBHelper extends SQLiteOpenHelper {
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) {
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.STAR_STATUS, starStatus);
@@ -506,15 +467,6 @@ public class DBHelper extends SQLiteOpenHelper {
return (rowsUpdated == 1);
}
public static boolean updateLoyaltyCardBalance(SQLiteDatabase database, final int id, final BigDecimal newBalance) {
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.BALANCE, newBalance.toString());
int rowsUpdated = database.update(LoyaltyCardDbIds.TABLE, contentValues,
whereAttrs(LoyaltyCardDbIds.ID),
withArgs(id));
return (rowsUpdated == 1);
}
public static LoyaltyCard getLoyaltyCard(SQLiteDatabase database, final int id) {
Cursor data = database.query(LoyaltyCardDbIds.TABLE, null, whereAttrs(LoyaltyCardDbIds.ID), withArgs(id), null, null, null);
@@ -597,35 +549,9 @@ public class DBHelper extends SQLiteOpenHelper {
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) {
// An empty string will match everything
return getLoyaltyCardCursor(database, LoyaltyCardArchiveFilter.All);
}
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, LoyaltyCardArchiveFilter archiveFilter) {
// An empty string will match everything
return getLoyaltyCardCursor(database, "", archiveFilter);
return getLoyaltyCardCursor(database, "");
}
/**
@@ -634,8 +560,8 @@ public class DBHelper extends SQLiteOpenHelper {
* @param filter
* @return Cursor
*/
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, final String filter, LoyaltyCardArchiveFilter archiveFilter) {
return getLoyaltyCardCursor(database, filter, null, archiveFilter);
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, final String filter) {
return getLoyaltyCardCursor(database, filter, null);
}
/**
@@ -645,8 +571,8 @@ public class DBHelper extends SQLiteOpenHelper {
* @param group
* @return Cursor
*/
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, final String filter, Group group, LoyaltyCardArchiveFilter archiveFilter) {
return getLoyaltyCardCursor(database, filter, group, LoyaltyCardOrder.Alpha, LoyaltyCardOrderDirection.Ascending, archiveFilter);
public static Cursor getLoyaltyCardCursor(SQLiteDatabase database, final String filter, Group group) {
return getLoyaltyCardCursor(database, filter, group, LoyaltyCardOrder.Alpha, LoyaltyCardOrderDirection.Ascending);
}
/**
@@ -657,7 +583,7 @@ public class DBHelper extends SQLiteOpenHelper {
* @param order
* @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();
String limitString = "";
@@ -680,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);
return database.rawQuery("SELECT " + LoyaltyCardDbIds.TABLE + ".* FROM " + LoyaltyCardDbIds.TABLE +
@@ -692,9 +613,7 @@ public class DBHelper extends SQLiteOpenHelper {
" ON " + LoyaltyCardDbFTS.TABLE + "." + LoyaltyCardDbFTS.ID + " = " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.ID +
(filter.trim().isEmpty() ? " " : " AND " + LoyaltyCardDbFTS.TABLE + " MATCH ? ") +
groupFilter.toString() +
archiveFilterString +
" ORDER BY " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.ARCHIVE_STATUS + " ASC, " +
LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.STAR_STATUS + " DESC, " +
" ORDER BY " + LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.STAR_STATUS + " DESC, " +
" (CASE WHEN " + LoyaltyCardDbIds.TABLE + "." + orderField + " IS NULL THEN 1 ELSE 0 END), " +
LoyaltyCardDbIds.TABLE + "." + orderField + " COLLATE NOCASE " + getDbDirection(order, direction) + ", " +
LoyaltyCardDbIds.TABLE + "." + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC " +

View File

@@ -1,31 +1,29 @@
package protect.card_locker;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.databinding.GroupLayoutBinding;
import protect.card_locker.preferences.Settings;
public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.GroupListItemViewHolder> {
public final Context mContext;
Settings mSettings;
private final Context mContext;
private final GroupAdapterListener mListener;
SQLiteDatabase mDatabase;
public GroupCursorAdapter(Context inputContext, Cursor inputCursor, GroupAdapterListener inputListener) {
super(inputCursor, DBHelper.LoyaltyCardDbGroups.ORDER);
setHasStableIds(true);
mContext = inputContext;
mSettings = new Settings(inputContext);
mContext = inputContext.getApplicationContext();
mListener = inputListener;
mDatabase = new DBHelper(inputContext).getReadableDatabase();
@@ -34,14 +32,9 @@ public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.Gro
@NonNull
@Override
public GroupCursorAdapter.GroupListItemViewHolder onCreateViewHolder(@NonNull ViewGroup inputParent, int inputViewType) {
return new GroupListItemViewHolder(
GroupLayoutBinding.inflate(
LayoutInflater.from(inputParent.getContext()),
inputParent,
false
)
);
public GroupCursorAdapter.GroupListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType) {
View itemView = LayoutInflater.from(inputParent.getContext()).inflate(R.layout.group_layout, inputParent, false);
return new GroupListItemViewHolder(itemView);
}
public void onBindViewHolder(GroupListItemViewHolder inputHolder, Cursor inputCursor) {
@@ -50,18 +43,10 @@ public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.Gro
inputHolder.mName.setText(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.mCardCount.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
applyClickEvents(inputHolder);
}
@@ -85,16 +70,16 @@ public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.Gro
public static class GroupListItemViewHolder extends RecyclerView.ViewHolder {
public TextView mName, mCardCount;
public Button mMoveUp, mMoveDown, mEdit, mDelete;
public AppCompatImageButton mMoveUp, mMoveDown, mEdit, mDelete;
public GroupListItemViewHolder(GroupLayoutBinding groupLayoutBinding) {
super(groupLayoutBinding.getRoot());
mName = groupLayoutBinding.name;
mCardCount = groupLayoutBinding.cardCount;
mMoveUp = groupLayoutBinding.moveUp;
mMoveDown = groupLayoutBinding.moveDown;
mEdit = groupLayoutBinding.edit;
mDelete = groupLayoutBinding.delete;
public GroupListItemViewHolder(View inputView) {
super(inputView);
mName = inputView.findViewById(R.id.name);
mCardCount = inputView.findViewById(R.id.cardCount);
mMoveUp = inputView.findViewById(R.id.moveUp);
mMoveDown = inputView.findViewById(R.id.moveDown);
mEdit = inputView.findViewById(R.id.edit);
mDelete = inputView.findViewById(R.id.delete);
}
}
}

View File

@@ -1,7 +1,10 @@
package protect.card_locker;
import android.Manifest;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -16,33 +19,32 @@ import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
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 androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.ImportExportActivityBinding;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.ImportExportResultType;
public class ImportExportActivity extends CatimaAppCompatActivity {
private ImportExportActivityBinding binding;
private static final String TAG = "Catima";
private static final int PERMISSIONS_EXTERNAL_STORAGE = 1;
private ImportExportTask importExporter;
private String importAlertTitle;
@@ -56,19 +58,35 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ImportExportActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.importExport);
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.import_export_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
Intent fileIntent = getIntent();
if (fileIntent != null && fileIntent.getType() != null) {
chooseImportType(false, fileIntent.getData());
// If the application does not have permissions to external
// storage, ask for it now
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(ImportExportActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSIONS_EXTERNAL_STORAGE);
}
// would use ActivityResultContracts.CreateDocument() but mime type cannot be set
@@ -86,10 +104,10 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
try {
OutputStream writer = getContentResolver().openOutputStream(uri);
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) {
Log.e(TAG, "Failed to export file: " + result.toString(), e);
onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), uri);
onExportComplete(ImportExportResult.GenericFailure, uri);
}
});
@@ -120,9 +138,9 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
intentCreateDocumentAction.setType("application/zip");
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "catima.zip");
Button exportButton = binding.exportButton;
Button exportButton = findViewById(R.id.exportButton);
exportButton.setOnClickListener(v -> {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(ImportExportActivity.this);
AlertDialog.Builder builder = new AlertDialog.Builder(ImportExportActivity.this);
builder.setTitle(R.string.exportPassword);
FrameLayout container = new FrameLayout(ImportExportActivity.this);
@@ -152,12 +170,12 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
});
// Check that there is a file manager available
Button importFilesystem = binding.importOptionFilesystemButton;
importFilesystem.setOnClickListener(v -> chooseImportType(false, null));
Button importFilesystem = findViewById(R.id.importOptionFilesystemButton);
importFilesystem.setOnClickListener(v -> chooseImportType(false));
// Check that there is an app that data can be imported from
Button importApplication = binding.importOptionApplicationButton;
importApplication.setOnClickListener(v -> chooseImportType(true, null));
Button importApplication = findViewById(R.id.importOptionApplicationButton);
importApplication.setOnClickListener(v -> chooseImportType(true));
}
private void openFileForImport(Uri uri, char[] password) {
@@ -167,13 +185,11 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
startImport(reader, uri, importDataFormat, password, true);
} catch (IOException 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);
}
}
private void chooseImportType(boolean choosePicker,
@Nullable Uri fileData) {
private void chooseImportType(boolean choosePicker) {
List<CharSequence> betaImportOptions = new ArrayList<>();
betaImportOptions.add("Fidme");
betaImportOptions.add("Stocard");
@@ -187,7 +203,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
importOptions.add(importOption);
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.chooseImportType)
.setItems(importOptions.toArray(new CharSequence[importOptions.size()]), (dialog, which) -> {
switch (which) {
@@ -225,12 +241,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
throw new IllegalArgumentException("Unknown DataFormat");
}
if (fileData != null) {
openFileForImport(fileData, null);
return;
}
new MaterialAlertDialogBuilder(this)
new AlertDialog.Builder(this)
.setTitle(importAlertTitle)
.setMessage(importAlertMessage)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@@ -255,22 +266,59 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
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) {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
@Override
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
onImportComplete(result, targetUri, dataFormat);
if (closeWhenDone) {
try {
target.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
ImportExportTask.TaskCompleteListener listener = (result, dataFormat1) -> {
onImportComplete(result, targetUri, dataFormat1);
if (closeWhenDone) {
try {
target.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
};
startProgressNotification(true);
importExporter = new ImportExportTask(ImportExportActivity.this,
dataFormat, target, password, listener);
mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter);
@@ -278,25 +326,47 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
private void startExport(final OutputStream target, final Uri targetUri, char[] password, final boolean closeWhenDone) {
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
@Override
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
onExportComplete(result, targetUri);
if (closeWhenDone) {
try {
target.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
ImportExportTask.TaskCompleteListener listener = (result, dataFormat) -> {
onExportComplete(result, targetUri);
if (closeWhenDone) {
try {
target.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
};
startProgressNotification(false);
importExporter = new ImportExportTask(ImportExportActivity.this,
DataFormat.Catima, target, password, listener);
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter);
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSIONS_EXTERNAL_STORAGE) {
// If request is cancelled, the result arrays are empty.
boolean success = grantResults.length > 0;
for (int grant : grantResults) {
if (grant != PackageManager.PERMISSION_GRANTED) {
success = false;
}
}
if (!success) {
// External storage permission rejected, inform user that
// import/export is prevented
Toast.makeText(getApplicationContext(), R.string.noExternalStoragePermissionError,
Toast.LENGTH_LONG).show();
}
}
}
@Override
protected void onDestroy() {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
@@ -317,7 +387,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
}
private void retryWithPassword(DataFormat dataFormat, Uri uri) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.passwordRequired);
final EditText input = new EditText(this);
@@ -332,68 +402,28 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
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) {
ImportExportResultType resultType = result.resultType();
endProgressNotification(true, result, null);
if (resultType == ImportExportResultType.BadPassword) {
if (result == ImportExportResult.BadPassword) {
retryWithPassword(dataFormat, path);
return;
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(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) {
ImportExportResultType resultType = result.resultType();
PendingIntent pendingIntent = null;
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.exportSuccessfulTitle : R.string.exportFailedTitle);
builder.setMessage(buildResultDialogMessage(result, false));
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
if (result == ImportExportResult.Success) {
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
sendIntent.setType("text/csv");
if (resultType == ImportExportResultType.Success) {
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
// set flag to give temporary permission to external app to use the FileProvider
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
builder.setPositiveButton(sendLabel, (dialog, which) -> {
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();
});
pendingIntent = PendingIntent.getActivity(this, NOTIFICATION_EXPORT, sendIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
builder.create().show();
endProgressNotification(false, result, pendingIntent);
}
}

View File

@@ -1,7 +1,7 @@
package protect.card_locker;
import android.app.Activity;
import android.app.ProgressDialog;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
import android.database.sqlite.SQLiteDatabase;
@@ -13,25 +13,23 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import androidx.core.app.NotificationCompat;
import protect.card_locker.async.CompatCallable;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.ImportExportResultType;
import protect.card_locker.importexport.MultiFormatExporter;
import protect.card_locker.importexport.MultiFormatImporter;
public class ImportExportTask implements CompatCallable<ImportExportResult> {
private static final String TAG = "Catima";
private Activity activity;
private boolean doImport;
private DataFormat format;
private final Activity activity;
private final boolean doImport;
private final DataFormat format;
private OutputStream outputStream;
private InputStream inputStream;
private char[] password;
private TaskCompleteListener listener;
private ProgressDialog progress;
private final char[] password;
private final TaskCompleteListener listener;
/**
* 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) {
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) {
ImportExportResult result;
ImportExportResult result = ImportExportResult.GenericFailure;
try {
OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
result = MultiFormatExporter.exportData(context, database, stream, format, password);
writer.close();
} catch (IOException e) {
result = new ImportExportResult(ImportExportResultType.GenericFailure, e.toString());
Log.e(TAG, "Unable to export file", e);
}
@@ -86,20 +83,6 @@ public class ImportExportTask implements CompatCallable<ImportExportResult> {
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) {
final SQLiteDatabase database = new DBHelper(activity).getWritableDatabase();
ImportExportResult result;
@@ -116,15 +99,15 @@ public class ImportExportTask implements CompatCallable<ImportExportResult> {
}
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");
}
protected void onCancelled() {
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled");
@Override
public void onPreExecute() {
}
protected void stop() {

View File

@@ -18,7 +18,6 @@ import java.util.List;
public class ImportURIHelper {
private static final String STORE = DBHelper.LoyaltyCardDbIds.STORE;
private static final String NOTE = DBHelper.LoyaltyCardDbIds.NOTE;
private static final String VALID_FROM = DBHelper.LoyaltyCardDbIds.VALID_FROM;
private static final String EXPIRY = DBHelper.LoyaltyCardDbIds.EXPIRY;
private static final String BALANCE = DBHelper.LoyaltyCardDbIds.BALANCE;
private static final String BALANCE_TYPE = DBHelper.LoyaltyCardDbIds.BALANCE_TYPE;
@@ -46,11 +45,8 @@ public class ImportURIHelper {
}
private boolean isImportUri(Uri uri) {
// Remove trailing slash added by some browsers (if it exists)
final String uriPath = uri.getPath().replaceAll("/$", "");
for (int i = 0; i < hosts.length; i++) {
if (uri.getHost().equals(hosts[i]) && uriPath.equals(paths[i])) {
if (uri.getHost().equals(hosts[i]) && uri.getPath().equals(paths[i])) {
return true;
}
}
@@ -66,7 +62,6 @@ public class ImportURIHelper {
try {
// These values are allowed to be null
CatimaBarcode barcodeType = null;
Date validFrom = null;
Date expiry = null;
BigDecimal balance = new BigDecimal("0");
Currency balanceType = null;
@@ -111,10 +106,6 @@ public class ImportURIHelper {
if (unparsedBalanceType != null && !unparsedBalanceType.equals("")) {
balanceType = Currency.getInstance(unparsedBalanceType);
}
String unparsedValidFrom = kv.get(VALID_FROM);
if (unparsedValidFrom != null && !unparsedValidFrom.equals("")) {
validFrom = new Date(Long.parseLong(unparsedValidFrom));
}
String unparsedExpiry = kv.get(EXPIRY);
if (unparsedExpiry != null && !unparsedExpiry.equals("")) {
expiry = new Date(Long.parseLong(unparsedExpiry));
@@ -125,8 +116,8 @@ public class ImportURIHelper {
headerColor = Integer.parseInt(unparsedHeaderColor);
}
return new LoyaltyCard(-1, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0, Utils.getUnixTime(), 100, 0);
} catch (NumberFormatException | UnsupportedEncodingException | ArrayIndexOutOfBoundsException ex) {
return new LoyaltyCard(-1, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0, Utils.getUnixTime(), 100);
} catch (NullPointerException | NumberFormatException | UnsupportedEncodingException ex) {
throw new InvalidObjectException("Not a valid import URI");
}
}
@@ -158,9 +149,6 @@ public class ImportURIHelper {
if (loyaltyCard.balanceType != null) {
fragment = appendFragment(fragment, BALANCE_TYPE, loyaltyCard.balanceType.getCurrencyCode());
}
if (loyaltyCard.validFrom != null) {
fragment = appendFragment(fragment, VALID_FROM, String.valueOf(loyaltyCard.validFrom.getTime()));
}
if (loyaltyCard.expiry != null) {
fragment = appendFragment(fragment, EXPIRY, String.valueOf(loyaltyCard.expiry.getTime()));
}

View File

@@ -10,9 +10,6 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
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
@@ -51,6 +48,7 @@ class LetterBitmap {
public LetterBitmap(Context context, String displayName, String key, int tileLetterFontSize,
int width, int height, Integer backgroundColor, Integer textColor) {
TextPaint paint = new TextPaint();
paint.setTypeface(Typeface.create("sans-serif-light", Typeface.BOLD));
if (textColor != null) {
paint.setColor(textColor);
@@ -60,8 +58,6 @@ class LetterBitmap {
paint.setTextAlign(Paint.Align.CENTER);
paint.setAntiAlias(true);
paint.setTextSize(tileLetterFontSize);
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
if (backgroundColor == null) {
mColor = getDefaultColor(context, key);
@@ -70,31 +66,22 @@ class LetterBitmap {
}
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
String firstChar = displayName.substring(0, 1).toUpperCase();
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());
String firstChar = displayName.substring(0, 1);
final Canvas c = new Canvas();
c.setBitmap(mBitmap);
c.drawColor(mColor);
Rect bounds = new Rect();
paint.getTextBounds(firstChar, 0, firstChar.length(), bounds);
c.drawText(firstChar,
0, firstChar.length(),
width / 2.0f, (height - (bounds.bottom + bounds.top)) / 2.0f
, paint);
char[] firstCharArray = new char[1];
firstCharArray[0] = firstChar.toUpperCase().charAt(0);
paint.setTextSize(tileLetterFontSize);
// The bounds that enclose the letter
Rect bounds = new Rect();
paint.getTextBounds(firstCharArray, 0, 1, bounds);
c.drawText(firstCharArray, 0, 1, width / 2.0f, height / 2.0f
+ (bounds.bottom - bounds.top) / 2.0f, paint);
}
/**
@@ -125,10 +112,6 @@ class LetterBitmap {
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
* color is provided.

View File

@@ -14,7 +14,6 @@ public class LoyaltyCard implements Parcelable {
public final int id;
public final String store;
public final String note;
public final Date validFrom;
public final Date expiry;
public final BigDecimal balance;
public final Currency balanceType;
@@ -30,20 +29,16 @@ public class LoyaltyCard implements Parcelable {
public final Integer headerColor;
public final int starStatus;
public final int archiveStatus;
public final long lastUsed;
public int zoomLevel;
public LoyaltyCard(final int id, final String store, final String note, final Date validFrom,
final Date expiry, final BigDecimal balance, final Currency balanceType,
final String cardId, @Nullable final String barcodeId,
@Nullable final CatimaBarcode barcodeType,
@Nullable final Integer headerColor, final int starStatus,
final long lastUsed, final int zoomLevel, final int archiveStatus) {
public LoyaltyCard(final int id, final String store, final String note, final Date expiry,
final BigDecimal balance, final Currency balanceType, final String cardId,
@Nullable final String barcodeId, @Nullable final CatimaBarcode barcodeType,
@Nullable final Integer headerColor, final int starStatus, final long lastUsed, final int zoomLevel) {
this.id = id;
this.store = store;
this.note = note;
this.validFrom = validFrom;
this.expiry = expiry;
this.balance = balance;
this.balanceType = balanceType;
@@ -54,15 +49,12 @@ public class LoyaltyCard implements Parcelable {
this.starStatus = starStatus;
this.lastUsed = lastUsed;
this.zoomLevel = zoomLevel;
this.archiveStatus = archiveStatus;
}
protected LoyaltyCard(Parcel in) {
id = in.readInt();
store = in.readString();
note = in.readString();
long tmpValidFrom = in.readLong();
validFrom = tmpValidFrom != -1 ? new Date(tmpValidFrom) : null;
long tmpExpiry = in.readLong();
expiry = tmpExpiry != -1 ? new Date(tmpExpiry) : null;
balance = (BigDecimal) in.readValue(BigDecimal.class.getClassLoader());
@@ -76,7 +68,6 @@ public class LoyaltyCard implements Parcelable {
starStatus = in.readInt();
lastUsed = in.readLong();
zoomLevel = in.readInt();
archiveStatus = in.readInt();
}
@Override
@@ -84,7 +75,6 @@ public class LoyaltyCard implements Parcelable {
parcel.writeInt(id);
parcel.writeString(store);
parcel.writeString(note);
parcel.writeLong(validFrom != null ? validFrom.getTime() : -1);
parcel.writeLong(expiry != null ? expiry.getTime() : -1);
parcel.writeValue(balance);
parcel.writeValue(balanceType);
@@ -95,14 +85,12 @@ public class LoyaltyCard implements Parcelable {
parcel.writeInt(starStatus);
parcel.writeLong(lastUsed);
parcel.writeInt(zoomLevel);
parcel.writeInt(archiveStatus);
}
public static LoyaltyCard toLoyaltyCard(Cursor cursor) {
int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
String store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
long validFromLong = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.VALID_FROM));
long expiryLong = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXPIRY));
BigDecimal balance = new BigDecimal(cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE)));
String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID));
@@ -110,7 +98,6 @@ public class LoyaltyCard implements Parcelable {
int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS));
long lastUsed = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.LAST_USED));
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 balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE);
@@ -118,7 +105,6 @@ public class LoyaltyCard implements Parcelable {
CatimaBarcode barcodeType = null;
Currency balanceType = null;
Date validFrom = null;
Date expiry = null;
Integer headerColor = null;
@@ -130,10 +116,6 @@ public class LoyaltyCard implements Parcelable {
balanceType = Currency.getInstance(cursor.getString(balanceTypeColumn));
}
if (validFromLong > 0) {
validFrom = new Date(validFromLong);
}
if (expiryLong > 0) {
expiry = new Date(expiryLong);
}
@@ -142,7 +124,7 @@ public class LoyaltyCard implements Parcelable {
headerColor = cursor.getInt(headerColorColumn);
}
return new LoyaltyCard(id, store, note, validFrom, 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

View File

@@ -1,7 +1,6 @@
package protect.card_locker;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
@@ -16,157 +15,60 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
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 protect.card_locker.databinding.LoyaltyCardLayoutBinding;
import com.google.android.material.card.MaterialCardView;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Currency;
import java.util.Date;
import protect.card_locker.preferences.Settings;
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder> {
private int mCurrentSelectedIndex = -1;
Settings mSettings;
boolean mDarkModeEnabled;
public final Context mContext;
private final Context mContext;
private final CardAdapterListener mListener;
protected SparseBooleanArray mSelectedItems;
protected SparseBooleanArray mAnimationItemsIndex;
private boolean mReverseAllAnimations = false;
private boolean mShowNameBelowThumbnail;
private boolean mShowNote;
private boolean mShowBalance;
private boolean mShowValidity;
private boolean mShowDetails = true;
public LoyaltyCardCursorAdapter(Context inputContext, Cursor inputCursor, CardAdapterListener inputListener) {
super(inputCursor, DBHelper.LoyaltyCardDbIds.ID);
setHasStableIds(true);
mContext = inputContext;
mSettings = new Settings(inputContext);
mContext = inputContext.getApplicationContext();
mListener = inputListener;
mSelectedItems = new SparseBooleanArray();
mAnimationItemsIndex = new SparseBooleanArray();
mDarkModeEnabled = Utils.isDarkModeEnabled(inputContext);
refreshState();
swapCursor(inputCursor);
}
private void saveDetailState(int stateId, boolean value) {
SharedPreferences cardDetailsPref = mContext.getSharedPreferences(
mContext.getString(R.string.sharedpreference_card_details),
Context.MODE_PRIVATE);
SharedPreferences.Editor cardDetailsPrefEditor = cardDetailsPref.edit();
cardDetailsPrefEditor.putBoolean(mContext.getString(stateId), value);
cardDetailsPrefEditor.apply();
}
public void refreshState() {
// Retrieve user details preference
SharedPreferences cardDetailsPref = mContext.getSharedPreferences(
mContext.getString(R.string.sharedpreference_card_details),
Context.MODE_PRIVATE);
mShowNameBelowThumbnail = cardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_name_below_thumbnail), false);
mShowNote = cardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_note), true);
mShowBalance = cardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_balance), true);
mShowValidity = cardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show_validity), true);
}
public void showNameBelowThumbnail(boolean show) {
mShowNameBelowThumbnail = show;
public void showDetails(boolean show) {
mShowDetails = show;
notifyDataSetChanged();
saveDetailState(R.string.sharedpreference_card_details_show_name_below_thumbnail, show);
}
public boolean showingNameBelowThumbnail() {
return mShowNameBelowThumbnail;
public boolean showingDetails() {
return mShowDetails;
}
public void showNote(boolean show) {
mShowNote = show;
notifyDataSetChanged();
saveDetailState(R.string.sharedpreference_card_details_show_note, show);
}
public boolean showingNote() {
return mShowNote;
}
public void showBalance(boolean show) {
mShowBalance = show;
notifyDataSetChanged();
saveDetailState(R.string.sharedpreference_card_details_show_balance, show);
}
public boolean showingBalance() {
return mShowBalance;
}
public void showValidity(boolean show) {
mShowValidity = show;
notifyDataSetChanged();
saveDetailState(R.string.sharedpreference_card_details_show_validity, show);
}
public boolean showingValidity() {
return mShowValidity;
}
public void showSelectDetailDisplayDialog() {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(mContext);
builder.setTitle(R.string.action_show_details);
builder.setMultiChoiceItems(
new String[]{
mContext.getString(R.string.show_name_below_image_thumbnail),
mContext.getString(R.string.show_note),
mContext.getString(R.string.show_balance),
mContext.getString(R.string.show_validity)
},
new boolean[]{
showingNameBelowThumbnail(),
showingNote(),
showingBalance(),
showingValidity()
},
(dialogInterface, i, b) -> {
switch (i) {
case 0: showNameBelowThumbnail(b); break;
case 1: showNote(b); break;
case 2: showBalance(b); break;
case 3: showValidity(b); break;
default: throw new IndexOutOfBoundsException("No such index exists in LoyaltyCardCursorAdapter show details view");
}
}
);
builder.setPositiveButton(R.string.ok, (dialog, i) -> dialog.dismiss());
AlertDialog dialog = builder.create();
dialog.show();
}
@NonNull
@Override
public LoyaltyCardListItemViewHolder onCreateViewHolder(@NonNull ViewGroup inputParent, int inputViewType) {
LoyaltyCardLayoutBinding loyaltyCardLayoutBinding = LoyaltyCardLayoutBinding.inflate(
LayoutInflater.from(inputParent.getContext()),
inputParent,
false
);
return new LoyaltyCardListItemViewHolder(loyaltyCardLayoutBinding, mListener);
public LoyaltyCardListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType) {
View itemView = LayoutInflater.from(inputParent.getContext()).inflate(R.layout.loyalty_card_layout, inputParent, false);
return new LoyaltyCardListItemViewHolder(itemView, mListener);
}
public LoyaltyCard getCard(int position) {
@@ -179,43 +81,37 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
inputHolder.mDivider.setVisibility(View.GONE);
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(inputCursor);
Bitmap icon = Utils.retrieveCardImage(mContext, loyaltyCard.id, ImageLocationType.icon);
if (mShowNameBelowThumbnail && icon != null) {
inputHolder.setStoreField(loyaltyCard.store);
} else {
inputHolder.setStoreField(null);
}
if (mShowNote && !loyaltyCard.note.isEmpty()) {
inputHolder.setStoreField(loyaltyCard.store);
if (mShowDetails && !loyaltyCard.note.isEmpty()) {
inputHolder.setNoteField(loyaltyCard.note);
} else {
inputHolder.setNoteField(null);
}
if (mShowBalance && !loyaltyCard.balance.equals(new BigDecimal("0"))) {
inputHolder.setExtraField(inputHolder.mBalanceField, Utils.formatBalance(mContext, loyaltyCard.balance, loyaltyCard.balanceType), null);
if (mShowDetails && !loyaltyCard.balance.equals(new BigDecimal("0"))) {
inputHolder.setBalanceField(loyaltyCard.balance, loyaltyCard.balanceType);
} else {
inputHolder.setExtraField(inputHolder.mBalanceField, null, null);
inputHolder.setBalanceField(null, null);
}
if (mShowValidity && loyaltyCard.validFrom != null) {
inputHolder.setExtraField(inputHolder.mValidFromField, DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.validFrom), Utils.isNotYetValid(loyaltyCard.validFrom) ? Color.RED : null);
if (mShowDetails && loyaltyCard.expiry != null) {
inputHolder.setExpiryField(loyaltyCard.expiry);
} else {
inputHolder.setExtraField(inputHolder.mValidFromField, null, null);
inputHolder.setExpiryField(null);
}
if (mShowValidity && loyaltyCard.expiry != null) {
inputHolder.setExtraField(inputHolder.mExpiryField, DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry), Utils.hasExpired(loyaltyCard.expiry) ? Color.RED : null);
Bitmap cardIcon = Utils.retrieveCardImage(mContext, loyaltyCard.id, ImageLocationType.icon);
if (cardIcon != null) {
inputHolder.mCardIcon.setImageBitmap(cardIcon);
inputHolder.mCardIcon.setScaleType(ImageView.ScaleType.CENTER_CROP);
} else {
inputHolder.setExtraField(inputHolder.mExpiryField, null, null);
inputHolder.mCardIcon.setImageBitmap(Utils.generateIcon(mContext, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile());
inputHolder.mCardIcon.setScaleType(ImageView.ScaleType.FIT_CENTER);
}
inputHolder.setIconBackgroundColor(loyaltyCard.headerColor != null ? loyaltyCard.headerColor : ContextCompat.getColor(mContext, R.color.colorPrimary));
inputHolder.mCardIcon.setContentDescription(loyaltyCard.store);
Utils.setIconOrTextWithBackground(mContext, loyaltyCard, icon, inputHolder.mCardIcon, inputHolder.mCardText);
inputHolder.setIconBackgroundColor(Utils.getHeaderColor(mContext, loyaltyCard));
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));
applyIconAnimation(inputHolder, inputCursor.getPosition());
@@ -241,12 +137,14 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
private void applyIconAnimation(LoyaltyCardListItemViewHolder inputHolder, int inputPosition) {
if (itemSelected(inputPosition)) {
inputHolder.mCardIcon.setVisibility(View.GONE);
inputHolder.mTickIcon.setVisibility(View.VISIBLE);
if (mCurrentSelectedIndex == inputPosition) {
resetCurrentIndex();
}
} else {
inputHolder.mTickIcon.setVisibility(View.GONE);
inputHolder.mCardIcon.setVisibility(View.VISIBLE);
if ((mReverseAllAnimations && mAnimationItemsIndex.get(inputPosition, false)) || mCurrentSelectedIndex == inputPosition) {
resetCurrentIndex();
}
@@ -300,32 +198,28 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
public class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder {
public TextView mCardText, mStoreField, mNoteField, mBalanceField, mValidFromField, mExpiryField;
public ImageView mCardIcon, mStarBackground, mStarBorder, mTickIcon, mArchivedBackground;
public TextView mStoreField, mNoteField, mBalanceField, mExpiryField;
public ImageView mCardIcon, mStarBackground, mStarBorder, mTickIcon;
public MaterialCardView mRow;
public ConstraintLayout mStar, mArchived;
public ConstraintLayout mStar;
public View mDivider;
private int mIconBackgroundColor;
protected LoyaltyCardListItemViewHolder(LoyaltyCardLayoutBinding loyaltyCardLayoutBinding, CardAdapterListener inputListener) {
super(loyaltyCardLayoutBinding.getRoot());
View inputView = loyaltyCardLayoutBinding.getRoot();
mRow = loyaltyCardLayoutBinding.row;
mDivider = loyaltyCardLayoutBinding.infoDivider;
mStoreField = loyaltyCardLayoutBinding.store;
mNoteField = loyaltyCardLayoutBinding.note;
mBalanceField = loyaltyCardLayoutBinding.balance;
mValidFromField = loyaltyCardLayoutBinding.validFrom;
mExpiryField = loyaltyCardLayoutBinding.expiry;
mCardIcon = loyaltyCardLayoutBinding.thumbnail;
mCardText = loyaltyCardLayoutBinding.thumbnailText;
mStar = loyaltyCardLayoutBinding.star;
mStarBackground = loyaltyCardLayoutBinding.starBackground;
mStarBorder = loyaltyCardLayoutBinding.starBorder;
mArchived = loyaltyCardLayoutBinding.archivedIcon;
mArchivedBackground = loyaltyCardLayoutBinding.archiveBackground;
mTickIcon = loyaltyCardLayoutBinding.selectedThumbnail;
protected LoyaltyCardListItemViewHolder(View inputView, CardAdapterListener inputListener) {
super(inputView);
mRow = inputView.findViewById(R.id.row);
mDivider = inputView.findViewById(R.id.info_divider);
mStoreField = inputView.findViewById(R.id.store);
mNoteField = inputView.findViewById(R.id.note);
mBalanceField = inputView.findViewById(R.id.balance);
mExpiryField = inputView.findViewById(R.id.expiry);
mCardIcon = inputView.findViewById(R.id.thumbnail);
mStar = inputView.findViewById(R.id.star);
mStarBackground = inputView.findViewById(R.id.star_background);
mStarBorder = inputView.findViewById(R.id.star_border);
mTickIcon = inputView.findViewById(R.id.selected_thumbnail);
inputView.setOnLongClickListener(view -> {
inputListener.onRowClicked(getAdapterPosition());
inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
@@ -333,43 +227,9 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
});
}
private void setExtraField(TextView field, String text, Integer color) {
// If text is null, hide the field
// If iconColor is null, use the default text and icon color based on theme
if (text == null) {
field.setVisibility(View.GONE);
field.requestLayout();
return;
}
field.setVisibility(View.VISIBLE);
field.setText(text);
field.setTextColor(color != null ? color : MaterialColors.getColor(mContext, com.google.android.material.R.attr.colorSecondary, ContextCompat.getColor(mContext, mDarkModeEnabled ? R.color.md_theme_dark_secondary : R.color.md_theme_light_secondary)));
mDivider.setVisibility(View.VISIBLE);
field.setVisibility(View.VISIBLE);
Drawable icon = field.getCompoundDrawables()[0];
if (icon != null) {
icon.mutate();
field.setCompoundDrawablesRelative(icon, null, null, null);
if (color != null) {
icon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP));
} else {
icon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(mDarkModeEnabled ? Color.WHITE : Color.BLACK, BlendModeCompat.SRC_ATOP));
}
}
field.requestLayout();
}
public void setStoreField(String text) {
if (text == null) {
mStoreField.setVisibility(View.GONE);
} else {
mStoreField.setVisibility(View.VISIBLE);
mStoreField.setText(text);
}
mStoreField.setText(text);
mStoreField.setTextSize(mSettings.getFontSizeMax(mSettings.getMediumFont()));
mStoreField.requestLayout();
}
@@ -379,11 +239,55 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
} else {
mNoteField.setVisibility(View.VISIBLE);
mNoteField.setText(text);
mNoteField.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
}
mNoteField.requestLayout();
}
public void toggleCardStateIcon(boolean enableStar, boolean enableArchive, boolean colorByTheme) {
public void setBalanceField(BigDecimal balance, Currency balanceType) {
if (balance == null) {
mBalanceField.setVisibility(View.GONE);
} else {
int size = mSettings.getFontSizeMax(mSettings.getSmallFont());
int drawableSize = dpToPx((size * 24) / 14, mContext);
mDivider.setVisibility(View.VISIBLE);
mBalanceField.setVisibility(View.VISIBLE);
Drawable balanceIcon = mBalanceField.getCompoundDrawables()[0];
balanceIcon.setBounds(0, 0, drawableSize, drawableSize);
mBalanceField.setCompoundDrawablesRelative(balanceIcon, null, null, null);
if (mDarkModeEnabled) {
balanceIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
}
mBalanceField.setText(Utils.formatBalance(mContext, balance, balanceType));
mBalanceField.setTextSize(size);
}
mBalanceField.requestLayout();
}
public void setExpiryField(Date expiry) {
if (expiry == null) {
mExpiryField.setVisibility(View.GONE);
} else {
int size = mSettings.getFontSizeMax(mSettings.getSmallFont());
int drawableSize = dpToPx((size * 24) / 14, mContext);
mDivider.setVisibility(View.VISIBLE);
mExpiryField.setVisibility(View.VISIBLE);
Drawable expiryIcon = mExpiryField.getCompoundDrawables()[0];
expiryIcon.setBounds(0, 0, drawableSize, drawableSize);
mExpiryField.setCompoundDrawablesRelative(expiryIcon, null, null, null);
if (Utils.hasExpired(expiry)) {
expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.RED, BlendModeCompat.SRC_ATOP));
mExpiryField.setTextColor(Color.RED);
} else if (mDarkModeEnabled) {
expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
}
mExpiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(expiry));
mExpiryField.setTextSize(size);
}
mExpiryField.requestLayout();
}
public void toggleStar(boolean enable, boolean colorByTheme) {
/* the below code does not work in android 5! hence the change of drawable instead
boolean needDarkForeground = Utils.needsDarkForeground(mIconBackgroundColor);
Drawable borderDrawable = mStarBorder.getDrawable().mutate();
@@ -397,33 +301,20 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
if (colorByTheme) {
dark = !mDarkModeEnabled;
}
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);
mStarBackground.setImageResource(R.drawable.ic_starred_black);
} else {
mStarBorder.setImageResource(R.drawable.ic_unstarred_white);
mStarBackground.setImageResource(R.drawable.ic_starred_white);
mArchivedBackground.setImageResource(R.drawable.ic_baseline_archive_24);
}
if (enableStar) {
if (enable) {
mStar.setVisibility(View.VISIBLE);
} else{
} else {
mStar.setVisibility(View.GONE);
}
if (enableArchive) {
mArchived.setVisibility(View.VISIBLE);
} else{
mArchived.setVisibility(View.GONE);
}
mStarBorder.invalidate();
mStarBackground.invalidate();
mArchivedBackground.invalidate();
}
public void setIconBackgroundColor(int color) {

View File

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,6 @@ public enum LoyaltyCardField {
id,
store,
note,
validFrom,
expiry,
balance,
balanceType,
@@ -12,6 +11,5 @@ public enum LoyaltyCardField {
barcodeId,
barcodeType,
headerColor,
starStatus,
archiveStatus
starStatus
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -9,15 +9,12 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.database.CursorIndexOutOfBoundsException;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.CheckBox;
import android.widget.Toast;
@@ -27,53 +24,38 @@ import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.splashscreen.SplashScreen;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import protect.card_locker.databinding.ArchiveActivityBinding;
import protect.card_locker.databinding.ContentMainBinding;
import protect.card_locker.databinding.MainActivityBinding;
import protect.card_locker.databinding.SortingOptionBinding;
import protect.card_locker.preferences.SettingsActivity;
public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
private MainActivityBinding binding;
private ArchiveActivityBinding archiveActivityBinding;
private ContentMainBinding contentMainBinding;
public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener, GestureDetector.OnGestureListener {
private static final String TAG = "Catima";
public static final String RESTART_ACTIVITY_INTENT = "restart_activity_intent";
private static final int MEDIUM_SCALE_FACTOR_DIP = 460;
private SQLiteDatabase mDatabase;
private LoyaltyCardCursorAdapter mAdapter;
private ActionMode mCurrentActionMode;
private SearchView mSearchView;
private int mLoyaltyCardCount = 0;
private GestureDetector mGestureDetector;
protected String mFilter = "";
protected Object mGroup = null;
protected DBHelper.LoyaltyCardOrder mOrder = DBHelper.LoyaltyCardOrder.Alpha;
protected DBHelper.LoyaltyCardOrderDirection mOrderDirection = DBHelper.LoyaltyCardOrderDirection.Ascending;
protected int selectedTab = 0;
private RecyclerView mCardList;
private View mHelpSection;
private View mHelpText;
private View mNoMatchingCardsText;
private View mNoGroupCardsText;
private TabLayout groupsTabLayout;
private boolean mArchiveMode;
public static final String BUNDLE_ARCHIVE_MODE = "archiveMode";
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
private ActivityResultLauncher<Intent> mSettingsLauncher;
@@ -144,7 +126,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
inputMode.finish();
return true;
} else if (inputItem.getItemId() == R.id.action_delete) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(MainActivity.this);
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
// The following may seem weird, but it is necessary to give translators enough flexibility.
// For example, in Russian, Android's plural quantity "one" actually refers to "any number ending on 1 but not ending in 11".
// So while in English the extra non-plural form seems unnecessary duplication, it is necessary to give translators enough flexibility.
@@ -158,18 +140,20 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
DBHelper db = new DBHelper(MainActivity.this);
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);
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
}
TabLayout.Tab tab = groupsTabLayout.getTabAt(selectedTab);
TabLayout.Tab tab = ((TabLayout) findViewById(R.id.groups)).getTabAt(selectedTab);
mGroup = tab != null ? tab.getTag() : null;
updateLoyaltyCardList(true);
updateLoyaltyCardList();
dialog.dismiss();
});
@@ -177,40 +161,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
AlertDialog dialog = builder.create();
dialog.show();
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;
}
@@ -226,38 +176,23 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
@Override
protected void onCreate(Bundle inputSavedInstanceState) {
extractIntentFields(getIntent());
SplashScreen.installSplashScreen(this);
super.onCreate(inputSavedInstanceState);
if (!mArchiveMode) {
binding = MainActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.app_name);
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
groupsTabLayout = binding.groups;
contentMainBinding = ContentMainBinding.bind(binding.include.getRoot());
} else {
archiveActivityBinding = ArchiveActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.archiveList);
setContentView(archiveActivityBinding.getRoot());
setSupportActionBar(archiveActivityBinding.toolbar);
groupsTabLayout = archiveActivityBinding.groups;
contentMainBinding = ContentMainBinding.bind(archiveActivityBinding.include.getRoot());
}
if(mArchiveMode) {
enableToolbarBackButton();
}
SplashScreen.installSplashScreen(this);
setTitle(R.string.app_name);
setContentView(R.layout.main_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mDatabase = new DBHelper(this).getWritableDatabase();
TabLayout groupsTabLayout = findViewById(R.id.groups);
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
selectedTab = tab.getPosition();
Log.d("onTabSelected", "Tab Position " + tab.getPosition());
mGroup = tab.getTag();
updateLoyaltyCardList(false);
updateLoyaltyCardList();
// Store active tab in Shared Preference to restore next app launch
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
@@ -278,17 +213,26 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
});
mHelpSection = contentMainBinding.helpSection;
mNoMatchingCardsText = contentMainBinding.noMatchingCardsText;
mNoGroupCardsText = contentMainBinding.noGroupCardsText;
mCardList = contentMainBinding.list;
mGestureDetector = new GestureDetector(this, this);
View.OnTouchListener gestureTouchListener = (v, event) -> mGestureDetector.onTouchEvent(event);
mHelpText = findViewById(R.id.helpText);
mNoMatchingCardsText = findViewById(R.id.noMatchingCardsText);
mNoGroupCardsText = findViewById(R.id.noGroupCardsText);
mCardList = findViewById(R.id.list);
mHelpText.setOnTouchListener(gestureTouchListener);
mNoMatchingCardsText.setOnTouchListener(gestureTouchListener);
mCardList.setOnTouchListener(gestureTouchListener);
mNoGroupCardsText.setOnTouchListener(gestureTouchListener);
mAdapter = new LoyaltyCardCursorAdapter(this, null, this);
mCardList.setAdapter(mAdapter);
registerForContextMenu(mCardList);
mGroup = null;
updateLoyaltyCardList(true);
updateLoyaltyCardList();
/*
* This was added for Huawei, but Huawei is just too much of a fucking pain.
@@ -322,17 +266,21 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
*/
mBarcodeScannerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
// Exit early if the user cancelled the scan (pressed back/home)
if (result.getResultCode() != RESULT_OK) {
return;
}
Intent intent = result.getData();
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, this);
Bundle inputBundle = intent.getExtras();
String group = inputBundle != null ? inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
processBarcodeValues(barcodeValues, group);
if (!barcodeValues.isEmpty()) {
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle newBundle = new Bundle();
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
Bundle inputBundle = intent.getExtras();
if (inputBundle != null && inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) != null) {
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP));
}
newIntent.putExtras(newBundle);
startActivity(newIntent);
}
});
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
@@ -349,8 +297,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
protected void onResume() {
super.onResume();
mAdapter.refreshState();
if (mCurrentActionMode != null) {
mAdapter.clearSelections();
mCurrentActionMode.finish();
@@ -361,30 +307,21 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
// Start of active tab logic
TabLayout groupsTabLayout = findViewById(R.id.groups);
updateTabGroups(groupsTabLayout);
// Restore selected tab from Shared Preference
// Restore settings from Shared Preference
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
Context.MODE_PRIVATE);
selectedTab = activeTabPref.getInt(getString(R.string.sharedpreference_active_tab), 0);
// Restore sort preferences from Shared Preferences
// If one of the sorting prefererences has never been set or is set to an invalid value,
// stick to the defaults.
SharedPreferences sortPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_sort),
Context.MODE_PRIVATE);
String orderString = sortPref.getString(getString(R.string.sharedpreference_sort_order), null);
String orderDirectionString = sortPref.getString(getString(R.string.sharedpreference_sort_direction), null);
if (orderString != null && orderDirectionString != null) {
try {
mOrder = DBHelper.LoyaltyCardOrder.valueOf(orderString);
mOrderDirection = DBHelper.LoyaltyCardOrderDirection.valueOf(orderDirectionString);
} catch (IllegalArgumentException ignored) {
}
try {
mOrder = DBHelper.LoyaltyCardOrder.valueOf(sortPref.getString(getString(R.string.sharedpreference_sort_order), null));
mOrderDirection = DBHelper.LoyaltyCardOrderDirection.valueOf(sortPref.getString(getString(R.string.sharedpreference_sort_direction), null));
} catch (IllegalArgumentException | NullPointerException ignored) {
}
mGroup = null;
@@ -398,32 +335,26 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
groupsTabLayout.selectTab(tab);
assert tab != null;
mGroup = tab.getTag();
} else if (!mArchiveMode) {
scaleScreen();
}
updateLoyaltyCardList(true);
updateLoyaltyCardList();
// End of active tab logic
if (!mArchiveMode) {
FloatingActionButton addButton = binding.fabAdd;
addButton.setOnClickListener(v -> {
Intent intent = new Intent(getApplicationContext(), ScanActivity.class);
Bundle bundle = new Bundle();
if (selectedTab != 0) {
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, groupsTabLayout.getTabAt(selectedTab).getText().toString());
}
intent.putExtras(bundle);
mBarcodeScannerLauncher.launch(intent);
});
addButton.bringToFront();
}
FloatingActionButton addButton = findViewById(R.id.fabAdd);
addButton.setOnClickListener(v -> {
Intent intent = new Intent(getApplicationContext(), ScanActivity.class);
Bundle bundle = new Bundle();
if (selectedTab != 0) {
bundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, groupsTabLayout.getTabAt(selectedTab).getText().toString());
}
intent.putExtras(bundle);
mBarcodeScannerLauncher.launch(intent);
});
addButton.bringToFront();
}
@Override
public void onBackPressed() {
if (mSearchView != null && !mSearchView.isIconified()) {
if (!mSearchView.isIconified()) {
mSearchView.setIconified(true);
return;
}
@@ -431,37 +362,20 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
super.onBackPressed();
}
private void displayCardSetupOptions(Menu menu, boolean shouldShow) {
for (int id : new int[]{R.id.action_search, R.id.action_shown_details, R.id.action_sort}) {
menu.findItem(id).setVisible(shouldShow);
}
}
private void updateLoyaltyCardCount() {
mLoyaltyCardCount = DBHelper.getLoyaltyCardCount(mDatabase);
}
private void updateLoyaltyCardList(boolean updateCount) {
private void updateLoyaltyCardList() {
Group group = null;
if (mGroup != null) {
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) {
updateLoyaltyCardCount();
// Update menu icons if necessary
invalidateOptionsMenu();
}
if (mLoyaltyCardCount > 0) {
if (DBHelper.getLoyaltyCardCount(mDatabase) > 0) {
// We want the cardList to be visible regardless of the filtered match count
// to ensure that the noMatchingCardsText doesn't end up being shown below
// the keyboard
mHelpSection.setVisibility(View.GONE);
mHelpText.setVisibility(View.GONE);
mNoGroupCardsText.setVisibility(View.GONE);
if (mAdapter.getItemCount() > 0) {
mCardList.setVisibility(View.VISIBLE);
mNoMatchingCardsText.setVisibility(View.GONE);
@@ -478,15 +392,8 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
}
} 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);
mHelpSection.setVisibility(View.VISIBLE);
mHelpText.setVisibility(View.VISIBLE);
mNoMatchingCardsText.setVisibility(View.GONE);
mNoGroupCardsText.setVisibility(View.GONE);
}
@@ -496,72 +403,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
}
private void processBarcodeValues(BarcodeValues barcodeValues, String group) {
if (barcodeValues.isEmpty()) {
throw new IllegalArgumentException("barcodesValues may not be empty");
}
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle newBundle = new Bundle();
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
if (group != null) {
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
}
newIntent.putExtras(newBundle);
startActivity(newIntent);
}
private void onSharedIntent(Intent intent) {
String receivedAction = intent.getAction();
String receivedType = intent.getType();
// Check if an image was shared to us
if (Intent.ACTION_SEND.equals(receivedAction)) {
if (!receivedType.startsWith("image/")) {
Log.e(TAG, "Wrong mime-type");
return;
}
BarcodeValues barcodeValues;
Bitmap bitmap;
Uri data = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (data == null) {
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
finish();
return;
}
try {
bitmap = Utils.retrieveImageFromUri(this, data);
} catch (IOException e) {
Log.e(TAG, "Error getting data from image file");
e.printStackTrace();
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
finish();
return;
}
barcodeValues = Utils.getBarcodeFromBitmap(bitmap);
if (barcodeValues.isEmpty()) {
Log.i(TAG, "No barcode found in image file");
Toast.makeText(this, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
finish();
return;
}
processBarcodeValues(barcodeValues, null);
}
}
private void extractIntentFields(Intent intent) {
final Bundle b = intent.getExtras();
mArchiveMode = b != null && b.getBoolean(BUNDLE_ARCHIVE_MODE, false);
onSharedIntent(intent);
}
public void updateTabGroups(TabLayout groupsTabLayout) {
List<Group> newGroups = DBHelper.getGroups(mDatabase);
@@ -586,18 +427,11 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
groupsTabLayout.setVisibility(View.VISIBLE);
}
@Override
public boolean onCreateOptionsMenu(Menu inputMenu) {
if (!mArchiveMode) {
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
} else {
getMenuInflater().inflate(R.menu.archive_menu, inputMenu);
}
displayCardSetupOptions(inputMenu, mLoyaltyCardCount > 0);
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
if (searchManager != null) {
@@ -620,24 +454,16 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
public boolean onQueryTextChange(String newText) {
mFilter = newText;
TabLayout groupsTabLayout = findViewById(R.id.groups);
TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition());
mGroup = currentTab != null ? currentTab.getTag() : null;
updateLoyaltyCardList(false);
updateLoyaltyCardList();
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);
}
@@ -645,18 +471,24 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
public boolean onOptionsItemSelected(MenuItem inputItem) {
int id = inputItem.getItemId();
if (id == android.R.id.home) {
onBackPressed();
}
if (id == R.id.action_unfold) {
boolean shouldShow = !mAdapter.showingDetails();
if (id == R.id.action_shown_details) {
mAdapter.showSelectDetailDisplayDialog();
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;
}
if (id == R.id.action_sort) {
TabLayout.Tab tab = ((TabLayout) findViewById(R.id.groups)).getTabAt(selectedTab);
AtomicInteger currentIndex = new AtomicInteger();
List<DBHelper.LoyaltyCardOrder> loyaltyCardOrders = Arrays.asList(DBHelper.LoyaltyCardOrder.values());
for (int i = 0; i < loyaltyCardOrders.size(); i++) {
@@ -666,29 +498,23 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(MainActivity.this);
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle(R.string.sort_by);
SortingOptionBinding sortingOptionBinding = SortingOptionBinding
.inflate(LayoutInflater.from(MainActivity.this), null, false);
final View customLayout = sortingOptionBinding.getRoot();
final View customLayout = getLayoutInflater().inflate(R.layout.sorting_option, null);
builder.setView(customLayout);
CheckBox showReversed = sortingOptionBinding.checkBoxReverse;
showReversed.setChecked(mOrderDirection == DBHelper.LoyaltyCardOrderDirection.Descending);
CheckBox ch = (CheckBox) customLayout.findViewById(R.id.checkBox_reverse);
ch.setChecked(mOrderDirection == DBHelper.LoyaltyCardOrderDirection.Descending);
builder.setSingleChoiceItems(R.array.sort_types_array, currentIndex.get(), (dialog, which) -> currentIndex.set(which));
builder.setPositiveButton(R.string.sort, (dialog, which) -> {
setSort(
loyaltyCardOrders.get(currentIndex.get()),
showReversed.isChecked() ? DBHelper.LoyaltyCardOrderDirection.Descending : DBHelper.LoyaltyCardOrderDirection.Ascending
);
if (ch.isChecked()) {
setSort(loyaltyCardOrders.get(currentIndex.get()), DBHelper.LoyaltyCardOrderDirection.Descending);
} else {
setSort(loyaltyCardOrders.get(currentIndex.get()), DBHelper.LoyaltyCardOrderDirection.Ascending);
}
dialog.dismiss();
});
@@ -706,15 +532,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
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) {
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
startActivity(i);
@@ -733,7 +550,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
return true;
}
return super.onOptionsItemSelected(inputItem);
}
@@ -752,7 +568,85 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
sortPrefEditor.apply();
// Update card list
updateLoyaltyCardList(false);
updateLoyaltyCardList();
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
mGestureDetector.onTouchEvent(ev);
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.d(TAG, "On fling");
// Don't swipe if we have too much vertical movement
if (Math.abs(velocityY) > (0.75 * Math.abs(velocityX))) {
return false;
}
TabLayout groupsTabLayout = findViewById(R.id.groups);
if (groupsTabLayout.getTabCount() < 2) {
return false;
}
Integer currentTab = groupsTabLayout.getSelectedTabPosition();
Log.d("onFling", "Current Tab " + currentTab);
// Swipe right
if (velocityX < -150) {
Log.d("onFling", "Right Swipe detected " + velocityX);
Integer nextTab = currentTab + 1;
if (nextTab == groupsTabLayout.getTabCount()) {
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(0));
} else {
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(nextTab));
}
return true;
}
// Swipe left
if (velocityX > 150) {
Log.d("onFling", "Left Swipe detected " + velocityX);
Integer nextTab = currentTab - 1;
if (nextTab < 0) {
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(groupsTabLayout.getTabCount() - 1));
} else {
groupsTabLayout.selectTab(groupsTabLayout.getTabAt(nextTab));
}
return true;
}
return false;
}
@Override
@@ -767,16 +661,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
toggleSelection(inputPosition);
}
private void scaleScreen() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
binding.include.welcomeIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
}
private void toggleSelection(int inputPosition) {
mAdapter.toggleSelection(inputPosition);
int count = mAdapter.getSelectedItemCount();
@@ -787,46 +671,10 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
mCurrentActionMode.setTitle(getResources().getQuantityString(R.plurals.selectedCardCount, count, count));
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) {
starItem.setVisible(!hasStarred);
unstarItem.setVisible(!hasUnstarred);
editItem.setVisible(true);
editItem.setEnabled(true);
} else {
starItem.setVisible(hasUnstarred);
unstarItem.setVisible(hasStarred);
editItem.setVisible(false);
editItem.setEnabled(false);
}
@@ -835,7 +683,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
}
@Override
public void onRowClicked(int inputPosition) {
if (mAdapter.getSelectedItemCount() > 0) {
@@ -857,22 +704,15 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
return;
}
Intent intent = new Intent(this, LoyaltyCardViewActivity.class);
intent.setAction("");
Intent i = new Intent(this, LoyaltyCardViewActivity.class);
i.setAction("");
final Bundle b = new Bundle();
b.putInt("id", loyaltyCard.id);
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);
i.putExtras(b);
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard);
startActivity(intent);
startActivity(i);
}
}
}

View File

@@ -1,19 +1,17 @@
package protect.card_locker;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.ArrayList;
@@ -21,15 +19,13 @@ import java.util.HashMap;
import java.util.Map;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.databinding.ActivityManageGroupBinding;
public class ManageGroupActivity extends CatimaAppCompatActivity implements ManageGroupCursorAdapter.CardAdapterListener {
private ActivityManageGroupBinding binding;
private SQLiteDatabase mDatabase;
private ManageGroupCursorAdapter mAdapter;
@@ -38,7 +34,7 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
protected Group mGroup = null;
private RecyclerView mCardList;
private TextView noGroupCardsText;
private TextView mHelpText;
private EditText mGroupNameText;
private boolean mGroupNameNotInUse;
@@ -46,18 +42,17 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
@Override
protected void onCreate(Bundle inputSavedInstanceState) {
super.onCreate(inputSavedInstanceState);
binding = ActivityManageGroupBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.activity_manage_group);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mDatabase = new DBHelper(this).getWritableDatabase();
noGroupCardsText = binding.include.noGroupCardsText;
mCardList = binding.include.list;
FloatingActionButton saveButton = binding.fabSave;
mHelpText = findViewById(R.id.helpText);
mCardList = findViewById(R.id.list);
FloatingActionButton saveButton = findViewById(R.id.fabSave);
mGroupNameText = binding.editTextGroupName;
mGroupNameText = findViewById(R.id.editTextGroupName);
mGroupNameText.addTextChangedListener(new TextWatcher() {
@Override
@@ -109,7 +104,12 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
mGroupNameText.setText(inputSavedInstanceState.getString(SAVE_INSTANCE_CURRENT_GROUP_NAME));
}
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar == null) {
throw (new RuntimeException("mActionBar is not expected to be null here"));
}
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
saveButton.setOnClickListener(v -> {
String currentGroupName = mGroupNameText.getText().toString().trim();
@@ -132,7 +132,7 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
finish();
});
// 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();
}
@@ -156,26 +156,6 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
return ret;
}
@Override
public boolean onCreateOptionsMenu(Menu inputMenu) {
getMenuInflater().inflate(R.menu.card_details_menu, inputMenu);
return super.onCreateOptionsMenu(inputMenu);
}
@Override
public boolean onOptionsItemSelected(MenuItem inputItem) {
int id = inputItem.getItemId();
if (id == R.id.action_shown_details) {
mAdapter.showSelectDetailDisplayDialog();
invalidateOptionsMenu();
return true;
}
return super.onOptionsItemSelected(inputItem);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
@@ -190,16 +170,16 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
if (mAdapter.getItemCount() == 0) {
mCardList.setVisibility(View.GONE);
noGroupCardsText.setVisibility(View.VISIBLE);
mHelpText.setVisibility(View.VISIBLE);
} else {
mCardList.setVisibility(View.VISIBLE);
noGroupCardsText.setVisibility(View.GONE);
mHelpText.setVisibility(View.GONE);
}
}
private void leaveWithoutSaving() {
if (hasChanged()) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(ManageGroupActivity.this);
AlertDialog.Builder builder = new AlertDialog.Builder(ManageGroupActivity.this);
builder.setTitle(R.string.leaveWithoutSaveTitle);
builder.setMessage(R.string.leaveWithoutSaveConfirmation);
builder.setPositiveButton(R.string.confirm, (dialog, which) -> finish());

View File

@@ -8,14 +8,11 @@ import android.os.Bundle;
import android.text.InputType;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.List;
@@ -27,10 +24,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.databinding.ManageGroupsActivityBinding;
public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener {
private ManageGroupsActivityBinding binding;
private static final String TAG = "Catima";
private SQLiteDatabase mDatabase;
@@ -41,12 +35,14 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ManageGroupsActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.groups);
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.manage_groups_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
mDatabase = new DBHelper(this).getWritableDatabase();
}
@@ -55,12 +51,12 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
protected void onResume() {
super.onResume();
FloatingActionButton addButton = binding.fabAdd;
FloatingActionButton addButton = findViewById(R.id.fabAdd);
addButton.setOnClickListener(v -> createGroup());
addButton.bringToFront();
mGroupList = binding.include.list;
mHelpText = binding.include.helpText;
mGroupList = findViewById(R.id.list);
mHelpText = findViewById(R.id.helpText);
// Init group list
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
@@ -112,62 +108,24 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
return super.onOptionsItemSelected(item);
}
private void setGroupNameError(EditText input) {
String string = sanitizeAddGroupNameField(input.getText());
if (string.length() == 0) {
input.setError(getString(R.string.group_name_is_empty));
return;
}
if (DBHelper.getGroup(mDatabase, string) != null) {
input.setError(getString(R.string.group_name_already_in_use));
return;
}
input.setError(null);
}
private String sanitizeAddGroupNameField(CharSequence s) {
return s.toString().trim();
}
private void createGroup() {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AlertDialogTheme);
builder.setTitle(R.string.enter_group_name);
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.addTextChangedListener(new SimpleTextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
setGroupNameError(input);
}
});
setGroupNameError(input);
// Add spacing to EditText
FrameLayout container = new FrameLayout(this);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
int contentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
params.leftMargin = contentPadding;
params.topMargin = contentPadding / 2;
params.rightMargin = contentPadding;
input.setLayoutParams(params);
container.addView(input);
builder.setView(container);
builder.setView(input);
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
CharSequence error = input.getError();
if (error != null) {
Toast.makeText(getApplicationContext(), error.toString(), Toast.LENGTH_SHORT).show();
String inputString = input.getText().toString().trim();
if (inputString.length() == 0) {
Toast.makeText(getApplicationContext(), R.string.group_name_is_empty, Toast.LENGTH_SHORT).show();
return;
}
DBHelper.insertGroup(mDatabase, sanitizeAddGroupNameField(input.getText()));
if (DBHelper.getGroup(mDatabase, inputString) != null) {
Toast.makeText(getApplicationContext(), R.string.group_name_already_in_use, Toast.LENGTH_SHORT).show();
return;
}
DBHelper.insertGroup(mDatabase, inputString);
updateGroupList();
});
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
@@ -235,7 +193,7 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
public void onDeleteButtonClicked(View view) {
final String groupName = getGroupName(view);
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.deleteConfirmationGroup);
builder.setMessage(groupName);

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,29 +0,0 @@
package protect.card_locker;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class OpenWebLinkHandler {
private static final String TAG = "Catima";
public void openBrowser(AppCompatActivity activity, String url) {
if (url == null) {
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
try {
activity.startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(activity, R.string.failedToOpenUrl, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
}

View File

@@ -1,94 +0,0 @@
package protect.card_locker;
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class PermissionUtils {
/**
* Check if storage read permission is needed.
*
* This is only necessary on Android 6.0 (Marshmallow) and below. See
* https://github.com/CatimaLoyalty/Android/issues/979 for more info.
*
* @param activity
* @return
*/
private static boolean needsStorageReadPermission(Activity activity) {
// Testing showed this permission wasn't needed for anything Catima did past Marshmallow
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return false;
}
return ContextCompat.checkSelfPermission(activity, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED;
}
/**
* Check if camera permission is needed.
*
* @param activity
* @return
*/
public static boolean needsCameraPermission(Activity activity) {
// Android only introduced the runtime permission system in Marshmallow (Android 6.0)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false;
}
return ContextCompat.checkSelfPermission(activity, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED;
}
/**
* Call onRequestPermissionsResult after storage read permission was granted.
* Mocks a successful grant if a grant is not necessary.
*
* @param activity
* @param requestCode
*/
public static void requestStorageReadPermission(CatimaAppCompatActivity activity, int requestCode) {
String[] permissions = new String[]{ android.Manifest.permission.READ_EXTERNAL_STORAGE };
int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED };
if (needsStorageReadPermission(activity)) {
ActivityCompat.requestPermissions(activity, permissions, requestCode);
} else {
// FIXME: This points to onMockedRequestPermissionResult instead of to
// onRequestPermissionResult because onRequestPermissionResult was only introduced in
// Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too.
//
// When minSdk becomes 23, this should point to onRequestPermissionResult directly and
// the activity input variable should be changed from CatimaAppCompatActivity to
// Activity.
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
}
}
/**
* Call onRequestPermissionsResult after camera permission was granted.
* Mocks a successful grant if a grant is not necessary.
*
* @param activity
* @param requestCode
*/
public static void requestCameraPermission(CatimaAppCompatActivity activity, int requestCode) {
String[] permissions = new String[]{ Manifest.permission.CAMERA };
int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED };
if (needsCameraPermission(activity)) {
ActivityCompat.requestPermissions(activity, permissions, requestCode);
} else {
// FIXME: This points to onMockedRequestPermissionResult instead of to
// onRequestPermissionResult because onRequestPermissionResult was only introduced in
// Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too.
//
// When minSdk becomes 23, this should point to onRequestPermissionResult directly and
// the activity input variable should be changed from CatimaAppCompatActivity to
// Activity.
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
}
}
}

View File

@@ -1,30 +1,16 @@
package protect.card_locker;
import android.Manifest;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import com.google.zxing.ResultPoint;
import com.google.zxing.client.android.Intents;
import com.journeyapps.barcodescanner.BarcodeCallback;
@@ -34,8 +20,10 @@ import com.journeyapps.barcodescanner.DecoratedBarcodeView;
import java.util.List;
import protect.card_locker.databinding.CustomBarcodeScannerBinding;
import protect.card_locker.databinding.ScanActivityBinding;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
/**
* Custom Scannner Activity extending from Activity to display a custom layout form scanner view.
@@ -44,16 +32,8 @@ import protect.card_locker.databinding.ScanActivityBinding;
* originally licensed under Apache 2.0
*/
public class ScanActivity extends CatimaAppCompatActivity {
private ScanActivityBinding binding;
private CustomBarcodeScannerBinding customBarcodeScannerBinding;
private static final String TAG = "Catima";
private static final int MEDIUM_SCALE_FACTOR_DIP = 460;
private static final int COMPAT_SCALE_FACTOR_DIP = 320;
private static final int PERMISSION_SCAN_ADD_FROM_IMAGE = 100;
private CaptureManager capture;
private DecoratedBarcodeView barcodeScannerView;
@@ -75,22 +55,23 @@ public class ScanActivity extends CatimaAppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ScanActivityBinding.inflate(getLayoutInflater());
customBarcodeScannerBinding = CustomBarcodeScannerBinding.bind(binding.zxingBarcodeScanner);
setTitle(R.string.scanCardBarcode);
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.scan_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
extractIntentFields(getIntent());
manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
photoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_IMAGE_FILE, result.getResultCode(), result.getData()));
customBarcodeScannerBinding.addFromImage.setOnClickListener(this::addFromImage);
customBarcodeScannerBinding.addManually.setOnClickListener(this::addManually);
findViewById(R.id.add_from_image).setOnClickListener(this::addFromImage);
findViewById(R.id.add_manually).setOnClickListener(this::addManually);
barcodeScannerView = binding.zxingBarcodeScanner;
barcodeScannerView = findViewById(R.id.zxing_barcode_scanner);
// Even though we do the actual decoding with the barcodeScannerView
// CaptureManager needs to be running to show the camera and scanning bar
@@ -127,10 +108,6 @@ public class ScanActivity extends CatimaAppCompatActivity {
protected void onResume() {
super.onResume();
capture.onResume();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
showCameraPermissionMissingText(false);
}
scaleScreen();
}
@Override
@@ -193,22 +170,27 @@ public class ScanActivity extends CatimaAppCompatActivity {
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
BarcodeValues barcodeValues;
if (barcodeValues.isEmpty()) {
try {
barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
} catch (NullPointerException e) {
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
return;
}
Intent manualResult = new Intent();
Bundle manualResultBundle = new Bundle();
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, barcodeValues.content());
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, barcodeValues.format());
if (addGroup != null) {
manualResultBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
if (!barcodeValues.isEmpty()) {
Intent manualResult = new Intent();
Bundle manualResultBundle = new Bundle();
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, barcodeValues.content());
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, barcodeValues.format());
if (addGroup != null) {
manualResultBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
}
manualResult.putExtras(manualResultBundle);
ScanActivity.this.setResult(RESULT_OK, manualResult);
finish();
}
manualResult.putExtras(manualResultBundle);
ScanActivity.this.setResult(RESULT_OK, manualResult);
finish();
}
public void addManually(View view) {
@@ -222,74 +204,8 @@ public class ScanActivity extends CatimaAppCompatActivity {
}
public void addFromImage(View view) {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE);
}
private void addFromImageAfterPermission() {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("image/*");
Intent contentIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentIntent.setType("image/*");
Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(R.string.addFromImage));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { contentIntent });
try {
photoPickerLauncher.launch(chooserIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
private void showCameraPermissionMissingText(boolean show) {
customBarcodeScannerBinding.cameraPermissionDeniedLayout.cameraPermissionDeniedClickableArea.setOnClickListener(show ? v -> {
navigateToSystemPermissionSetting();
} : null);
customBarcodeScannerBinding.cardInputContainer.setBackgroundColor(show ? obtainThemeAttribute(com.google.android.material.R.attr.colorSurface) : Color.TRANSPARENT);
customBarcodeScannerBinding.cameraPermissionDeniedLayout.getRoot().setVisibility(show ? View.VISIBLE : View.GONE);
}
private void scaleScreen() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
customBarcodeScannerBinding.cameraPermissionDeniedLayout.cameraPermissionDeniedIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
customBarcodeScannerBinding.cameraPermissionDeniedLayout.cameraPermissionDeniedTitle.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
}
private int obtainThemeAttribute(int attribute) {
TypedValue typedValue = new TypedValue();
getTheme().resolveAttribute(attribute, typedValue, true);
return typedValue.data;
}
private void navigateToSystemPermissionSetting() {
Intent permissionIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", getPackageName(), null));
permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(permissionIntent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
onMockedRequestPermissionsResult(requestCode, permissions, grantResults);
}
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
showCameraPermissionMissingText(!granted);
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
if (granted) {
addFromImageAfterPermission();
} else {
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG).show();
}
}
photoPickerLauncher.launch(photoPickerIntent);
}
}

View File

@@ -33,6 +33,7 @@ class ShortcutHelper {
private static final int ADAPTIVE_BITMAP_SIZE = 108 * ADAPTIVE_BITMAP_SCALE;
private static final int ADAPTIVE_BITMAP_VISIBLE_SIZE = 72 * ADAPTIVE_BITMAP_SCALE;
private static final int ADAPTIVE_BITMAP_IMAGE_SIZE = ADAPTIVE_BITMAP_VISIBLE_SIZE + 5 * ADAPTIVE_BITMAP_SCALE;
private static final int PADDING_COLOR = Color.argb(255, 255, 255, 255);
private static final int PADDING_COLOR_OVERLAY = Color.argb(127, 0, 0, 0);
/**
@@ -144,7 +145,7 @@ class ShortcutHelper {
if (iconBitmap == null) {
iconBitmap = Utils.generateIcon(context, loyaltyCard, true).getLetterTile();
} else {
iconBitmap = createAdaptiveBitmap(iconBitmap, Utils.getHeaderColor(context, loyaltyCard));
iconBitmap = createAdaptiveBitmap(iconBitmap, loyaltyCard.headerColor == null ? PADDING_COLOR : loyaltyCard.headerColor);
}
IconCompat icon = IconCompat.createWithAdaptiveBitmap(iconBitmap);

View File

@@ -22,8 +22,4 @@ public class ThirdPartyInfo {
public String license() {
return mLicense;
}
public String toHtml() {
return String.format("<a href=\"%s\">%s</a> (%s)", url(), name(), license());
}
}

View File

@@ -1,82 +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;
import androidx.core.view.WindowInsetsControllerCompat;
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) {
View decorView = getWindow().getDecorView();
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(getWindow(), decorView);
wic.setAppearanceLightStatusBars(!darkMode);
} 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, com.google.android.material.R.attr.colorSurface, ContextCompat.getColor(this, R.color.md_theme_light_surface));
int colorOnSurface = MaterialColors.getColor(this, com.google.android.material.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,32 +5,17 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.ImageDecoder;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Build;
import android.os.LocaleList;
import android.provider.MediaStore;
import android.util.Log;
import android.util.TypedValue;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.RawRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.graphics.ColorUtils;
import androidx.exifinterface.media.ExifInterface;
import androidx.palette.graphics.Palette;
import com.google.android.material.color.DynamicColors;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
@@ -39,19 +24,14 @@ import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Currency;
import java.util.Date;
@@ -59,6 +39,9 @@ import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Map;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.graphics.ColorUtils;
import androidx.exifinterface.media.ExifInterface;
import protect.card_locker.preferences.Settings;
public class Utils {
@@ -115,17 +98,6 @@ public class Utils {
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
}
/**
* Returns the Barcode format and content based on the result of an activity.
* It shows toasts to notify the end-user as needed itself and will return an empty
* BarcodeValues object if the activity was cancelled or nothing could be found.
*
* @param requestCode
* @param resultCode
* @param intent
* @param context
* @return BarcodeValues
*/
static public BarcodeValues parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
String contents;
String format;
@@ -137,16 +109,14 @@ public class Utils {
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
Log.i(TAG, "Received image file with possible barcode");
Uri data = intent.getData();
if (data == null) {
Log.e(TAG, "Intent did not contain any data");
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
return new BarcodeValues(null, null);
}
Bitmap bitmap;
try {
bitmap = retrieveImageFromUri(context, data);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ImageDecoder.Source image_source = ImageDecoder.createSource(context.getContentResolver(), intent.getData());
bitmap = ImageDecoder.decodeBitmap(image_source, (decoder, info, source) -> decoder.setMutableRequired(true));
} else {
bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), intent.getData());
}
} catch (IOException e) {
Log.e(TAG, "Error getting data from image file");
e.printStackTrace();
@@ -186,20 +156,6 @@ public class Utils {
throw new UnsupportedOperationException("Unknown request code for parseSetBarcodeActivityResult");
}
static public Bitmap retrieveImageFromUri(Context context, Uri data) throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ImageDecoder.Source image_source = ImageDecoder.createSource(context.getContentResolver(), data);
return ImageDecoder.decodeBitmap(image_source, (decoder, info, source) -> decoder.setMutableRequired(true));
} else {
return getBitmapSdkLessThan29(data, context);
}
}
@SuppressWarnings("deprecation")
private static Bitmap getBitmapSdkLessThan29(Uri data, Context context) throws IOException {
return MediaStore.Images.Media.getBitmap(context.getContentResolver(), data);
}
static public BarcodeValues getBarcodeFromBitmap(Bitmap bitmap) {
// This function is vulnerable to OOM, so we try again with a smaller bitmap is we get OOM
for (int i = 0; i < 10; i++) {
@@ -233,21 +189,7 @@ public class Utils {
}
}
static public Boolean isNotYetValid(Date validFromDate) {
// The note in `hasExpired` does not apply here, since the bug was fixed before this feature was added.
return validFromDate.after(getStartOfToday().getTime());
}
static public Boolean hasExpired(Date expiryDate) {
// Note: In #1083 it was discovered that `DatePickerFragment` may sometimes store the expiryDate
// at 12:00 PM instead of 12:00 AM in the DB. While this has been fixed and the 12-hour difference
// is not a problem for the way the comparison currently works, it's good to keep in mind such
// dates may exist in the DB in case the comparison changes in the future and the new one relies
// on both dates being set at 12:00 AM.
return expiryDate.before(getStartOfToday().getTime());
}
static private Calendar getStartOfToday() {
// today
Calendar date = new GregorianCalendar();
// reset hour, minutes, seconds and millis
@@ -255,7 +197,8 @@ public class Utils {
date.set(Calendar.MINUTE, 0);
date.set(Calendar.SECOND, 0);
date.set(Calendar.MILLISECOND, 0);
return date;
return expiryDate.before(date.getTime());
}
static public String formatBalance(Context context, BigDecimal value, Currency currency) {
@@ -263,7 +206,7 @@ public class Utils {
if (currency == null) {
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();
@@ -288,19 +231,32 @@ public class Utils {
return numberFormat.format(value);
}
static public BigDecimal parseBalance(String value, Currency currency) throws ParseException {
NumberFormat numberFormat = NumberFormat.getInstance();
static public Boolean currencyHasDecimals(Currency currency) {
if (currency == null) {
numberFormat.setMaximumFractionDigits(0);
} else {
numberFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
numberFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
return false;
}
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) {
@@ -395,14 +351,6 @@ public class Utils {
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) {
FileInputStream in;
try {
@@ -446,7 +394,8 @@ public class Utils {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
setLocalesSdkLessThan24(chosenLocale, configuration, res);
configuration.locale = chosenLocale != null ? chosenLocale : Locale.getDefault();
res.updateConfiguration(configuration, res.getDisplayMetrics());
return context;
}
@@ -456,12 +405,6 @@ public class Utils {
return context.createConfigurationContext(configuration);
}
@SuppressWarnings("deprecation")
private static void setLocalesSdkLessThan24(Locale chosenLocale, Configuration configuration, Resources res) {
configuration.locale = chosenLocale != null ? chosenLocale : Locale.getDefault();
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
static public long getUnixTime() {
return System.currentTimeMillis() / 1000;
}
@@ -505,129 +448,4 @@ public class Utils {
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.applyToActivityIfAvailable(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 int getHeaderColorFromImage(Bitmap image, int fallback) {
if (image == null) {
return fallback;
}
return new Palette.Builder(image).generate().getDominantColor(androidx.appcompat.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);
}
public static String readTextFile(Context context, @RawRes int resourceId) throws IOException {
InputStream input = context.getResources().openRawResource(resourceId);
BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
StringBuilder result = new StringBuilder();
while (true) {
String nextLine = reader.readLine();
if (nextLine == null || nextLine.isEmpty()) {
reader.close();
break;
}
result.append("\n");
result.append(nextLine);
}
return result.toString();
}
public static void setIconOrTextWithBackground(Context context, LoyaltyCard loyaltyCard, Bitmap icon, ImageView backgroundOrIcon, TextView textWhenNoImage) {
if (icon != null) {
Log.d("onResume", "setting icon image");
textWhenNoImage.setVisibility(View.GONE);
backgroundOrIcon.setImageBitmap(icon);
backgroundOrIcon.setBackgroundColor(Color.TRANSPARENT);
} else {
textWhenNoImage.setVisibility(View.VISIBLE);
int headerColor = getHeaderColor(context, loyaltyCard);
backgroundOrIcon.setImageBitmap(null);
backgroundOrIcon.setBackgroundColor(headerColor);
textWhenNoImage.setText(loyaltyCard.store);
textWhenNoImage.setTextColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
}
}
public static boolean installedFromGooglePlay(Context context) {
try {
String packageName = context.getPackageName();
String installer;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
installer = context.getPackageManager().getInstallSourceInfo(packageName).getInstallingPackageName();
} else {
installer = context.getPackageManager().getInstallerPackageName(packageName);
}
return installer.equals("com.android.vending");
} catch (Throwable ignored) {
return false;
}
}
public static int getHeaderColor(Context context, LoyaltyCard loyaltyCard) {
return loyaltyCard.headerColor != null ? loyaltyCard.headerColor : LetterBitmap.getDefaultColor(context, loyaltyCard.store);
}
}

View File

@@ -15,15 +15,7 @@ import java.io.Reader;
import java.nio.charset.StandardCharsets;
public class ZipUtils {
public static Bitmap readImage(ZipInputStream zipInputStream) {
return BitmapFactory.decodeStream(zipInputStream);
}
public static JSONObject readJSON(ZipInputStream zipInputStream) throws IOException, JSONException {
return new JSONObject(read(zipInputStream));
}
private static String read(ZipInputStream zipInputStream) throws IOException {
static public String read(ZipInputStream zipInputStream) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
Reader reader = new BufferedReader(new InputStreamReader(zipInputStream, StandardCharsets.UTF_8));
int c;
@@ -32,4 +24,12 @@ public class ZipUtils {
}
return stringBuilder.toString();
}
static public Bitmap readImage(ZipInputStream zipInputStream) {
return BitmapFactory.decodeStream(zipInputStream);
}
static public JSONObject readJSON(ZipInputStream zipInputStream) throws IOException, JSONException {
return new JSONObject(read(zipInputStream));
}
}

View File

@@ -1,172 +0,0 @@
package protect.card_locker.contentprovider;
import static protect.card_locker.DBHelper.LoyaltyCardDbIds;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import protect.card_locker.BuildConfig;
import protect.card_locker.DBHelper;
import protect.card_locker.preferences.Settings;
public class CardsContentProvider extends ContentProvider {
private static final String TAG = "Catima";
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".contentprovider.cards";
public static class Version {
public static final String MAJOR_COLUMN = "major";
public static final String MINOR_COLUMN = "minor";
public static final int MAJOR = 1;
public static final int MINOR = 0;
}
private static final int URI_VERSION = 0;
private static final int URI_CARDS = 1;
private static final int URI_GROUPS = 2;
private static final int URI_CARD_GROUPS = 3;
private static final String[] CARDS_DEFAULT_PROJECTION = new String[]{
LoyaltyCardDbIds.ID,
LoyaltyCardDbIds.STORE,
LoyaltyCardDbIds.VALID_FROM,
LoyaltyCardDbIds.EXPIRY,
LoyaltyCardDbIds.BALANCE,
LoyaltyCardDbIds.BALANCE_TYPE,
LoyaltyCardDbIds.NOTE,
LoyaltyCardDbIds.HEADER_COLOR,
LoyaltyCardDbIds.CARD_ID,
LoyaltyCardDbIds.BARCODE_ID,
LoyaltyCardDbIds.BARCODE_TYPE,
LoyaltyCardDbIds.STAR_STATUS,
LoyaltyCardDbIds.LAST_USED,
LoyaltyCardDbIds.ARCHIVE_STATUS,
};
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH) {{
addURI(AUTHORITY, "version", URI_VERSION);
addURI(AUTHORITY, "cards", URI_CARDS);
addURI(AUTHORITY, "groups", URI_GROUPS);
addURI(AUTHORITY, "card_groups", URI_CARD_GROUPS);
}};
@Override
public boolean onCreate() {
return true;
}
@Nullable
@Override
public Cursor query(@NonNull final Uri uri,
@Nullable final String[] projection,
@Nullable final String selection,
@Nullable final String[] selectionArgs,
@Nullable final String sortOrder) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Disable the content provider on SDK < 23 since it grants dangerous
// permissions at install-time
Log.w(TAG, "Content provider read is only available for SDK >= 23");
return null;
}
final Settings settings = new Settings(getContext());
if (!settings.getAllowContentProviderRead()) {
Log.w(TAG, "Content provider read is disabled");
return null;
}
final String table;
String[] updatedProjection = projection;
switch (uriMatcher.match(uri)) {
case URI_VERSION:
return queryVersion();
case URI_CARDS:
table = DBHelper.LoyaltyCardDbIds.TABLE;
// Restrict columns to the default projection (omit internal columns such as zoom level)
if (projection == null) {
updatedProjection = CARDS_DEFAULT_PROJECTION;
} else {
final Set<String> defaultProjection = new HashSet<>(Arrays.asList(CARDS_DEFAULT_PROJECTION));
updatedProjection = Arrays.stream(projection).filter(defaultProjection::contains).toArray(String[]::new);
}
break;
case URI_GROUPS:
table = DBHelper.LoyaltyCardDbGroups.TABLE;
break;
case URI_CARD_GROUPS:
table = DBHelper.LoyaltyCardDbIdsGroups.TABLE;
break;
default:
Log.w(TAG, "Unrecognized URI " + uri);
return null;
}
final DBHelper dbHelper = new DBHelper(getContext());
final SQLiteDatabase database = dbHelper.getReadableDatabase();
return database.query(
table,
updatedProjection,
selection,
selectionArgs,
null,
null,
sortOrder
);
}
private Cursor queryVersion() {
final String[] columns = new String[]{Version.MAJOR_COLUMN, Version.MINOR_COLUMN};
final MatrixCursor matrixCursor = new MatrixCursor(columns);
matrixCursor.addRow(new Object[]{Version.MAJOR, Version.MINOR});
return matrixCursor;
}
@Nullable
@Override
public String getType(@NonNull final Uri uri) {
// MIME types are not relevant (for now at least)
return null;
}
@Nullable
@Override
public Uri insert(@NonNull final Uri uri,
@Nullable final ContentValues values) {
// This content provider is read-only for now, so we always return null
return null;
}
@Override
public int delete(@NonNull final Uri uri,
@Nullable final String selection,
@Nullable final String[] selectionArgs) {
// This content provider is read-only for now, so we always return 0
return 0;
}
@Override
public int update(@NonNull final Uri uri,
@Nullable final ContentValues values,
@Nullable final String selection,
@Nullable final String[] selectionArgs) {
// This content provider is read-only for now, so we always return 0
return 0;
}
}

View File

@@ -8,14 +8,22 @@ public class CSVHelpers {
/**
* Extract a string from the items array. The index into the array
* is determined by looking up the index in the fields map using the
* "key" as the key. If no such key exists, defaultValue is returned.
* "key" as the key. If no such key exists, defaultValue is returned
* if it is not null. Otherwise, a FormatException is thrown.
*/
static String extractString(String key, CSVRecord record, String defaultValue) {
static String extractString(String key, CSVRecord record, String defaultValue)
throws FormatException {
String toReturn = defaultValue;
if (record.isMapped(key)) {
return record.get(key);
toReturn = record.get(key);
} else {
if (defaultValue == null) {
throw new FormatException("Field not used but expected: " + key);
}
}
return defaultValue;
return toReturn;
}
/**
@@ -24,15 +32,15 @@ public class CSVHelpers {
* "key" as the key. If no such key exists, or the data is not a valid
* int, a FormatException is thrown.
*/
static Integer extractInt(String key, CSVRecord record)
static Integer extractInt(String key, CSVRecord record, boolean nullIsOk)
throws FormatException {
if (!record.isMapped(key)) {
if (record.isMapped(key) == false) {
throw new FormatException("Field not used but expected: " + key);
}
String value = record.get(key);
if (value.isEmpty()) {
throw new FormatException("Field is empty: " + key);
if (value.isEmpty() && nullIsOk) {
return null;
}
try {
@@ -48,15 +56,15 @@ public class CSVHelpers {
* "key" as the key. If no such key exists, or the data is not a valid
* int, a FormatException is thrown.
*/
static Long extractLong(String key, CSVRecord record)
static Long extractLong(String key, CSVRecord record, boolean nullIsOk)
throws FormatException {
if (!record.isMapped(key)) {
if (record.isMapped(key) == false) {
throw new FormatException("Field not used but expected: " + key);
}
String value = record.get(key);
if (value.isEmpty()) {
throw new FormatException("Field is empty: " + key);
if (value.isEmpty() && nullIsOk) {
return null;
}
try {

View File

@@ -127,7 +127,6 @@ public class CatimaExporter implements Exporter {
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
DBHelper.LoyaltyCardDbIds.STORE,
DBHelper.LoyaltyCardDbIds.NOTE,
DBHelper.LoyaltyCardDbIds.VALID_FROM,
DBHelper.LoyaltyCardDbIds.EXPIRY,
DBHelper.LoyaltyCardDbIds.BALANCE,
DBHelper.LoyaltyCardDbIds.BALANCE_TYPE,
@@ -136,8 +135,7 @@ public class CatimaExporter implements Exporter {
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
DBHelper.LoyaltyCardDbIds.STAR_STATUS,
DBHelper.LoyaltyCardDbIds.LAST_USED,
DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS);
DBHelper.LoyaltyCardDbIds.LAST_USED);
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(database);
@@ -147,7 +145,6 @@ public class CatimaExporter implements Exporter {
printer.printRecord(card.id,
card.store,
card.note,
card.validFrom != null ? card.validFrom.getTime() : "",
card.expiry != null ? card.expiry.getTime() : "",
card.balance,
card.balanceType,
@@ -156,8 +153,7 @@ public class CatimaExporter implements Exporter {
card.barcodeType != null ? card.barcodeType.name() : "",
card.headerColor,
card.starStatus,
card.lastUsed,
card.archiveStatus);
card.lastUsed);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();

View File

@@ -13,6 +13,7 @@ import org.apache.commons.csv.CSVRecord;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -54,7 +55,7 @@ public class CatimaImporter implements Importer {
String fileName = Uri.parse(localFileHeader.getFileName()).getLastPathSegment();
if (fileName.equals("catima.csv")) {
importCSV(context, database, zipInputStream);
importCSV(context, database, new ByteArrayInputStream(ZipUtils.read(zipInputStream).getBytes(StandardCharsets.UTF_8)));
} else if (fileName.endsWith(".png")) {
Utils.saveCardImage(context, ZipUtils.readImage(zipInputStream), fileName);
} else {
@@ -67,17 +68,26 @@ public class CatimaImporter implements Importer {
bufferedInputStream.reset();
importCSV(context, database, bufferedInputStream);
}
input.close();
}
public void importCSV(Context context, SQLiteDatabase database, InputStream input) throws IOException, FormatException, InterruptedException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
int version = parseVersion(bufferedReader);
bufferedReader.mark(100);
Integer version = 1;
try {
version = Integer.parseInt(bufferedReader.readLine());
} catch (NumberFormatException _e) {
// Assume version 1
}
bufferedReader.reset();
switch (version) {
case 1:
parseV1(database, bufferedReader);
parseV1(context, database, bufferedReader);
break;
case 2:
parseV2(context, database, bufferedReader);
@@ -85,14 +95,16 @@ public class CatimaImporter implements Importer {
default:
throw new FormatException(String.format("No code to parse version %s", version));
}
bufferedReader.close();
}
public void parseV1(SQLiteDatabase database, BufferedReader input) throws IOException, FormatException, InterruptedException {
public void parseV1(Context context, SQLiteDatabase database, BufferedReader input) throws IOException, FormatException, InterruptedException {
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.builder().setHeader().build());
try {
for (CSVRecord record : parser) {
importLoyaltyCard(database, record);
importLoyaltyCard(context, database, record);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
@@ -106,8 +118,8 @@ public class CatimaImporter implements Importer {
}
public void parseV2(Context context, SQLiteDatabase database, BufferedReader input) throws IOException, FormatException, InterruptedException {
int part = 0;
StringBuilder stringPart = new StringBuilder();
Integer part = 0;
String stringPart = "";
try {
while (true) {
@@ -123,7 +135,7 @@ public class CatimaImporter implements Importer {
break;
case 1:
try {
parseV2Groups(database, stringPart.toString());
parseV2Groups(database, stringPart);
sectionParsed = true;
} catch (FormatException e) {
// We may have a multiline field, try again
@@ -131,7 +143,7 @@ public class CatimaImporter implements Importer {
break;
case 2:
try {
parseV2Cards(context, database, stringPart.toString());
parseV2Cards(context, database, stringPart);
sectionParsed = true;
} catch (FormatException e) {
// We may have a multiline field, try again
@@ -139,7 +151,7 @@ public class CatimaImporter implements Importer {
break;
case 3:
try {
parseV2CardGroups(database, stringPart.toString());
parseV2CardGroups(database, stringPart);
sectionParsed = true;
} catch (FormatException e) {
// We may have a multiline field, try again
@@ -155,12 +167,12 @@ public class CatimaImporter implements Importer {
if (sectionParsed) {
part += 1;
stringPart = new StringBuilder();
stringPart = "";
} else {
stringPart.append(tmp).append('\n');
stringPart += tmp + "\n";
}
} else {
stringPart.append(tmp).append('\n');
stringPart += tmp + "\n";
}
}
} catch (FormatException e) {
@@ -214,7 +226,7 @@ public class CatimaImporter implements Importer {
}
for (CSVRecord record : records) {
importLoyaltyCard(database, record);
importLoyaltyCard(context, database, record);
}
}
@@ -243,42 +255,13 @@ public class CatimaImporter implements Importer {
}
}
/**
* Parse the version number of the import file
*
* @param reader the reader containing the import file
* @return the parsed version number, defaulting to 1 if none is found
* @throws IOException there was a problem reading the file
*/
private int parseVersion(BufferedReader reader) throws IOException {
reader.mark(10); // slightly over the search limit just to be sure
StringBuilder sb = new StringBuilder();
int searchLimit = 5; // gives you version numbers up to 99999
int codePoint;
// search until the next whitespace, indicating the end of the version
while (!Character.isWhitespace(codePoint = reader.read())) {
// we found something that isn't a digit, or we ran out of chars
if (!Character.isDigit(codePoint) || searchLimit <= 0) {
reader.reset();
return 1; // default value
}
sb.append((char) codePoint);
searchLimit--;
}
reader.reset();
if (sb.length() == 0) {
return 1;
}
return Integer.parseInt(sb.toString());
}
/**
* Import a single loyalty card into the database using the given
* session.
*/
private void importLoyaltyCard(SQLiteDatabase database, CSVRecord record)
throws FormatException {
int id = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ID, record);
private void importLoyaltyCard(Context context, SQLiteDatabase database, CSVRecord record)
throws IOException, FormatException {
int id = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
String store = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
if (store.isEmpty()) {
@@ -286,38 +269,19 @@ public class CatimaImporter implements Importer {
}
String note = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.NOTE, record, "");
Date validFrom = null;
Long validFromLong;
try {
validFromLong = CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.VALID_FROM, record);
} catch (FormatException ignored) {
validFromLong = null;
}
if (validFromLong != null) {
validFrom = new Date(validFromLong);
}
Date expiry = null;
Long expiryLong;
try {
expiryLong = CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.EXPIRY, record);
} catch (FormatException ignored) {
expiryLong = null;
}
if (expiryLong != null) {
expiry = new Date(expiryLong);
expiry = new Date(CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.EXPIRY, record, true));
} catch (NullPointerException | FormatException e) {
}
// These fields did not exist in versions 1.8.1 and before
// We default to 0 so we can still import old backups
BigDecimal balance = new BigDecimal("0");
String balanceString = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE, record, null);
if (balanceString != null) {
try {
balance = new BigDecimal(CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE, record, null));
} catch (NumberFormatException ignored) {
}
BigDecimal balance;
try {
balance = new BigDecimal(CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE, record, null));
} catch (FormatException _e) {
// These fields did not exist in versions 1.8.1 and before
// We catch this exception so we can still import old backups
balance = new BigDecimal("0");
}
Currency balanceType = null;
@@ -343,38 +307,29 @@ public class CatimaImporter implements Importer {
}
Integer headerColor = null;
try {
headerColor = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record);
} catch (FormatException ignored) {
if (record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR)) {
headerColor = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true);
}
int starStatus = 0;
try {
starStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record);
starStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record, false);
} catch (FormatException _e) {
// This field did not exist in versions 0.28 and before
// We catch this exception so we can still import old backups
}
if (starStatus != 1) starStatus = 0;
int archiveStatus = 0;
try {
archiveStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS, record);
} 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;
try {
lastUsed = CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.LAST_USED, record);
lastUsed = CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.LAST_USED, record, false);
} catch (FormatException _e) {
// This field did not exist in versions 2.5.0 and before
// We catch this exception so we can still import old backups
}
DBHelper.insertLoyaltyCard(database, id, store, note, validFrom, 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);
}
/**
@@ -384,10 +339,6 @@ public class CatimaImporter implements Importer {
private void importGroup(SQLiteDatabase database, CSVRecord record) throws FormatException {
String id = CSVHelpers.extractString(DBHelper.LoyaltyCardDbGroups.ID, record, null);
if (id == null) {
throw new FormatException("Group has no ID: " + record);
}
DBHelper.insertGroup(database, id);
}
@@ -396,15 +347,11 @@ public class CatimaImporter implements Importer {
* session.
*/
private void importCardGroupMapping(SQLiteDatabase database, CSVRecord record) throws FormatException {
int cardId = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record);
Integer cardId = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record, false);
String groupId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIdsGroups.groupID, record, null);
if (groupId == null) {
throw new FormatException("Group has no ID: " + record);
}
List<Group> cardGroups = DBHelper.getLoyaltyCardGroups(database, cardId);
cardGroups.add(DBHelper.getGroup(database, groupId));
DBHelper.setLoyaltyCardGroups(database, cardId, cardGroups);
}
}
}

View File

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

View File

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

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.util.Log;
import java.io.IOException;
import java.io.OutputStream;
import protect.card_locker.DBHelper;
public class MultiFormatExporter {
private static final String TAG = "Catima";
@@ -31,20 +34,20 @@ public class MultiFormatExporter {
break;
}
String error;
if (exporter != null) {
try {
exporter.exportData(context, database, output, password);
return new ImportExportResult(ImportExportResultType.Success);
} catch (Exception e) {
return ImportExportResult.Success;
} catch (IOException e) {
Log.e(TAG, "Failed to export data", e);
} catch (InterruptedException 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 org.json.JSONException;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
public class MultiFormatImporter {
private static final String TAG = "Catima";
@@ -40,31 +47,23 @@ public class MultiFormatImporter {
break;
}
String error = null;
if (importer != null) {
database.beginTransaction();
try {
importer.importData(context, database, input, password);
database.setTransactionSuccessful();
return new ImportExportResult(ImportExportResultType.Success);
return ImportExportResult.Success;
} catch (ZipException e) {
if (e.getType().equals(ZipException.Type.WRONG_PASSWORD)) {
return new ImportExportResult(ImportExportResultType.BadPassword);
} else {
Log.e(TAG, "Failed to import data", e);
error = e.toString();
}
} catch (Exception e) {
return ImportExportResult.BadPassword;
} catch (IOException | FormatException | InterruptedException | JSONException | ParseException | NullPointerException e) {
Log.e(TAG, "Failed to import data", e);
error = e.toString();
} finally {
database.endTransaction();
}
} else {
error = "Unsupported data format imported: " + format.name();
Log.e(TAG, error);
Log.e(TAG, "Unsupported data format imported: " + format.name());
}
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.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.util.Log;
import com.google.zxing.BarcodeFormat;
@@ -40,17 +39,15 @@ import protect.card_locker.ZipUtils;
* A header is expected for the each table showing the names of the columns.
*/
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 {
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());
try {
for (CSVRecord record : parser) {
HashMap<String, Object> recordData = new HashMap<>();
HashMap<String, String> recordData = new HashMap<>();
recordData.put("name", record.get("name"));
recordData.put("barcodeFormat", record.get("barcodeFormat"));
@@ -65,8 +62,6 @@ public class StocardImporter implements Importer {
ZipInputStream zipInputStream = new ZipInputStream(input, password);
String[] providersFileName = null;
String[] customProvidersBaseName = null;
String customProviderId = "";
String[] cardBaseName = null;
String cardName = "";
LocalFileHeader localFileHeader;
@@ -74,102 +69,62 @@ public class StocardImporter implements Importer {
String fileName = localFileHeader.getFileName();
String[] nameParts = fileName.split("/");
if (nameParts.length < 2) {
continue;
}
if (providersFileName == null) {
providersFileName = new String[]{
"extracts",
nameParts[1],
nameParts[0],
"sync",
"data",
"users",
nameParts[1],
"analytics-properties",
"content.json"
};
customProvidersBaseName = new String[]{
"extracts",
nameParts[1],
"users",
nameParts[1],
"loyalty-card-custom-providers"
nameParts[0],
"analytics-properties.json"
};
cardBaseName = new String[]{
"extracts",
nameParts[1],
nameParts[0],
"sync",
"data",
"users",
nameParts[1],
nameParts[0],
"loyalty-cards"
};
}
if (startsWith(nameParts, customProvidersBaseName, 1)) {
// Extract providerId
customProviderId = nameParts[customProvidersBaseName.length].split("\\.", 2)[0];
// Name file
if (fileName.endsWith(customProviderId + "/content.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)) {
// Extract cardName
cardName = nameParts[cardBaseName.length].split("\\.", 2)[0];
// This is the card itself
if (fileName.endsWith(cardName + "/content.json")) {
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
if (nameParts.length == cardBaseName.length + 1) {
// Ignore the .txt file
if (fileName.endsWith(".json")) {
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap,
cardName,
"cardId",
jsonObject.getString("input_id")
);
// Provider ID can be either custom or not, extract whatever version is relevant
String customProviderPrefix = "/users/" + nameParts[1] + "/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,
cardName,
"_providerId",
providerId
);
if (jsonObject.has("input_barcode_format")) {
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"barcodeType",
jsonObject.getString("input_barcode_format")
"cardId",
jsonObject.getString("input_id")
);
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"_providerId",
jsonObject
.getJSONObject("input_provider_reference")
.getString("identifier")
.substring("/loyalty-card-providers/".length())
);
if (jsonObject.has("input_barcode_format")) {
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"barcodeType",
jsonObject.getString("input_barcode_format")
);
}
}
} else if (fileName.endsWith("notes/default/content.json")) {
loyaltyCardHashMap = appendToHashMap(
} else if (fileName.endsWith("notes/default.json")) {
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"note",
@@ -177,14 +132,14 @@ public class StocardImporter implements Importer {
.getString("content")
);
} else if (fileName.endsWith("/images/front.png")) {
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"frontImage",
ZipUtils.readImage(zipInputStream)
);
} else if (fileName.endsWith("/images/back.png")) {
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"backImage",
@@ -200,41 +155,22 @@ public class StocardImporter implements Importer {
for (HashMap<String, Object> loyaltyCardData : loyaltyCardHashMap.values()) {
String providerId = (String) loyaltyCardData.get("_providerId");
HashMap<String, String> providerData = providers.get(providerId);
if (providerId == null) {
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 store = providerData != null ? providerData.get("name") : providerId;
String note = (String) Utils.mapGetOrDefault(loyaltyCardData, "note", "");
String cardId = (String) loyaltyCardData.get("cardId");
String barcodeTypeString = (String) Utils.mapGetOrDefault(loyaltyCardData, "barcodeType", providerData != null ? providerData.get("barcodeFormat") : null);
CatimaBarcode barcodeType = null;
if (barcodeTypeString != null && !barcodeTypeString.isEmpty()) {
if (barcodeTypeString != null) {
if (barcodeTypeString.equals("RSS_DATABAR_EXPANDED")) {
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.RSS_EXPANDED);
} else if (barcodeTypeString.equals("GS1_128")) {
barcodeType = CatimaBarcode.fromBarcode(BarcodeFormat.CODE_128);
} else {
barcodeType = CatimaBarcode.fromName(barcodeTypeString);
}
}
int headerColor = Utils.getRandomHeaderColor(context);
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, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, 0, null,0);
if (cardIcon != null) {
Utils.saveCardImage(context, cardIcon, (int) loyaltyCardInternalId, ImageLocationType.icon);
}
long loyaltyCardInternalId = DBHelper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, 0, null);
if (loyaltyCardData.containsKey("frontImage")) {
Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("frontImage"), (int) loyaltyCardInternalId, ImageLocationType.front);
@@ -261,7 +197,7 @@ public class StocardImporter implements Importer {
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);
if (loyaltyCardData == null) {
loyaltyCardData = new HashMap<>();

View File

@@ -126,7 +126,7 @@ public class VoucherVaultImporter implements Importer {
throw new FormatException("Unknown colour type found: " + colorFromJSON);
}
DBHelper.insertLoyaltyCard(database, store, "", null, 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();

View File

@@ -63,12 +63,36 @@ public class Settings {
return AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
}
public double getFontSizeScale() {
return getInt(R.string.settings_key_max_font_size_scale, R.integer.settings_max_font_size_scale_pct) / 100.0;
}
public int getSmallFont() {
return 14;
}
public int getMediumFont() {
return 28;
}
public int getLargeFont() {
return 40;
}
public int getFontSizeMin(int fontSize) {
return (int) (Math.round(fontSize / 2.0) - 1);
}
public int getFontSizeMax(int fontSize) {
return (int) Math.round(fontSize * getFontSizeScale());
}
public boolean useMaxBrightnessDisplayingBarcode() {
return getBoolean(R.string.settings_key_display_barcode_max_brightness, true);
}
public String getCardViewOrientation() {
return getString(R.string.settings_key_card_orientation, getResString(R.string.settings_key_follow_system_orientation));
public boolean getLockBarcodeScreenOrientation() {
return getBoolean(R.string.settings_key_lock_barcode_orientation, false);
}
public boolean getKeepScreenOn() {
@@ -78,16 +102,4 @@ public class Settings {
public boolean getDisableLockscreenWhileViewingCard() {
return getBoolean(R.string.settings_key_disable_lockscreen_while_viewing_card, true);
}
public boolean getAllowContentProviderRead() {
return getBoolean(R.string.settings_key_allow_content_provider_read, 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

@@ -3,44 +3,45 @@ package protect.card_locker.preferences;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.MenuItem;
import com.google.android.material.color.DynamicColors;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.DialogFragment;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import nl.invissvenska.numberpickerpreference.NumberDialogPreference;
import nl.invissvenska.numberpickerpreference.NumberPickerPreferenceDialogFragment;
import protect.card_locker.CatimaAppCompatActivity;
import protect.card_locker.MainActivity;
import protect.card_locker.R;
import protect.card_locker.Utils;
import protect.card_locker.databinding.SettingsActivityBinding;
public class SettingsActivity extends CatimaAppCompatActivity {
private SettingsActivityBinding binding;
private final static String RELOAD_MAIN_STATE = "mReloadMain";
private SettingsFragment fragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = SettingsActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.settings);
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.settings_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
// Display the fragment as the main content.
fragment = new SettingsFragment();
@@ -98,8 +99,23 @@ public class SettingsActivity extends CatimaAppCompatActivity {
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
// Show pretty names and summaries
ListPreference themePreference = findPreference(getResources().getString(R.string.settings_key_theme));
// Show pretty names
ListPreference localePreference = findPreference(getResources().getString(R.string.settings_key_locale));
assert localePreference != null;
CharSequence[] entryValues = localePreference.getEntryValues();
List<CharSequence> entries = new ArrayList<>();
for (CharSequence entry : entryValues) {
if (entry.length() == 0) {
entries.add(getResources().getString(R.string.settings_system_locale));
} else {
Locale entryLocale = Utils.stringToLocale(entry.toString());
entries.add(entryLocale.getDisplayName(entryLocale));
}
}
localePreference.setEntries(entries.toArray(new CharSequence[entryValues.length]));
Preference themePreference = findPreference(getResources().getString(R.string.settings_key_theme));
assert themePreference != null;
themePreference.setOnPreferenceChangeListener((preference, o) -> {
if (o.toString().equals(getResources().getString(R.string.settings_key_light_theme))) {
@@ -113,47 +129,16 @@ public class SettingsActivity extends CatimaAppCompatActivity {
return true;
});
ListPreference themeColorPreference = findPreference(getResources().getString(R.string.setting_key_theme_color));
assert themeColorPreference != null;
themeColorPreference.setOnPreferenceChangeListener((preference, o) -> {
Preference colorPreference = findPreference(getResources().getString(R.string.setting_key_theme_color));
assert colorPreference != null;
colorPreference.setOnPreferenceChangeListener((preference, o) -> {
refreshActivity(true);
return true;
});
if (!DynamicColors.isDynamicColorAvailable()) {
themeColorPreference.setEntryValues(R.array.color_values_no_dynamic);
themeColorPreference.setEntries(R.array.color_value_strings_no_dynamic);
}
Preference oledDarkPreference = findPreference(getResources().getString(R.string.settings_key_oled_dark));
assert oledDarkPreference != null;
oledDarkPreference.setOnPreferenceChangeListener((preference, newValue) -> {
refreshActivity(true);
return true;
});
ListPreference localePreference = findPreference(getResources().getString(R.string.settings_key_locale));
assert localePreference != null;
CharSequence[] entryValues = localePreference.getEntryValues();
List<CharSequence> entries = new ArrayList<>();
for (CharSequence entry : entryValues) {
if (entry.length() == 0) {
entries.add(getResources().getString(R.string.settings_system_locale));
} else {
Locale entryLocale = Utils.stringToLocale(entry.toString());
entries.add(entryLocale.getDisplayName(entryLocale));
}
}
localePreference.setEntries(entries.toArray(new CharSequence[entryValues.length]));
localePreference.setOnPreferenceChangeListener((preference, newValue) -> {
refreshActivity(true);
return true;
});
// Disable content provider on SDK < 23 since dangerous permissions
// are granted at install-time
Preference contentProviderReadPreference = findPreference(getResources().getString(R.string.settings_key_allow_content_provider_read));
assert contentProviderReadPreference != null;
contentProviderReadPreference.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
}
private void refreshActivity(boolean reloadMain) {
@@ -163,5 +148,25 @@ public class SettingsActivity extends CatimaAppCompatActivity {
activity.recreate();
}
}
@Override
public void onDisplayPreferenceDialog(Preference preference) {
if (preference instanceof NumberDialogPreference) {
NumberDialogPreference dialogPreference = (NumberDialogPreference) preference;
DialogFragment dialogFragment = NumberPickerPreferenceDialogFragment
.newInstance(
dialogPreference.getKey(),
dialogPreference.getMinValue(),
dialogPreference.getMaxValue(),
dialogPreference.getStepValue(),
dialogPreference.getUnitText()
);
dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(getParentFragmentManager(), DIALOG_FRAGMENT_TAG);
} else {
super.onDisplayPreferenceDialog(preference);
}
}
}
}

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

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval" android:useLevel="true"
android:dither="true">
<size android:height="12dip" android:width="12dip"/>
<solid android:color="@android:color/white"/>
</shape>

View File

@@ -1,5 +0,0 @@
<vector 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="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
</vector>

View File

@@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#BD0000"
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="M10.94,8.12L7.48,4.66L9,3h6l1.83,2H20c1.1,0 2,0.9 2,2v12c0,0.05 -0.01,0.1 -0.02,0.16l-5.1,-5.1C16.96,13.71 17,13.36 17,13c0,-2.76 -2.24,-5 -5,-5C11.64,8 11.29,8.04 10.94,8.12zM20.49,23.31L18.17,21H4c-1.1,0 -2,-0.9 -2,-2V7c0,-0.59 0.27,-1.12 0.68,-1.49l-2,-2L2.1,2.1l19.8,19.8L20.49,23.31zM14.49,17.32l-1.5,-1.5C12.67,15.92 12.35,16 12,16c-1.66,0 -3,-1.34 -3,-3c0,-0.35 0.08,-0.67 0.19,-0.98l-1.5,-1.5C7.25,11.24 7,12.09 7,13c0,2.76 2.24,5 5,5C12.91,18 13.76,17.75 14.49,17.32z"/>
</vector>

View File

@@ -1,24 +0,0 @@
<layer-list xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="?attr/colorSurface" />
<corners
android:bottomLeftRadius="28dp"
android:bottomRightRadius="28dp"
android:topLeftRadius="28dp"
android:topRightRadius="28dp" />
</shape>
</item>
<item>
<shape>
<solid android:color="@color/m3_popupmenu_overlay_color"
tools:ignore="PrivateResource" />
<corners
android:bottomLeftRadius="28dp"
android:bottomRightRadius="28dp"
android:topLeftRadius="28dp"
android:topRightRadius="28dp" />
</shape>
</item>
</layer-list>

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: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"/>

View File

@@ -1,5 +1,5 @@
<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="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="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</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:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<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:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<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="?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="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
</vector>

View File

@@ -1,5 +0,0 @@
<vector 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="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
</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="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
</vector>

View File

@@ -1,5 +0,0 @@
<vector 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="M7,18c-1.1,0 -1.99,0.9 -1.99,2S5.9,22 7,22s2,-0.9 2,-2 -0.9,-2 -2,-2zM1,2v2h2l3.6,7.59 -1.35,2.45c-0.16,0.28 -0.25,0.61 -0.25,0.96 0,1.1 0.9,2 2,2h12v-2L7.42,15c-0.14,0 -0.25,-0.11 -0.25,-0.25l0.03,-0.12 0.9,-1.63h7.45c0.75,0 1.41,-0.41 1.75,-1.03l3.58,-6.49c0.08,-0.14 0.12,-0.31 0.12,-0.48 0,-0.55 -0.45,-1 -1,-1L5.21,4l-0.94,-2L1,2zM17,18c-1.1,0 -1.99,0.9 -1.99,2s0.89,2 1.99,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@@ -1,5 +1,5 @@
<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">
<path android:fillColor="@android:color/white" android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
</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: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"/>

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: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"/>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
android:viewportHeight="24">
<path
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"/>

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: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"/>

View File

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

View File

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

View File

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

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: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"/>

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

@@ -0,0 +1,5 @@
<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="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"/>
</vector>

View File

@@ -0,0 +1,5 @@
<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="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"/>
</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: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"/>

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: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"/>

View File

@@ -0,0 +1,5 @@
<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="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View File

@@ -1,5 +0,0 @@
<vector 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="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
</vector>

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