Compare commits

..

1 Commits

Author SHA1 Message Date
Sylvia van Os
65d8d45018 WIP 2022-10-05 21:48:31 +02:00
1059 changed files with 7665 additions and 14946 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 testReleaseUnitTest
run: ./gradlew testReleaseUnitTest || ./gradlew testReleaseUnitTest
- name: SpotBugs
run: ./gradlew spotbugsRelease
- name: Archive test results

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,73 +1,26 @@
# Changelog
## 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)
## Unreleased - 115
- 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)
## v2.20.0 - 114
- Add Monochrome icon for Android 13
- Improve first launch screen
- Fidme import fixes
## v2.19.0 - 113 (2022-08-14)
## v2.19.0 - 113
- Add previous and next buttons to the loyalty card view
- Fix foreground colour on edit button
- Replace floppy disk save icon with checkmark
## v2.18.2 - 112 (2022-07-29)
## v2.18.2 - 112
- Make the possibility to set a custom header more visible
## v2.18.1 - 111 (2022-07-24)
## v2.18.1 - 111
- Arabic language support
- Display archived card count in group overview
@@ -77,11 +30,11 @@
- 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)
## v2.17.1 - 109
- Fix incorrect text colour on "No barcode" button
## v2.17.0 - 108 (2022-06-24)
## v2.17.0 - 108
- Add card duplication feature
- Don't allow choosing expiry before 1970 (they never worked anyway)
@@ -643,7 +596,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

@@ -100,5 +100,5 @@ your real name, saying:
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,27 +1,27 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.6)
CFPropertyList (3.0.5)
rexml
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
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.597.0)
aws-sdk-core (3.131.1)
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.525.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.122.0)
aws-sdk-core (~> 3, >= 3.165.0)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.57.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.114.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.5.2)
aws-sigv4 (1.5.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
@@ -34,10 +34,10 @@ GEM
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.8.1)
dotenv (2.7.6)
emoji_regex (3.2.3)
excon (0.99.0)
faraday (1.10.3)
excon (0.92.3)
faraday (1.10.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -65,8 +65,8 @@ GEM
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.7)
fastlane (2.213.0)
fastimage (2.2.6)
fastlane (2.206.2)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -90,7 +90,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 +106,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.21.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-core (0.5.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@@ -117,27 +117,27 @@ 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.10.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-playcustomapp_v1 (0.7.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.14.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)
google-cloud-errors (1.2.0)
google-cloud-storage (1.36.2)
addressable (~> 2.8)
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)
googleauth (1.1.3)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@@ -148,20 +148,20 @@ GEM
http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.6.3)
jwt (2.7.0)
jmespath (1.6.1)
json (2.6.2)
jwt (2.4.1)
memoist (0.16.2)
mini_magick (4.12.0)
mini_magick (4.11.0)
mini_mime (1.1.2)
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)
plist (3.6.0)
public_suffix (4.0.7)
rake (13.0.6)
representable (3.2.0)
declarative (< 0.1.0)
@@ -173,12 +173,12 @@ GEM
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.17.0)
signet (0.16.1)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
faraday (>= 0.17.5, < 3.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)
@@ -194,9 +194,9 @@ GEM
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.8.1)
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 +215,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 124
versionName "2.24.0"
minSdkVersion 21
targetSdkVersion 31
versionCode 114
versionName "2.20.0"
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.2'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
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.google.android.material:material:1.6.1'
implementation 'com.github.yalantis:ucrop:2.2.8'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6'
// Splash Screen
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'androidx.core:core-splashscreen:1.0.0'
// 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.5.0'
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.11.2'
// 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.9'
}
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,11 +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">
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"
@@ -31,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"
@@ -106,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"

View File

