mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2025-12-24 15:47:53 -05:00
Compare commits
17 Commits
v2.27.0
...
feature/mu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0547fb9949 | ||
|
|
d092cbe930 | ||
|
|
67383a7f10 | ||
|
|
5b5ade0edc | ||
|
|
efd0f4ef39 | ||
|
|
67ada5c027 | ||
|
|
520f0da983 | ||
|
|
343d710b2a | ||
|
|
1c71dc964f | ||
|
|
524d649523 | ||
|
|
e39593228d | ||
|
|
27d718b461 | ||
|
|
490281bff9 | ||
|
|
5030e4c67a | ||
|
|
29eac68e3f | ||
|
|
422501da3e | ||
|
|
23bd60b476 |
30
.github/dependabot.yml
vendored
30
.github/dependabot.yml
vendored
@@ -1,30 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/"
|
||||
registries:
|
||||
- google
|
||||
- gradlePluginPortal
|
||||
- jitpack
|
||||
- mavenCentral
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
# Workaround for https://github.com/dependabot/dependabot-core/issues/6888
|
||||
registries:
|
||||
google:
|
||||
type: maven-repository
|
||||
url: "https://dl.google.com/dl/android/maven2/"
|
||||
gradlePluginPortal:
|
||||
type: maven-repository
|
||||
url: "https://plugins.gradle.org/m2/"
|
||||
jitpack:
|
||||
type: maven-repository
|
||||
url: "https://jitpack.io/"
|
||||
mavenCentral:
|
||||
type: maven-repository
|
||||
url: "https://repo1.maven.org/maven2/"
|
||||
54
.github/workflows/android.yml
vendored
54
.github/workflows/android.yml
vendored
@@ -1,54 +0,0 @@
|
||||
name: Android CI
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- staging
|
||||
- trying
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
permissions:
|
||||
actions: none
|
||||
checks: none
|
||||
contents: read
|
||||
deployments: none
|
||||
discussions: none
|
||||
id-token: none
|
||||
issues: none
|
||||
packages: none
|
||||
pages: none
|
||||
pull-requests: none
|
||||
repository-projects: none
|
||||
security-events: none
|
||||
statuses: none
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- 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: Build
|
||||
run: ./gradlew assembleRelease
|
||||
- name: Check lint
|
||||
run: ./gradlew lintRelease
|
||||
- name: Run unit tests
|
||||
run: timeout 5m ./gradlew testReleaseUnitTest || { ./gradlew --stop && timeout 5m ./gradlew testReleaseUnitTest; }
|
||||
- name: SpotBugs
|
||||
run: ./gradlew spotbugsRelease
|
||||
- name: Archive test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4.3.0
|
||||
with:
|
||||
name: test-results
|
||||
path: app/build/reports
|
||||
42
.github/workflows/changelog-to-fastlane.yml
vendored
42
.github/workflows/changelog-to-fastlane.yml
vendored
@@ -1,42 +0,0 @@
|
||||
name: Convert CHANGELOG to Fastlane
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'CHANGELOG.md'
|
||||
permissions:
|
||||
actions: none
|
||||
checks: none
|
||||
contents: write
|
||||
deployments: none
|
||||
discussions: none
|
||||
id-token: none
|
||||
issues: none
|
||||
packages: none
|
||||
pages: none
|
||||
pull-requests: write
|
||||
repository-projects: none
|
||||
security-events: none
|
||||
statuses: none
|
||||
jobs:
|
||||
convert_changelog_to_fastlane:
|
||||
runs-on: ubuntu-latest
|
||||
name: Convert CHANGELOG to Fastlane
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
id: checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5.0.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Run converter script
|
||||
run: python .scripts/changelog_to_fastlane.py
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5.0.2
|
||||
with:
|
||||
title: "Update Fastlane changelogs"
|
||||
commit-message: "Update Fastlane changelogs"
|
||||
branch-suffix: timestamp
|
||||
40
.github/workflows/contributors-to-file.yml
vendored
40
.github/workflows/contributors-to-file.yml
vendored
@@ -1,40 +0,0 @@
|
||||
name: Write contributors to file
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '3 4 * * 0'
|
||||
permissions:
|
||||
actions: none
|
||||
checks: none
|
||||
contents: write
|
||||
deployments: none
|
||||
discussions: none
|
||||
id-token: none
|
||||
issues: none
|
||||
packages: none
|
||||
pages: none
|
||||
pull-requests: write
|
||||
repository-projects: none
|
||||
security-events: none
|
||||
statuses: none
|
||||
jobs:
|
||||
contributors_to_file:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
name: Write contributors to file
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
id: checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Update contributors
|
||||
id: update_contributors
|
||||
uses: TheLastProject/contributors-to-file-action@v3.2.0
|
||||
with:
|
||||
file_in_repo: app/src/main/res/raw/contributors.txt
|
||||
min_commit_count: 5
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5.0.2
|
||||
with:
|
||||
title: "Update contributors"
|
||||
commit-message: "Update contributors"
|
||||
branch-suffix: timestamp
|
||||
45
.github/workflows/generate-feature-graphic.yml
vendored
45
.github/workflows/generate-feature-graphic.yml
vendored
@@ -1,45 +0,0 @@
|
||||
name: Generate feature graphic
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'fastlane/**/title.txt'
|
||||
permissions:
|
||||
actions: none
|
||||
checks: none
|
||||
contents: write
|
||||
deployments: none
|
||||
discussions: none
|
||||
id-token: none
|
||||
issues: none
|
||||
packages: none
|
||||
pages: none
|
||||
pull-requests: write
|
||||
repository-projects: none
|
||||
security-events: none
|
||||
statuses: none
|
||||
jobs:
|
||||
generate-feature-graphic:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- name: Install requirements
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install optipng mat2
|
||||
# Install 200 weight versions of relevant Noto (to use for languages not supported by Lexend Deca)
|
||||
sudo apt-get install fonts-noto-extra fonts-noto-cjk-extra
|
||||
# Custom fonts
|
||||
mkdir "$HOME/.fonts"
|
||||
find .scripts/generate_feature_graphic/fonts -name '*.ttf' -exec cp {} "$HOME/.fonts" \;
|
||||
fc-cache
|
||||
- name: Generate featureGraphic.png for each language
|
||||
run: .scripts/generate_feature_graphic/generate_feature_graphic.sh
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5.0.2
|
||||
with:
|
||||
title: "Update feature graphic"
|
||||
commit-message: "Update feature graphic"
|
||||
branch-suffix: timestamp
|
||||
33
.github/workflows/gradle-update.yml
vendored
33
.github/workflows/gradle-update.yml
vendored
@@ -1,33 +0,0 @@
|
||||
name: Gradle update
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '3 6 * * *'
|
||||
permissions:
|
||||
actions: none
|
||||
checks: none
|
||||
contents: write
|
||||
deployments: none
|
||||
discussions: none
|
||||
id-token: none
|
||||
issues: none
|
||||
packages: none
|
||||
pages: none
|
||||
pull-requests: write
|
||||
repository-projects: none
|
||||
security-events: none
|
||||
statuses: none
|
||||
jobs:
|
||||
gradle-update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- uses: obfusk/gradle-update-action@v2.0.0
|
||||
id: gradle-update
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5.0.2
|
||||
with:
|
||||
title: "Update Gradle to ${{ steps.gradle-update.outputs.version }}"
|
||||
commit-message: "Update Gradle to ${{ steps.gradle-update.outputs.version }}"
|
||||
branch-suffix: timestamp
|
||||
38
.github/workflows/update-locales.yml
vendored
38
.github/workflows/update-locales.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Update locales
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- app/src/main/res/values-*/strings.xml
|
||||
- app/src/main/res/values/settings.xml
|
||||
permissions:
|
||||
actions: none
|
||||
checks: none
|
||||
contents: write
|
||||
deployments: none
|
||||
discussions: none
|
||||
id-token: none
|
||||
issues: none
|
||||
packages: none
|
||||
pages: none
|
||||
pull-requests: write
|
||||
repository-projects: none
|
||||
security-events: none
|
||||
statuses: none
|
||||
jobs:
|
||||
update-locales:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- name: Add new locales
|
||||
run: .scripts/new-locales.py
|
||||
- name: Update locales
|
||||
run: .scripts/locales.py
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5.0.2
|
||||
with:
|
||||
title: "Update locales"
|
||||
commit-message: "Update locales"
|
||||
branch-suffix: timestamp
|
||||
23
.gitignore
vendored
23
.gitignore
vendored
@@ -1,27 +1,8 @@
|
||||
# Android Studio generated (superseded/unused rules commented out)
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
#/.idea/caches
|
||||
#/.idea/libraries
|
||||
#/.idea/modules.xml
|
||||
#/.idea/workspace.xml
|
||||
#/.idea/navEditor.xml
|
||||
#/.idea/assetWizardSettings.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
#local.properties
|
||||
|
||||
# Android extras
|
||||
/app/*.log
|
||||
/app/build
|
||||
/app/release
|
||||
/.idea
|
||||
|
||||
# Bundle
|
||||
/.bundle/
|
||||
/vendor/bundle
|
||||
/lib/bundler/man/
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
changelogs = {}
|
||||
|
||||
with open('CHANGELOG.md') as changelog:
|
||||
version_code = None
|
||||
text = []
|
||||
|
||||
for line in changelog:
|
||||
if line.startswith("## "):
|
||||
if version_code != None:
|
||||
changelogs[version_code] = text
|
||||
|
||||
text = []
|
||||
match = re.match("## \S* - (\d*).*", line)
|
||||
if not match:
|
||||
raise ValueError(f"Invalid version line: {line}")
|
||||
version_code = match.group(1)
|
||||
elif line:
|
||||
# Turn Markdown [links](to_url) into links (to_url)
|
||||
text.append(re.sub(r'\[(.*?)\]\((.*?)\)', r'\1 (\2)', line))
|
||||
|
||||
for version, description in changelogs.items():
|
||||
description = "".join(description).strip()
|
||||
|
||||
if not description:
|
||||
continue
|
||||
|
||||
with open(os.path.join("fastlane", "metadata", "android", "en-US", "changelogs", f"{version}.txt"), "w") as fastlane_file:
|
||||
fastlane_file.write(description)
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import csv
|
||||
import json
|
||||
import msgpack
|
||||
|
||||
MSGPACK = "bootstrapdata.msgpack"
|
||||
OUTFILE = "stocard_stores.csv"
|
||||
|
||||
|
||||
def load(fh):
|
||||
data = []
|
||||
for r in msgpack.Unpacker(fh, raw=False):
|
||||
if r["collection"] == "/loyalty-card-providers/":
|
||||
d = json.loads(r["data"])
|
||||
data.append([r["resource_id"], d["name"], d["default_barcode_format"]])
|
||||
return data
|
||||
|
||||
|
||||
def save(data, output_file=OUTFILE):
|
||||
with open(output_file, "w") as fh:
|
||||
writer = csv.writer(fh, lineterminator="\n")
|
||||
writer.writerow(["_id", "name", "barcodeFormat"])
|
||||
for row in data:
|
||||
writer.writerow(row)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
epilog=f"INPUT_FILE must be a .msgpack or .apk and defaults to {MSGPACK}; "
|
||||
f"OUTPUT_FILE defaults to {OUTFILE}")
|
||||
parser.add_argument("input_file", metavar="INPUT_FILE", nargs="?", default=MSGPACK)
|
||||
parser.add_argument("output_file", metavar="OUTPUT_FILE", nargs="?", default=OUTFILE)
|
||||
args = parser.parse_args()
|
||||
if args.input_file.lower().endswith(".apk"):
|
||||
import zipfile
|
||||
with zipfile.ZipFile(args.input_file) as zf:
|
||||
with zf.open(f"assets/{MSGPACK}") as fh:
|
||||
data = load(fh)
|
||||
else:
|
||||
with open(args.input_file, "rb") as fh:
|
||||
data = load(fh)
|
||||
save(data, args.output_file)
|
||||
@@ -1,15 +0,0 @@
|
||||
<svg width="1024" height="500" viewBox="0 0 1024 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1024" height="500" fill="#223355"/>
|
||||
<text fill="white" xml:space="preserve" style="" font-family="Yesteryear" font-size="150" letter-spacing="0em"><tspan x="470.082" y="285.511">Catima
|
||||
</tspan></text>
|
||||
<path d="M381.046 147.001L236.3 211.446L276.524 301.79L421.27 237.345L381.046 147.001Z" fill="#F0F0F0" stroke="#C80000" stroke-width="2"/>
|
||||
<path d="M402.077 219.13L240.07 147L191.984 255.004L353.99 327.135L402.077 219.13Z" fill="#F0F0F0" stroke="#C80000" stroke-width="2"/>
|
||||
<path d="M437.17 236.241L251.831 183.096L220.071 293.855L405.41 347L437.17 236.241Z" fill="#C80000" stroke="#C80000" stroke-width="6" stroke-linejoin="round"/>
|
||||
<path d="M412.879 178.633H220.071V293.855H412.879V178.633Z" fill="#FF0000" stroke="#FF0000" stroke-width="6" stroke-linejoin="round"/>
|
||||
<path d="M221.482 296.217C238.316 296.217 251.963 269.366 251.963 236.244C251.963 203.121 238.316 176.27 221.482 176.27C204.647 176.27 191 203.121 191 236.244C191 269.366 204.647 296.217 221.482 296.217Z" fill="#FF0000" stroke="#FF0000" stroke-width="3.44232" stroke-linejoin="round"/>
|
||||
<path d="M307.256 250.444C307.256 253.187 306.289 255.842 304.526 257.944C302.763 260.045 300.316 261.458 297.614 261.934C294.913 262.41 292.13 261.92 289.755 260.548C287.379 259.177 285.563 257.012 284.625 254.435" stroke="#F0F0F0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M330.301 254.298C329.363 256.875 327.547 259.04 325.171 260.411C322.796 261.783 320.013 262.273 317.312 261.797C314.61 261.321 312.163 259.908 310.4 257.807C308.637 255.706 307.671 253.05 307.671 250.307" stroke="#F0F0F0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M248.345 225.937L266.818 207.465L285.29 225.937" stroke="#F0F0F0" stroke-width="2"/>
|
||||
<path d="M329.625 225.937L348.098 207.465L366.571 225.937" stroke="#F0F0F0" stroke-width="2"/>
|
||||
<text fill="white" xml:space="preserve" style="" font-family="Lexend Deca" font-size="35" font-weight="200" letter-spacing="0em"><tspan x="466" y="340">Loyalty Card Wallet</tspan></text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
@@ -1,93 +0,0 @@
|
||||
Copyright 2018 The Lexend Project Authors (https://github.com/googlefonts/lexend), with Reserved Font Name “RevReading Lexend”.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,94 +0,0 @@
|
||||
Copyright (c) 2011 by Brian J. Bonislawsky DBA Astigmatic (AOETI)
|
||||
(astigma@astigmatic.com), with Reserved Font Names "Yesteryear"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
@@ -1,47 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
script_location="$(dirname "$(readlink -f "$0")")"
|
||||
|
||||
for lang in "$script_location/../../fastlane/metadata/android/"*; do
|
||||
pushd "$lang"
|
||||
# Place temporary copy for editing if needed
|
||||
cp "$script_location/featureGraphic.svg" featureGraphic.svg
|
||||
if grep -q — title.txt; then
|
||||
# Try splitting title.txt on — (em dash)
|
||||
IFS='—' read -r appname subtext < title.txt
|
||||
else
|
||||
# No result, try splitting on - (dash)
|
||||
IFS='-' read -r appname subtext < title.txt
|
||||
fi
|
||||
export appname=${appname%% }
|
||||
export subtext=${subtext## }
|
||||
# If there is subtext, change the .svg accordingly
|
||||
if [ -n "$subtext" ]; then
|
||||
perl -pi -e 's/Catima/$ENV{appname}/' featureGraphic.svg
|
||||
perl -pi -e 's/Loyalty Card Wallet/$ENV{subtext}/' featureGraphic.svg
|
||||
# Set correct font or font size for language if needed
|
||||
# (Lexend Deca has limited support and some characters are big)
|
||||
# We specifically need the Serif version because of the 200 weight
|
||||
case "$(basename "$lang")" in
|
||||
bg|el-GR|ru-RU|uk) sed -i "s/Lexend Deca/Noto Serif/" featureGraphic.svg ;;
|
||||
ja-JP) sed -i "s/Lexend Deca/Noto Serif CJK JP/" featureGraphic.svg ;;
|
||||
ko) sed -i "s/Lexend Deca/Noto Serif CJK KR/" featureGraphic.svg ;;
|
||||
kn-IN) sed -i -e 's/font-size="150"/font-size="100"/' -e 's/y="285.511"/y="235.511"/' featureGraphic.svg ;;
|
||||
zh-CN) sed -i "s/Lexend Deca/Noto Serif CJK SC/" featureGraphic.svg ;;
|
||||
zh-TW) sed -i "s/Lexend Deca/Noto Serif CJK TC/" featureGraphic.svg ;;
|
||||
*) ;;
|
||||
esac
|
||||
fi
|
||||
# Ensure images directory exists
|
||||
mkdir -p images
|
||||
# Generate .png
|
||||
convert featureGraphic.svg images/featureGraphic.png
|
||||
# Optimize .png
|
||||
optipng images/featureGraphic.png
|
||||
# Remove metadata (timestamps) from .png
|
||||
mat2 --inplace images/featureGraphic.png
|
||||
# Remove temporary .svg
|
||||
rm featureGraphic.svg
|
||||
popd
|
||||
done
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
root = ET.parse("app/src/main/res/values/settings.xml").getroot()
|
||||
for e in root.findall("string-array"):
|
||||
if e.get("name") == "locale_values":
|
||||
locales = [x.text for x in e if x.text]
|
||||
break
|
||||
|
||||
locales = [
|
||||
# e.g. de or es-rAR (not es-AR)
|
||||
loc.replace("-", "-r") if "-" in loc and loc[loc.index("-") + 1] != "r" else loc
|
||||
for loc in locales
|
||||
]
|
||||
|
||||
res = ", ".join(f'"{loc}"' for loc in locales)
|
||||
sed = [
|
||||
"sed",
|
||||
"-i",
|
||||
f"s/resourceConfigurations .*/resourceConfigurations += listOf({res})/",
|
||||
"app/build.gradle.kts"
|
||||
]
|
||||
subprocess.run(sed, check=True)
|
||||
|
||||
with open("app/src/main/res/xml/locales_config.xml", "w") as fh:
|
||||
fh.write('<?xml version="1.0" encoding="utf-8"?>\n')
|
||||
fh.write('<locale-config xmlns:android="http://schemas.android.com/apk/res/android">\n')
|
||||
fh.write(' <locale android:name="en-US" />\n')
|
||||
for loc in locales:
|
||||
if loc != "en":
|
||||
# e.g. de or en-AR (not es-rAR)
|
||||
loc = loc.replace("-r", "-")
|
||||
fh.write(f' <locale android:name="{loc}" />\n')
|
||||
fh.write('</locale-config>\n')
|
||||
@@ -1,120 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import glob
|
||||
import re
|
||||
|
||||
from typing import Iterator, List, Tuple
|
||||
|
||||
import requests
|
||||
|
||||
MIN_PERCENT = 90
|
||||
NOT_LANGS = ("night", "w600dp")
|
||||
REPLACE_CODES = {
|
||||
"el": "el-rGR",
|
||||
"id": "in-rID",
|
||||
"ro": "ro-rRO",
|
||||
"zh_Hans": "zh-rCN",
|
||||
"zh_Hant": "zh-rTW",
|
||||
}
|
||||
STATS_URL = "https://hosted.weblate.org/api/components/catima/catima/statistics/"
|
||||
|
||||
|
||||
def get_weblate_langs() -> List[Tuple[str, int]]:
|
||||
r = requests.get(STATS_URL, timeout=5)
|
||||
r.raise_for_status()
|
||||
results = []
|
||||
for lang in r.json()["results"]:
|
||||
if lang["code"] != "en":
|
||||
code = REPLACE_CODES.get(lang["code"], lang["code"]).replace("_", "-r")
|
||||
results.append((code, round(lang["translated_percent"])))
|
||||
return sorted(results)
|
||||
|
||||
|
||||
def get_dir_langs() -> List[str]:
|
||||
results = []
|
||||
for d in glob.glob("app/src/main/res/values-*"):
|
||||
code = d.split("-", 1)[1]
|
||||
if code not in NOT_LANGS:
|
||||
results.append(code)
|
||||
return sorted(results)
|
||||
|
||||
|
||||
def get_xml_langs() -> List[Tuple[str, bool]]:
|
||||
results = []
|
||||
in_section = False
|
||||
with open("app/src/main/res/values/settings.xml") as fh:
|
||||
for line in fh:
|
||||
if not in_section and 'name="locale_values"' in line:
|
||||
in_section = True
|
||||
elif in_section:
|
||||
if "string-array" in line:
|
||||
break
|
||||
disabled = "<!--" in line
|
||||
if m := re.search(r">(.*)<", line):
|
||||
if m[1] != "en":
|
||||
results.append((m[1], disabled))
|
||||
return sorted(results)
|
||||
|
||||
|
||||
def update_xml_langs(langs: List[Tuple[str, bool]]) -> None:
|
||||
lines: List[str] = []
|
||||
in_section = False
|
||||
with open("app/src/main/res/values/settings.xml") as fh:
|
||||
for line in fh:
|
||||
if not in_section and 'name="locale_values"' in line:
|
||||
in_section = True
|
||||
elif in_section:
|
||||
if "string-array" in line:
|
||||
in_section = False
|
||||
lines.extend(_lang_lines(langs))
|
||||
else:
|
||||
continue
|
||||
lines.append(line)
|
||||
with open("app/src/main/res/values/settings.xml", "w") as fh:
|
||||
for line in lines:
|
||||
fh.write(line)
|
||||
|
||||
|
||||
def _lang_lines(langs: List[Tuple[str, bool]]) -> Iterator[str]:
|
||||
yield " <item />\n"
|
||||
for lang, disabled in sorted(langs + [("en", False)]):
|
||||
if disabled:
|
||||
yield f" <!-- <item>{lang}</item> -->\n"
|
||||
else:
|
||||
yield f" <item>{lang}</item>\n"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
web_langs = get_weblate_langs()
|
||||
dir_langs = get_dir_langs()
|
||||
xml_langs = get_xml_langs()
|
||||
|
||||
web_codes = set(code for code, _ in web_langs)
|
||||
dir_codes = set(dir_langs)
|
||||
xml_codes = set(code for code, _ in xml_langs)
|
||||
|
||||
if diff := web_codes - dir_codes:
|
||||
print(f"WARNING: Weblate codes w/o dir: {diff}")
|
||||
if diff := xml_codes - dir_codes:
|
||||
print(f"WARNING: XML codes w/o dir: {diff}")
|
||||
|
||||
percentages = dict(web_langs)
|
||||
all_langs = xml_langs[:]
|
||||
|
||||
# add new langs as disabled
|
||||
for code in dir_codes - xml_codes:
|
||||
all_langs.append((code, True))
|
||||
|
||||
# enable disabled langs if they are at least MIN_PERCENT translated now
|
||||
updated_langs = sorted(
|
||||
(code, percentages[code] < MIN_PERCENT if disabled else disabled)
|
||||
for code, disabled in all_langs
|
||||
)
|
||||
|
||||
if updated_langs != xml_langs:
|
||||
print("Updating...")
|
||||
update_xml_langs(updated_langs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
16
.travis.yml
Normal file
16
.travis.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
language: android
|
||||
sudo: true
|
||||
|
||||
install:
|
||||
- echo y | android update sdk -u -a -t tools
|
||||
- echo y | android update sdk -u -a -t platform-tools
|
||||
- echo y | android update sdk -u -a -t build-tools-26.0.2
|
||||
- yes | sdkmanager "platforms;android-27"
|
||||
- echo y | android update sdk -u -a -t extra-google-m2repository
|
||||
- echo y | android update sdk -u -a -t extra-android-m2repository
|
||||
|
||||
script: ./gradlew assembleRelease testReleaseUnitTest lintRelease findbugs
|
||||
|
||||
after_failure:
|
||||
- cat app/build/reports/findbugs/findbugs.html
|
||||
- cat app/build/reports/tests/debug/index.html
|
||||
10
.tx/config
Normal file
10
.tx/config
Normal file
@@ -0,0 +1,10 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[loyalty-card-locker.stringsxml]
|
||||
file_filter = app/src/main/res/values-<lang>/strings.xml
|
||||
minimum_perc = 0
|
||||
source_file = app/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
|
||||
797
CHANGELOG.md
797
CHANGELOG.md
@@ -1,520 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## v2.27.0 - 132 (2024-01-30)
|
||||
|
||||
- Refine "Add card" workflow
|
||||
- Validation flow improvements
|
||||
- Fix edge case causing invalid UI state when toggling showing archive
|
||||
- Use theme or card colour for navigation bar (Android 8.1+)
|
||||
- Updated validity and expiry date selector
|
||||
- Add option to always rotate (ignoring system settings)
|
||||
|
||||
## v2.26.0 - 131 (2023-09-14)
|
||||
|
||||
- Move "Archive mode" into "Display options" (previously "Show details") menu
|
||||
- Android 13 per-app language support
|
||||
- Embed privacy policy, changelog and license in the app
|
||||
|
||||
## v2.25.3 - 130 (2023-08-25)
|
||||
|
||||
- Minor UI fixes
|
||||
- Fix valid from and expiry dates being reset when rotating the card editing screen
|
||||
- Fix crash when rotating screen while the color picker is shown
|
||||
- Stocard import fixes
|
||||
|
||||
## v2.25.2 - 129 (2023-07-27)
|
||||
|
||||
- Improved Catima importer (fixes cards missing when importing)
|
||||
- Fix crash when rotating screen while setting valid from/expiry date
|
||||
- Minor UI tweaks
|
||||
|
||||
## v2.25.1 - 128 (2023-07-17)
|
||||
|
||||
- Fix rare crash
|
||||
|
||||
## v2.25.0 - 127 (2023-07-09)
|
||||
|
||||
- Barcode rendering improvements
|
||||
- Basic interoperability with external apps (Android 6.0+)
|
||||
- Reorganized settings screen
|
||||
- Fix importing from some browsers that add a trailing / to the share URL
|
||||
|
||||
## v2.24.2 - 126 (2023-06-18)
|
||||
|
||||
- Various RTL fixes
|
||||
|
||||
## v2.24.1 - 125 (2023-06-11)
|
||||
|
||||
- Deal more gracefully with missing header colours
|
||||
|
||||
## v2.24.0 - 124 (2023-06-10)
|
||||
|
||||
- Support selecting exactly which details to view in card overview
|
||||
|
||||
## v2.23.3 - 123 (2023-06-03)
|
||||
|
||||
- Minor UI improvements
|
||||
- Fix new design not being usable on devices with square screens
|
||||
|
||||
## v2.23.2 - 122 (2023-05-30)
|
||||
|
||||
- Long-press card icon in view activity to change it
|
||||
- Improve button styling in Groups screen
|
||||
- Fix long barcode values causing barcode to scale down to nothing
|
||||
|
||||
## v2.23.1 - 121 (2023-05-27)
|
||||
|
||||
- Update used libraries
|
||||
|
||||
## v2.23.0 - 120 (2023-05-25)
|
||||
|
||||
- Complete redesign of main and loyalty card view screens
|
||||
- Material You design for the settings screen
|
||||
- Fix crash when using "Take a photo" with disabled camera app
|
||||
|
||||
## v2.22.1 - 119 (2023-04-14)
|
||||
|
||||
- Use Material You colours on more devices (Google library update)
|
||||
|
||||
## v2.22.0 - 118 (2023-03-18)
|
||||
|
||||
- Support setting start of card validity
|
||||
- Fix Stocard import (Stocard's export format changed)
|
||||
|
||||
## v2.21.2 - 117 (2023-01-27)
|
||||
|
||||
- Remove unnecessary permissions
|
||||
- Target Android 13
|
||||
|
||||
## v2.21.1 - 116 (2022-12-06)
|
||||
|
||||
- Fix quick spend dialog not allowing , separator
|
||||
- Support loading image from file manager
|
||||
|
||||
## v2.21.0 - 115 (2022-11-06)
|
||||
|
||||
- Open image in gallery on long-press
|
||||
- Apply Material style to dialogs
|
||||
- Support creating card by sharing an image to Catima
|
||||
- Add quick spend button to card screen
|
||||
|
||||
## v2.20.0 - 114 (2022-09-21)
|
||||
|
||||
- Add Monochrome icon for Android 13
|
||||
- Improve first launch screen
|
||||
- Fidme import fixes
|
||||
|
||||
## v2.19.0 - 113 (2022-08-14)
|
||||
|
||||
- Add previous and next buttons to the loyalty card view
|
||||
- Fix foreground colour on edit button
|
||||
- Replace floppy disk save icon with checkmark
|
||||
|
||||
## v2.18.2 - 112 (2022-07-29)
|
||||
|
||||
- Make the possibility to set a custom header more visible
|
||||
|
||||
## v2.18.1 - 111 (2022-07-24)
|
||||
|
||||
- Arabic language support
|
||||
- Display archived card count in group overview
|
||||
- Fix balance parsing bugs (made cards not savable in Arabic and other language with non-Western numbers)
|
||||
- Fix custom theme not applying to main screen correctly
|
||||
- Improve display of selected cards
|
||||
- Fix crash when leaving cardview in RTL layouts for cards with expiry or balance
|
||||
- Fix back arrow in card view pointing the wrong way in RTL layouts
|
||||
|
||||
## v2.17.1 - 109 (2022-06-28)
|
||||
|
||||
- Fix incorrect text colour on "No barcode" button
|
||||
|
||||
## v2.17.0 - 108 (2022-06-24)
|
||||
|
||||
- Add card duplication feature
|
||||
- Don't allow choosing expiry before 1970 (they never worked anyway)
|
||||
- Add support for archiving cards
|
||||
- Move delete from edit to view
|
||||
- Remove rotation lock icon in favour of a new rotation lock setting
|
||||
|
||||
## v2.16.3 - 107 (2022-04-15)
|
||||
|
||||
- Stocard import fixes
|
||||
|
||||
## v2.16.2 - 106 (2022-03-31)
|
||||
|
||||
- Fix some character sequences being shown as a single character
|
||||
|
||||
## v2.16.1 - 105 (2022-03-25)
|
||||
|
||||
- Fix gray block appearing on invalid value for barcode
|
||||
- Stocard import fixes
|
||||
|
||||
## v2.16.0 - 104 (2022-03-09)
|
||||
|
||||
- Save card detail expansion state
|
||||
- Minor UI fixes
|
||||
|
||||
## v2.15.2 - 103 (2022-02-11)
|
||||
|
||||
- Fix manual language selection not applying everywhere
|
||||
- Fix crash in edit view on regionless locale
|
||||
|
||||
## v2.15.1 - 102 (2022-02-10)
|
||||
|
||||
- Various minor fixes
|
||||
- Fix crash when using Norwegian translation
|
||||
|
||||
## v2.15.0 - 101 (2022-02-06)
|
||||
|
||||
- Fix cropper not using theme colour
|
||||
- Fix minor theming issues
|
||||
- Add pure black dark theme for OLED screens
|
||||
|
||||
## v2.14.1 - 100 (2022-01-15)
|
||||
|
||||
- Hide search, expand and sort icons until there is at least 1 card
|
||||
- Various theming fixes
|
||||
|
||||
## v2.14.0 - 99 (2022-01-14)
|
||||
|
||||
- Material You redesign
|
||||
|
||||
## v2.13.1 - 98 (2022-01-09)
|
||||
|
||||
- Fix various TalkBack-related bugs
|
||||
|
||||
## v2.13.0 - 97 (2022-01-03)
|
||||
|
||||
- Fixed pressing the save button multiple times creating multiple entries
|
||||
- Lower card header size when hiding details to fit even more cards
|
||||
- Restructure edit screen
|
||||
- Improve star icon contrast in main view
|
||||
|
||||
## v2.12.0 - 96 (2021-12-23)
|
||||
|
||||
- Add CODE 93 support
|
||||
- Various minor bugfixes and improvements
|
||||
|
||||
## v2.11.2 - 95 (2021-12-04)
|
||||
|
||||
- Fix crash on sharing card
|
||||
|
||||
## v2.11.1 - 94 (2021-11-30)
|
||||
|
||||
- Fix blurriness of main screen letter icons
|
||||
- Fix icons sometimes disappearing after selection
|
||||
- Fix status bar icons possibly being invisible on Android 5
|
||||
|
||||
## v2.11.0 - 93 (2021-11-28)
|
||||
|
||||
- Add Catima to [Quick Access Device Controls](https://developer.android.com/guide/topics/ui/device-control)
|
||||
- Fix some groups not showing up correctly in group management screen
|
||||
|
||||
## v2.10.0 - 92 (2021-11-20)
|
||||
|
||||
- New main screen layout
|
||||
- Fix bottomsheet sizing issues when switching in and out of fullscreen
|
||||
|
||||
## v2.9.0 - 91 (2021-11-14)
|
||||
|
||||
- Improved group management support
|
||||
- Support cropping images
|
||||
- Fix image data loss when saving after rotating in edit view
|
||||
- Ability to set a custom image as card icon
|
||||
|
||||
## v2.8.1 - 90 (2021-10-27)
|
||||
|
||||
- Fix dots in card view having the wrong colour when changing theme manually
|
||||
- Fix crash in card view on rotation/theme change
|
||||
- Fix flashing of cards list
|
||||
- Fix text overlaying star icon
|
||||
|
||||
## v2.8.0 - 89 (2021-10-25)
|
||||
|
||||
- Fix swiping between groups not working on an empty group
|
||||
- Allow password-protecting exports
|
||||
- Improve usage of space for QR codes
|
||||
- Save the last used zoom level per card
|
||||
- Fix a crash when swiping right after a tap
|
||||
|
||||
## v2.7.3 - 88 (2021-10-10)
|
||||
|
||||
- Fix incorrect migration making first card become invisible
|
||||
|
||||
## v2.7.2 - 87 (2021-10-09)
|
||||
|
||||
- Fix regression breaking import/export
|
||||
|
||||
## v2.7.1 - 86 (2021-10-07)
|
||||
|
||||
- Improve search with spaces
|
||||
|
||||
## v2.7.0 - 85 (2021-10-05)
|
||||
|
||||
Android 4.4 is no longer supported starting with this release. If you want to use Catima on Android 4.4, please use version 2.6.1.
|
||||
|
||||
- Improved Android 12 support
|
||||
- Improved about screen
|
||||
- Search now ignores accents
|
||||
|
||||
## v2.6.1 - 84 (2021-09-25)
|
||||
|
||||
- Minor bugfixes and improvements
|
||||
|
||||
## v2.6.0 - 83 (2021-09-19)
|
||||
|
||||
- Support for changing the sorting order
|
||||
- Prevent Out Of Memory on scanning large pictures for barcode
|
||||
|
||||
## v2.5.0 - 82 (2021-09-10)
|
||||
|
||||
- Improved support for screen readers
|
||||
- Don't crash when trying to open a video from gallery
|
||||
- Swipe support on loyalty card view screen
|
||||
- Don't reset group on back button press
|
||||
|
||||
## v2.4.0 - 81 (2021-08-29)
|
||||
|
||||
- Improve card list for landscape and tablet display
|
||||
- Add theming colour support (thanks, Subhashish Anand!)
|
||||
- Don't close scan activity on camera error (so manual entry is still possible)
|
||||
- Add all contributors to the about dialog
|
||||
|
||||
## v2.3.0 - 80 (2021-08-19)
|
||||
|
||||
- Fix images not imported from backup
|
||||
- Option to override language
|
||||
|
||||
## v2.2.3 - 79 (2021-08-13)
|
||||
|
||||
- Fix widget creating different-looking shortcut than app shortcuts
|
||||
- Replace default Android black screen with splash screen
|
||||
|
||||
## v2.2.2 - 78 (2021-08-08)
|
||||
|
||||
- Fix crash on rotation in loyalty card edit activity
|
||||
|
||||
## v2.2.1 - 77 (2021-08-07)
|
||||
|
||||
- Improve Stocard importer
|
||||
- Fix importing Catima export with multiline note
|
||||
- Scale card title in acceptable range
|
||||
- Animation improvements
|
||||
|
||||
## v2.2.0 - 76 (2021-08-02)
|
||||
|
||||
- Make links in notes clickable
|
||||
- Pre-select group the user is currently in when creating a new card
|
||||
- Comma-separate group names in loyalty card view
|
||||
- Fix maximize button appearing on no barcode
|
||||
|
||||
## v2.1.0 - 75 (2021-08-01)
|
||||
|
||||
- Fix selected colour in colour changing dialog
|
||||
- Support for deleting multiple cards at once
|
||||
- Fix possible ArithmeticException when resizing image
|
||||
- Fix fullscreen is closed when rotating device
|
||||
|
||||
## v2.0.4 - 74 (2021-07-27)
|
||||
|
||||
- Fix shortcut creation
|
||||
- Generate card-specific shortcut icon
|
||||
- Fix ability to change loyalty card colour
|
||||
|
||||
## v2.0.3 - 73 (2021-07-25)
|
||||
|
||||
- Fix loading photos when editing existing card
|
||||
|
||||
## v2.0.2 - 72 (2021-07-25)
|
||||
|
||||
- Fix inability to configure photos in new loyalty card
|
||||
|
||||
## v2.0.1 - 71 (2021-07-21)
|
||||
|
||||
- Several minor translation and UI fixes
|
||||
- Fix crash in import/sharing loyalty card on Android 6
|
||||
|
||||
## v2.0 - 70 (2021-07-14)
|
||||
|
||||
- BREAKING CHANGE: The backup format changed, see https://github.com/TheLastProject/Catima/wiki/Export-format
|
||||
- BREAKING CHANGE: The URL sharing format changed, see https://github.com/TheLastProject/Catima/wiki/Card-sharing-URL-format
|
||||
- Make it possible to enable or disable the flashlight while scanning
|
||||
- Add UPC-E support
|
||||
- Support adding a front and back photo to each card
|
||||
- Support importing password-protected zip files
|
||||
- Support importing from Stocard (Beta)
|
||||
- Fix useless whitespace in notes from Fidme import
|
||||
- Support new Voucher Vault export format
|
||||
- Fix Floating Action Buttons being behind other UI elements on Android 4
|
||||
- Fix loyalty card viewer appbar top margin
|
||||
|
||||
## v1.14.1 - 69 (2021-06-14)
|
||||
|
||||
- Add missing barcode ID to export
|
||||
- Don't show update barcode dialog if value is the same as card ID
|
||||
- Add Finnish translation
|
||||
|
||||
## v1.14 - 68 (2021-06-07)
|
||||
|
||||
- Support new PDF417 export from Voucher Vault
|
||||
- Support copying multiple barcodes at once
|
||||
- Support sharing multiple loyalty cards at once
|
||||
- Ask to update barcode value if card ID changes
|
||||
|
||||
## v1.13 - 67 (2021-04-10)
|
||||
|
||||
- Add option to set a separate barcode value from card ID
|
||||
- Simplify font sizing configuration
|
||||
- Several small UI fixes
|
||||
- Use letter icon for shortcuts too
|
||||
- Always show all barcode types in manual entry
|
||||
- Remove privacy policy first start dialog
|
||||
|
||||
## v1.12 - 66 (2021-03-30)
|
||||
|
||||
- Support importing [Fidme](https://play.google.com/store/apps/details?id=fr.snapp.fidme) exports
|
||||
- Allow importing a card from a picture stored in the user's Android gallery
|
||||
- Fix multiline note cutoff
|
||||
- Change "Thank you" text on privacy dialog to "Accept" because Huawei is overly pedantic
|
||||
|
||||
## v1.11 - 64 (2021-03-21)
|
||||
|
||||
- Add privacy policy dialog on first start (required by Huawei)
|
||||
|
||||
## v1.10 - 63 (2021-03-07)
|
||||
|
||||
- Support importing [Voucher Vault](https://github.com/tim-smart/vouchervault/) exports
|
||||
- Option to keep the screen on while viewing a loyalty card
|
||||
- Option to suspend the lock screen while viewing a loyalty card
|
||||
|
||||
## v1.9.2 - 62 (2021-02-24)
|
||||
|
||||
- Fix parsing balance for countries using space as separator
|
||||
|
||||
## v1.9.1 - 61 (2021-02-23)
|
||||
|
||||
- Improve balance parsing logic
|
||||
- Fix currency decimal display on main screen
|
||||
|
||||
## v1.9 - 59 (2021-02-22)
|
||||
|
||||
- Add balance support
|
||||
- Reorganize barcode tab of edit view
|
||||
|
||||
## v1.8.1 - 58 (2021-02-12)
|
||||
|
||||
- Fix Crash on versions before Android 7
|
||||
|
||||
## v1.8 - 57 (2021-01-28)
|
||||
|
||||
- Add support for scaling the barcode when moving to top to fit even more small scanners
|
||||
- Fix bottom sheet jumping after switching to fullscreen
|
||||
- Make header in loyalty card view small in landscape mode
|
||||
- Fix cards not staying in group when group gets renamed
|
||||
|
||||
## v1.7.1 - 56 (2021-01-18)
|
||||
|
||||
- Fix crash on switching to barcode tab in edit view if there is no barcode
|
||||
|
||||
## v1.7.0 - 55 (2021-01-18)
|
||||
|
||||
- Separate edit UI in tabs to make it feel more spacious
|
||||
- Add expiry field support
|
||||
|
||||
## v1.6.2 - 54 (2021-01-04)
|
||||
|
||||
- Fix edit button or more info bottom sheet drawing over barcode ID
|
||||
|
||||
## v1.6.1 - 64 (2020-12-16)
|
||||
|
||||
- Fix regression causing manual barcode entry to not be saved
|
||||
|
||||
## v1.6.0 - 52 (2020-12-15)
|
||||
|
||||
- Automatically focus text field when creating or editing a group
|
||||
- Fix blurry icons (use SVG everywhere)
|
||||
- Always open camera but add manual scan button to camera view
|
||||
|
||||
## v1.5.1 - 51 (2020-12-03)
|
||||
|
||||
- Fix bottomsheet background being transparent
|
||||
|
||||
## v1.5.0 - 50 (2020-12-03)
|
||||
|
||||
- Improve contrast by always using white text on red buttons
|
||||
- Draggable bottom sheet in loyalty card view
|
||||
|
||||
## v1.4.1 - 49 (2020-12-01)
|
||||
|
||||
- Improved translations
|
||||
- Small UI fixes
|
||||
|
||||
## v1.4.0 - 48 (2020-11-28)
|
||||
|
||||
- Move About screen into its own activity
|
||||
- Ask user if they want to use their camera or manually enter ID on add/edit card
|
||||
- Make group ordering manual instead of forced alphabetically
|
||||
|
||||
## v1.3.0 - 47 (2020-11-22)
|
||||
|
||||
- Always show all import/export options and show a toast on actual issues (improves compat with XPrivacyLua)
|
||||
- Ask for confirmation when leaving edit view after making changes without saving
|
||||
|
||||
## v1.2.2 - 46 (2020-11-19)
|
||||
|
||||
- Remember active group tab between screens and sessions
|
||||
|
||||
## v1.2.1 - 45 (2020-11-17)
|
||||
|
||||
- Fix home screen swiping triggering during vertical swipes too
|
||||
|
||||
## v1.2.0 - 44 (2020-11-17)
|
||||
|
||||
- Add swiping between groups on the home screen
|
||||
- Fix crash with cards lacking header colour
|
||||
|
||||
## v1.1.0 - 43 (2020-11-11)
|
||||
|
||||
- Improved edit UI
|
||||
- Removed header text colour option (now automatically generated based on brightness)
|
||||
- Updated translations
|
||||
|
||||
## v1.0.1 - 42 (2020-11-07)
|
||||
|
||||
- Fix crash in search with no groups
|
||||
|
||||
## v1.0 - 41 (2020-11-06)
|
||||
|
||||
- Added rounded edges to card icons on main overview
|
||||
- Added support for grouping entries
|
||||
|
||||
## v0.29 - 40 (2020-10-29)
|
||||
|
||||
- Rebrand to Catima
|
||||
- Removed intro
|
||||
- Add floating action buttons
|
||||
- Fix Android 5 crash when opening About screen
|
||||
- Add favourites support
|
||||
- Fix disabled auto-rotate being ignored
|
||||
|
||||
## v0.28 - 39 (2020-03-09)
|
||||
|
||||
- Fix barcode centering when exiting full screen ([#351](https://github.com/brarcher/loyalty-card-locker/pull/351))
|
||||
- Allow backup export location to be selected ([#352](https://github.com/brarcher/loyalty-card-locker/pull/352))
|
||||
- Update translations ([#357](https://github.com/brarcher/loyalty-card-locker/pull/357)) & ([#362](https://github.com/brarcher/loyalty-card-locker/pull/362))
|
||||
|
||||
## v0.27 - 38 (2020-01-26)
|
||||
## v0.27 (2020-01-26)
|
||||
|
||||
Changes:
|
||||
- Tapping on a barcode now moves it to the top of the screen ([#348](https://github.com/brarcher/loyalty-card-locker/pull/348))
|
||||
- Add white space around barcodes to improve scanning in dark mode ([#328](https://github.com/brarcher/loyalty-card-locker/issues/328))
|
||||
- Fix swapped import buttons. ([#346](https://github.com/brarcher/loyalty-card-locker/pull/346))
|
||||
|
||||
## v0.26.1 - 37 (2020-01-09)
|
||||
## v0.26.1 (2020-01-09)
|
||||
|
||||
Changes:
|
||||
- Fix issue with sharing cards without background color ([#343](https://github.com/brarcher/loyalty-card-locker/pull/343))
|
||||
|
||||
## v0.26 - 36 (2020-01-05)
|
||||
## v0.26 (2020-01-05)
|
||||
|
||||
Changes:
|
||||
- Add ability to search for a card ([#320](https://github.com/brarcher/loyalty-card-locker/pull/320))
|
||||
- Add ability to share and receive loyalty cards ([#321](https://github.com/brarcher/loyalty-card-locker/pull/321))
|
||||
- Dark mode support ([#322](https://github.com/brarcher/loyalty-card-locker/pull/322))
|
||||
@@ -524,215 +22,254 @@ Android 4.4 is no longer supported starting with this release. If you want to us
|
||||
- Improve notification and app icon visibility ([#330](https://github.com/brarcher/loyalty-card-locker/pull/330))
|
||||
- Update target SDK to Android 10
|
||||
- Improve the following translations:
|
||||
- German
|
||||
- Italian
|
||||
- Dutch
|
||||
- Polish
|
||||
- Russian
|
||||
* German
|
||||
* Italian
|
||||
* Dutch
|
||||
* Polish
|
||||
* Russian
|
||||
|
||||
## v0.25.4 - 35 (2019-10-04)
|
||||
## v0.25.4 (2019-10-04)
|
||||
|
||||
Changes
|
||||
- Enable app backups
|
||||
- Update French and Slovenian translations
|
||||
|
||||
## v0.25.3 - 34 (2019-03-02)
|
||||
## v0.25.3 (2019-03-02)
|
||||
|
||||
Changes
|
||||
- Update Russian translations
|
||||
|
||||
## v0.25.2 - 33 (2019-01-05)
|
||||
|
||||
## v0.25.2 (2019-01-05)
|
||||
|
||||
Changes
|
||||
- Update and add translations
|
||||
|
||||
## v0.25.1 - 32 (2018-10-14)
|
||||
## v0.25.1 (2018-10-14)
|
||||
|
||||
- Fix creating new card by manually entering barcode ([issue #272](https://github.com/brarcher/loyalty-card-locker/issues/272))
|
||||
Changes:
|
||||
- Fix creating new card by manually entering barcode (https://github.com/brarcher/loyalty-card-locker/issues/272)
|
||||
|
||||
## v0.25 - 31 (2018-10-07)
|
||||
## v0.25 (2018-10-07)
|
||||
|
||||
- Sort card list case insensitive ([pull #266](https://github.com/brarcher/loyalty-card-locker/pull/266))
|
||||
- Add setting to lock orientation for all cards ([pull #269](https://github.com/brarcher/loyalty-card-locker/pull/269)
|
||||
Changes:
|
||||
- Sort card list case insensitive (https://github.com/brarcher/loyalty-card-locker/pull/266)
|
||||
- Add setting to lock orientation for all cards (https://github.com/brarcher/loyalty-card-locker/pull/269)
|
||||
|
||||
## v0.24 - 30 (2018-07-31)
|
||||
## v0.24 (2018-07-31)
|
||||
|
||||
- Add a setting to control screen brightness when displaying a barcode ([pull #259](https://github.com/brarcher/loyalty-card-locker/pull/259))
|
||||
- Add Greek translations ([pull #252](https://github.com/brarcher/loyalty-card-locker/pull/252))
|
||||
- Add Slovenian translations ([pull #260](https://github.com/brarcher/loyalty-card-locker/pull/260))
|
||||
- Update translations ([pull #260](https://github.com/brarcher/loyalty-card-locker/pull/260), [pull #254](https://github.com/brarcher/loyalty-card-locker/pull/254))
|
||||
Changes:
|
||||
- Add a setting to control screen brightness when displaying a barcode (https://github.com/brarcher/loyalty-card-locker/pull/259)
|
||||
- Add Greek translations (https://github.com/brarcher/loyalty-card-locker/pull/252)
|
||||
- Add Slovenian translations (https://github.com/brarcher/loyalty-card-locker/pull/260)
|
||||
- Update translations (https://github.com/brarcher/loyalty-card-locker/pull/260, https://github.com/brarcher/loyalty-card-locker/pull/254)
|
||||
|
||||
## v0.23.4 - 29 (2018-05-12)
|
||||
## v0.23.4 (2018-05-12)
|
||||
|
||||
- Fix Spanish translations ([pull #244](https://github.com/brarcher/loyalty-card-locker/pull/244))
|
||||
- Update translations ([pull #244](https://github.com/brarcher/loyalty-card-locker/pull/244))
|
||||
Changes:
|
||||
- Fix Spanish translations (https://github.com/brarcher/loyalty-card-locker/pull/244)
|
||||
- Update translations (https://github.com/brarcher/loyalty-card-locker/pull/244)
|
||||
|
||||
## v0.23.3 - 28 (2018-05-05)
|
||||
## v0.23.3 (2018-05-05)
|
||||
|
||||
Changes:
|
||||
- Added translations
|
||||
- Polish ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
|
||||
- Spanish ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
|
||||
- Slovak ([pull #232](https://github.com/brarcher/loyalty-card-locker/pull/232))
|
||||
- Updated translations ([pull #239](https://github.com/brarcher/loyalty-card-locker/pull/239))
|
||||
* Polish (https://github.com/brarcher/loyalty-card-locker/pull/232)
|
||||
* Spanish (https://github.com/brarcher/loyalty-card-locker/pull/232)
|
||||
* Slovak (https://github.com/brarcher/loyalty-card-locker/pull/232)
|
||||
- Updated translations (https://github.com/brarcher/loyalty-card-locker/pull/239)
|
||||
|
||||
## v0.23.2 - 27 (2018-03-11)
|
||||
## v0.23.2 (2018-03-11)
|
||||
|
||||
- Reduce min SDK from 17 to 15. ([pull #226](https://github.com/brarcher/loyalty-card-locker/pull/226))
|
||||
- Remove usage of legacy apache library, used only in unit tests but no longer needed. ([pull #225](https://github.com/brarcher/loyalty-card-locker/pull/225))
|
||||
Changes:
|
||||
- Reduce min SDK from 17 to 15. (https://github.com/brarcher/loyalty-card-locker/pull/226)
|
||||
- Remove usage of legacy apache library, used only in unit tests but no longer needed. (https://github.com/brarcher/loyalty-card-locker/pull/225)
|
||||
|
||||
## v0.23.1 - 26 (2018-03-07)
|
||||
## v0.23.1 (2018-03-07)
|
||||
|
||||
- Prevent crash when rendering a barcode exhausts the application's memory. ([pull #219](https://github.com/brarcher/loyalty-card-locker/pull/219))
|
||||
Changes:
|
||||
- Prevent crash when rendering a barcode exhausts the application's memory. (https://github.com/brarcher/loyalty-card-locker/pull/219)
|
||||
|
||||
## v0.23 - 25 (2018-02-28)
|
||||
## v0.23 (2018-02-28)
|
||||
|
||||
- Reduce space in header when viewing a card. ([pull #213](https://github.com/brarcher/loyalty-card-locker/pull/213))
|
||||
- Disable beep when scanning a barcode. ([pull #216](https://github.com/brarcher/loyalty-card-locker/pull/216))
|
||||
Changes:
|
||||
- Reduce space in header when viewing a card. (https://github.com/brarcher/loyalty-card-locker/pull/213)
|
||||
- Disable beep when scanning a barcode. (https://github.com/brarcher/loyalty-card-locker/pull/216)
|
||||
|
||||
## v0.22 - 24 (2018-02-19)
|
||||
## v0.22 (2018-02-19)
|
||||
|
||||
- Update translations. ([pull #208](https://github.com/brarcher/loyalty-card-locker/pull/208))
|
||||
- Barcode rendering updates: ([pull #209](https://github.com/brarcher/loyalty-card-locker/pull/209))
|
||||
- Reload card view activity when screen is rotated, so barcode image is correct size.
|
||||
- Render 1D barcodes in a larger space, allowing them to better fill the screen.
|
||||
Changes:
|
||||
- Update translations. (https://github.com/brarcher/loyalty-card-locker/pull/208)
|
||||
- Barcode rendering updates: (https://github.com/brarcher/loyalty-card-locker/pull/209)
|
||||
* Reload card view activity when screen is rotated, so barcode image is correct size.
|
||||
* Render 1D barcodes in a larger space, allowing them to better fill the screen.
|
||||
|
||||
## v0.21 - 23 (2018-02-17)
|
||||
## v0.21 (2018-02-17)
|
||||
|
||||
- Add quiet space at the start/end of barcodes. ([pull #200](https://github.com/brarcher/loyalty-card-locker/pull/200))
|
||||
- Add options to configure the colors used for the store name font and background. ([pull #203](https://github.com/brarcher/loyalty-card-locker/pull/203))
|
||||
- Add options to adjust font sizes on the card listing page and single card page. ([pull #204](https://github.com/brarcher/loyalty-card-locker/pull/204))
|
||||
Changes
|
||||
- Add quiet space at the start/end of barcodes. (https://github.com/brarcher/loyalty-card-locker/pull/200)
|
||||
- Add options to configure the colors used for the store name font and background. (https://github.com/brarcher/loyalty-card-locker/pull/203)
|
||||
- Add options to adjust font sizes on the card listing page and single card page. (https://github.com/brarcher/loyalty-card-locker/pull/204)
|
||||
|
||||
## v0.20 - 22 (2018-02-10)
|
||||
## v0.20 (2018-02-10)
|
||||
|
||||
- Changes to Card view to display the note, allow the card ID to take multiple lines, and show the store name. ([pull #197](https://github.com/brarcher/loyalty-card-locker/pull/197))
|
||||
Changes:
|
||||
- Changes to Card view to display the note, allow the card ID to take multiple lines, and show the store name. (https://github.com/brarcher/loyalty-card-locker/pull/197)
|
||||
|
||||
## v0.19 - 21 (2018-02-01)
|
||||
## v0.19 (2018-02-01)
|
||||
|
||||
- Improved layout for card list. ([pull #188](https://github.com/brarcher/loyalty-card-locker/pull/188))
|
||||
- Improved layout when viewing a card. ([pull #190](https://github.com/brarcher/loyalty-card-locker/pull/190))
|
||||
Changes:
|
||||
- Improved layout for card list. (https://github.com/brarcher/loyalty-card-locker/pull/188)
|
||||
- Improved layout when viewing a card. (https://github.com/brarcher/loyalty-card-locker/pull/190)
|
||||
|
||||
## v0.18.1 - 20 (2018-01-24)
|
||||
## v0.18.1 (2018-01-24)
|
||||
|
||||
- Workaround crash during install on some Android versions (likely Android 5 and below). ([pull #184](https://github.com/brarcher/loyalty-card-locker/pull/184))
|
||||
Changes:
|
||||
- Workaround crash during install on some Android versions (likely Android 5 and below). (https://github.com/brarcher/loyalty-card-locker/pull/184)
|
||||
|
||||
## v0.18 - 19 (2018-01-19)
|
||||
## v0.18 (2018-01-19)
|
||||
|
||||
- Fix crash when importing certain types of corrupted CSV files. ([pull #177](https://github.com/brarcher/loyalty-card-locker/pull/177))
|
||||
- Fix importing backups directly from the file system. ([pull #180](https://github.com/brarcher/loyalty-card-locker/pull/180))
|
||||
- Fix importing backups from certain types of content providers. ([pull #179](https://github.com/brarcher/loyalty-card-locker/pull/179))
|
||||
Changes:
|
||||
- Fix crash when importing certain types of corrupted CSV files. (https://github.com/brarcher/loyalty-card-locker/pull/177)
|
||||
- Fix importing backups directly from the file system. (https://github.com/brarcher/loyalty-card-locker/pull/180)
|
||||
- Fix importing backups from certain types of content providers. (https://github.com/brarcher/loyalty-card-locker/pull/179)
|
||||
|
||||
## v0.17 - 18 (2018-01-11)
|
||||
## v0.17 (2018-01-11)
|
||||
|
||||
- Fix issue on Android SDK 24+ where using the file chooser import option would cause a crash. ([pull #170](https://github.com/brarcher/loyalty-card-locker/pull/170))
|
||||
- New icon and color scheme. ([pull #171](https://github.com/brarcher/loyalty-card-locker/pull/171))
|
||||
Changes:
|
||||
- Fix issue on Android SDK 24+ where using the file chooser import option would cause a crash. (https://github.com/brarcher/loyalty-card-locker/pull/170)
|
||||
- New icon and color scheme. (https://github.com/brarcher/loyalty-card-locker/pull/171)
|
||||
|
||||
## v0.16 - 17 (2017-11-29)
|
||||
## v0.16 (2017-11-29)
|
||||
|
||||
- Add support for adding loyalty card shortcuts from the launcher/homescreen. ([pull #161](https://github.com/brarcher/loyalty-card-locker/pull/161))
|
||||
- Remove support for adding loyalty card shortcuts from the app itself. This removes the need for the shortcut permission. ([pull #163](https://github.com/brarcher/loyalty-card-locker/pull/163))
|
||||
Changes:
|
||||
- Add support for adding loyalty card shortcuts from the launcher/homescreen. (https://github.com/brarcher/loyalty-card-locker/pull/161)
|
||||
- Remove support for adding loyalty card shortcuts from the app itself. This removes the need for the shortcut permission. (https://github.com/brarcher/loyalty-card-locker/pull/163)
|
||||
|
||||
## v0.15 - 16 (2017-11-25)
|
||||
## v0.15 (2017-11-25)
|
||||
|
||||
- Add support for adding shortcuts to home screen when adding or editing a card. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
|
||||
- Remove widget, as it was a poor substitute for shortcuts. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
|
||||
- Fix exporting backups on Android 7+. ([pull #153](https://github.com/brarcher/loyalty-card-locker/pull/153))
|
||||
- Report more accurate mime type when exporting backup data. ([pull #156](https://github.com/brarcher/loyalty-card-locker/pull/156))
|
||||
- Fix bug where a card could not be edited. ([pull #155](https://github.com/brarcher/loyalty-card-locker/pull/155))
|
||||
Changes:
|
||||
- Add support for adding shortcuts to home screen when adding or editing a card. (https://github.com/brarcher/loyalty-card-locker/pull/155)
|
||||
- Remove widget, as it was a poor substitute for shortcuts. (https://github.com/brarcher/loyalty-card-locker/pull/155)
|
||||
- Fix exporting backups on Android 7+. (https://github.com/brarcher/loyalty-card-locker/pull/153)
|
||||
- Report more accurate mime type when exporting backup data. (https://github.com/brarcher/loyalty-card-locker/pull/156)
|
||||
- Fix bug where a card could not be edited. (https://github.com/brarcher/loyalty-card-locker/pull/155)
|
||||
|
||||
## v0.14 - 15 (2017-10-26)
|
||||
## v0.14 (2017-10-26)
|
||||
|
||||
- Add support for app shortcuts (Android 7.1+), where the most recently used cards will appear as shortcuts. ([pull #145](https://github.com/brarcher/loyalty-card-locker/pull/145))
|
||||
- Add a widget which works like a pinned app shortcut, to support devices which run below Android 7.1. ([pull #142](https://github.com/brarcher/loyalty-card-locker/pull/142))
|
||||
Changes:
|
||||
- Add support for app shortcuts (Android 7.1+), where the most recently used cards will appear as shortcuts. (https://github.com/brarcher/loyalty-card-locker/pull/145)
|
||||
- Add a widget which works like a pinned app shortcut, to support devices which run below Android 7.1. (https://github.com/brarcher/loyalty-card-locker/pull/142)
|
||||
|
||||
## v0.13 - 14 (2017-07-25)
|
||||
## v0.13 (2017-07-25)
|
||||
|
||||
- Add screen rotation lock menu option when displaying a card. If locked, the screen will transition to its "natural" orientation and further screen rotation will be blocked. ([pull #128](https://github.com/brarcher/loyalty-card-locker/pull/128))
|
||||
- If a card is selected from the main screen but cannot be loaded, the application fails gracefully and posts a message. ([pull #132](https://github.com/brarcher/loyalty-card-locker/pull/132))
|
||||
- Fix case where layout IDs for intro wizard could not be found. ([pull #128](https://github.com/brarcher/loyalty-card-locker/pull/128))
|
||||
Changes:
|
||||
- Add screen rotation lock menu option when displaying a card. If locked, the screen will transition to its "natural" orientation and further screen rotation will be blocked. (https://github.com/brarcher/loyalty-card-locker/pull/128)
|
||||
- If a card is selected from the main screen but cannot be loaded, the application fails gracefully and posts a message. (https://github.com/brarcher/loyalty-card-locker/pull/132)
|
||||
- Fix case where layout IDs for intro wizard could not be found. (https://github.com/brarcher/loyalty-card-locker/pull/128)
|
||||
|
||||
## v0.12 - 13 (2017-07-16)
|
||||
## v0.12 (2017-07-16)
|
||||
|
||||
- A change in v0.11 reduced the memory usage of barcode drawing, but affected the barcode dimensions. This is now changed to maintain the barcode dimensions while reducing memory usage. ([pull #126](https://github.com/brarcher/loyalty-card-locker/pull/126))
|
||||
- Update German and French translations. ([pull #122](https://github.com/brarcher/loyalty-card-locker/pull/122), [pull #124](https://github.com/brarcher/loyalty-card-locker/pull/124), [pull #125](https://github.com/brarcher/loyalty-card-locker/pull/125))
|
||||
Changes:
|
||||
|
||||
## v0.11.1 - 12 (2017-06-29)
|
||||
- A change in v0.11 reduced the memory usage of barcode drawing, but affected the barcode dimensions. This is now changed to maintain the barcode dimensions while reducing memory usage. (https://github.com/brarcher/loyalty-card-locker/pull/126)
|
||||
- Update German and French translations. (https://github.com/brarcher/loyalty-card-locker/pull/122, https://github.com/brarcher/loyalty-card-locker/pull/124, https://github.com/brarcher/loyalty-card-locker/pull/125)
|
||||
|
||||
## v0.11.1 (2017-06-29)
|
||||
|
||||
Changes:
|
||||
- Prevent a crash when rotation the screen in the first run intro wizard.
|
||||
|
||||
## v0.11 - 11 (2017-06-26)
|
||||
## v0.11 (2017-06-26)
|
||||
|
||||
- When editing a card ID, pre-populate the existing ID to start. ([pull #94](https://github.com/brarcher/loyalty-card-locker/pull/94))
|
||||
- Limit the width of generated barcodes to reduce memory usage and out of memory errors. ([pull #103](https://github.com/brarcher/loyalty-card-locker/pull/103))
|
||||
- When editing a card, change the "Enter Card" button to say "Edit Card" if a card ID already exists. ([pull #104](https://github.com/brarcher/loyalty-card-locker/pull/104))
|
||||
- Change the color scheme to be softer and compatible with the app icon, and change the layout when viewing a card to be cleaner. ([pull #107](https://github.com/brarcher/loyalty-card-locker/pull/107))
|
||||
- Add an intro wizard which launches on the app's first launch. ([pull #108](https://github.com/brarcher/loyalty-card-locker/pull/108))
|
||||
Improvements:
|
||||
- When editing a card ID, pre-populate the existing ID to start. (https://github.com/brarcher/loyalty-card-locker/pull/94)
|
||||
- Limit the width of generated barcodes to reduce memory usage and out of memory errors. (https://github.com/brarcher/loyalty-card-locker/pull/103)
|
||||
- When editing a card, change the "Enter Card" button to say "Edit Card" if a card ID already exists. (https://github.com/brarcher/loyalty-card-locker/pull/104)
|
||||
- Change the color scheme to be softer and compatible with the app icon, and change the layout when viewing a card to be cleaner. (https://github.com/brarcher/loyalty-card-locker/pull/107)
|
||||
- Add an intro wizard which launches on the app's first launch. (https://github.com/brarcher/loyalty-card-locker/pull/108)
|
||||
|
||||
## v0.10 - 10 (2017-02-12)
|
||||
## v0.10 (2017-02-12)
|
||||
|
||||
- Changed the default import/export filename. ([pull #84](https://github.com/brarcher/loyalty-card-locker/pull/84))
|
||||
- Correct string on the import/export page. ([pull #87](https://github.com/brarcher/loyalty-card-locker/pull/87))
|
||||
- Improve layout of card view page. The text should be easier to read, and is selectable with a long click. ([pull #91](https://github.com/brarcher/loyalty-card-locker/pull/91))
|
||||
Improvements:
|
||||
- Changed the default import/export filename. (https://github.com/brarcher/loyalty-card-locker/pull/84)
|
||||
- Correct string on the import/export page. (https://github.com/brarcher/loyalty-card-locker/pull/87)
|
||||
- Improve layout of card view page. The text should be easier to read, and is selectable with a long click. (https://github.com/brarcher/loyalty-card-locker/pull/91)
|
||||
|
||||
## v0.9 - 9 (2017-01-17)
|
||||
## v0.9 (2017-01-17)
|
||||
|
||||
The "Locker" part of the name was not intuitive. To help remedy this a new application icon was created by betsythefc which better represents the purpose of the application: to store loyalty cards which use barcodes. Along with this new icon the name of the application has been changed to "Loyalty Card Keychain".
|
||||
|
||||
Additional features/improvements:
|
||||
|
||||
- Importing/Exporting cards was changed to be more flexible. ([pull #76](https://github.com/brarcher/loyalty-card-locker/pull/76))
|
||||
- Translations for Lithuanian added. ([pull #62](https://github.com/brarcher/loyalty-card-locker/pull/62))
|
||||
- Translations for French added. ([pull #80](https://github.com/brarcher/loyalty-card-locker/pull/80))
|
||||
- Importing/Exporting cards was changed to be more flexible. (https://github.com/brarcher/loyalty-card-locker/pull/76)
|
||||
- Translations for Lithuanian added. (https://github.com/brarcher/loyalty-card-locker/pull/62)
|
||||
- Translations for French added. (https://github.com/brarcher/loyalty-card-locker/pull/80)
|
||||
|
||||
## v0.8 - 8 (2016-11-22)
|
||||
## v0.8 (2016-11-22)
|
||||
|
||||
- Screen brightness increased to its maximum when displaying a card, to help barcode scanners successfully capture the barcode. ([pull #54](https://github.com/brarcher/loyalty-card-locker/pull/54))
|
||||
- Add a delete confirmation when deleting a card. ([pull #55](https://github.com/brarcher/loyalty-card-locker/pull/55))
|
||||
- Add translations for German ([pull #57](https://github.com/brarcher/loyalty-card-locker/pull/57)) and Czech ([pull #58](https://github.com/brarcher/loyalty-card-locker/pull/58)).
|
||||
- Clarification change for Italian translation. ([pull #66](https://github.com/brarcher/loyalty-card-locker/pull/66))
|
||||
New features/improvements:
|
||||
- Screen brightness increased to its maximum when displaying a card, to help barcode scanners successfully capture the barcode. (https://github.com/brarcher/loyalty-card-locker/pull/54)
|
||||
- Add a delete confirmation when deleting a card. (https://github.com/brarcher/loyalty-card-locker/pull/55)
|
||||
- Add translations for German (https://github.com/brarcher/loyalty-card-locker/pull/57) and Czech (https://github.com/brarcher/loyalty-card-locker/pull/58).
|
||||
- Clarification change for Italian translation. (https://github.com/brarcher/loyalty-card-locker/pull/66)
|
||||
|
||||
## v0.7 - 7 (2016-07-14)
|
||||
## v0.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
|
||||
New features/improvements:
|
||||
- Long-click of a card brings up option to copy card ID to the clipboard. (https://github.com/brarcher/loyalty-card-locker/issues/49)
|
||||
|
||||
## v0.6 - 6 (2016-05-23)
|
||||
Bug fixes:
|
||||
- Back button on Input/Export view now works, moving user to main view
|
||||
|
||||
- Allow user to enter barcode manually. If a user elects to enter a barcode manually, a list of all valid and supported barcode images is displayed. The user then may select the barcode image which matches what the user wants. [issue #33](https://github.com/brarcher/loyalty-card-locker/issues/33), [pull #44](https://github.com/brarcher/loyalty-card-locker/pull/44)
|
||||
- Resolve issue where some displayed barcodes were blurry. ([issue #37](https://github.com/brarcher/loyalty-card-locker/issues/37))
|
||||
## v0.6 (2016-05-23)
|
||||
|
||||
## v0.5 - 5 (2016-05-16)
|
||||
New features/improvements:
|
||||
- Allow user to enter barcode manually. If a user elects to enter a barcode manually, a list of all valid and supported barcode images is displayed. The user then may select the barcode image which matches what the user wants. https://github.com/brarcher/loyalty-card-locker/issues/33, https://github.com/brarcher/loyalty-card-locker/pull/44
|
||||
|
||||
- An about dialog can be opened from the main screen, which gives details about the application and project on GitHub ([issue #19](https://github.com/brarcher/loyalty-card-locker/issues/19))
|
||||
- Allow loyalty card information to be imported from/exported to a CSV file in external storage ([issue #36](https://github.com/brarcher/loyalty-card-locker/issues/36), [issue #20](https://github.com/brarcher/loyalty-card-locker/issues/20))
|
||||
Bug fixes:
|
||||
- Resolve issue where some displayed barcodes were blurry. (https://github.com/brarcher/loyalty-card-locker/issues/37)
|
||||
|
||||
## v0.4 - 4 (2016-04-09)
|
||||
## v0.5 (2016-05-16)
|
||||
|
||||
New features/improvements:
|
||||
- An about dialog can be opened from the main screen, which gives details about the application and project on GitHub (https://github.com/brarcher/loyalty-card-locker/issues/19)
|
||||
- Allow loyalty card information to be imported from/exported to a CSV file in external storage (https://github.com/brarcher/loyalty-card-locker/issues/36 https://github.com/brarcher/loyalty-card-locker/issues/20)
|
||||
|
||||
## v0.4 (2016-04-09)
|
||||
|
||||
New features/improvements:
|
||||
- Dutch translation
|
||||
- Allow name field to be editable after adding loyalty card
|
||||
- Add an optional note field
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- Resolve all issues identified by FindBugs and require all FindBugs issues be resolved prior to pull request acceptance
|
||||
|
||||
## v0.3 - 3 (2016-02-11)
|
||||
## v0.3 (2016-02-11)
|
||||
|
||||
- Now officially supports the following list of 1D and 2D barcodes:
|
||||
- AZTEC
|
||||
- CODABAR
|
||||
- CODE_39
|
||||
- CODE_128
|
||||
- DATA_MATRIX
|
||||
- EAN_8
|
||||
- EAN_13
|
||||
- ITF
|
||||
- PDF_417
|
||||
- QR_CODE
|
||||
- UPC_A
|
||||
|
||||
* AZTEC
|
||||
* CODABAR
|
||||
* CODE_39
|
||||
* CODE_128
|
||||
* DATA_MATRIX
|
||||
* EAN_8
|
||||
* EAN_13
|
||||
* ITF
|
||||
* PDF_417
|
||||
* QR_CODE
|
||||
* UPC_A
|
||||
- Generated barcodes are larger, easier to scan from a scanning device
|
||||
|
||||
## v0.2 - 2 (2016-02-07)
|
||||
## v0.2 (2016-02-07)
|
||||
|
||||
- Italian translations
|
||||
- Support for all 1D barcode types. (Originally only product 1D barcodes were supported)
|
||||
- Add required camera permission, which was initially missing.
|
||||
|
||||
## v0.1 - 1 (2016-01-30)
|
||||
|
||||
## v0.1 (2016-01-30)
|
||||
|
||||
- Ability to create/edit/delete loyalty cards
|
||||
- Capture barcode of loyalty card using a camera
|
||||
|
||||
@@ -1,29 +1,14 @@
|
||||
# How to Submit Patches to the Catima Project
|
||||
How to Submit Patches to the Loyalty Card Keychain Project
|
||||
===============================================================================
|
||||
https://github.com/brarcher/budget-watch
|
||||
|
||||
This document is intended to act as a guide to help you contribute to the
|
||||
Catima project. It is not perfect, and there will always be exceptions
|
||||
Loyalty Card Keychain project. It is not perfect, and there will always be exceptions
|
||||
to the rules described here, but by following the instructions below you
|
||||
should have a much easier time getting your work merged with the upstream
|
||||
project.
|
||||
|
||||
When contributing, you certify that you agree to and have the rights to submit
|
||||
your contribution under the project's license and understand that git will
|
||||
store your name and email address in project history indefinitely.
|
||||
|
||||
## Translation Changes
|
||||
|
||||
Translation changes are managed through [Weblate](https://hosted.weblate.org/projects/catima/).
|
||||
Please do not supply translation updates directly through GitHub.
|
||||
|
||||
Weblate requires an account to translate changes, so please log in before
|
||||
you start translating.
|
||||
|
||||
While using Weblate, please do not ignore any of its warnings. They exist
|
||||
for good reason.
|
||||
|
||||
## Code Changes
|
||||
|
||||
### Test Your Code
|
||||
## Test Your Code
|
||||
|
||||
There are four possible tests you can run to verify your code. The first
|
||||
is unit tests, which check the basic functionality of the application, and
|
||||
@@ -36,21 +21,21 @@ These are the Android lint checker, run using:
|
||||
|
||||
# ./gradlew lintRelease
|
||||
|
||||
and SpotBugs, run using:
|
||||
and FindBugs, run using:
|
||||
|
||||
# ./gradlew spotbugsRelease
|
||||
# ./gradlew findbugs
|
||||
|
||||
The final check is by testing the application on a live device and verifying
|
||||
the basic functionality works as expected.
|
||||
|
||||
### Make Sure Your Code is Tested
|
||||
## Make Sure Your Code is Tested
|
||||
|
||||
The Catima code uses a fair number of unit tests to verify that
|
||||
The Loyalty Card Keychain code uses a fair number of unit tests to verify that
|
||||
the basic functionality is working. Submissions which add functionality
|
||||
or significantly change the existing code should include additional tests
|
||||
to verify the proper operation of the proposed changes.
|
||||
|
||||
### Explain Your Work
|
||||
## Explain Your Work
|
||||
|
||||
At the top of every patch you should include a description of the problem you
|
||||
are trying to solve, how you solved it, and why you chose the solution you
|
||||
@@ -59,10 +44,48 @@ if you can describe/include a reproducer for the problem in the description as
|
||||
well as instructions on how to test for the bug and verify that it has been
|
||||
fixed.
|
||||
|
||||
### Submit Patch(es) for Review
|
||||
## Sign Your Work
|
||||
|
||||
The sign-off is a simple line at the end of the patch description, which
|
||||
certifies that you wrote it or otherwise have the right to pass it on as an
|
||||
open-source patch. The "Developer's Certificate of Origin" pledge is taken
|
||||
from the Linux Kernel and the rules are pretty simple:
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
|
||||
... then you just add a line to the bottom of your patch description, with
|
||||
your real name, saying:
|
||||
|
||||
Signed-off-by: Random J Developer <random@developer.example.org>
|
||||
|
||||
## Submit Patch(es) for Review
|
||||
|
||||
Finally, you will need to submit your patches so that they can be reviewed
|
||||
and potentially merged into the main 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
|
||||
and potentially merged into the main Loyalty Card Keychain repository. The preferred
|
||||
way to do this is to submit a Pull Request to the Loyalty Card Keychain project.
|
||||
Changes need to apply cleanly onto the master branch and pass all
|
||||
unit tests and produce no errors during static analysis.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
github: TheLastProject
|
||||
custom:
|
||||
- "https://paypal.me/sylviavanos"
|
||||
217
Gemfile.lock
217
Gemfile.lock
@@ -1,217 +0,0 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.6)
|
||||
rexml
|
||||
addressable (2.8.5)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.824.0)
|
||||
aws-sdk-core (3.181.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.71.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.134.0)
|
||||
aws-sdk-core (~> 3, >= 3.181.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.6)
|
||||
aws-sigv4 (1.6.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.103.0)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.7)
|
||||
fastlane (2.215.1)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
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)
|
||||
naturally (~> 2.2)
|
||||
optparse (~> 0.1.1)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (~> 3)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.49.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
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-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.3.1)
|
||||
google-cloud-storage (1.44.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.19.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.8.0)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.6.2)
|
||||
json (2.6.3)
|
||||
jwt (2.7.1)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.3.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.3)
|
||||
rake (13.0.6)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.6)
|
||||
rouge (2.0.7)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.18.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (2.4.2)
|
||||
webrick (1.8.1)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.22.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.26
|
||||
18
PRIVACY.md
18
PRIVACY.md
@@ -1,18 +0,0 @@
|
||||
**Last updated**
|
||||
August 30 2023
|
||||
|
||||
# Privacy Policy
|
||||
Catima does not collect or transmit any personal information.
|
||||
|
||||
To ensure correct app functionality, we require access to the following:
|
||||
|
||||
- Camera: We need access to your camera to be able to scan barcodes. The app can still be used when camera access is denied, but you will have to manually type the barcode information.
|
||||
- Storage (Android 5 and 6 only): We need access to your device storage to create or import backups. The app can still be used when storage access is denied, but you will not be able to create or import backups.
|
||||
|
||||
Catima offers a feature to share cards with other users. All the relevant data is in the generated shareable URLs and never transmitted to our servers. When viewed through catima.app, the data in the URL is rendered using client-side Javascript to further ensure no data is ever transmitted to us.
|
||||
|
||||
# Changes
|
||||
This Privacy Policy may be updated from time to time for any reason. We will notify you of any changes to our Privacy Policy by posting the new Privacy Policy to https://catima.app/privacy-policy/. A snapshot of the Privacy Policy is available within the Catima app, though it may be outdated. When the Privacy Policy on the website and in the app differ, the website should be considered leading. You are advised to consult the Privacy Policy regularly for any changes, as continued use is deemed approval of all changes.
|
||||
|
||||
# Contact us
|
||||
If you have any questions regarding privacy while using the Application, or have questions about our practices, please contact us via email at catima.g9ex3@hackerchick.me.
|
||||
13
SECURITY.md
13
SECURITY.md
@@ -1,13 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
Catima is designed to use as little permissions as possible to limit both the attack surface as well as the damage that can be done when abusing a security flaw.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the most recent stable release is supported.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Security vulnerabilities can be reported through [GitHub Security Advisories](https://github.com/CatimaLoyalty/Android/security/advisories) or [the contact info written on my personal website](https://sylviavanos.nl/#contact). Currently, Matrix is the only end-to-end encrypted option.
|
||||
|
||||
Please note that only security vulnerabilities in Catima should be reported as stated above. For other issues, including antivirus false positives and malicious applications trying to trick people into granting them Catima's "Read Cards" permission, please use [regular issues](https://github.com/CatimaLoyalty/Android/issues).
|
||||
1
app/.gitignore
vendored
Normal file
1
app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
76
app/build.gradle
Normal file
76
app/build.gradle
Normal file
@@ -0,0 +1,76 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'findbugs'
|
||||
|
||||
findbugs {
|
||||
sourceSets = []
|
||||
ignoreFailures = false
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
applicationId "protect.card_locker"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode 38
|
||||
versionName "0.27"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
disable "GoogleAppIndexingWarning"
|
||||
disable "ButtonStyle"
|
||||
disable "AlwaysShowAction"
|
||||
disable "MissingTranslation"
|
||||
disable "MissingPrefix"
|
||||
}
|
||||
|
||||
// Starting with Android Studio 3 Robolectric is unable to find resources.
|
||||
// The following allows it to find the resources.
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'androidx.appcompat:appcompat:1.2.0-alpha01'
|
||||
compile 'com.google.android.material:material:1.2.0-alpha03'
|
||||
compile 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
compile 'com.journeyapps:zxing-android-embedded:3.5.0@aar'
|
||||
compile 'com.google.zxing:core:3.3.0'
|
||||
compile 'org.apache.commons:commons-csv:1.5'
|
||||
compile 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
compile 'com.jaredrummler:colorpicker:1.0.2'
|
||||
compile group: 'com.google.guava', name: 'guava', version: '20.0'
|
||||
compile 'com.github.apl-devs:appintro:v4.2.0'
|
||||
compile "com.vanniktech:vntnumberpickerpreference:1.0.0"
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile "org.robolectric:robolectric:4.0.2"
|
||||
}
|
||||
|
||||
task findbugs(type: FindBugs, dependsOn: 'assembleDebug') {
|
||||
|
||||
description 'Run findbugs'
|
||||
group 'verification'
|
||||
|
||||
classes = fileTree('build/intermediates/javac/debug/compileDebugJavaWithJavac/classes')
|
||||
source = fileTree('src/main/java')
|
||||
classpath = files()
|
||||
|
||||
effort = 'max'
|
||||
|
||||
excludeFilter = file("./config/findbugs/exclude.xml")
|
||||
|
||||
reports {
|
||||
xml.enabled = false
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
||||
import com.github.spotbugs.snom.SpotBugsTask
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("com.github.spotbugs")
|
||||
}
|
||||
|
||||
spotbugs {
|
||||
ignoreFailures.set(false)
|
||||
setEffort("max")
|
||||
excludeFilter.set(file("./config/spotbugs/exclude.xml"))
|
||||
reportsDir.set(layout.buildDirectory.file("reports/spotbugs/").get().asFile)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "protect.card_locker"
|
||||
compileSdk = 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "me.hackerchick.catima"
|
||||
minSdk = 21
|
||||
targetSdk = 33
|
||||
versionCode = 132
|
||||
versionName = "2.27.0"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
multiDexEnabled = true
|
||||
|
||||
resourceConfigurations += listOf("ar", "bg", "bn", "bn-rIN", "bs", "cs", "da", "de", "el-rGR", "en", "eo", "es", "es-rAR", "fi", "fr", "he-rIL", "hi", "hr", "hu", "in-rID", "is", "it", "ja", "ko", "lt", "lv", "nb-rNO", "nl", "oc", "pl", "pt-rPT", "ro-rRO", "ru", "sk", "sl", "sv", "tr", "uk", "vi", "zh-rCN", "zh-rTW")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
encoding = "UTF-8"
|
||||
|
||||
// Flag to enable support for the new language APIs
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("test") {
|
||||
resources.srcDirs("src/test/res")
|
||||
}
|
||||
}
|
||||
|
||||
// Starting with Android Studio 3 Robolectric is unable to find resources.
|
||||
// The following allows it to find the resources.
|
||||
testOptions.unitTests.isIncludeAndroidResources = true
|
||||
tasks.withType<Test>().configureEach {
|
||||
testLogging {
|
||||
events("started", "passed", "skipped", "failed")
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
lintConfig = file("lint.xml")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// AndroidX
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.exifinterface:exifinterface:1.3.7")
|
||||
implementation("androidx.palette:palette:1.0.0")
|
||||
implementation("androidx.preference:preference:1.2.1")
|
||||
implementation("com.google.android.material:material:1.9.0")
|
||||
implementation("com.github.yalantis:ucrop:2.2.8")
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
|
||||
|
||||
// Splash Screen
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
|
||||
// Third-party
|
||||
implementation("com.journeyapps:zxing-android-embedded:4.3.0@aar")
|
||||
implementation("com.google.zxing:core:3.5.2")
|
||||
implementation("org.apache.commons:commons-csv:1.9.0")
|
||||
implementation("com.jaredrummler:colorpicker:1.1.0")
|
||||
implementation("net.lingala.zip4j:zip4j:2.11.5")
|
||||
|
||||
// SpotBugs
|
||||
implementation("io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0")
|
||||
|
||||
// Testing
|
||||
testImplementation("androidx.test:core:1.5.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("org.robolectric:robolectric:4.11.1")
|
||||
}
|
||||
|
||||
tasks.withType<SpotBugsTask>().configureEach {
|
||||
description = "Run spotbugs"
|
||||
group = "verification"
|
||||
|
||||
//classes = fileTree("build/intermediates/javac/debug/compileDebugJavaWithJavac/classes")
|
||||
//source = fileTree("src/main/java")
|
||||
//classpath = files()
|
||||
|
||||
reports.maybeCreate("xml").required.set(false)
|
||||
reports.maybeCreate("html").required.set(true)
|
||||
}
|
||||
|
||||
tasks.register("copyRawResFiles", Copy::class) {
|
||||
from(
|
||||
layout.projectDirectory.file("../CHANGELOG.md"),
|
||||
layout.projectDirectory.file("../PRIVACY.md")
|
||||
)
|
||||
into(layout.projectDirectory.dir("src/main/res/raw"))
|
||||
rename { it.lowercase() }
|
||||
}.also {
|
||||
tasks.preBuild.dependsOn(it)
|
||||
tasks.getByName<Delete>("clean") {
|
||||
val filesNamesToDelete = listOf("CHANGELOG", "PRIVACY")
|
||||
filesNamesToDelete.forEach { fileName ->
|
||||
delete(layout.projectDirectory.file("src/main/res/raw/${fileName.lowercase()}.md"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,5 @@
|
||||
<Match>
|
||||
<Class name="~.*Manifest\$.*"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<Class name="~.*Binding" />
|
||||
</Match>
|
||||
|
||||
</FindBugsFilter>
|
||||
@@ -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>
|
||||
2
app/proguard-rules.pro
vendored
2
app/proguard-rules.pro
vendored
@@ -2,7 +2,7 @@
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/brarcher/Library/Android/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.kts.
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application>
|
||||
{
|
||||
public ApplicationTest()
|
||||
{
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Depuración de Catima</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Débogage de Catima</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">कैटिमा डीबग</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">ಕ್ಯಾಟಿಮಾ ಡೀಬಗ್</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima 디버그</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima-avlusing</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima-foutopsporing</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Depuração Catima</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Depanare Catima</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Отладка Catima</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Hata Ayaklama</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Gỡ lỗi Catima</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima 调试</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,18 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<manifest package="protect.card_locker"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<permission
|
||||
android:description="@string/permissionReadCardsDescription"
|
||||
android:icon="@drawable/ic_launcher_foreground"
|
||||
android:label="@string/permissionReadCardsLabel"
|
||||
android:name="${applicationId}.READ_CARDS"
|
||||
android:protectionLevel="dangerous" />
|
||||
|
||||
<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.CAMERA"/>
|
||||
<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"
|
||||
@@ -24,165 +19,81 @@
|
||||
<application
|
||||
android:name=".LoyaltyCardLockerApplication"
|
||||
android:allowBackup="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:localeConfig="@xml/locales_config">
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.App.Starting">
|
||||
android:name="protect.card_locker.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<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/*" />
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".AboutActivity"
|
||||
android:label="@string/about"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
<activity
|
||||
android:name=".ManageGroupsActivity"
|
||||
android:label="@string/groups"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
<activity
|
||||
android:name=".ManageGroupActivity"
|
||||
android:label="@string/group_edit"
|
||||
android:theme="@style/AppTheme.NoActionBar"/>
|
||||
<activity
|
||||
android:name=".LoyaltyCardViewActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
android:label=""
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:exported="true"/>
|
||||
<activity
|
||||
android:name=".LoyaltyCardEditActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
<intent-filter
|
||||
android:autoVerify="true"
|
||||
android:label="@string/app_name">
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:exported="true">
|
||||
<intent-filter android:label="@string/intent_import_card_from_url">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- Main card sharing URIs -->
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data
|
||||
android:host="@string/intent_import_card_from_url_host_catima_app"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_catima_app" />
|
||||
</intent-filter>
|
||||
<intent-filter android:label="@string/app_name">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- Old card sharing URIs -->
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data
|
||||
android:host="@string/intent_import_card_from_url_host_thelastproject"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_thelastproject" />
|
||||
<data
|
||||
android:host="@string/intent_import_card_from_url_host_brarcher"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_brarcher" />
|
||||
<!-- Accepts URIs that begin with "https://github.com/brarcher/loyalty-card-locker/” -->
|
||||
<data android:scheme="https"
|
||||
android:host="@string/intent_import_card_from_url_host"
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ScanActivity"
|
||||
android:label="@string/scanCardBarcode"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
<activity
|
||||
android:name=".BarcodeSelectorActivity"
|
||||
android:label="@string/selectBarcodeTitle"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:windowSoftInputMode="stateHidden"/>
|
||||
<activity
|
||||
android:name=".preferences.SettingsActivity"
|
||||
android:label="@string/settings"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
android:configChanges="orientation|screenSize"/>
|
||||
<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:configChanges="orientation|screenSize"
|
||||
android:theme="@style/AppTheme.NoActionBar"/>
|
||||
<activity
|
||||
android:name=".intro.IntroActivity"
|
||||
android:label=""
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:theme="@style/AppTheme.NoActionBar"/>
|
||||
<activity
|
||||
android:name=".CardShortcutConfigure"
|
||||
android:exported="true"
|
||||
android:label="@string/cardShortcut"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.CREATE_SHORTCUT" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<action android:name="android.intent.action.CREATE_SHORTCUT"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".UCropWrapper"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
|
||||
<provider
|
||||
android:name=".contentprovider.CardsContentProvider"
|
||||
android:authorities="${applicationId}.contentprovider.cards"
|
||||
android:exported="true"
|
||||
android:readPermission="${applicationId}.READ_CARDS"/>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
android:authorities="${applicationId}">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_provider_paths" />
|
||||
android:resource="@xml/file_provider_paths"/>
|
||||
</provider>
|
||||
<service android:name=".CardsOnPowerScreenService" android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_CONTROLS" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.controls.ControlsProviderService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,148 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Spanned;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
|
||||
import protect.card_locker.databinding.AboutActivityBinding;
|
||||
|
||||
public class AboutActivity extends CatimaAppCompatActivity {
|
||||
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private AboutActivityBinding binding;
|
||||
private AboutContent content;
|
||||
|
||||
@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();
|
||||
|
||||
TextView copyright = binding.creditsSub;
|
||||
copyright.setText(content.getCopyrightShort());
|
||||
TextView versionHistory = binding.versionHistorySub;
|
||||
versionHistory.setText(content.getVersionHistory());
|
||||
|
||||
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/donate");
|
||||
|
||||
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);
|
||||
|
||||
bindClickListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
finish();
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
content.destroy();
|
||||
clearClickListeners();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
private void bindClickListeners() {
|
||||
binding.versionHistory.setOnClickListener(this::showHistory);
|
||||
binding.translate.setOnClickListener(this::openExternalBrowser);
|
||||
binding.license.setOnClickListener(this::showLicense);
|
||||
binding.repo.setOnClickListener(this::openExternalBrowser);
|
||||
binding.privacy.setOnClickListener(this::showPrivacy);
|
||||
binding.reportError.setOnClickListener(this::openExternalBrowser);
|
||||
binding.rate.setOnClickListener(this::openExternalBrowser);
|
||||
binding.donate.setOnClickListener(this::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() {
|
||||
showHTML(R.string.credits, content.getContributorInfo(), null);
|
||||
}
|
||||
|
||||
private void showHistory(View view) {
|
||||
showHTML(R.string.version_history, content.getHistoryInfo(), view);
|
||||
}
|
||||
|
||||
private void showLicense(View view) {
|
||||
showHTML(R.string.license, content.getLicenseInfo(), view);
|
||||
}
|
||||
|
||||
private void showPrivacy(View view) {
|
||||
showHTML(R.string.privacy_policy, content.getPrivacyInfo(), view);
|
||||
}
|
||||
|
||||
private void showHTML(@StringRes int title, final Spanned text, @Nullable View view) {
|
||||
int dialogContentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
|
||||
TextView textView = new TextView(this);
|
||||
textView.setText(text);
|
||||
Utils.makeTextViewLinksClickable(textView, text);
|
||||
ScrollView scrollView = new ScrollView(this);
|
||||
scrollView.addView(textView);
|
||||
scrollView.setPadding(dialogContentPadding, dialogContentPadding / 2, dialogContentPadding, 0);
|
||||
|
||||
// Create dialog
|
||||
MaterialAlertDialogBuilder materialAlertDialogBuilder = new MaterialAlertDialogBuilder(this);
|
||||
materialAlertDialogBuilder
|
||||
.setTitle(title)
|
||||
.setView(scrollView)
|
||||
.setPositiveButton(R.string.ok, null);
|
||||
|
||||
// Add View online button if an URL is linked to this view
|
||||
if (view != null && view.getTag() != null) {
|
||||
materialAlertDialogBuilder.setNeutralButton(R.string.view_online, (dialog, which) -> openExternalBrowser(view));
|
||||
}
|
||||
|
||||
// Show dialog
|
||||
materialAlertDialogBuilder.show();
|
||||
}
|
||||
|
||||
private void openExternalBrowser(View view) {
|
||||
Object tag = view.getTag();
|
||||
if (tag instanceof String && ((String) tag).startsWith("https://")) {
|
||||
(new OpenWebLinkHandler()).openBrowser(this, (String) tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.text.Spanned;
|
||||
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 getCopyrightShort() {
|
||||
return context.getString(R.string.app_copyright_short);
|
||||
}
|
||||
|
||||
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 getHistory() {
|
||||
String versionHistory;
|
||||
try {
|
||||
versionHistory = Utils.readTextFile(context, R.raw.changelog)
|
||||
.replace("# Changelog\n\n", "");
|
||||
} catch (IOException ignored) {
|
||||
return "";
|
||||
}
|
||||
return Utils.linkify(Utils.basicMDToHTML(versionHistory))
|
||||
.replace("\n", "<br />");
|
||||
}
|
||||
|
||||
public String getLicense() {
|
||||
try {
|
||||
return Utils.readTextFile(context, R.raw.license);
|
||||
} catch (IOException ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public String getPrivacy() {
|
||||
String privacyPolicy;
|
||||
try {
|
||||
privacyPolicy = Utils.readTextFile(context, R.raw.privacy)
|
||||
.replace("# Privacy Policy\n", "");
|
||||
} catch (IOException ignored) {
|
||||
return "";
|
||||
}
|
||||
return Utils.linkify(Utils.basicMDToHTML(privacyPolicy))
|
||||
.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 Spanned getContributorInfo() {
|
||||
StringBuilder contributorInfo = new StringBuilder();
|
||||
contributorInfo.append(getCopyright());
|
||||
contributorInfo.append("<br/><br/>");
|
||||
contributorInfo.append(context.getString(R.string.app_copyright_old));
|
||||
contributorInfo.append("<br/><br/>");
|
||||
contributorInfo.append(String.format(context.getString(R.string.app_contributors), getContributors()));
|
||||
contributorInfo.append("<br/><br/>");
|
||||
contributorInfo.append(String.format(context.getString(R.string.app_libraries), getThirdPartyLibraries()));
|
||||
contributorInfo.append("<br/><br/>");
|
||||
contributorInfo.append(String.format(context.getString(R.string.app_resources), getUsedThirdPartyAssets()));
|
||||
|
||||
return HtmlCompat.fromHtml(contributorInfo.toString(), HtmlCompat.FROM_HTML_MODE_COMPACT);
|
||||
}
|
||||
|
||||
public Spanned getHistoryInfo() {
|
||||
return HtmlCompat.fromHtml(getHistory(), HtmlCompat.FROM_HTML_MODE_COMPACT);
|
||||
}
|
||||
|
||||
public Spanned getLicenseInfo() {
|
||||
return HtmlCompat.fromHtml(getLicense(), HtmlCompat.FROM_HTML_MODE_LEGACY);
|
||||
}
|
||||
|
||||
public Spanned getPrivacyInfo() {
|
||||
return HtmlCompat.fromHtml(getPrivacy(), HtmlCompat.FROM_HTML_MODE_COMPACT);
|
||||
}
|
||||
|
||||
public String getVersionHistory() {
|
||||
return String.format(context.getString(R.string.debug_version_fmt), getAppVersion());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
public interface BarcodeImageWriterResultCallback {
|
||||
void onBarcodeImageWriterResult(boolean success);
|
||||
}
|
||||
@@ -1,34 +1,26 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import protect.card_locker.async.CompatCallable;
|
||||
|
||||
/**
|
||||
* This task will generate a barcode and load it into an ImageView.
|
||||
* Only a weak reference of the ImageView is kept, so this class will not
|
||||
* prevent the ImageView from being garbage collected.
|
||||
*/
|
||||
public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private static final int IS_VALID = 999;
|
||||
private final Context mContext;
|
||||
private boolean isSuccesful;
|
||||
class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
|
||||
// When drawn in a smaller window 1D barcodes for some reason end up
|
||||
// squished, whereas 2D barcodes look fine.
|
||||
@@ -36,82 +28,48 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
|
||||
private static final int MAX_WIDTH_2D = 500;
|
||||
|
||||
private final WeakReference<ImageView> imageViewReference;
|
||||
private final WeakReference<TextView> textViewReference;
|
||||
private String cardId;
|
||||
private final CatimaBarcode format;
|
||||
private final String cardId;
|
||||
private final BarcodeFormat format;
|
||||
private final int imageHeight;
|
||||
private final int imageWidth;
|
||||
private final int imagePadding;
|
||||
private final boolean widthPadding;
|
||||
private final boolean showFallback;
|
||||
private final BarcodeImageWriterResultCallback callback;
|
||||
|
||||
BarcodeImageWriterTask(
|
||||
Context context, ImageView imageView, String cardIdString,
|
||||
CatimaBarcode barcodeFormat, TextView textView,
|
||||
boolean showFallback, BarcodeImageWriterResultCallback callback, boolean roundCornerPadding
|
||||
) {
|
||||
mContext = context;
|
||||
|
||||
isSuccesful = true;
|
||||
this.callback = callback;
|
||||
|
||||
BarcodeImageWriterTask(ImageView imageView, String cardIdString,
|
||||
BarcodeFormat barcodeFormat)
|
||||
{
|
||||
// Use a WeakReference to ensure the ImageView can be garbage collected
|
||||
imageViewReference = new WeakReference<>(imageView);
|
||||
textViewReference = new WeakReference<>(textView);
|
||||
|
||||
cardId = cardIdString;
|
||||
format = barcodeFormat;
|
||||
|
||||
int imageViewHeight = imageView.getHeight();
|
||||
int imageViewWidth = imageView.getWidth();
|
||||
|
||||
// Some barcodes already have internal whitespace and shouldn't get extra padding
|
||||
// TODO: Get rid of this hack by somehow detecting this extra whitespace
|
||||
if (roundCornerPadding && !barcodeFormat.hasInternalPadding()) {
|
||||
imagePadding = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, context.getResources().getDisplayMetrics()));
|
||||
} else {
|
||||
imagePadding = 0;
|
||||
}
|
||||
|
||||
if (format.isSquare() && imageViewWidth > imageViewHeight) {
|
||||
imageViewWidth -= imagePadding;
|
||||
widthPadding = true;
|
||||
} else {
|
||||
imageViewHeight -= imagePadding;
|
||||
widthPadding = false;
|
||||
}
|
||||
|
||||
final int MAX_WIDTH = getMaxWidth(format);
|
||||
|
||||
if (format.isSquare()) {
|
||||
imageHeight = imageWidth = Math.min(imageViewHeight, Math.min(MAX_WIDTH, imageViewWidth));
|
||||
} else if (imageView.getWidth() < MAX_WIDTH) {
|
||||
imageHeight = imageViewHeight;
|
||||
imageWidth = imageViewWidth;
|
||||
} else {
|
||||
if(imageView.getWidth() < MAX_WIDTH)
|
||||
{
|
||||
imageHeight = imageView.getHeight();
|
||||
imageWidth = imageView.getWidth();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scale down the image to reduce the memory needed to produce it
|
||||
imageWidth = MAX_WIDTH;
|
||||
double ratio = (double) MAX_WIDTH / (double) imageViewWidth;
|
||||
imageHeight = (int) (imageViewHeight * ratio);
|
||||
double ratio = (double)MAX_WIDTH / (double)imageView.getWidth();
|
||||
imageHeight = (int)(imageView.getHeight() * ratio);
|
||||
}
|
||||
|
||||
this.showFallback = showFallback;
|
||||
}
|
||||
|
||||
private int getMaxWidth(CatimaBarcode format) {
|
||||
switch (format.format()) {
|
||||
private int getMaxWidth(BarcodeFormat format)
|
||||
{
|
||||
switch(format)
|
||||
{
|
||||
// 2D barcodes
|
||||
case AZTEC:
|
||||
case DATA_MATRIX:
|
||||
case MAXICODE:
|
||||
case PDF_417:
|
||||
case QR_CODE:
|
||||
return MAX_WIDTH_2D;
|
||||
|
||||
// 2D but rectangular versions get blurry otherwise
|
||||
case DATA_MATRIX:
|
||||
return MAX_WIDTH_1D;
|
||||
|
||||
// 1D barcodes:
|
||||
case CODABAR:
|
||||
case CODE_39:
|
||||
@@ -130,53 +88,23 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
|
||||
}
|
||||
}
|
||||
|
||||
private String getFallbackString(CatimaBarcode format) {
|
||||
switch (format.format()) {
|
||||
// 2D barcodes
|
||||
case AZTEC:
|
||||
return "AZTEC";
|
||||
case DATA_MATRIX:
|
||||
return "DATA_MATRIX";
|
||||
case PDF_417:
|
||||
return "PDF_417";
|
||||
case QR_CODE:
|
||||
return "QR_CODE";
|
||||
|
||||
// 1D barcodes:
|
||||
case CODABAR:
|
||||
return "C0C";
|
||||
case CODE_39:
|
||||
return "CODE_39";
|
||||
case CODE_93:
|
||||
return "CODE_93";
|
||||
case CODE_128:
|
||||
return "CODE_128";
|
||||
case EAN_8:
|
||||
return "32123456";
|
||||
case EAN_13:
|
||||
return "5901234123457";
|
||||
case ITF:
|
||||
return "1003";
|
||||
case UPC_A:
|
||||
return "123456789012";
|
||||
case UPC_E:
|
||||
return "0123456";
|
||||
default:
|
||||
throw new IllegalArgumentException("No fallback known for this barcode type");
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap generate() {
|
||||
if (cardId.isEmpty()) {
|
||||
public Bitmap doInBackground(Void... params)
|
||||
{
|
||||
if (cardId.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
MultiFormatWriter writer = new MultiFormatWriter();
|
||||
BitMatrix bitMatrix;
|
||||
try {
|
||||
try {
|
||||
bitMatrix = writer.encode(cardId, format.format(), imageWidth, imageHeight, null);
|
||||
} catch (Exception e) {
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
bitMatrix = writer.encode(cardId, format, imageWidth, imageHeight, null);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
// Cast a wider net here and catch any exception, as there are some
|
||||
// cases where an encoder may fail if the data is invalid for the
|
||||
// barcode type. If this happens, we want to fail gracefully.
|
||||
@@ -191,9 +119,11 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
|
||||
|
||||
int[] pixels = new int[bitMatrixWidth * bitMatrixHeight];
|
||||
|
||||
for (int y = 0; y < bitMatrixHeight; y++) {
|
||||
for (int y = 0; y < bitMatrixHeight; y++)
|
||||
{
|
||||
int offset = y * bitMatrixWidth;
|
||||
for (int x = 0; x < bitMatrixWidth; x++) {
|
||||
for (int x = 0; x < bitMatrixWidth; x++)
|
||||
{
|
||||
int color = bitMatrix.get(x, y) ? BLACK : WHITE;
|
||||
pixels[offset + x] = color;
|
||||
}
|
||||
@@ -213,108 +143,48 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
|
||||
int widthScale = imageWidth / bitMatrixHeight;
|
||||
int scalingFactor = Math.min(heightScale, widthScale);
|
||||
|
||||
if (scalingFactor > 1) {
|
||||
if(scalingFactor > 1)
|
||||
{
|
||||
bitmap = Bitmap.createScaledBitmap(bitmap, bitMatrixWidth * scalingFactor, bitMatrixHeight * scalingFactor, false);
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
} catch (WriterException e) {
|
||||
}
|
||||
catch (WriterException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to generate barcode of type " + format + ": " + cardId, e);
|
||||
} catch (OutOfMemoryError e) {
|
||||
}
|
||||
catch(OutOfMemoryError e)
|
||||
{
|
||||
Log.w(TAG, "Insufficient memory to render barcode, "
|
||||
+ imageWidth + "x" + imageHeight + ", " + format.name()
|
||||
+ ", length=" + cardId.length(), e);
|
||||
+ imageWidth + "x" + imageHeight + ", " + format.name()
|
||||
+ ", length=" + cardId.length(), e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Bitmap doInBackground(Void... params) {
|
||||
// Only do the hard tasks if we've not already been cancelled
|
||||
if (!Thread.currentThread().isInterrupted()) {
|
||||
Bitmap bitmap = generate();
|
||||
|
||||
if (bitmap == null) {
|
||||
isSuccesful = false;
|
||||
|
||||
if (showFallback && !Thread.currentThread().isInterrupted()) {
|
||||
Log.i(TAG, "Barcode generation failed, generating fallback...");
|
||||
cardId = getFallbackString(format);
|
||||
bitmap = generate();
|
||||
return bitmap;
|
||||
}
|
||||
} else {
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
// We've been interrupted - create a empty fallback
|
||||
Bitmap.Config config = Bitmap.Config.ARGB_8888;
|
||||
return Bitmap.createBitmap(imageWidth, imageHeight, config);
|
||||
}
|
||||
|
||||
public void onPostExecute(Object castResult) {
|
||||
Bitmap result = (Bitmap) castResult;
|
||||
|
||||
protected void onPostExecute(Bitmap result)
|
||||
{
|
||||
Log.i(TAG, "Finished generating barcode image of type " + format + ": " + cardId);
|
||||
ImageView imageView = imageViewReference.get();
|
||||
if (imageView == null) {
|
||||
if(imageView == null)
|
||||
{
|
||||
// The ImageView no longer exists, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
String formatPrettyName = format.prettyName();
|
||||
|
||||
imageView.setTag(isSuccesful);
|
||||
|
||||
imageView.setImageBitmap(result);
|
||||
imageView.setContentDescription(mContext.getString(R.string.barcodeImageDescriptionWithType, formatPrettyName));
|
||||
TextView textView = textViewReference.get();
|
||||
|
||||
if (result != null) {
|
||||
if(result != null)
|
||||
{
|
||||
Log.i(TAG, "Displaying barcode");
|
||||
if (widthPadding) {
|
||||
imageView.setPadding(imagePadding / 2, 0, imagePadding / 2, 0);
|
||||
} else {
|
||||
imageView.setPadding(0, imagePadding / 2, 0, imagePadding / 2);
|
||||
}
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
|
||||
if (isSuccesful) {
|
||||
imageView.setColorFilter(null);
|
||||
} else {
|
||||
imageView.setColorFilter(Color.LTGRAY, PorterDuff.Mode.LIGHTEN);
|
||||
}
|
||||
|
||||
if (textView != null) {
|
||||
textView.setVisibility(View.VISIBLE);
|
||||
textView.setText(formatPrettyName);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.i(TAG, "Barcode generation failed, removing image from display");
|
||||
imageView.setVisibility(View.GONE);
|
||||
if (textView != null) {
|
||||
textView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
if (callback != null) {
|
||||
callback.onBarcodeImageWriterResult(isSuccesful);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreExecute() {
|
||||
// No Action
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided to comply with Callable while keeping the original Syntax of AsyncTask
|
||||
*
|
||||
* @return generated Bitmap
|
||||
*/
|
||||
@Override
|
||||
public Bitmap call() {
|
||||
return doInBackground();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,23 +2,29 @@ package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import protect.card_locker.databinding.BarcodeSelectorActivityBinding;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This activity is callable and will allow a user to enter
|
||||
@@ -26,73 +32,192 @@ import protect.card_locker.databinding.BarcodeSelectorActivityBinding;
|
||||
* the data. The user may then select any barcode, where its
|
||||
* 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";
|
||||
public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "LoyaltyCardLocker";
|
||||
|
||||
// Result this activity will return
|
||||
public static final String BARCODE_CONTENTS = "contents";
|
||||
public static final String BARCODE_FORMAT = "format";
|
||||
|
||||
private final Handler typingDelayHandler = new Handler(Looper.getMainLooper());
|
||||
public static final Integer INPUT_DELAY = 250;
|
||||
// These are all the barcode types that the zxing library
|
||||
// is able to generate a barcode for, and thus should be
|
||||
// the only barcodes which we should attempt to scan.
|
||||
public static final Collection<String> SUPPORTED_BARCODE_TYPES = Collections.unmodifiableList(
|
||||
Arrays.asList(
|
||||
BarcodeFormat.AZTEC.name(),
|
||||
BarcodeFormat.CODE_39.name(),
|
||||
BarcodeFormat.CODE_128.name(),
|
||||
BarcodeFormat.CODABAR.name(),
|
||||
BarcodeFormat.DATA_MATRIX.name(),
|
||||
BarcodeFormat.EAN_8.name(),
|
||||
BarcodeFormat.EAN_13.name(),
|
||||
BarcodeFormat.ITF.name(),
|
||||
BarcodeFormat.PDF_417.name(),
|
||||
BarcodeFormat.QR_CODE.name(),
|
||||
BarcodeFormat.UPC_A.name()
|
||||
));
|
||||
|
||||
private BarcodeSelectorAdapter mAdapter;
|
||||
private Map<String, Integer> barcodeViewMap;
|
||||
private LinkedList<AsyncTask> barcodeGeneratorTasks = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
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;
|
||||
mAdapter = new BarcodeSelectorAdapter(this, new ArrayList<>(), this);
|
||||
mBarcodeList.setAdapter(mAdapter);
|
||||
barcodeViewMap = ImmutableMap.<String, Integer>builder()
|
||||
.put(BarcodeFormat.AZTEC.name(), R.id.aztecBarcode)
|
||||
.put(BarcodeFormat.CODE_39.name(), R.id.code39Barcode)
|
||||
.put(BarcodeFormat.CODE_128.name(), R.id.code128Barcode)
|
||||
.put(BarcodeFormat.CODABAR.name(), R.id.codabarBarcode)
|
||||
.put(BarcodeFormat.DATA_MATRIX.name(), R.id.datamatrixBarcode)
|
||||
.put(BarcodeFormat.EAN_8.name(), R.id.ean8Barcode)
|
||||
.put(BarcodeFormat.EAN_13.name(), R.id.ean13Barcode)
|
||||
.put(BarcodeFormat.ITF.name(), R.id.itfBarcode)
|
||||
.put(BarcodeFormat.PDF_417.name(), R.id.pdf417Barcode)
|
||||
.put(BarcodeFormat.QR_CODE.name(), R.id.qrcodeBarcode)
|
||||
.put(BarcodeFormat.UPC_A.name(), R.id.upcaBarcode)
|
||||
.build();
|
||||
|
||||
cardId.addTextChangedListener(new SimpleTextWatcher() {
|
||||
EditText cardId = findViewById(R.id.cardId);
|
||||
cardId.addTextChangedListener(new TextWatcher()
|
||||
{
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
// Delay the input processing so we avoid overload
|
||||
typingDelayHandler.removeCallbacksAndMessages(null);
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after)
|
||||
{
|
||||
// Noting to do
|
||||
}
|
||||
|
||||
typingDelayHandler.postDelayed(() -> {
|
||||
Log.d(TAG, "Entered text: " + s);
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count)
|
||||
{
|
||||
Log.d(TAG, "Entered text: " + s);
|
||||
|
||||
runOnUiThread(() -> {
|
||||
generateBarcodes(s.toString());
|
||||
});
|
||||
}, INPUT_DELAY);
|
||||
// Stop any async tasks which may not have been started yet
|
||||
for(AsyncTask task : barcodeGeneratorTasks)
|
||||
{
|
||||
task.cancel(false);
|
||||
}
|
||||
barcodeGeneratorTasks.clear();
|
||||
|
||||
// Update barcodes
|
||||
for(String key : barcodeViewMap.keySet())
|
||||
{
|
||||
ImageView image = findViewById(barcodeViewMap.get(key));
|
||||
createBarcodeOption(image, key, s.toString());
|
||||
}
|
||||
|
||||
View noBarcodeButtonView = findViewById(R.id.noBarcode);
|
||||
setButtonListener(noBarcodeButtonView, s.toString());
|
||||
noBarcodeButtonView.setEnabled(s.length() > 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s)
|
||||
{
|
||||
// Noting to do
|
||||
}
|
||||
});
|
||||
|
||||
final Bundle b = getIntent().getExtras();
|
||||
final String initialCardId = b != null ? b.getString("initialCardId") : null;
|
||||
|
||||
if (initialCardId != null) {
|
||||
if(initialCardId != null)
|
||||
{
|
||||
cardId.setText(initialCardId);
|
||||
} else {
|
||||
generateBarcodes("");
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
private void setButtonListener(final View button, final String cardId)
|
||||
{
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Log.d(TAG, "Selected no barcode");
|
||||
Intent result = new Intent();
|
||||
result.putExtra(BARCODE_FORMAT, "");
|
||||
result.putExtra(BARCODE_CONTENTS, cardId);
|
||||
BarcodeSelectorActivity.this.setResult(RESULT_OK, result);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId)
|
||||
{
|
||||
final BarcodeFormat format = BarcodeFormat.valueOf(formatType);
|
||||
if(format == null)
|
||||
{
|
||||
Log.w(TAG, "Unsupported barcode format: " + formatType);
|
||||
return;
|
||||
}
|
||||
|
||||
image.setImageBitmap(null);
|
||||
image.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
Log.d(TAG, "Selected barcode type " + formatType);
|
||||
Intent result = new Intent();
|
||||
result.putExtra(BARCODE_FORMAT, formatType);
|
||||
result.putExtra(BARCODE_CONTENTS, cardId);
|
||||
BarcodeSelectorActivity.this.setResult(RESULT_OK, result);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
if(image.getHeight() == 0)
|
||||
{
|
||||
// The size of the ImageView is not yet available as it has not
|
||||
// yet been drawn. Wait for it to be drawn so the size is available.
|
||||
image.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener()
|
||||
{
|
||||
@Override
|
||||
public void onGlobalLayout()
|
||||
{
|
||||
Log.d(TAG, "Global layout finished, type: + " + formatType + ", width: " + image.getWidth());
|
||||
if (Build.VERSION.SDK_INT < 16)
|
||||
{
|
||||
image.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
image.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
|
||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format);
|
||||
barcodeGeneratorTasks.add(task);
|
||||
task.execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format);
|
||||
barcodeGeneratorTasks.add(task);
|
||||
task.execute();
|
||||
}
|
||||
mAdapter.setBarcodes(barcodes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
if (item.getItemId() == android.R.id.home)
|
||||
{
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
return true;
|
||||
@@ -100,26 +225,4 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRowClicked(int inputPosition, View view) {
|
||||
CatimaBarcodeWithValue barcodeWithValue = mAdapter.getItem(inputPosition);
|
||||
CatimaBarcode catimaBarcode = barcodeWithValue.catimaBarcode();
|
||||
|
||||
if (!mAdapter.isValid(view)) {
|
||||
Toast.makeText(this, getString(R.string.wrongValueForBarcodeType), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
String barcodeFormat = catimaBarcode.format().name();
|
||||
String value = barcodeWithValue.value();
|
||||
|
||||
Log.d(TAG, "Selected barcode type " + barcodeFormat);
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra(BARCODE_FORMAT, barcodeFormat);
|
||||
result.putExtra(BARCODE_CONTENTS, value);
|
||||
BarcodeSelectorActivity.this.setResult(RESULT_OK, result);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import protect.card_locker.async.TaskHandler;
|
||||
import protect.card_locker.databinding.BarcodeLayoutBinding;
|
||||
|
||||
public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue> {
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private final TaskHandler mTasks = new TaskHandler();
|
||||
private final BarcodeSelectorListener mListener;
|
||||
|
||||
private static class ViewHolder {
|
||||
ImageView image;
|
||||
TextView text;
|
||||
}
|
||||
|
||||
public interface BarcodeSelectorListener {
|
||||
void onRowClicked(int inputPosition, View view);
|
||||
}
|
||||
|
||||
public BarcodeSelectorAdapter(Context context, ArrayList<CatimaBarcodeWithValue> barcodes, BarcodeSelectorListener barcodeSelectorListener) {
|
||||
super(context, 0, barcodes);
|
||||
mListener = barcodeSelectorListener;
|
||||
}
|
||||
|
||||
public void setBarcodes(ArrayList<CatimaBarcodeWithValue> barcodes) {
|
||||
clear();
|
||||
addAll(barcodes);
|
||||
notifyDataSetChanged();
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
CatimaBarcodeWithValue catimaBarcodeWithValue = getItem(position);
|
||||
CatimaBarcode catimaBarcode = catimaBarcodeWithValue.catimaBarcode();
|
||||
String value = catimaBarcodeWithValue.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.setTag(viewHolder);
|
||||
} else {
|
||||
viewHolder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
createBarcodeOption(viewHolder.image, catimaBarcode.format().name(), value, viewHolder.text);
|
||||
|
||||
View finalConvertView = convertView;
|
||||
convertView.setOnClickListener(view -> mListener.onRowClicked(position, finalConvertView));
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
public boolean isValid(View view) {
|
||||
ViewHolder viewHolder = (ViewHolder) view.getTag();
|
||||
return viewHolder.image.getTag() != null && (boolean) viewHolder.image.getTag();
|
||||
}
|
||||
|
||||
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text) {
|
||||
final CatimaBarcode format = CatimaBarcode.fromName(formatType);
|
||||
|
||||
image.setImageBitmap(null);
|
||||
image.setClipToOutline(true);
|
||||
|
||||
if (image.getHeight() == 0) {
|
||||
// The size of the ImageView is not yet available as it has not
|
||||
// yet been drawn. Wait for it to be drawn so the size is available.
|
||||
image.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
Log.d(TAG, "Global layout finished, type: + " + formatType + ", width: " + image.getWidth());
|
||||
image.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
|
||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||
|
||||
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null, true);
|
||||
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||
BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask(getContext(), image, cardId, format, text, true, null, true);
|
||||
mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
public class BarcodeValues {
|
||||
private final String mFormat;
|
||||
private final String mContent;
|
||||
|
||||
public BarcodeValues(String format, String content) {
|
||||
mFormat = format;
|
||||
mContent = content;
|
||||
}
|
||||
|
||||
public String format() {
|
||||
return mFormat;
|
||||
}
|
||||
|
||||
public String content() {
|
||||
return mContent;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mFormat == null && mContent == null;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public abstract class BaseCursorAdapter<V extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<V> {
|
||||
public Cursor mCursor;
|
||||
private boolean mDataValid;
|
||||
private int mRowIDColumn;
|
||||
|
||||
private String mRowIDColumnName;
|
||||
|
||||
public BaseCursorAdapter(Cursor inputCursor, String rowIDColumnName) {
|
||||
setHasStableIds(true);
|
||||
|
||||
mRowIDColumnName = rowIDColumnName;
|
||||
|
||||
swapCursor(inputCursor);
|
||||
}
|
||||
|
||||
public abstract void onBindViewHolder(V inputHolder, Cursor inputCursor);
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(V inputHolder, int inputPosition) {
|
||||
if (!mDataValid) {
|
||||
throw new IllegalStateException("Cannot bind view holder when cursor is in invalid state.");
|
||||
}
|
||||
|
||||
if (!mCursor.moveToPosition(inputPosition)) {
|
||||
throw new IllegalStateException("Could not move cursor to position " + inputPosition + " when trying to bind view holder");
|
||||
}
|
||||
|
||||
onBindViewHolder(inputHolder, mCursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (mDataValid) {
|
||||
return mCursor.getCount();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int inputPosition) {
|
||||
if (!mDataValid) {
|
||||
throw new IllegalStateException("Cannot lookup item id when cursor is in invalid state.");
|
||||
}
|
||||
|
||||
if (!mCursor.moveToPosition(inputPosition)) {
|
||||
throw new IllegalStateException("Could not move cursor to position " + inputPosition + " when trying to get an item id");
|
||||
}
|
||||
|
||||
return mCursor.getLong(mRowIDColumn);
|
||||
}
|
||||
|
||||
public void swapCursor(Cursor inputCursor) {
|
||||
if (inputCursor == mCursor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (inputCursor != null) {
|
||||
mCursor = inputCursor;
|
||||
mRowIDColumn = mCursor.getColumnIndex(mRowIDColumnName);
|
||||
mDataValid = true;
|
||||
notifyDataSetChanged();
|
||||
} else {
|
||||
notifyItemRangeRemoved(0, getItemCount());
|
||||
mCursor = null;
|
||||
mRowIDColumn = -1;
|
||||
mDataValid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +1,83 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import android.os.Parcelable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import protect.card_locker.databinding.SimpleToolbarListActivityBinding;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* The configuration screen for creating a shortcut.
|
||||
*/
|
||||
public class CardShortcutConfigure extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
|
||||
private SimpleToolbarListActivityBinding binding;
|
||||
static final String TAG = "Catima";
|
||||
private SQLiteDatabase mDatabase;
|
||||
private LoyaltyCardCursorAdapter mAdapter;
|
||||
public class CardShortcutConfigure extends AppCompatActivity
|
||||
{
|
||||
static final String TAG = "LoyaltyCardLocker";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
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;
|
||||
toolbar.setTitle(R.string.shortcutSelectCard);
|
||||
setSupportActionBar(toolbar);
|
||||
setContentView(R.layout.main_activity);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setVisibility(View.GONE);
|
||||
|
||||
final DBHelper db = new DBHelper(this);
|
||||
|
||||
// If there are no cards, bail
|
||||
int cardCount = DBHelper.getLoyaltyCardCount(mDatabase);
|
||||
if (cardCount == 0) {
|
||||
if(db.getLoyaltyCardCount() == 0)
|
||||
{
|
||||
Toast.makeText(this, R.string.noCardsMessage, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
final RecyclerView cardList = binding.list;
|
||||
GridLayoutManager layoutManager = (GridLayoutManager) cardList.getLayoutManager();
|
||||
if (layoutManager != null) {
|
||||
layoutManager.setSpanCount(getResources().getInteger(R.integer.main_view_card_columns));
|
||||
}
|
||||
final ListView cardList = findViewById(R.id.list);
|
||||
cardList.setVisibility(View.VISIBLE);
|
||||
|
||||
Cursor cardCursor = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.All);
|
||||
mAdapter = new LoyaltyCardCursorAdapter(this, cardCursor, this, null);
|
||||
cardList.setAdapter(mAdapter);
|
||||
}
|
||||
Cursor cardCursor = db.getLoyaltyCardCursor();
|
||||
|
||||
private void onClickAction(int position) {
|
||||
Cursor selected = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.All);
|
||||
selected.moveToPosition(position);
|
||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
|
||||
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
|
||||
cardList.setAdapter(adapter);
|
||||
|
||||
Log.d(TAG, "Creating shortcut for card " + loyaltyCard.store + "," + loyaltyCard.id);
|
||||
cardList.setOnItemClickListener(new AdapterView.OnItemClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
Cursor selected = (Cursor) parent.getItemAtPosition(position);
|
||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
|
||||
|
||||
ShortcutInfoCompat shortcut = ShortcutHelper.createShortcutBuilder(CardShortcutConfigure.this, loyaltyCard).build();
|
||||
Log.d(TAG, "Creating shortcut for card " + loyaltyCard.store + "," + loyaltyCard.id);
|
||||
|
||||
setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(CardShortcutConfigure.this, shortcut));
|
||||
Intent shortcutIntent = new Intent(CardShortcutConfigure.this, LoyaltyCardViewActivity.class);
|
||||
shortcutIntent.setAction(Intent.ACTION_MAIN);
|
||||
// Prevent instances of the view activity from piling up; if one exists let this
|
||||
// one replace it.
|
||||
shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt("id", loyaltyCard.id);
|
||||
bundle.putBoolean("view", true);
|
||||
shortcutIntent.putExtras(bundle);
|
||||
|
||||
finish();
|
||||
}
|
||||
Parcelable icon = Intent.ShortcutIconResource.fromContext(CardShortcutConfigure.this, R.mipmap.ic_launcher);
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, loyaltyCard.store);
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon);
|
||||
setResult(RESULT_OK, intent);
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu inputMenu) {
|
||||
getMenuInflater().inflate(R.menu.card_details_menu, inputMenu);
|
||||
|
||||
return super.onCreateOptionsMenu(inputMenu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem inputItem) {
|
||||
int id = inputItem.getItemId();
|
||||
|
||||
if (id == R.id.action_display_options) {
|
||||
mAdapter.showDisplayOptionsDialog();
|
||||
invalidateOptionsMenu();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(inputItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRowClicked(int inputPosition) {
|
||||
onClickAction(inputPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRowLongClicked(int inputPosition) {
|
||||
// do nothing
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.service.controls.Control;
|
||||
import android.service.controls.ControlsProviderService;
|
||||
import android.service.controls.DeviceTypes;
|
||||
import android.service.controls.actions.ControlAction;
|
||||
import android.service.controls.templates.StatelessTemplate;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Flow;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
public class CardsOnPowerScreenService extends ControlsProviderService {
|
||||
|
||||
public static final String PREFIX = "catima-";
|
||||
static final String TAG = "Catima";
|
||||
private SQLiteDatabase mDatabase;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
mDatabase = new DBHelper(this).getReadableDatabase();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Flow.Publisher<Control> createPublisherForAllAvailable() {
|
||||
Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(mDatabase, DBHelper.LoyaltyCardArchiveFilter.Unarchived);
|
||||
return subscriber -> {
|
||||
while (loyaltyCardCursor.moveToNext()) {
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(loyaltyCardCursor);
|
||||
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra("id", card.id);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), card.id, openIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
subscriber.onNext(
|
||||
new Control.StatelessBuilder(PREFIX + card.id, pendingIntent)
|
||||
.setControlId(PREFIX + card.id)
|
||||
.setTitle(card.store)
|
||||
.setDeviceType(DeviceTypes.TYPE_GENERIC_OPEN_CLOSE)
|
||||
.setSubtitle(card.note)
|
||||
.setCustomIcon(Icon.createWithBitmap(getIcon(this, card)))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
subscriber.onComplete();
|
||||
};
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Flow.Publisher<Control> createPublisherFor(@NonNull List<String> controlIds) {
|
||||
return subscriber -> {
|
||||
subscriber.onSubscribe(new NoOpSubscription());
|
||||
for (String controlId : controlIds) {
|
||||
Control control;
|
||||
Integer cardId = this.controlIdToCardId(controlId);
|
||||
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, cardId);
|
||||
if (card != null) {
|
||||
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra("id", card.id);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), card.id, openIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
control = new Control.StatefulBuilder(controlId, pendingIntent)
|
||||
.setTitle(card.store)
|
||||
.setDeviceType(DeviceTypes.TYPE_GENERIC_OPEN_CLOSE)
|
||||
.setSubtitle(card.note)
|
||||
.setStatus(Control.STATUS_OK)
|
||||
.setControlTemplate(new StatelessTemplate(controlId))
|
||||
.setCustomIcon(Icon.createWithBitmap(getIcon(this, card)))
|
||||
.build();
|
||||
} else {
|
||||
Intent mainScreenIntent = new Intent(this, MainActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), -1, mainScreenIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
control = new Control.StatefulBuilder(controlId, pendingIntent)
|
||||
.setStatus(Control.STATUS_NOT_FOUND)
|
||||
.build();
|
||||
}
|
||||
Log.d(TAG, "Dispatching widget " + controlId);
|
||||
subscriber.onNext(control);
|
||||
}
|
||||
subscriber.onComplete();
|
||||
};
|
||||
}
|
||||
|
||||
private Bitmap getIcon(Context context, LoyaltyCard loyaltyCard) {
|
||||
Bitmap cardIcon = Utils.retrieveCardImage(context, loyaltyCard.id, ImageLocationType.icon);
|
||||
|
||||
if (cardIcon != null) {
|
||||
return cardIcon;
|
||||
}
|
||||
|
||||
return Utils.generateIcon(this, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile();
|
||||
}
|
||||
|
||||
private Integer controlIdToCardId(String controlId) {
|
||||
if (controlId == null)
|
||||
return null;
|
||||
if (!controlId.startsWith(PREFIX)) {
|
||||
Log.w(TAG, "Unsupported control ID format: " + controlId);
|
||||
return null;
|
||||
}
|
||||
controlId = controlId.substring(PREFIX.length());
|
||||
try {
|
||||
return Integer.parseInt(controlId);
|
||||
} catch (RuntimeException ex) {
|
||||
Log.e(TAG, "Unsupported control ID format. Expected numeric after prefix, found: " + controlId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performControlAction(@NonNull String controlId, @NonNull ControlAction action, @NonNull Consumer<Integer> consumer) {
|
||||
consumer.accept(ControlAction.RESPONSE_OK);
|
||||
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra("id", controlIdToCardId(controlId));
|
||||
startActivity(openIntent);
|
||||
|
||||
closePowerScreenOnAndroid11();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"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
|
||||
// On Android 12, we don't need it, and Google will probably get angry if we ask for it
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
||||
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A no-op subscription.
|
||||
* <p>
|
||||
* Flow.Subscriptions are made to last during time and receive periodic updates.
|
||||
* Our app does not require sending periodic updates of loyalty cards, so we are just ignoring anything in the subscription
|
||||
* Also, our db is quick enough to respond that the Publisher is immediately sending and completing data.
|
||||
* This facility is overkill, but if we don't call onSubscribe the service won't work
|
||||
*/
|
||||
private static class NoOpSubscription implements Flow.Subscription {
|
||||
@Override
|
||||
public void request(long l) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
|
||||
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 {
|
||||
protected boolean activityOverridesNavBarColor = false;
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
// Apply chosen language
|
||||
super.attachBaseContext(Utils.updateBaseContextLocale(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Utils.patchColors(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
// material 3 designer does not consider status bar colors
|
||||
// XXX changing this in onCreate causes issues with the splash screen activity, so doing this here
|
||||
Window window = getWindow();
|
||||
if (window != null) {
|
||||
boolean darkMode = Utils.isDarkModeEnabled(this);
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
View decorView = window.getDecorView();
|
||||
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView);
|
||||
wic.setAppearanceLightStatusBars(!darkMode);
|
||||
window.setStatusBarColor(Color.TRANSPARENT);
|
||||
} else {
|
||||
// icons are always white back then
|
||||
window.setStatusBarColor(darkMode ? Color.TRANSPARENT : Color.argb(127, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
// XXX android 9 and below has a nasty rendering bug if the theme was patched earlier
|
||||
Utils.postPatchColors(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (!activityOverridesNavBarColor) {
|
||||
Utils.setNavigationBarColor(this, null, Utils.resolveBackgroundColor(this), !Utils.isDarkModeEnabled(this));
|
||||
}
|
||||
}
|
||||
|
||||
protected void enableToolbarBackButton() {
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
}
|
||||
}
|
||||
@@ -1,96 +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.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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.journeyapps.barcodescanner.CaptureManager;
|
||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
||||
|
||||
public class CatimaCaptureManager extends CaptureManager {
|
||||
private final Context mContext;
|
||||
|
||||
public CatimaCaptureManager(Activity activity, DecoratedBarcodeView barcodeView) {
|
||||
super(activity, barcodeView);
|
||||
|
||||
mContext = activity.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void displayFrameworkBugMessageAndExit(String message) {
|
||||
// We don't want to exit, as we also have a enter from card image and add manually button here
|
||||
// So we show a toast instead
|
||||
Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVPrinter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
/**
|
||||
* Class for exporting the database into CSV (Comma Separate Values)
|
||||
* format.
|
||||
*/
|
||||
public class CsvDatabaseExporter implements DatabaseExporter
|
||||
{
|
||||
public void exportData(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException
|
||||
{
|
||||
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
|
||||
|
||||
// Print the header
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
|
||||
DBHelper.LoyaltyCardDbIds.STORE,
|
||||
DBHelper.LoyaltyCardDbIds.NOTE,
|
||||
DBHelper.LoyaltyCardDbIds.CARD_ID,
|
||||
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
|
||||
DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR,
|
||||
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
|
||||
|
||||
Cursor cursor = db.getLoyaltyCardCursor();
|
||||
|
||||
while(cursor.moveToNext())
|
||||
{
|
||||
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
|
||||
|
||||
printer.printRecord(card.id,
|
||||
card.store,
|
||||
card.note,
|
||||
card.cardId,
|
||||
card.headerColor,
|
||||
card.headerTextColor,
|
||||
card.barcodeType);
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
|
||||
printer.close();
|
||||
}
|
||||
}
|
||||
147
app/src/main/java/protect/card_locker/CsvDatabaseImporter.java
Normal file
147
app/src/main/java/protect/card_locker/CsvDatabaseImporter.java
Normal file
@@ -0,0 +1,147 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* Class for importing a database from CSV (Comma Separate Values)
|
||||
* formatted data.
|
||||
*
|
||||
* The database's loyalty cards are expected to appear in the CSV data.
|
||||
* A header is expected for the each table showing the names of the columns.
|
||||
*/
|
||||
public class CsvDatabaseImporter implements DatabaseImporter
|
||||
{
|
||||
public void importData(DBHelper db, InputStreamReader input) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.withHeader());
|
||||
|
||||
SQLiteDatabase database = db.getWritableDatabase();
|
||||
database.beginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
for (CSVRecord record : parser)
|
||||
{
|
||||
importLoyaltyCard(database, db, record);
|
||||
|
||||
if(Thread.currentThread().isInterrupted())
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
parser.close();
|
||||
database.setTransactionSuccessful();
|
||||
}
|
||||
catch(IllegalArgumentException|IllegalStateException e)
|
||||
{
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.endTransaction();
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* if it is not null. Otherwise, a FormatException is thrown.
|
||||
*/
|
||||
private String extractString(String key, CSVRecord record, String defaultValue)
|
||||
throws FormatException
|
||||
{
|
||||
String toReturn = defaultValue;
|
||||
|
||||
if(record.isMapped(key))
|
||||
{
|
||||
toReturn = record.get(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(defaultValue == null)
|
||||
{
|
||||
throw new FormatException("Field not used but expected: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an integer 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, or the data is not a valid
|
||||
* int, a FormatException is thrown.
|
||||
*/
|
||||
private Integer extractInt(String key, CSVRecord record, boolean nullIsOk)
|
||||
throws FormatException
|
||||
{
|
||||
if(record.isMapped(key) == false)
|
||||
{
|
||||
throw new FormatException("Field not used but expected: " + key);
|
||||
}
|
||||
|
||||
String value = record.get(key);
|
||||
if(value.isEmpty() && nullIsOk)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Integer.parseInt(record.get(key));
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
throw new FormatException("Failed to parse field: " + key, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a single loyalty card into the database using the given
|
||||
* session.
|
||||
*/
|
||||
private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record)
|
||||
throws IOException, FormatException
|
||||
{
|
||||
int id = extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
|
||||
|
||||
String store = extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
|
||||
if(store.isEmpty())
|
||||
{
|
||||
throw new FormatException("No store listed, but is required");
|
||||
}
|
||||
|
||||
String note = extractString(DBHelper.LoyaltyCardDbIds.NOTE, record, "");
|
||||
|
||||
String cardId = extractString(DBHelper.LoyaltyCardDbIds.CARD_ID, record, "");
|
||||
if(cardId.isEmpty())
|
||||
{
|
||||
throw new FormatException("No card ID listed, but is required");
|
||||
}
|
||||
|
||||
String barcodeType = extractString(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, record, "");
|
||||
|
||||
Integer headerColor = null;
|
||||
Integer headerTextColor = null;
|
||||
|
||||
if(record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR) &&
|
||||
record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR))
|
||||
{
|
||||
headerColor = extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true);
|
||||
headerTextColor = extractInt(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR, record, true);
|
||||
}
|
||||
|
||||
helper.insertLoyaltyCard(database, id, store, note, cardId, barcodeType, headerColor, headerTextColor);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user