@@ -1,53 +1,138 @@
package protect.card_locker;
import android.content.ActivityNotFoundException;
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 android.widget.Toast;
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("uCrop", "https://github.com/Yalantis/uCrop", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("Zip4j", "https://github.com/srikanth-lingala/zip4j", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("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 +145,36 @@ 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/CatimaLoyalty/Android/blob/master/LICENSE";
} else if (id == R.id.repo) {
url = "https://github.com/CatimaLoyalty/Android/";
} else if (id == R.id.privacy) {
url = "https://catima.app/privacy-policy/";
} else if (id == R.id.report_error) {
url = "https://github.com/CatimaLoyalty/Android/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));
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.failedToOpenUrl, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
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

@@ -17,6 +17,7 @@ import com.google.zxing.common.BitMatrix;
import java.lang.ref.WeakReference;
import protect.card_locker.async.CompatCallable;
import protect.card_locker.barcodes.Barcode;
/**
* This task will generate a barcode and load it into an ImageView.
@@ -38,16 +39,16 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private final WeakReference<TextView> textViewReference;
private String cardId;
private final CatimaBarcode format;
private final Barcode format;
private final int imageHeight;
private final int imageWidth;
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
Barcode barcodeFormat, TextView textView,
boolean showFallback, Runnable callback, boolean roundCornerPadding
) {
mContext = context;
@@ -90,68 +91,8 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
this.showFallback = showFallback;
}
private int getMaxWidth(CatimaBarcode format) {
switch (format.format()) {
// 2D barcodes
case AZTEC:
case DATA_MATRIX:
case MAXICODE:
case PDF_417:
case QR_CODE:
return MAX_WIDTH_2D;
// 1D barcodes:
case CODABAR:
case CODE_39:
case CODE_93:
case CODE_128:
case EAN_8:
case EAN_13:
case ITF:
case UPC_A:
case UPC_E:
case RSS_14:
case RSS_EXPANDED:
case UPC_EAN_EXTENSION:
default:
return MAX_WIDTH_1D;
}
}
private String getFallbackString(CatimaBarcode format) {
switch (format.format()) {
// 2D barcodes
case AZTEC:
return "AZTEC";
case DATA_MATRIX:
return "DATA_MATRIX";
case PDF_417:
return "PDF_417";
case QR_CODE:
return "QR_CODE";
// 1D barcodes:
case CODABAR:
return "C0C";
case CODE_39:
return "CODE_39";
case CODE_93:
return "CODE_93";
case CODE_128:
return "CODE_128";
case EAN_8:
return "32123456";
case EAN_13:
return "5901234123457";
case ITF:
return "1003";
case UPC_A:
return "123456789012";
case UPC_E:
return "0123456";
default:
throw new IllegalArgumentException("No fallback known for this barcode type");
}
private int getMaxWidth(Barcode format) {
return format.is2D() ? MAX_WIDTH_2D : MAX_WIDTH_1D;
}
private Bitmap generate() {
@@ -227,7 +168,7 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
if (showFallback && !Thread.currentThread().isInterrupted()) {
Log.i(TAG, "Barcode generation failed, generating fallback...");
cardId = getFallbackString(format);
cardId = format.exampleValue();
bitmap = generate();
return bitmap;
}
@@ -282,7 +223,7 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
}
if (callback != null) {
callback.onBarcodeImageWriterResult(isSuccesful);
callback.run();
}
}

View File

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

View File

@@ -13,9 +13,11 @@ import android.widget.TextView;
import java.util.ArrayList;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.BarcodeLayoutBinding;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
import protect.card_locker.barcodes.BarcodeWithValue;
public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue> {
public class BarcodeSelectorAdapter extends ArrayAdapter<BarcodeWithValue> {
private static final String TAG = "Catima";
private final TaskHandler mTasks = new TaskHandler();
@@ -30,12 +32,12 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue>
void onRowClicked(int inputPosition, View view);
}
public BarcodeSelectorAdapter(Context context, ArrayList<CatimaBarcodeWithValue> barcodes, BarcodeSelectorListener barcodeSelectorListener) {
public BarcodeSelectorAdapter(Context context, ArrayList<BarcodeWithValue> barcodes, BarcodeSelectorListener barcodeSelectorListener) {
super(context, 0, barcodes);
mListener = barcodeSelectorListener;
}
public void setBarcodes(ArrayList<CatimaBarcodeWithValue> barcodes) {
public void setBarcodes(ArrayList<BarcodeWithValue> barcodes) {
clear();
addAll(barcodes);
notifyDataSetChanged();
@@ -44,18 +46,17 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue>
@Override
public View getView(int position, View convertView, ViewGroup parent) {
CatimaBarcodeWithValue catimaBarcodeWithValue = getItem(position);
CatimaBarcode catimaBarcode = catimaBarcodeWithValue.catimaBarcode();
String value = catimaBarcodeWithValue.value();
BarcodeWithValue barcodeWithValue = getItem(position);
Barcode catimaBarcode = barcodeWithValue.barcode();
String value = barcodeWithValue.value();
ViewHolder viewHolder;
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();
@@ -75,7 +76,7 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue>
}
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text) {
final CatimaBarcode format = CatimaBarcode.fromName(formatType);
final Barcode format = BarcodeFactory.fromName(formatType);
image.setImageBitmap(null);
image.setClipToOutline(true);

View File

@@ -14,13 +14,10 @@ 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;
static final String TAG = "Catima";
private SQLiteDatabase mDatabase;
private LoyaltyCardCursorAdapter mAdapter;
@@ -28,15 +25,15 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
@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);
@@ -53,7 +50,7 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
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));
@@ -81,7 +78,7 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
@Override
public boolean onCreateOptionsMenu(Menu inputMenu) {
getMenuInflater().inflate(R.menu.card_details_menu, inputMenu);
Utils.updateMenuCardDetailsButtonState(inputMenu.findItem(R.id.action_unfold), mAdapter.showingDetails());
return super.onCreateOptionsMenu(inputMenu);
}
@@ -89,8 +86,8 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
public boolean onOptionsItemSelected(MenuItem inputItem) {
int id = inputItem.getItemId();
if (id == R.id.action_shown_details) {
mAdapter.showSelectDetailDisplayDialog();
if (id == R.id.action_unfold) {
mAdapter.showDetails(!mAdapter.showingDetails());
invalidateOptionsMenu();
return true;

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;
@@ -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

@@ -6,11 +6,8 @@ import android.os.Build;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.WindowInsetsControllerCompat;
public class CatimaAppCompatActivity extends AppCompatActivity {
@Override
@@ -32,10 +29,8 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
// 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);
getWindow().getDecorView().setSystemUiVisibility(darkMode ? 0 : View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
// icons are always white back then
getWindow().setStatusBarColor(darkMode ? Color.TRANSPARENT : Color.argb(127, 0, 0, 0));
@@ -43,14 +38,4 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
// XXX android 9 and below has a nasty rendering bug if the theme was patched earlier
Utils.postPatchColors(this);
}
protected void enableToolbarBackButton() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
}
}

View File

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

View File

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

View File

@@ -18,10 +18,12 @@ import java.util.Currency;
import java.util.Date;
import java.util.List;
import protect.card_locker.barcodes.Barcode;
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 = 15;
public static class LoyaltyCardDbGroups {
public static final String TABLE = "groups";
@@ -33,7 +35,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";
@@ -96,7 +97,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," +
@@ -316,11 +316,6 @@ public class DBHelper extends SQLiteOpenHelper {
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,9 +360,9 @@ 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 String barcodeId, final CatimaBarcode barcodeType, final Integer headerColor,
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 Barcode barcodeType, final Integer headerColor,
final int starStatus, final Long lastUsed, final int archiveStatus) {
database.beginTransaction();
@@ -375,7 +370,6 @@ public class DBHelper extends SQLiteOpenHelper {
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);
@@ -399,10 +393,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 Barcode barcodeType,
final Integer headerColor, final int starStatus, final Long lastUsed, final int archiveStatus) {
database.beginTransaction();
// Card
@@ -410,7 +403,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);
@@ -434,17 +426,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 Barcode barcodeType,
final Integer headerColor, final int starStatus, final Long lastUsed, final int archiveStatus) {
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);
@@ -506,15 +496,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);

View File

@@ -7,17 +7,15 @@ 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> {
Settings mSettings;
public final Context mContext;
private final GroupAdapterListener mListener;
SQLiteDatabase mDatabase;
@@ -25,6 +23,7 @@ public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.Gro
public GroupCursorAdapter(Context inputContext, Cursor inputCursor, GroupAdapterListener inputListener) {
super(inputCursor, DBHelper.LoyaltyCardDbGroups.ORDER);
setHasStableIds(true);
mSettings = new Settings(inputContext);
mContext = inputContext;
mListener = inputListener;
mDatabase = new DBHelper(inputContext).getReadableDatabase();
@@ -34,14 +33,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) {
@@ -62,6 +56,8 @@ public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.Gro
}
inputHolder.mCardCount.setText(cardCountText);
inputHolder.mName.setTextSize(mSettings.getFontSizeMax(mSettings.getMediumFont()));
inputHolder.mCardCount.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
applyClickEvents(inputHolder);
}
@@ -85,16 +81,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

@@ -24,25 +24,21 @@ 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;
@@ -59,16 +55,26 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
@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
@@ -120,9 +126,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 +158,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) {
@@ -171,9 +177,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
}
}
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 +191,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 +229,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() {
@@ -297,6 +296,30 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
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 +340,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);
@@ -360,7 +383,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
return;
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.importSuccessfulTitle : R.string.importFailedTitle);
builder.setMessage(buildResultDialogMessage(result, true));
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
@@ -371,7 +394,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
private void onExportComplete(ImportExportResult result, final Uri path) {
ImportExportResultType resultType = result.resultType();
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(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());

View File

@@ -15,10 +15,12 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
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;
@@ -62,8 +64,7 @@ public class ImportURIHelper {
try {
// These values are allowed to be null
CatimaBarcode barcodeType = null;
Date validFrom = null;
Barcode barcodeType = null;
Date expiry = null;
BigDecimal balance = new BigDecimal("0");
Currency balanceType = null;
@@ -97,7 +98,7 @@ public class ImportURIHelper {
String unparsedBarcodeType = kv.get(BARCODE_TYPE);
if (unparsedBarcodeType != null && !unparsedBarcodeType.equals("")) {
barcodeType = CatimaBarcode.fromName(unparsedBarcodeType);
barcodeType = BarcodeFactory.fromName(unparsedBarcodeType);
}
String unparsedBalance = kv.get(BALANCE);
@@ -108,10 +109,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));
@@ -122,8 +119,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,0);
} catch (NullPointerException | NumberFormatException | UnsupportedEncodingException ex) {
throw new InvalidObjectException("Not a valid import URI");
}
}
@@ -155,9 +152,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

@@ -9,12 +9,13 @@ import java.util.Currency;
import java.util.Date;
import androidx.annotation.Nullable;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
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;
@@ -24,7 +25,7 @@ public class LoyaltyCard implements Parcelable {
public final String barcodeId;
@Nullable
public final CatimaBarcode barcodeType;
public final Barcode barcodeType;
@Nullable
public final Integer headerColor;
@@ -34,16 +35,14 @@ public class LoyaltyCard implements Parcelable {
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,
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 Barcode barcodeType,
@Nullable final Integer headerColor, final int starStatus,
final long lastUsed, final int zoomLevel, final int archiveStatus) {
this.id = id;
this.store = store;
this.note = note;
this.validFrom = validFrom;
this.expiry = expiry;
this.balance = balance;
this.balanceType = balanceType;
@@ -61,8 +60,6 @@ public class LoyaltyCard implements Parcelable {
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());
@@ -70,7 +67,7 @@ public class LoyaltyCard implements Parcelable {
cardId = in.readString();
barcodeId = in.readString();
String tmpBarcodeType = in.readString();
barcodeType = !tmpBarcodeType.isEmpty() ? CatimaBarcode.fromName(tmpBarcodeType) : null;
barcodeType = !tmpBarcodeType.isEmpty() ? BarcodeFactory.fromName(tmpBarcodeType) : null;
int tmpHeaderColor = in.readInt();
headerColor = tmpHeaderColor != -1 ? tmpHeaderColor : null;
starStatus = in.readInt();
@@ -84,7 +81,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);
@@ -102,7 +98,6 @@ public class LoyaltyCard implements Parcelable {
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));
@@ -116,24 +111,19 @@ public class LoyaltyCard implements Parcelable {
int balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE);
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
CatimaBarcode barcodeType = null;
Barcode barcodeType = null;
Currency balanceType = null;
Date validFrom = null;
Date expiry = null;
Integer headerColor = null;
if (cursor.isNull(barcodeTypeColumn) == false) {
barcodeType = CatimaBarcode.fromName(cursor.getString(barcodeTypeColumn));
barcodeType = BarcodeFactory.fromName(cursor.getString(barcodeTypeColumn));
}
if (cursor.isNull(balanceTypeColumn) == false) {
balanceType = Currency.getInstance(cursor.getString(balanceTypeColumn));
}
if (validFromLong > 0) {
validFrom = new Date(validFromLong);
}
if (expiryLong > 0) {
expiry = new Date(expiryLong);
}
@@ -142,7 +132,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,archived);
}
@Override

View File

@@ -17,39 +17,34 @@ 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 java.util.Currency;
import java.util.Date;
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 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 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;
public LoyaltyCardCursorAdapter(Context inputContext, Cursor inputCursor, CardAdapterListener inputListener) {
super(inputCursor, DBHelper.LoyaltyCardDbIds.ID);
setHasStableIds(true);
mSettings = new Settings(inputContext);
mContext = inputContext;
mListener = inputListener;
mSelectedItems = new SparseBooleanArray();
@@ -62,111 +57,35 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
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);
mShowDetails = cardDetailsPref.getBoolean(mContext.getString(R.string.sharedpreference_card_details_show), 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);
// Store in Shared Preference to restore next adapter launch
SharedPreferences cardDetailsPref = mContext.getSharedPreferences(
mContext.getString(R.string.sharedpreference_card_details),
Context.MODE_PRIVATE);
SharedPreferences.Editor cardDetailsPrefEditor = cardDetailsPref.edit();
cardDetailsPrefEditor.putBoolean(mContext.getString(R.string.sharedpreference_card_details_show), show);
cardDetailsPrefEditor.apply();
}
public boolean 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,41 +98,36 @@ 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);
setHeaderHeight(inputHolder, mShowDetails);
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.mCardIcon.setContentDescription(loyaltyCard.store);
Utils.setIconOrTextWithBackground(mContext, loyaltyCard, icon, inputHolder.mCardIcon, inputHolder.mCardText);
inputHolder.setIconBackgroundColor(loyaltyCard.headerColor != null ? loyaltyCard.headerColor : androidx.appcompat.R.attr.colorPrimary);
inputHolder.setIconBackgroundColor(loyaltyCard.headerColor != null ? loyaltyCard.headerColor : R.attr.colorPrimary);
inputHolder.toggleCardStateIcon(loyaltyCard.starStatus != 0, loyaltyCard.archiveStatus != 0, itemSelected(inputCursor.getPosition()));
@@ -225,6 +139,19 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
inputHolder.mRow.requestLayout();
}
private void setHeaderHeight(LoyaltyCardListItemViewHolder inputHolder, boolean expanded) {
int iconHeight;
if (expanded) {
iconHeight = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
iconHeight = (int) mContext.getResources().getDimension(R.dimen.cardThumbnailSize);
}
inputHolder.mIconLayout.getLayoutParams().height = expanded ? 0 : iconHeight;
inputHolder.mCardIcon.getLayoutParams().height = iconHeight;
inputHolder.mTickIcon.getLayoutParams().height = iconHeight;
}
private void applyClickEvents(LoyaltyCardListItemViewHolder inputHolder, final int inputPosition) {
inputHolder.mRow.setOnClickListener(inputView -> mListener.onRowClicked(inputPosition));
@@ -300,32 +227,32 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
public class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder {
public TextView mCardText, mStoreField, mNoteField, mBalanceField, mValidFromField, mExpiryField;
public TextView mStoreField, mNoteField, mBalanceField, mExpiryField;
public ImageView mCardIcon, mStarBackground, mStarBorder, mTickIcon, mArchivedBackground;
public MaterialCardView mRow;
public MaterialCardView mRow, mIconLayout;
public ConstraintLayout mStar, mArchived;
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);
mIconLayout = inputView.findViewById(R.id.icon_layout);
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);
mArchived = inputView.findViewById(R.id.archivedIcon);
mArchivedBackground = inputView.findViewById(R.id.archive_background);
mTickIcon = inputView.findViewById(R.id.selected_thumbnail);
inputView.setOnLongClickListener(view -> {
inputListener.onRowClicked(getAdapterPosition());
inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
@@ -333,43 +260,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,10 +272,60 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
} else {
mNoteField.setVisibility(View.VISIBLE);
mNoteField.setText(text);
mNoteField.setTextSize(mSettings.getFontSizeMax(mSettings.getSmallFont()));
}
mNoteField.requestLayout();
}
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];
if (balanceIcon != null) {
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];
if (expiryIcon != null) {
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));
} else if (mDarkModeEnabled) {
expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
}
}
mExpiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(expiry));
if (Utils.hasExpired(expiry)) {
mExpiryField.setTextColor(Color.RED);
}
mExpiryField.setTextSize(size);
}
mExpiryField.requestLayout();
}
public void toggleCardStateIcon(boolean enableStar, boolean enableArchive, boolean colorByTheme) {
/* the below code does not work in android 5! hence the change of drawable instead
boolean needDarkForeground = Utils.needsDarkForeground(mIconBackgroundColor);

View File

@@ -1,5 +1,6 @@
package protect.card_locker;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.DatePickerDialog;
@@ -19,11 +20,9 @@ import android.os.LocaleList;
import android.text.Editable;
import android.text.InputType;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
@@ -31,7 +30,6 @@ import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@@ -39,7 +37,6 @@ import android.widget.Toast;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
@@ -60,7 +57,6 @@ import java.util.Calendar;
import java.util.Collections;
import java.util.Currency;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -72,8 +68,7 @@ import java.util.concurrent.Callable;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.appcompat.widget.Toolbar;
@@ -82,11 +77,10 @@ import androidx.core.content.FileProvider;
import androidx.exifinterface.media.ExifInterface;
import androidx.fragment.app.DialogFragment;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.LayoutChipChoiceBinding;
import protect.card_locker.databinding.LoyaltyCardEditActivityBinding;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements BarcodeImageWriterResultCallback {
private LoyaltyCardEditActivityBinding binding;
public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
private static final String TAG = "Catima";
private final String STATE_TAB_INDEX = "savedTab";
@@ -100,7 +94,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
private final String STATE_FRONT_IMAGE_REMOVED = "frontImageRemoved";
private final String STATE_BACK_IMAGE_REMOVED = "backImageRemoved";
private final String STATE_ICON_REMOVED = "iconRemoved";
private final String STATE_OPEN_SET_ICON_MENU = "openSetIconMenu";
private final String TEMP_CAMERA_IMAGE_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_camera_image.jpg";
private final String TEMP_CROP_IMAGE_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_crop_image.png";
@@ -114,14 +107,10 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_FRONT = 100;
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_BACK = 101;
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_ICON = 102;
private static final int PERMISSION_REQUEST_STORAGE_IMAGE_FRONT = 103;
private static final int PERMISSION_REQUEST_STORAGE_IMAGE_BACK = 104;
private static final int PERMISSION_REQUEST_STORAGE_IMAGE_ICON = 105;
public static final String BUNDLE_ID = "id";
public static final String BUNDLE_DUPLICATE_ID = "duplicateId";
public static final String BUNDLE_UPDATE = "update";
public static final String BUNDLE_OPEN_SET_ICON_MENU = "openSetIconMenu";
public static final String BUNDLE_CARDID = "cardId";
public static final String BUNDLE_BARCODEID = "barcodeId";
public static final String BUNDLE_BARCODETYPE = "barcodeType";
@@ -134,7 +123,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
EditText storeFieldEdit;
EditText noteFieldEdit;
ChipGroup groupsChips;
AutoCompleteTextView validFromField;
AutoCompleteTextView expiryField;
EditText balanceField;
AutoCompleteTextView balanceCurrencyField;
@@ -156,7 +144,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
int loyaltyCardId;
boolean updateLoyaltyCard;
boolean duplicateFromLoyaltyCardId;
boolean openSetIconMenu;
String cardId;
String barcodeId;
String barcodeType;
@@ -174,6 +161,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
AlertDialog confirmExitDialog = null;
boolean validBalance = true;
Runnable barcodeImageGenerationFinishedCallback;
HashMap<String, Currency> currencies = new HashMap<>();
LoyaltyCard tempLoyaltyCard;
@@ -212,13 +201,12 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
(int) (fieldName == LoyaltyCardField.id ? value : loyaltyCard.id),
(String) (fieldName == LoyaltyCardField.store ? value : loyaltyCard.store),
(String) (fieldName == LoyaltyCardField.note ? value : loyaltyCard.note),
(Date) (fieldName == LoyaltyCardField.validFrom ? value : loyaltyCard.validFrom),
(Date) (fieldName == LoyaltyCardField.expiry ? value : loyaltyCard.expiry),
(BigDecimal) (fieldName == LoyaltyCardField.balance ? value : loyaltyCard.balance),
(Currency) (fieldName == LoyaltyCardField.balanceType ? value : loyaltyCard.balanceType),
(String) (fieldName == LoyaltyCardField.cardId ? value : loyaltyCard.cardId),
(String) (fieldName == LoyaltyCardField.barcodeId ? value : loyaltyCard.barcodeId),
(CatimaBarcode) (fieldName == LoyaltyCardField.barcodeType ? value : loyaltyCard.barcodeType),
(Barcode) (fieldName == LoyaltyCardField.barcodeType ? value : loyaltyCard.barcodeType),
(Integer) (fieldName == LoyaltyCardField.headerColor ? value : loyaltyCard.headerColor),
(int) (fieldName == LoyaltyCardField.starStatus ? value : loyaltyCard.starStatus),
0, // Unimportant, always set to null in doSave so the DB updates it to the current timestamp
@@ -243,8 +231,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
updateLoyaltyCard = b != null && b.getBoolean(BUNDLE_UPDATE, false);
duplicateFromLoyaltyCardId = b != null && b.getBoolean(BUNDLE_DUPLICATE_ID, false);
openSetIconMenu = b != null && b.getBoolean(BUNDLE_OPEN_SET_ICON_MENU, false);
cardId = b != null ? b.getString(BUNDLE_CARDID) : null;
barcodeId = b != null ? b.getString(BUNDLE_BARCODEID) : null;
barcodeType = b != null ? b.getString(BUNDLE_BARCODETYPE) : null;
@@ -260,7 +246,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
@Override
public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
tabs = binding.tabs;
tabs = findViewById(R.id.tabs);
savedInstanceState.putInt(STATE_TAB_INDEX, tabs.getSelectedTabPosition());
savedInstanceState.putParcelable(STATE_TEMP_CARD, tempLoyaltyCard);
savedInstanceState.putInt(STATE_REQUESTED_IMAGE, mRequestedImage);
@@ -291,14 +277,13 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
savedInstanceState.putInt(STATE_FRONT_IMAGE_REMOVED, mFrontImageRemoved ? 1 : 0);
savedInstanceState.putInt(STATE_BACK_IMAGE_REMOVED, mBackImageRemoved ? 1 : 0);
savedInstanceState.putInt(STATE_ICON_REMOVED, mIconRemoved ? 1 : 0);
savedInstanceState.putInt(STATE_OPEN_SET_ICON_MENU, openSetIconMenu ? 1 : 0);
}
@Override
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
tempLoyaltyCard = savedInstanceState.getParcelable(STATE_TEMP_CARD);
super.onRestoreInstanceState(savedInstanceState);
tabs = binding.tabs;
tabs = findViewById(R.id.tabs);
tabs.selectTab(tabs.getTabAt(savedInstanceState.getInt(STATE_TAB_INDEX)));
mRequestedImage = savedInstanceState.getInt(STATE_REQUESTED_IMAGE);
mFrontImageUnsaved = savedInstanceState.getInt(STATE_FRONT_IMAGE_UNSAVED) == 1;
@@ -309,17 +294,19 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
mFrontImageRemoved = savedInstanceState.getInt(STATE_FRONT_IMAGE_REMOVED) == 1;
mBackImageRemoved = savedInstanceState.getInt(STATE_BACK_IMAGE_REMOVED) == 1;
mIconRemoved = savedInstanceState.getInt(STATE_ICON_REMOVED) == 1;
openSetIconMenu = savedInstanceState.getInt(STATE_OPEN_SET_ICON_MENU) == 1;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = LoyaltyCardEditActivityBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
toolbar = binding.toolbar;
setContentView(R.layout.loyalty_card_edit_activity);
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
mDatabase = new DBHelper(this).getWritableDatabase();
@@ -331,29 +318,35 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
currencies.put(currency.getSymbol(), currency);
}
tabs = binding.tabs;
thumbnail = binding.thumbnail;
thumbnailEditIcon = binding.thumbnailEditIcon;
storeFieldEdit = binding.storeNameEdit;
noteFieldEdit = binding.noteEdit;
groupsChips = binding.groupChips;
validFromField = binding.validFromField;
expiryField = binding.expiryField;
balanceField = binding.balanceField;
balanceCurrencyField = binding.balanceCurrencyField;
cardIdFieldView = binding.cardIdView;
barcodeIdField = binding.barcodeIdField;
barcodeTypeField = binding.barcodeTypeField;
barcodeImage = binding.barcode;
tabs = findViewById(R.id.tabs);
thumbnail = findViewById(R.id.thumbnail);
thumbnailEditIcon = findViewById(R.id.thumbnailEditIcon);
storeFieldEdit = findViewById(R.id.storeNameEdit);
noteFieldEdit = findViewById(R.id.noteEdit);
groupsChips = findViewById(R.id.groupChips);
expiryField = findViewById(R.id.expiryField);
balanceField = findViewById(R.id.balanceField);
balanceCurrencyField = findViewById(R.id.balanceCurrencyField);
cardIdFieldView = findViewById(R.id.cardIdView);
barcodeIdField = findViewById(R.id.barcodeIdField);
barcodeTypeField = findViewById(R.id.barcodeTypeField);
barcodeImage = findViewById(R.id.barcode);
barcodeImage.setClipToOutline(true);
barcodeImageLayout = binding.barcodeLayout;
barcodeCaptureLayout = binding.barcodeCaptureLayout;
cardImageFrontHolder = binding.frontImageHolder;
cardImageBackHolder = binding.backImageHolder;
cardImageFront = binding.frontImage;
cardImageBack = binding.backImage;
barcodeImageLayout = findViewById(R.id.barcodeLayout);
barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
cardImageFrontHolder = findViewById(R.id.frontImageHolder);
cardImageBackHolder = findViewById(R.id.backImageHolder);
cardImageFront = findViewById(R.id.frontImage);
cardImageBack = findViewById(R.id.backImage);
enterButton = binding.enterButton;
enterButton = findViewById(R.id.enterButton);
barcodeImageGenerationFinishedCallback = () -> {
if (!(boolean) barcodeImage.getTag()) {
barcodeImageLayout.setVisibility(View.GONE);
Toast.makeText(LoyaltyCardEditActivity.this, getString(R.string.wrongValueForBarcodeType), Toast.LENGTH_LONG).show();
}
};
storeFieldEdit.addTextChangedListener(new SimpleTextWatcher() {
@Override
@@ -370,9 +363,38 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
});
addDateFieldTextChangedListener(validFromField, R.string.anyDate, R.string.chooseValidFromDate, LoyaltyCardField.validFrom);
expiryField.addTextChangedListener(new SimpleTextWatcher() {
CharSequence lastValue;
addDateFieldTextChangedListener(expiryField, R.string.never, R.string.chooseExpiryDate, LoyaltyCardField.expiry);
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
lastValue = s;
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.toString().equals(getString(R.string.never))) {
expiryField.setTag(null);
} else if (s.toString().equals(getString(R.string.chooseExpiryDate))) {
if (!lastValue.toString().equals(getString(R.string.chooseExpiryDate))) {
expiryField.setText(lastValue);
}
DialogFragment datePickerFragment = new DatePickerFragment(LoyaltyCardEditActivity.this, expiryField);
datePickerFragment.show(getSupportFragmentManager(), "datePicker");
}
updateTempState(LoyaltyCardField.expiry, expiryField.getTag());
}
@Override
public void afterTextChanged(Editable s) {
ArrayList<String> expiryList = new ArrayList<>();
expiryList.add(0, getString(R.string.never));
expiryList.add(1, getString(R.string.chooseExpiryDate));
ArrayAdapter<String> expiryAdapter = new ArrayAdapter<>(LoyaltyCardEditActivity.this, android.R.layout.select_dialog_item, expiryList);
expiryField.setAdapter(expiryAdapter);
}
});
balanceField.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
@@ -489,24 +511,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
barcodeIdField.setText(lastValue);
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(LoyaltyCardEditActivity.this);
AlertDialog.Builder builder = new AlertDialog.Builder(LoyaltyCardEditActivity.this);
builder.setTitle(R.string.setBarcodeId);
final EditText input = new EditText(LoyaltyCardEditActivity.this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
FrameLayout container = new FrameLayout(LoyaltyCardEditActivity.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);
if (tempLoyaltyCard.barcodeId != null) {
input.setText(tempLoyaltyCard.barcodeId);
}
builder.setView(container);
builder.setView(input);
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
// If the user manually changes the barcode again make sure we disable the
@@ -543,13 +555,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
updateTempState(LoyaltyCardField.barcodeType, null);
} else {
try {
CatimaBarcode barcodeFormat = CatimaBarcode.fromPrettyName(s.toString());
updateTempState(LoyaltyCardField.barcodeType, barcodeFormat);
if (!barcodeFormat.isSupported()) {
Toast.makeText(LoyaltyCardEditActivity.this, getString(R.string.unsupportedBarcodeType), Toast.LENGTH_LONG).show();
}
updateTempState(LoyaltyCardField.barcodeType, barcodeTypeField.getTag());
} catch (IllegalArgumentException e) {
}
}
@@ -680,16 +686,16 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
mCropperOptions.setAspectRatioOptions(selectedByDefault,
new AspectRatio(null, 1, 1),
new AspectRatio(getResources().getString(com.yalantis.ucrop.R.string.ucrop_label_original).toUpperCase(), sourceWidth, sourceHeight),
new AspectRatio(getResources().getString(R.string.ucrop_label_original).toUpperCase(), sourceWidth, sourceHeight),
new AspectRatio(getResources().getString(R.string.card).toUpperCase(), 85.6f, 53.98f)
);
// Fix theming
int colorPrimary = MaterialColors.getColor(this, androidx.appcompat.R.attr.colorPrimary, ContextCompat.getColor(this, R.color.md_theme_light_primary));
int colorOnPrimary = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnPrimary, ContextCompat.getColor(this, R.color.md_theme_light_onPrimary));
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));
int colorPrimary = MaterialColors.getColor(this, R.attr.colorPrimary, ContextCompat.getColor(this, R.color.md_theme_light_primary));
int colorOnPrimary = MaterialColors.getColor(this, R.attr.colorOnPrimary, ContextCompat.getColor(this, R.color.md_theme_light_onPrimary));
int colorSurface = MaterialColors.getColor(this, R.attr.colorSurface, ContextCompat.getColor(this, R.color.md_theme_light_surface));
int colorOnSurface = MaterialColors.getColor(this, R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
int colorBackground = MaterialColors.getColor(this, android.R.attr.colorBackground, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
mCropperOptions.setToolbarColor(colorSurface);
mCropperOptions.setStatusBarColor(colorSurface);
@@ -763,7 +769,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
} else {
// New card, use default values
tempLoyaltyCard = new LoyaltyCard(-1, "", "", null, null, new BigDecimal("0"), null, "", null, null, null, 0, Utils.getUnixTime(), 100,0);
tempLoyaltyCard = new LoyaltyCard(-1, "", "", null, new BigDecimal("0"), null, "", null, null, null, 0, Utils.getUnixTime(), 100,0);
}
}
@@ -806,12 +812,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
storeFieldEdit.setText(tempLoyaltyCard.store);
noteFieldEdit.setText(tempLoyaltyCard.note);
formatDateField(this, validFromField, tempLoyaltyCard.validFrom);
formatDateField(this, expiryField, tempLoyaltyCard.expiry);
formatExpiryField(this, expiryField, tempLoyaltyCard.expiry);
formatBalanceCurrencyField(tempLoyaltyCard.balanceType);
cardIdFieldView.setText(tempLoyaltyCard.cardId);
barcodeIdField.setText(tempLoyaltyCard.barcodeId != null ? tempLoyaltyCard.barcodeId : getString(R.string.sameAsCardId));
barcodeTypeField.setText(tempLoyaltyCard.barcodeType != null ? tempLoyaltyCard.barcodeType.prettyName() : getString(R.string.noBarcode));
setbarcodeTypeField(tempLoyaltyCard.barcodeType);
if (groupsChips.getChildCount() == 0) {
List<Group> existingGroups = DBHelper.getGroups(mDatabase);
@@ -825,9 +830,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
for (Group group : DBHelper.getGroups(mDatabase)) {
LayoutChipChoiceBinding chipChoiceBinding = LayoutChipChoiceBinding
.inflate(LayoutInflater.from(groupsChips.getContext()), groupsChips, false);
Chip chip = chipChoiceBinding.getRoot();
Chip chip = (Chip) getLayoutInflater().inflate(R.layout.layout_chip_choice, groupsChips, false);
chip.setText(group._id);
chip.setTag(group);
@@ -862,9 +865,10 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
// Update from intent
if (barcodeType != null) {
try {
barcodeTypeField.setText(CatimaBarcode.fromName(barcodeType).prettyName());
Barcode barcode = BarcodeFactory.fromName(barcodeType);
setbarcodeTypeField(barcode);
} catch (IllegalArgumentException e) {
barcodeTypeField.setText(getString(R.string.noBarcode));
setbarcodeTypeField(null);
}
}
@@ -899,7 +903,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
cardImageFrontHolder.setOnClickListener(new ChooseCardImage());
cardImageBackHolder.setOnClickListener(new ChooseCardImage());
FloatingActionButton saveButton = binding.fabSave;
FloatingActionButton saveButton = findViewById(R.id.fabSave);
saveButton.setOnClickListener(v -> doSave());
saveButton.bringToFront();
@@ -916,19 +920,12 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
onResuming = false;
// Fake click on the edit icon to cause the set icon option to pop up if the icon was
// long-pressed in the view activity
if (openSetIconMenu) {
openSetIconMenu = false;
thumbnail.callOnClick();
}
}
protected void setColorFromIcon() {
Object icon = thumbnail.getTag();
if (icon != null && (icon instanceof Bitmap)) {
int headerColor = Utils.getHeaderColorFromImage((Bitmap) icon, tempLoyaltyCard.headerColor != null ? tempLoyaltyCard.headerColor : androidx.appcompat.R.attr.colorPrimary);
int headerColor = Utils.getHeaderColorFromImage((Bitmap) icon, tempLoyaltyCard.headerColor != null ? tempLoyaltyCard.headerColor : R.attr.colorPrimary);
updateTempState(LoyaltyCardField.headerColor, headerColor);
@@ -949,62 +946,18 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
}
protected void addDateFieldTextChangedListener(AutoCompleteTextView dateField, @StringRes int defaultOptionStringId, @StringRes int chooseDateOptionStringId, LoyaltyCardField loyaltyCardField) {
dateField.addTextChangedListener(new SimpleTextWatcher() {
CharSequence lastValue;
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
lastValue = s;
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.toString().equals(getString(defaultOptionStringId))) {
dateField.setTag(null);
} else if (s.toString().equals(getString(chooseDateOptionStringId))) {
if (!lastValue.toString().equals(getString(chooseDateOptionStringId))) {
dateField.setText(lastValue);
}
DialogFragment datePickerFragment = new DatePickerFragment(
LoyaltyCardEditActivity.this,
dateField,
// if the expiry date is being set, set date picker's minDate to the 'valid from' date
loyaltyCardField == LoyaltyCardField.expiry ? (Date) validFromField.getTag() : null,
// if the 'valid from' date is being set, set date picker's maxDate to the expiry date
loyaltyCardField == LoyaltyCardField.validFrom ? (Date) expiryField.getTag() : null);
datePickerFragment.show(getSupportFragmentManager(), "datePicker");
}
updateTempState(loyaltyCardField, dateField.getTag());
}
@Override
public void afterTextChanged(Editable s) {
ArrayList<String> dropdownOptions = new ArrayList<>();
dropdownOptions.add(0, getString(defaultOptionStringId));
dropdownOptions.add(1, getString(chooseDateOptionStringId));
ArrayAdapter<String> dropdownOptionsAdapter = new ArrayAdapter<>(LoyaltyCardEditActivity.this, android.R.layout.select_dialog_item, dropdownOptions);
dateField.setAdapter(dropdownOptionsAdapter);
}
});
private void setbarcodeTypeField(Barcode barcode) {
barcodeTypeField.setTag(barcode);
barcodeTypeField.setText(barcode != null ? barcode.prettyName() : getString(R.string.noBarcode));
}
protected static void formatDateField(Context context, EditText textField, Date date) {
textField.setTag(date);
protected static void formatExpiryField(Context context, EditText expiryField, Date expiry) {
expiryField.setTag(expiry);
if (date == null) {
String text;
if (textField.getId() == R.id.validFromField) {
text = context.getString(R.string.anyDate);
} else if (textField.getId() == R.id.expiryField) {
text = context.getString(R.string.never);
} else {
throw new IllegalArgumentException("Unknown textField Id " + textField.getId());
}
textField.setText(text);
if (expiry == null) {
expiryField.setText(context.getString(R.string.never));
} else {
textField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(date));
expiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(expiry));
}
}
@@ -1025,59 +978,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
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;
Integer failureReason = null;
if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_FRONT) {
if (granted) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_FRONT) {
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_FRONT);
return;
}
failureReason = R.string.cameraPermissionRequired;
} else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_BACK) {
if (granted) {
} else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_BACK) {
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_BACK);
return;
}
failureReason = R.string.cameraPermissionRequired;
} else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_ICON) {
if (granted) {
} else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_ICON) {
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_ICON);
return;
}
failureReason = R.string.cameraPermissionRequired;
} else if (requestCode == PERMISSION_REQUEST_STORAGE_IMAGE_FRONT) {
if (granted) {
selectImageFromGallery(Utils.CARD_IMAGE_FROM_FILE_FRONT);
return;
}
failureReason = R.string.storageReadPermissionRequired;
} else if (requestCode == PERMISSION_REQUEST_STORAGE_IMAGE_BACK) {
if (granted) {
selectImageFromGallery(Utils.CARD_IMAGE_FROM_FILE_BACK);
return;
}
failureReason = R.string.storageReadPermissionRequired;
} else if (requestCode == PERMISSION_REQUEST_STORAGE_IMAGE_ICON) {
if (granted) {
selectImageFromGallery(Utils.CARD_IMAGE_FROM_FILE_ICON);
return;
}
failureReason = R.string.storageReadPermissionRequired;
}
if (failureReason != null) {
Toast.makeText(this, failureReason, Toast.LENGTH_LONG).show();
}
}
@@ -1094,7 +1002,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
return;
}
new MaterialAlertDialogBuilder(this)
new AlertDialog.Builder(this)
.setTitle(R.string.updateBarcodeQuestionTitle)
.setMessage(R.string.updateBarcodeQuestionText)
.setPositiveButton(R.string.yes, (dialog, which) -> {
@@ -1128,7 +1036,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
if (confirmExitDialog == null) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.leaveWithoutSaveTitle);
builder.setMessage(R.string.leaveWithoutSaveConfirmation);
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
@@ -1146,38 +1054,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
Uri photoURI = FileProvider.getUriForFile(LoyaltyCardEditActivity.this, BuildConfig.APPLICATION_ID, Utils.createTempFile(this, TEMP_CAMERA_IMAGE_NAME));
mRequestedImage = type;
try {
mPhotoTakerLauncher.launch(photoURI);
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.cameraPermissionDeniedTitle, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
private void selectImageFromGallery(int type) {
mRequestedImage = type;
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 {
mPhotoPickerLauncher.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);
}
}
@Override
public void onBarcodeImageWriterResult(boolean success) {
if (!success) {
barcodeImageLayout.setVisibility(View.GONE);
Toast.makeText(LoyaltyCardEditActivity.this, getString(R.string.wrongValueForBarcodeType), Toast.LENGTH_LONG).show();
}
mPhotoTakerLauncher.launch(photoURI);
}
class EditCardIdAndBarcode implements View.OnClickListener {
@@ -1261,37 +1138,58 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
}
cardOptions.put(getString(R.string.takePhoto), () -> {
int permissionRequestType;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int permissionRequestType;
if (v.getId() == R.id.frontImageHolder) {
permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_FRONT;
} else if (v.getId() == R.id.backImageHolder) {
permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_BACK;
} else if (v.getId() == R.id.thumbnail) {
permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_ICON;
if (v.getId() == R.id.frontImageHolder) {
permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_FRONT;
} else if (v.getId() == R.id.backImageHolder) {
permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_BACK;
} else if (v.getId() == R.id.thumbnail) {
permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_ICON;
} else {
throw new IllegalArgumentException("Unknown ID type " + v.getId());
}
requestPermissions(new String[]{Manifest.permission.CAMERA}, permissionRequestType);
} else {
throw new IllegalArgumentException("Unknown ID type " + v.getId());
int cardImageType;
if (v.getId() == R.id.frontImageHolder) {
cardImageType = Utils.CARD_IMAGE_FROM_CAMERA_FRONT;
} else if (v.getId() == R.id.backImageHolder) {
cardImageType = Utils.CARD_IMAGE_FROM_CAMERA_BACK;
} else if (v.getId() == R.id.thumbnail) {
cardImageType = Utils.CARD_IMAGE_FROM_CAMERA_ICON;
} else {
throw new IllegalArgumentException("Unknown ID type " + v.getId());
}
takePhotoForCard(cardImageType);
}
PermissionUtils.requestCameraPermission(LoyaltyCardEditActivity.this, permissionRequestType);
return null;
});
cardOptions.put(getString(R.string.addFromImage), () -> {
int permissionRequestType;
if (v.getId() == R.id.frontImageHolder) {
permissionRequestType = PERMISSION_REQUEST_STORAGE_IMAGE_FRONT;
mRequestedImage = Utils.CARD_IMAGE_FROM_FILE_FRONT;
} else if (v.getId() == R.id.backImageHolder) {
permissionRequestType = PERMISSION_REQUEST_STORAGE_IMAGE_BACK;
mRequestedImage = Utils.CARD_IMAGE_FROM_FILE_BACK;
} else if (v.getId() == R.id.thumbnail) {
permissionRequestType = PERMISSION_REQUEST_STORAGE_IMAGE_ICON;
mRequestedImage = Utils.CARD_IMAGE_FROM_FILE_ICON;
} else {
throw new IllegalArgumentException("Unknown ID type " + v.getId());
}
PermissionUtils.requestStorageReadPermission(LoyaltyCardEditActivity.this, permissionRequestType);
Intent i = new Intent(Intent.ACTION_PICK);
i.setType("image/*");
try {
mPhotoPickerLauncher.launch(i);
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
return null;
});
@@ -1308,7 +1206,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
throw new IllegalArgumentException("Unknown ID type " + v.getId());
}
new MaterialAlertDialogBuilder(LoyaltyCardEditActivity.this)
new AlertDialog.Builder(LoyaltyCardEditActivity.this)
.setTitle(getString(titleResource))
.setItems(cardOptions.keySet().toArray(new CharSequence[cardOptions.size()]), (dialog, which) -> {
Iterator<Callable<Void>> callables = cardOptions.values().iterator();
@@ -1336,17 +1234,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
implements DatePickerDialog.OnDateSetListener {
final Context context;
final EditText textFieldEdit;
@Nullable
final Date minDate;
@Nullable
final Date maxDate;
final EditText expiryFieldEdit;
DatePickerFragment(Context context, EditText textFieldEdit, @Nullable Date minDate, @Nullable Date maxDate) {
DatePickerFragment(Context context, EditText expiryFieldEdit) {
this.context = context;
this.textFieldEdit = textFieldEdit;
this.minDate = minDate;
this.maxDate = maxDate;
this.expiryFieldEdit = expiryFieldEdit;
}
@NonNull
@@ -1355,7 +1247,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
// Use the current date as the default date in the picker
final Calendar c = Calendar.getInstance();
Date date = (Date) textFieldEdit.getTag();
Date date = (Date) expiryFieldEdit.getTag();
if (date != null) {
c.setTime(date);
}
@@ -1366,29 +1258,23 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
// Create a new instance of DatePickerDialog and return it
DatePickerDialog datePickerDialog = new DatePickerDialog(getActivity(), this, year, month, day);
datePickerDialog.getDatePicker().setMinDate(minDate != null ? minDate.getTime() : getDefaultMinDateOfDatePicker());
datePickerDialog.getDatePicker().setMaxDate(maxDate != null ? maxDate.getTime() : getDefaultMaxDateOfDatePicker());
datePickerDialog.getDatePicker().setMinDate(getMinDateOfDatePicker());
return datePickerDialog;
}
private long getDefaultMinDateOfDatePicker() {
private long getMinDateOfDatePicker()
{
Calendar minDateCalendar = Calendar.getInstance();
minDateCalendar.set(1970, 0, 1);
return minDateCalendar.getTimeInMillis();
}
private long getDefaultMaxDateOfDatePicker() {
Calendar maxDateCalendar = Calendar.getInstance();
maxDateCalendar.set(2100, 11, 31);
return maxDateCalendar.getTimeInMillis();
}
public void onDateSet(DatePicker view, int year, int month, int day) {
Calendar c = new GregorianCalendar();
Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, year);
c.set(Calendar.MONTH, month);
c.set(Calendar.DAY_OF_MONTH, day);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.HOUR, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
@@ -1397,7 +1283,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
Date date = new Date(unixTime);
formatDateField(context, textFieldEdit, date);
formatExpiryField(context, expiryFieldEdit, date);
}
}
@@ -1438,9 +1324,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
// This makes the DBHelper set it to the current date
// So that new and edited card are always on top when sorting by recently used
if (updateLoyaltyCard) {
DBHelper.updateLoyaltyCard(mDatabase, loyaltyCardId, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.validFrom, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, tempLoyaltyCard.starStatus, null, tempLoyaltyCard.archiveStatus);
DBHelper.updateLoyaltyCard(mDatabase, loyaltyCardId, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, tempLoyaltyCard.starStatus, null, tempLoyaltyCard.archiveStatus);
} else {
loyaltyCardId = (int) DBHelper.insertLoyaltyCard(mDatabase, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.validFrom, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, 0, null, 0);
loyaltyCardId = (int) DBHelper.insertLoyaltyCard(mDatabase, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, 0, null, 0);
}
try {
@@ -1577,13 +1463,13 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Log.d(TAG, "ImageView size now known");
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, LoyaltyCardEditActivity.this, true);
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, barcodeImageGenerationFinishedCallback, true);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
}
});
} else {
Log.d(TAG, "ImageView size known known, creating barcode");
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, this, true);
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getApplicationContext(), barcodeImage, cardIdString, barcodeFormat, null, false, barcodeImageGenerationFinishedCallback, true);
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
}
}
@@ -1614,9 +1500,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
return;
}
View cardPart = binding.cardPart;
View optionsPart = binding.optionsPart;
View picturesPart = binding.picturesPart;
View cardPart = findViewById(R.id.cardPart);
View optionsPart = findViewById(R.id.optionsPart);
View picturesPart = findViewById(R.id.picturesPart);
if (getString(R.string.card).equals(part)) {
cardPart.setVisibility(View.VISIBLE);

View File

@@ -4,7 +4,6 @@ public enum LoyaltyCardField {
id,
store,
note,
validFrom,
expiry,
balance,
balanceType,

View File

File diff suppressed because it is too large Load Diff

View File

@@ -9,57 +9,55 @@ 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.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.SearchView;
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.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.ActionBar;
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.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
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 GestureDetector mGestureDetector;
private int mLoyaltyCardCount = 0;
protected String mFilter = "";
protected Object mGroup = null;
@@ -70,7 +68,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
private View mHelpSection;
private View mNoMatchingCardsText;
private View mNoGroupCardsText;
private TabLayout groupsTabLayout;
private boolean mArchiveMode;
public static final String BUNDLE_ARCHIVE_MODE = "archiveMode";
@@ -144,7 +141,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.
@@ -166,7 +163,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
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);
@@ -178,25 +175,28 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
dialog.show();
return true;
} else if (inputItem.getItemId() == R.id.action_archive) {
}
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);
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id,1);
updateLoyaltyCardList(false);
inputMode.finish();
invalidateOptionsMenu();
}
return true;
} else if (inputItem.getItemId() == R.id.action_unarchive) {
}
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);
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id,0);
updateLoyaltyCardList(false);
inputMode.finish();
invalidateOptionsMenu();
}
return true;
} else if (inputItem.getItemId() == R.id.action_star) {
}
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);
@@ -204,7 +204,8 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
inputMode.finish();
}
return true;
} else if (inputItem.getItemId() == R.id.action_unstar) {
}
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);
@@ -229,28 +230,28 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
extractIntentFields(getIntent());
SplashScreen.installSplashScreen(this);
super.onCreate(inputSavedInstanceState);
if (!mArchiveMode) {
binding = MainActivityBinding.inflate(getLayoutInflater());
if(!mArchiveMode) {
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());
setContentView(R.layout.main_activity);
}
else{
setTitle(R.string.archiveList);
setContentView(archiveActivityBinding.getRoot());
setSupportActionBar(archiveActivityBinding.toolbar);
groupsTabLayout = archiveActivityBinding.groups;
contentMainBinding = ContentMainBinding.bind(archiveActivityBinding.include.getRoot());
setContentView(R.layout.archive_activity);
}
if(mArchiveMode) {
enableToolbarBackButton();
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if(mArchiveMode){
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
mDatabase = new DBHelper(this).getWritableDatabase();
TabLayout groupsTabLayout = findViewById(R.id.groups);
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
@@ -278,10 +279,18 @@ 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);
mHelpSection = findViewById(R.id.helpSection);
mNoMatchingCardsText = findViewById(R.id.noMatchingCardsText);
mNoGroupCardsText = findViewById(R.id.noGroupCardsText);
mCardList = findViewById(R.id.list);
mNoMatchingCardsText.setOnTouchListener(gestureTouchListener);
mCardList.setOnTouchListener(gestureTouchListener);
mNoGroupCardsText.setOnTouchListener(gestureTouchListener);
mAdapter = new LoyaltyCardCursorAdapter(this, null, this);
mCardList.setAdapter(mAdapter);
@@ -322,17 +331,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 -> {
@@ -361,30 +374,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,15 +402,12 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
groupsTabLayout.selectTab(tab);
assert tab != null;
mGroup = tab.getTag();
} else if (!mArchiveMode) {
scaleScreen();
}
updateLoyaltyCardList(true);
// End of active tab logic
if (!mArchiveMode) {
FloatingActionButton addButton = binding.fabAdd;
FloatingActionButton addButton = findViewById(R.id.fabAdd);
addButton.setOnClickListener(v -> {
Intent intent = new Intent(getApplicationContext(), ScanActivity.class);
@@ -423,7 +424,8 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
@Override
public void onBackPressed() {
if (mSearchView != null && !mSearchView.isIconified()) {
if (!mSearchView.isIconified()) {
mSearchView.setIconified(true);
return;
}
@@ -432,7 +434,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
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}) {
for (int id : new int[]{R.id.action_search, R.id.action_unfold, R.id.action_sort}) {
menu.findItem(id).setVisible(shouldShow);
}
}
@@ -496,70 +498,9 @@ 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) {
@@ -591,12 +532,13 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
@Override
public boolean onCreateOptionsMenu(Menu inputMenu) {
if (!mArchiveMode) {
if(!mArchiveMode)
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
} else {
else{
getMenuInflater().inflate(R.menu.archive_menu, inputMenu);
}
Utils.updateMenuCardDetailsButtonState(inputMenu.findItem(R.id.action_unfold), mAdapter.showingDetails());
displayCardSetupOptions(inputMenu, mLoyaltyCardCount > 0);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
@@ -620,6 +562,7 @@ 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;
@@ -630,7 +573,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
});
}
if (!mArchiveMode) {
if(!mArchiveMode) {
if (DBHelper.getArchivedCardsCount(mDatabase) == 0) {
inputMenu.findItem(R.id.action_archived).setVisible(false);
} else {
@@ -649,8 +592,8 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
onBackPressed();
}
if (id == R.id.action_shown_details) {
mAdapter.showSelectDetailDisplayDialog();
if (id == R.id.action_unfold) {
mAdapter.showDetails(!mAdapter.showingDetails());
invalidateOptionsMenu();
return true;
@@ -666,15 +609,13 @@ 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;
CheckBox showReversed = (CheckBox) customLayout.findViewById(R.id.checkBox_reverse);
showReversed.setChecked(mOrderDirection == DBHelper.LoyaltyCardOrderDirection.Descending);
@@ -755,6 +696,84 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
updateLoyaltyCardList(false);
}
@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
public void onRowLongClicked(int inputPosition) {
enableActionMode(inputPosition);
@@ -767,16 +786,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();
@@ -795,10 +804,11 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
boolean hasStarred = false;
boolean hasUnstarred = false;
if (!mArchiveMode) {
if(!mArchiveMode) {
unarchiveItem.setVisible(false);
archiveItem.setVisible(true);
} else {
}
else{
unarchiveItem.setVisible(true);
archiveItem.setVisible(false);
}

View File

@@ -13,7 +13,6 @@ 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 +20,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;
@@ -46,18 +43,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;
noGroupCardsText = findViewById(R.id.noGroupCardsText);
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 +105,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();
@@ -159,7 +160,7 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
@Override
public boolean onCreateOptionsMenu(Menu inputMenu) {
getMenuInflater().inflate(R.menu.card_details_menu, inputMenu);
Utils.updateMenuCardDetailsButtonState(inputMenu.findItem(R.id.action_unfold), mAdapter.showingDetails());
return super.onCreateOptionsMenu(inputMenu);
}
@@ -167,8 +168,8 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
public boolean onOptionsItemSelected(MenuItem inputItem) {
int id = inputItem.getItemId();
if (id == R.id.action_shown_details) {
mAdapter.showSelectDetailDisplayDialog();
if (id == R.id.action_unfold) {
mAdapter.showDetails(!mAdapter.showingDetails());
invalidateOptionsMenu();
return true;
@@ -199,7 +200,7 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
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);
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

@@ -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,17 @@
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 +21,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 +33,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 +56,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 +109,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 +171,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 +205,13 @@ 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);
photoPickerLauncher.launch(photoPickerIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
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();
}
}
}
}

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

@@ -18,7 +18,6 @@ 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";
@@ -29,9 +28,7 @@ public class UCropWrapper extends UCropActivity {
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);
getWindow().getDecorView().setSystemUiVisibility(darkMode ? 0 : View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
// icons are always white back then
if (!darkMode) {
@@ -53,8 +50,8 @@ public class UCropWrapper extends UCropActivity {
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));
int colorSurface = MaterialColors.getColor(this, R.attr.colorSurface, ContextCompat.getColor(this, R.color.md_theme_light_surface));
int colorOnSurface = MaterialColors.getColor(this, R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
Drawable controlsBackgroundImageDrawable = controlsBackgroundImage.getBackground();
controlsBackgroundImageDrawable.mutate();

View File

@@ -11,25 +11,14 @@ 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;
@@ -39,17 +28,13 @@ 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;
@@ -59,6 +44,11 @@ import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Map;
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 protect.card_locker.preferences.Settings;
public class Utils {
@@ -115,17 +105,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 +116,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 +163,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 +196,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 +204,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) {
@@ -446,7 +396,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 +407,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;
}
@@ -543,7 +488,7 @@ public class Utils {
} else {
// final catch all in case of invalid theme value from older versions
// also handles R.string.settings_key_system_theme
DynamicColors.applyToActivityIfAvailable(activity);
DynamicColors.applyIfAvailable(activity);
}
if (isDarkModeEnabled(activity) && settings.getOledDark()) {
@@ -560,12 +505,22 @@ public class Utils {
activity.findViewById(android.R.id.content).setBackgroundColor(typedValue.data);
}
public static void updateMenuCardDetailsButtonState(MenuItem item, boolean currentlyExpanded) {
if (currentlyExpanded) {
item.setIcon(R.drawable.ic_baseline_unfold_less_24);
item.setTitle(R.string.action_hide_details);
} else {
item.setIcon(R.drawable.ic_baseline_unfold_more_24);
item.setTitle(R.string.action_show_details);
}
}
public static int getHeaderColorFromImage(Bitmap image, int fallback) {
if (image == null) {
return fallback;
}
return new Palette.Builder(image).generate().getDominantColor(androidx.appcompat.R.attr.colorPrimary);
return new Palette.Builder(image).generate().getDominantColor(R.attr.colorPrimary);
}
public static int getRandomHeaderColor(Context context) {
@@ -573,57 +528,4 @@ public class Utils {
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 = loyaltyCard.headerColor != null ? loyaltyCard.headerColor : LetterBitmap.getDefaultColor(context, loyaltyCard.store);
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;
}
}
}

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
@@ -147,7 +146,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,

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;
@@ -24,7 +25,6 @@ import java.util.Currency;
import java.util.Date;
import java.util.List;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.Group;
@@ -54,7 +54,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 +67,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 +94,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 +117,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 +134,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 +142,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 +150,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 +166,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 +225,7 @@ public class CatimaImporter implements Importer {
}
for (CSVRecord record : records) {
importLoyaltyCard(database, record);
importLoyaltyCard(context, database, record);
}
}
@@ -243,42 +254,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 +268,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,14 +306,14 @@ 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
@@ -359,7 +322,7 @@ public class CatimaImporter implements Importer {
int archiveStatus = 0;
try {
archiveStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS, record);
archiveStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS, record, false);
} catch (FormatException _e) {
// This field did not exist in versions 2.16.3 and before
// We catch this exception so we can still import old backups
@@ -368,13 +331,13 @@ public class CatimaImporter implements Importer {
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,archiveStatus);
}
/**
@@ -384,10 +347,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 +355,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

@@ -18,7 +18,6 @@ import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.Utils;
@@ -128,6 +127,6 @@ public class FidmeImporter implements Importer {
// 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, headerColor, starStatus, null,archiveStatus);
}
}

View File

@@ -24,7 +24,6 @@ import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.HashMap;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.ImageLocationType;
@@ -74,31 +73,29 @@ 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"
nameParts[0],
"analytics-properties.json"
};
customProvidersBaseName = new String[]{
"extracts",
nameParts[1],
nameParts[0],
"sync",
"data",
"users",
nameParts[1],
nameParts[0],
"loyalty-card-custom-providers"
};
cardBaseName = new String[]{
"extracts",
nameParts[1],
nameParts[0],
"sync",
"data",
"users",
nameParts[1],
nameParts[0],
"loyalty-cards"
};
}
@@ -108,15 +105,18 @@ public class StocardImporter implements Importer {
customProviderId = nameParts[customProvidersBaseName.length].split("\\.", 2)[0];
// Name file
if (fileName.endsWith(customProviderId + "/content.json")) {
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
if (nameParts.length == customProvidersBaseName.length + 1) {
// Ignore the .txt file
if (fileName.endsWith(".json")) {
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
providers = appendToHashMap(
providers,
customProviderId,
"name",
jsonObject.getString("name")
);
providers = appendToHashMap(
providers,
customProviderId,
"name",
jsonObject.getString("name")
);
}
} else if (fileName.endsWith("logo.png")) {
providers = appendToHashMap(
providers,
@@ -132,43 +132,46 @@ public class StocardImporter implements Importer {
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,
cardName,
"barcodeType",
jsonObject.getString("input_barcode_format")
"cardId",
jsonObject.getString("input_id")
);
// Provider ID can be either custom or not, extract whatever version is relevant
String customProviderPrefix = "/users/" + nameParts[0] + "/loyalty-card-custom-providers/";
String providerId = jsonObject
.getJSONObject("input_provider_reference")
.getString("identifier");
if (providerId.startsWith(customProviderPrefix)) {
providerId = providerId.substring(customProviderPrefix.length());
} else {
providerId = providerId.substring("/loyalty-card-providers/".length());
}
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap,
cardName,
"_providerId",
providerId
);
if (jsonObject.has("input_barcode_format")) {
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap,
cardName,
"barcodeType",
jsonObject.getString("input_barcode_format")
);
}
}
} else if (fileName.endsWith("notes/default/content.json")) {
} else if (fileName.endsWith("notes/default.json")) {
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap,
cardName,
@@ -230,7 +233,7 @@ public class StocardImporter implements Importer {
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);
long loyaltyCardInternalId = DBHelper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, 0, null,0);
if (cardIcon != null) {
Utils.saveCardImage(context, cardIcon, (int) loyaltyCardInternalId, ImageLocationType.icon);

View File

@@ -23,7 +23,6 @@ import java.util.Currency;
import java.util.Date;
import java.util.TimeZone;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.Utils;
@@ -126,7 +125,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(),0);
}
bufferedReader.close();

View File

@@ -63,6 +63,30 @@ 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);
}

View File

@@ -13,34 +13,36 @@ import java.util.List;
import java.util.Locale;
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 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();
@@ -159,5 +161,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

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

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android: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"/>

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,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>

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

@@ -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

@@ -1,5 +1,5 @@
<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="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
<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

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

@@ -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,16 +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.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" />
<path
android:fillColor="@android:color/white"
android:pathData="M18,15.782V22l4.886,-3.109z" />
<path
android:fillColor="@android:color/white"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12C2,17.52 6.47,22 11.99,22C13.417,22 14.772,21.699 16,21.162L16,18.928C14.823,19.608 13.458,20 12,20C7.58,20 4,16.42 4,12C4,7.58 7.58,4 12,4C16.42,4 20,7.58 20,12C20,13.061 19.791,14.073 19.416,15L21.541,15C21.839,14.053 22,13.045 22,12C22,6.48 17.52,2 11.99,2zM11,7L11,13L16,16L16,15L16.951,15L17,14.92L12.5,12.25L12.5,7L11,7z" />
</vector>

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="8dip" android:width="8dip"/>
<solid android:color="@android:color/white"/>
</shape>

View File

@@ -271,36 +271,6 @@
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/donate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
android:id="@+id/donate_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:padding="2dp"
android:text="@string/donate"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:fontFamily="sans-serif-medium"
android:text="@string/arrow"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/rate"
android:layout_width="match_parent"

View File

@@ -27,8 +27,6 @@
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="@+id/include"
layout="@layout/content_main" />
<include layout="@layout/content_main"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="protect.card_locker.MainActivity"
tools:showIn="@layout/custom_barcode_scanner">
<LinearLayout
android:id="@+id/camera_permission_denied_clickable_area"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/camera_permission_denied_icon"
android:layout_width="match_parent"
android:layout_height="84dp"
android:scaleType="fitCenter"
android:src="@drawable/camera_permission_denied" />
<TextView
android:id="@+id/camera_permission_denied_title"
style="@style/TextAppearance.Material3.HeadlineLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/cameraPermissionDeniedTitle" />
<TextView
android:id="@+id/camera_permission_denied_message"
style="@style/AppTheme.TextView.NoData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/noCameraPermissionDirectToSystemSetting" />
</LinearLayout>
</RelativeLayout>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@@ -9,21 +10,19 @@
tools:showIn="@layout/main_activity">
<LinearLayout
android:visibility="gone"
android:id="@+id/helpSection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:id="@+id/welcome_icon"
android:layout_width="match_parent"
android:layout_height="184dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_launcher_foreground" />
<TextView
android:id="@+id/welcome_text"
style="@style/TextAppearance.Material3.HeadlineLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -31,40 +30,40 @@
android:text="@string/welcome" />
<TextView
android:id="@+id/add_card_instruction"
style="@style/AppTheme.TextView.NoData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/noGiftCards" />
android:text="@string/noGiftCards"/>
</LinearLayout>
<TextView
android:id="@+id/noMatchingCardsText"
style="@style/AppTheme.TextView.NoData"
android:id="@+id/noMatchingCardsText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/noMatchingGiftCards"
android:visibility="gone" />
android:visibility="gone"/>
<TextView
android:id="@+id/noGroupCardsText"
style="@style/AppTheme.TextView.NoData"
android:id="@+id/noGroupCardsText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/noGroupCards"
android:visibility="gone" />
android:visibility="gone"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="@integer/main_view_card_columns"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="80dp"
android:clipToPadding="false"
android:scrollbars="vertical"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="@integer/main_view_card_columns" />
android:visibility="gone"/>
</RelativeLayout>

View File

@@ -1,57 +1,41 @@
<?xml version="1.0" encoding="utf-8"?><!-- Based on https://github.com/journeyapps/zxing-android-embedded/blob/0fdfbce9fb3285e985bad9971c5f7c0a7a334e7b/sample/src/main/res/layout/custom_barcode_scanner.xml originally released under Apache 2.0 -->
<?xml version="1.0" encoding="utf-8"?>
<!-- Based on https://github.com/journeyapps/zxing-android-embedded/blob/0fdfbce9fb3285e985bad9971c5f7c0a7a334e7b/sample/src/main/res/layout/custom_barcode_scanner.xml originally released under Apache 2.0 -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.journeyapps.barcodescanner.BarcodeView
android:id="@+id/zxing_barcode_surface"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:zxing_framing_rect_height="250dp"
app:zxing_framing_rect_width="250dp" />
android:id="@+id/zxing_barcode_surface"
app:zxing_framing_rect_width="250dp"
app:zxing_framing_rect_height="250dp"/>
<com.journeyapps.barcodescanner.ViewfinderView
android:id="@+id/zxing_viewfinder_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/zxing_viewfinder_view"
app:zxing_possible_result_points="@color/zxing_custom_possible_result_points"
app:zxing_result_view="@color/zxing_custom_result_view"
app:zxing_viewfinder_laser="@color/zxing_custom_viewfinder_laser"
app:zxing_viewfinder_mask="@color/zxing_custom_viewfinder_mask" />
app:zxing_viewfinder_mask="@color/zxing_custom_viewfinder_mask"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/card_input_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/activity_scanner_padding">
<include
android:id="@+id/camera_permission_denied_layout"
layout="@layout/camera_permission_failed_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:gravity="center_horizontal"
android:orientation="vertical">
<Button
android:id="@+id/add_from_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/addFromImage"
app:layout_constraintBottom_toTopOf="@+id/add_manually"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/camera_permission_denied_layout"
app:layout_constraintVertical_bias="1.0" />
android:text="@string/addFromImage" />
<Button
android:id="@+id/add_manually"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/addManually"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:text="@string/addManually" />
</LinearLayout>
</merge>

View File

@@ -37,48 +37,36 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
<ImageButton
android:id="@+id/moveUp"
style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_weight="1"
app:icon="@drawable/ic_baseline_arrow_drop_up_24"
app:iconGravity="textStart"
app:tint="?attr/colorOnPrimary"
app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24"
android:contentDescription="@string/moveUp"/>
<Button
<ImageButton
android:id="@+id/moveDown"
style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_weight="1"
app:icon="@drawable/ic_baseline_arrow_drop_down_24"
app:iconGravity="textStart"
app:tint="?attr/colorOnPrimary"
app:srcCompat="@drawable/ic_baseline_arrow_drop_down_24"
android:contentDescription="@string/moveDown"/>
<Button
<ImageButton
android:id="@+id/edit"
style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_weight="1"
app:icon="@drawable/ic_mode_edit_white_24dp"
app:iconGravity="textStart"
app:tint="?attr/colorOnPrimary"
app:srcCompat="@drawable/ic_mode_edit_white_24dp"
android:contentDescription="@string/edit"/>
<Button
<ImageButton
android:id="@+id/delete"
style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="@dimen/cardThumbnailSize"
android:layout_height="@dimen/cardThumbnailSize"
android:layout_weight="1"
app:icon="@drawable/ic_delete_white_24dp"
app:iconGravity="textStart"
app:tint="?attr/colorOnPrimary"
app:srcCompat="@drawable/ic_delete_white_24dp"
android:contentDescription="@string/delete"/>
</LinearLayout>

View File

@@ -288,7 +288,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:digits="0123456789,." />
android:digits="0123456789.,$" />
</com.google.android.material.textfield.TextInputLayout>
@@ -311,32 +311,6 @@
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<!-- Valid from -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/inputPadding"
android:paddingTop="@dimen/inputPadding"
android:orientation="horizontal">
<!-- Valid from -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/validFromView"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:hint="@string/validFromDate"
android:labelFor="@+id/validFromField">
<AutoCompleteTextView
android:id="@+id/validFromField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<!-- Expiration -->
<LinearLayout
android:layout_width="match_parent"

View File

@@ -9,7 +9,8 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:layout_marginBottom="4dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/icon_layout"
@@ -23,6 +24,7 @@
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:importantForAccessibility="no"
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -34,20 +36,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/thumbnail_text"
android:importantForAccessibility="no"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textStyle="bold"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="12sp"
app:autoSizeMaxTextSize="100sp"
app:autoSizeStepGranularity="2sp"
android:gravity="center"
android:maxLines="1"
android:layout_margin="20dp" />
<ImageView
android:importantForAccessibility="no"
android:id="@+id/selected_thumbnail"
@@ -136,10 +124,7 @@
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:textAppearance="?attr/textAppearanceHeadlineSmall"
android:textColor="?android:attr/textColorSecondary"
android:visibility="gone"
tools:visibility="visible"
android:textAppearance="?attr/textAppearanceHeadline1"
app:layout_constraintTop_toBottomOf="@+id/icon_layout"
app:layout_constraintBottom_toTopOf="@+id/note"
app:layout_constraintEnd_toEndOf="parent"
@@ -182,7 +167,6 @@
android:id="@+id/balance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
@@ -192,37 +176,16 @@
android:drawablePadding="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/info_divider"
app:layout_constraintBottom_toTopOf="@+id/validFrom"
app:layout_constraintBottom_toTopOf="@+id/expiry"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible"
tools:text="525 points"/>
<TextView
android:id="@+id/validFrom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
app:drawableLeftCompat="@drawable/ic_valid_from_24dp"
android:drawablePadding="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/balance"
app:layout_constraintBottom_toTopOf="@+id/expiry"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible"
tools:text="Today"/>
<TextView
android:id="@+id/expiry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
@@ -231,7 +194,7 @@
app:drawableLeftCompat="@drawable/ic_baseline_access_time_24"
android:drawablePadding="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/validFrom"
app:layout_constraintTop_toBottomOf="@+id/balance"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -6,199 +6,214 @@
android:id="@+id/coordinator_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fitsSystemWindows="false">
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:id="@+id/app_bar_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:clipChildren="false"
android:clipToPadding="false"
android:fitsSystemWindows="true"
android:weightSum="1.0">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
style="?attr/toolbarStyle" />
</com.google.android.material.appbar.AppBarLayout>
android:id="@+id/toolbar_landscape"
android:layout_width="fill_parent"
android:layout_height="?actionBarSize"
android:background="@android:color/transparent"
android:fitsSystemWindows="false"
android:paddingTop="0dp"
android:visibility="gone"
app:contentInsetStart="72.0dip"
app:layout_collapseMode="pin" />
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
android:layout_marginBottom="100dp"
android:orientation="vertical"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp">
<LinearLayout
android:id="@+id/icon_container"
android:layout_width="match_parent"
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbarLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp">
android:clipChildren="false"
android:clipToPadding="false"
android:minHeight="56.0dip"
app:contentScrim="?colorPrimary"
app:expandedTitleGravity="top"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp">
<Space
android:layout_width="50dp"
android:layout_height="0dp"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<com.google.android.material.card.MaterialCardView
android:id="@+id/icon_holder"
android:layout_width="match_parent"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
android:layout_margin="10dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="85.6f:53.98f">
<ImageView
android:id="@+id/icon_image"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/icon_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textStyle="bold"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="12sp"
app:autoSizeMaxTextSize="100sp"
app:autoSizeStepGranularity="2sp"
android:gravity="center"
android:maxLines="1"
android:layout_margin="20dp" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
<Space
android:layout_width="50dp"
android:layout_height="0dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp">
<!-- We don't use these buttons for Talkback -->
<ImageButton
android:importantForAccessibility="no"
android:id="@+id/main_left_button"
android:layout_width="50dp"
android:layout_height="match_parent"
app:srcCompat="@drawable/ic_baseline_chevron_left_24"
android:background="@android:color/transparent"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/card_holder"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<com.google.android.material.card.MaterialCardView
android:id="@+id/main_card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardCornerRadius="8dp"
android:layout_margin="10dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:orientation="vertical">
<ImageView
android:id="@+id/main_image"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:id="@+id/card_id_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_large"
android:ellipsize="end"
android:singleLine="true"
android:gravity="center"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- We don't use these buttons for Talkback -->
<ImageButton
android:importantForAccessibility="no"
android:id="@+id/main_right_button"
android:layout_width="50dp"
android:layout_height="match_parent"
app:srcCompat="@drawable/ic_baseline_chevron_right_24"
android:background="@android:color/transparent"/>
</LinearLayout>
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/fullscreen_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:importantForAccessibility="no"
android:id="@+id/fullscreen_image"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/scaler_guideline"/>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/scaler_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/fullscreen_button_minimize"
android:layout_marginBottom="25dp"
android:layout_marginStart="15.0dip"
android:layout_marginEnd="15.0dip">
<TextView
android:importantForAccessibility="no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/height"/>
<SeekBar
android:id="@+id/barcode_scaler"
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/storeName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentDescription="@string/setBarcodeHeight"
android:max="100" />
</LinearLayout>
android:layout_marginTop="?actionBarSize"
android:layout_marginBottom="?actionBarSize"
android:ellipsize="end"
android:maxLines="1"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="40sp"
app:layout_collapseMode="parallax" />
<ImageButton
android:id="@+id/fullscreen_button_minimize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/ic_baseline_expand_more_24"
android:tooltipText="@string/moveDown"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="25dp"
android:background="@android:color/transparent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.Toolbar
android:id="@id/toolbar"
android:layout_width="fill_parent"
android:layout_height="?actionBarSize"
android:background="@android:color/transparent"
app:contentInsetStart="72.0dip"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<ImageView
android:id="@+id/icon_image"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_alignParentTop="true"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_launcher_foreground"
tools:ignore="ContentDescription" />
<FrameLayout
android:clipChildren="false"
android:clipToPadding="false"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/mainLayout"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/centerGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5"/>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/scalerGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.75"/>
<ImageButton
android:id="@+id/maximizeButton"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginStart="15.0dip"
android:layout_marginTop="10dp"
android:layout_marginEnd="15.0dip"
android:background="?attr/colorPrimary"
android:contentDescription="@string/moveBarcodeToTopOfScreen"
android:padding="0dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/mainImage"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24"
app:tint="?attr/colorOnPrimary"
tools:visibility="visible" />
<ImageView
android:id="@+id/mainImage"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginStart="15.0dip"
android:layout_marginEnd="15.0dip"
app:layout_constraintBottom_toTopOf="@+id/centerGuideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/maximizeButton"/>
<ImageButton
android:id="@+id/minimizeButton"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginStart="15.0dip"
android:layout_marginTop="10dp"
android:layout_marginEnd="15.0dip"
android:background="?attr/colorPrimary"
android:contentDescription="@string/moveBarcodeToCenterOfScreen"
android:padding="0dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/mainImage"
app:srcCompat="@drawable/ic_baseline_arrow_drop_down_24"
app:tint="?attr/colorOnPrimary"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/dotIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="15.0dip"
android:layout_marginTop="10dp"
android:layout_marginEnd="15.0dip"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/minimizeButton"
tools:visibility="visible" />
<SeekBar
android:id="@+id/barcodeScaler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/inputPadding"
android:layout_marginStart="15.0dip"
android:layout_marginEnd="15.0dip"
android:contentDescription="@string/set_scale"
android:max="100"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/scalerGuideline" />
<TextView
android:id="@+id/cardIdView"
android:enabled="true"
android:textIsSelectable="true"
android:focusable="true"
android:longClickable="true"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="10.0dip"
android:layout_marginRight="10.0dip"
android:paddingBottom="80dp"
app:layout_constraintTop_toBottomOf="@+id/dotIndicator"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:textAlignment="center"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="@dimen/singleCardCardIdTextSizeMin"
app:autoSizeMaxTextSize="@dimen/singleCardCardIdTextSizeMax"
android:ellipsize="end"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:id="@+id/drop_shadow_actionbar"
android:layout_width="fill_parent"
android:layout_height="5.0dip"
android:layout_gravity="top"/>
</FrameLayout>
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottom_app_bar"
@@ -214,7 +229,7 @@
app:fabAlignmentMode="center">
<ImageButton
android:id="@+id/bottom_app_bar_previous_button"
android:id="@+id/button_previous"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="left"
@@ -226,7 +241,7 @@
android:visibility="gone" />
<ImageButton
android:id="@+id/bottom_app_bar_info_button"
android:id="@+id/button_show_info"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
@@ -238,7 +253,7 @@
android:visibility="gone" />
<ImageButton
android:id="@+id/bottom_app_bar_next_button"
android:id="@+id/button_next"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="right"
@@ -249,18 +264,6 @@
android:tooltipText="@string/nextCard"
android:visibility="gone" />
<ImageButton
android:id="@+id/bottom_app_bar_update_balance_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:background="@android:color/transparent"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:src="@drawable/ic_baseline_shopping_cart_24"
android:tooltipText="@string/updateBalance"
android:visibility="gone" />
</com.google.android.material.bottomappbar.BottomAppBar>
<com.google.android.material.floatingactionbutton.FloatingActionButton

View File

@@ -36,8 +36,6 @@
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="@+id/include"
layout="@layout/content_main" />
<include layout="@layout/content_main"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -29,8 +29,6 @@
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="@+id/include"
layout="@layout/group_main" />
<include layout="@layout/group_main"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.materialswitch.MaterialSwitch xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:focusable="false" />

View File

@@ -12,11 +12,10 @@
android:id="@+id/checkBox_reverse"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:paddingStart="20dp"
android:layout_marginLeft="20dp"
android:paddingLeft="20dp"
android:text="@string/reverse"
android:textSize="16sp"
android:textColor="?attr/colorOnSurfaceVariant"/>
android:textSize="19sp"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -10,9 +10,9 @@
app:showAsAction="always|collapseActionView"
android:visible="false"/>
<item
android:id="@+id/action_shown_details"
android:title="@string/action_show_details"
android:icon="@drawable/baseline_visibility_24"
android:id="@+id/action_unfold"
android:title="@string/action_hide_details"
android:icon="@drawable/ic_baseline_unfold_less_24"
app:showAsAction="always"
android:visible="false"/>
<item

View File

@@ -1,9 +1,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_shown_details"
android:title="@string/action_show_details"
android:icon="@drawable/baseline_visibility_24"
android:id="@+id/action_unfold"
android:title="@string/action_hide_details"
android:icon="@drawable/ic_baseline_unfold_less_24"
app:showAsAction="always"
android:visible="true"/>
</menu>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_delete"
android:icon="@drawable/ic_delete_white_24dp"
android:title="@string/delete"
app:showAsAction="always"/>
</menu>

View File

@@ -4,13 +4,13 @@
<item
android:id="@+id/action_share"
android:icon="@drawable/ic_share"
android:icon="@drawable/ic_share_white"
android:title="@string/share"
app:showAsAction="always"/>
<item
android:id="@+id/action_star_unstar"
android:icon="@drawable/ic_unstarred"
android:icon="@drawable/ic_unstarred_white"
android:title="@string/star"
app:showAsAction="always" />

View File

@@ -10,9 +10,9 @@
app:showAsAction="always|collapseActionView"
android:visible="false"/>
<item
android:id="@+id/action_shown_details"
android:title="@string/action_show_details"
android:icon="@drawable/baseline_visibility_24"
android:id="@+id/action_unfold"
android:title="@string/action_hide_details"
android:icon="@drawable/ic_baseline_unfold_less_24"
app:showAsAction="always"
android:visible="false"/>
<item

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