mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2025-12-24 15:47:53 -05:00
Compare commits
1 Commits
v2.29.1
...
badAttempt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f64449167 |
20
.github/dependabot.yml
vendored
20
.github/dependabot.yml
vendored
@@ -2,29 +2,9 @@ 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/"
|
||||
|
||||
6
.github/workflows/android.yml
vendored
6
.github/workflows/android.yml
vendored
@@ -29,10 +29,10 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- 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/actions/wrapper-validation@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- name: set up OpenJDK 17
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
run: ./gradlew spotbugsRelease
|
||||
- name: Archive test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
uses: actions/upload-artifact@v3.1.3
|
||||
with:
|
||||
name: test-results
|
||||
path: app/build/reports
|
||||
|
||||
6
.github/workflows/changelog-to-fastlane.yml
vendored
6
.github/workflows/changelog-to-fastlane.yml
vendored
@@ -27,15 +27,15 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
id: checkout
|
||||
uses: actions/checkout@v4.1.5
|
||||
uses: actions/checkout@v4.0.0
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v4.7.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@v6.0.5
|
||||
uses: peter-evans/create-pull-request@v5.0.2
|
||||
with:
|
||||
title: "Update Fastlane changelogs"
|
||||
commit-message: "Update Fastlane changelogs"
|
||||
|
||||
7
.github/workflows/contributors-to-file.yml
vendored
7
.github/workflows/contributors-to-file.yml
vendored
@@ -25,15 +25,14 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
id: checkout
|
||||
uses: actions/checkout@v4.1.5
|
||||
uses: actions/checkout@v4.0.0
|
||||
- name: Update contributors
|
||||
id: update_contributors
|
||||
uses: TheLastProject/contributors-to-file-action@v3.2.0
|
||||
uses: TheLastProject/contributors-to-file-action@v3.0.1
|
||||
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@v6.0.5
|
||||
uses: peter-evans/create-pull-request@v5.0.2
|
||||
with:
|
||||
title: "Update contributors"
|
||||
commit-message: "Update contributors"
|
||||
|
||||
38
.github/workflows/generate-feature-graphic.yml
vendored
38
.github/workflows/generate-feature-graphic.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
generate-feature-graphic:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- name: Install requirements
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -36,9 +36,41 @@ jobs:
|
||||
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
|
||||
run: |
|
||||
for lang in fastlane/metadata/android/*; do
|
||||
pushd "$lang"
|
||||
# Place temporary copy for editing if needed
|
||||
cp ../../../../.scripts/generate_feature_graphic/featureGraphic.svg featureGraphic.svg
|
||||
# Extract text after 'Catima - '
|
||||
export subtext="$(grep -oP '(?<=Catima \S ).*' title.txt || true)"
|
||||
# If there is subtext, change the .svg accordingly
|
||||
if [ -n "$subtext" ]; then
|
||||
perl -pi -e 's/Loyalty Card Wallet/$ENV{subtext}/' featureGraphic.svg
|
||||
# Set correct font for language if needed (Lexend Deca has limited support)
|
||||
# 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 ;;
|
||||
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
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6.0.5
|
||||
uses: peter-evans/create-pull-request@v5.0.2
|
||||
with:
|
||||
title: "Update feature graphic"
|
||||
commit-message: "Update feature graphic"
|
||||
|
||||
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.5
|
||||
- uses: obfusk/gradle-update-action@v2.0.0
|
||||
id: gradle-update
|
||||
- uses: gradle/actions/wrapper-validation@v3
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6.0.5
|
||||
with:
|
||||
title: "Update Gradle to ${{ steps.gradle-update.outputs.version }}"
|
||||
commit-message: "Update Gradle to ${{ steps.gradle-update.outputs.version }}"
|
||||
branch-suffix: timestamp
|
||||
7
.github/workflows/update-locales.yml
vendored
7
.github/workflows/update-locales.yml
vendored
@@ -5,7 +5,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- app/src/main/res/values-*/strings.xml
|
||||
- app/src/main/res/values/settings.xml
|
||||
permissions:
|
||||
actions: none
|
||||
@@ -25,13 +24,11 @@ jobs:
|
||||
update-locales:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- name: Add new locales
|
||||
run: .scripts/new-locales.py
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- name: Update locales
|
||||
run: .scripts/locales.py
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6.0.5
|
||||
uses: peter-evans/create-pull-request@v5.0.2
|
||||
with:
|
||||
title: "Update locales"
|
||||
commit-message: "Update locales"
|
||||
|
||||
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,25 +1,13 @@
|
||||
# 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
|
||||
local.properties
|
||||
.idea/
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
#local.properties
|
||||
|
||||
# Android extras
|
||||
/app/*.log
|
||||
/app/build
|
||||
/app/release
|
||||
/.idea
|
||||
build/
|
||||
captures/
|
||||
**/release
|
||||
**/debug
|
||||
app/*.log
|
||||
|
||||
# Bundle
|
||||
/.bundle/
|
||||
|
||||
@@ -1,55 +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
|
||||
elif grep -q – title.txt; then
|
||||
# No result, try splitting title.txt on – (en dash)
|
||||
IFS='–' read -r appname subtext < title.txt
|
||||
elif grep -q - title.txt; then
|
||||
# No result, try splitting on - (dash)
|
||||
IFS='-' read -r appname subtext < title.txt
|
||||
else
|
||||
# No result, use the full title as app name and default subtext
|
||||
appname=$(< title.txt)
|
||||
subtext="Loyalty Card Wallet"
|
||||
fi
|
||||
export appname=${appname%% }
|
||||
export subtext=${subtext## }
|
||||
# If the appname isn't Catima or there is subtext, change the .svg accordingly
|
||||
if [ "$appname" != "Catima" ] || [ -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 ;;
|
||||
hi-IN) sed -i -e "s/Yesteryear/Noto Serif Devanagari/" -e "s/Lexend Deca/Noto Serif Devanagari/" 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
|
||||
@@ -19,8 +19,8 @@ res = ", ".join(f'"{loc}"' for loc in locales)
|
||||
sed = [
|
||||
"sed",
|
||||
"-i",
|
||||
f"s/resourceConfigurations .*/resourceConfigurations += listOf({res})/",
|
||||
"app/build.gradle.kts"
|
||||
f"s/resourceConfigurations .*/resourceConfigurations += [{res}]/",
|
||||
"app/build.gradle"
|
||||
]
|
||||
subprocess.run(sed, check=True)
|
||||
|
||||
|
||||
@@ -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()
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@@ -1,30 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## v2.29.1 - 135 (2024-05-19)
|
||||
|
||||
- Various fixes and improvements to balance handling
|
||||
|
||||
## v2.29.0 - 134 (2024-04-19)
|
||||
|
||||
- Support for scanning PDF files for barcodes
|
||||
- Support for image files with multiple barcodes
|
||||
- Minor UI fixes
|
||||
|
||||
## v2.28.0 - 133 (2024-03-08)
|
||||
|
||||
- Target Android 14
|
||||
- Open card icon in gallery on touch
|
||||
- Improve design of Photos tab in edit view
|
||||
- Update spending screen to also support receiving
|
||||
|
||||
## v2.27.0 - 132 (2024-01-30)
|
||||
## Unreleased - 132
|
||||
|
||||
- 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)
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
# How to Submit Patches to the Catima Project
|
||||
How to Submit Patches to the Catima Project
|
||||
===============================================================================
|
||||
https://github.com/TheLastProject/Catima
|
||||
|
||||
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
|
||||
Catima 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/).
|
||||
@@ -59,6 +57,44 @@ if you can describe/include a reproducer for the problem in the description as
|
||||
well as instructions on how to test for the bug and verify that it has been
|
||||
fixed.
|
||||
|
||||
### Sign Your Work
|
||||
|
||||
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
|
||||
|
||||
106
Gemfile.lock
106
Gemfile.lock
@@ -1,32 +1,29 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
CFPropertyList (3.0.6)
|
||||
rexml
|
||||
addressable (2.8.6)
|
||||
addressable (2.8.5)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
artifactory (3.0.17)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.931.0)
|
||||
aws-sdk-core (3.196.1)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
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.8)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.81.0)
|
||||
aws-sdk-core (~> 3, >= 3.193.0)
|
||||
aws-sdk-kms (1.71.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.151.0)
|
||||
aws-sdk-core (~> 3, >= 3.194.0)
|
||||
aws-sdk-s3 (1.134.0)
|
||||
aws-sdk-core (~> 3, >= 3.181.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sigv4 (1.8.0)
|
||||
aws-sigv4 (~> 1.6)
|
||||
aws-sigv4 (1.6.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
@@ -35,10 +32,11 @@ GEM
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.110.0)
|
||||
excon (0.103.0)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
@@ -67,15 +65,15 @@ GEM
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.3.1)
|
||||
fastlane (2.220.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 (~> 1.2)
|
||||
colored
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
@@ -87,7 +85,6 @@ GEM
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
@@ -96,10 +93,10 @@ GEM
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
optparse (~> 0.1.1)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.5)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (~> 3)
|
||||
@@ -108,11 +105,11 @@ GEM
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-androidpublisher_v3 (0.49.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.3)
|
||||
google-apis-core (0.11.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
@@ -120,27 +117,28 @@ GEM
|
||||
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.31.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.7.0)
|
||||
google-cloud-env (>= 1.0, < 3.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.4.0)
|
||||
google-cloud-storage (1.47.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.31.0)
|
||||
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.1)
|
||||
googleauth (1.8.0)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
@@ -151,33 +149,30 @@ GEM
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.6.2)
|
||||
json (2.7.2)
|
||||
jwt (2.8.1)
|
||||
base64
|
||||
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.4.1)
|
||||
multipart-post (2.3.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.1)
|
||||
nkf (0.2.0)
|
||||
optparse (0.5.0)
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.7.1)
|
||||
public_suffix (5.0.5)
|
||||
rake (13.2.1)
|
||||
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.8)
|
||||
strscan (>= 3.0.9)
|
||||
rexml (3.2.6)
|
||||
rouge (2.0.7)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.5)
|
||||
signet (0.19.0)
|
||||
security (0.1.3)
|
||||
signet (0.18.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
@@ -185,19 +180,22 @@ GEM
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
strscan (3.1.0)
|
||||
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.2)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.5.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.24.0)
|
||||
xcodeproj (1.22.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
@@ -216,4 +214,4 @@ DEPENDENCIES
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.9
|
||||
2.3.26
|
||||
|
||||
165
app/build.gradle
Normal file
165
app/build.gradle
Normal file
@@ -0,0 +1,165 @@
|
||||
import com.github.spotbugs.snom.SpotBugsTask
|
||||
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'com.github.spotbugs'
|
||||
}
|
||||
|
||||
spotbugs {
|
||||
ignoreFailures = false
|
||||
effort = 'max'
|
||||
excludeFilter = file("./config/spotbugs/exclude.xml")
|
||||
reportsDir = file("$buildDir/reports/spotbugs/")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "me.hackerchick.catima"
|
||||
minSdk 21
|
||||
targetSdk 33
|
||||
versionCode 131
|
||||
versionName "2.26.0"
|
||||
|
||||
vectorDrawables.useSupportLibrary true
|
||||
multiDexEnabled true
|
||||
|
||||
resourceConfigurations += ["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", "ro-rRO", "ru", "sk", "sl", "sv", "tr", "uk", "zh-rTW", "zh-rCN"]
|
||||
|
||||
//testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
flavorDimensions "version"
|
||||
productFlavors {
|
||||
fdroid {
|
||||
dimension "version"
|
||||
}
|
||||
screengrab {
|
||||
dimension "version"
|
||||
applicationIdSuffix = ".screengrab"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
resValue "string", "app_name", "Catima"
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
resValue "string", "app_name", "Catima Debug"
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
encoding "UTF-8"
|
||||
|
||||
// Flag to enable support for the new language APIs
|
||||
coreLibraryDesugaringEnabled true
|
||||
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
|
||||
sourceSets {
|
||||
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 {
|
||||
all {
|
||||
testLogging {
|
||||
events 'started', 'passed', 'skipped', 'failed'
|
||||
}
|
||||
}
|
||||
includeAndroidResources true
|
||||
}
|
||||
}
|
||||
lint {
|
||||
lintConfig file('lint.xml')
|
||||
}
|
||||
namespace 'protect.card_locker'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// AndroidX
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.exifinterface:exifinterface:1.3.6'
|
||||
implementation 'androidx.palette:palette:1.0.0'
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
implementation 'com.github.yalantis:ucrop:2.2.8'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||
|
||||
// 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.10.3'
|
||||
|
||||
// Screenshots
|
||||
testImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||
testImplementation 'com.android.support.test:rules:1.0.2'
|
||||
testImplementation 'com.android.support.test:runner:1.0.2'
|
||||
testImplementation 'tools.fastlane:screengrab:2.1.1'
|
||||
}
|
||||
|
||||
tasks.withType(SpotBugsTask) {
|
||||
|
||||
description 'Run spotbugs'
|
||||
group 'verification'
|
||||
|
||||
//classes = fileTree('build/intermediates/javac/debug/compileDebugJavaWithJavac/classes')
|
||||
//source = fileTree('src/main/java')
|
||||
//classpath = files()
|
||||
|
||||
reports {
|
||||
xml.enabled = false
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('copyRawResFiles', Copy) {
|
||||
from layout.projectDirectory.file("../CHANGELOG.md"),
|
||||
layout.projectDirectory.file("../PRIVACY.md")
|
||||
into layout.projectDirectory.dir("src/main/res/raw")
|
||||
rename { String fileName -> fileName.toLowerCase() }
|
||||
}
|
||||
|
||||
project.afterEvaluate {
|
||||
tasks.each { task ->
|
||||
if (task != copyRawResFiles) {
|
||||
task.dependsOn(copyRawResFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "me.hackerchick.catima"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 135
|
||||
versionName = "2.29.1"
|
||||
|
||||
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.12.0")
|
||||
implementation("com.github.yalantis:ucrop:2.2.9")
|
||||
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.3")
|
||||
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.12.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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
@@ -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,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,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,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:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -24,7 +24,6 @@
|
||||
<application
|
||||
android:name=".LoyaltyCardLockerApplication"
|
||||
android:allowBackup="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
@@ -33,6 +32,7 @@
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.App.Starting">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@@ -44,7 +44,6 @@
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="application/pdf" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
@@ -112,12 +111,10 @@
|
||||
android:name=".preferences.SettingsActivity"
|
||||
android:label="@string/settings"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
<!-- FIXME: locked screenOrientation is a workaround for https://github.com/CatimaLoyalty/Android/issues/1715, remove when https://github.com/CatimaLoyalty/Android/issues/513 is fixed -->
|
||||
<activity
|
||||
android:name=".ImportExportActivity"
|
||||
android:label="@string/importExport"
|
||||
android:exported="true"
|
||||
android:screenOrientation="locked"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
|
||||
<!-- ZIP Intent Filter -->
|
||||
|
||||
@@ -7,11 +7,11 @@ 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 {
|
||||
@@ -43,7 +43,7 @@ public class AboutActivity extends CatimaAppCompatActivity {
|
||||
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");
|
||||
binding.donate.setTag("https://catima.app/contribute/#donating");
|
||||
|
||||
boolean installedFromGooglePlay = Utils.installedFromGooglePlay(this);
|
||||
// Hide Google Play rate button if not on Google Play
|
||||
@@ -98,7 +98,11 @@ public class AboutActivity extends CatimaAppCompatActivity {
|
||||
}
|
||||
|
||||
private void showCredits() {
|
||||
showHTML(R.string.credits, content.getContributorInfo(), null);
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.credits)
|
||||
.setMessage(content.getContributorInfo())
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showHistory(View view) {
|
||||
@@ -113,7 +117,7 @@ public class AboutActivity extends CatimaAppCompatActivity {
|
||||
showHTML(R.string.privacy_policy, content.getPrivacyInfo(), view);
|
||||
}
|
||||
|
||||
private void showHTML(@StringRes int title, final Spanned text, @Nullable View view) {
|
||||
private void showHTML(@StringRes int title, final Spanned text, View view) {
|
||||
int dialogContentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
|
||||
TextView textView = new TextView(this);
|
||||
textView.setText(text);
|
||||
@@ -121,21 +125,12 @@ public class AboutActivity extends CatimaAppCompatActivity {
|
||||
ScrollView scrollView = new ScrollView(this);
|
||||
scrollView.addView(textView);
|
||||
scrollView.setPadding(dialogContentPadding, dialogContentPadding / 2, dialogContentPadding, 0);
|
||||
|
||||
// Create dialog
|
||||
MaterialAlertDialogBuilder materialAlertDialogBuilder = new MaterialAlertDialogBuilder(this);
|
||||
materialAlertDialogBuilder
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.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();
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNeutralButton(R.string.view_online, (dialog, which) -> openExternalBrowser(view))
|
||||
.show();
|
||||
}
|
||||
|
||||
private void openExternalBrowser(View view) {
|
||||
|
||||
@@ -129,19 +129,19 @@ public class AboutContent {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public Spanned getContributorInfo() {
|
||||
public String getContributorInfo() {
|
||||
StringBuilder contributorInfo = new StringBuilder();
|
||||
contributorInfo.append(getCopyright());
|
||||
contributorInfo.append("<br/><br/>");
|
||||
contributorInfo.append("\n\n");
|
||||
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()));
|
||||
contributorInfo.append("\n\n");
|
||||
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_contributors), getContributors()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||
contributorInfo.append("\n\n");
|
||||
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_libraries), getThirdPartyLibraries()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||
contributorInfo.append("\n\n");
|
||||
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_resources), getUsedThirdPartyAssets()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||
|
||||
return HtmlCompat.fromHtml(contributorInfo.toString(), HtmlCompat.FROM_HTML_MODE_COMPACT);
|
||||
return contributorInfo.toString();
|
||||
}
|
||||
|
||||
public Spanned getHistoryInfo() {
|
||||
|
||||
@@ -12,12 +12,12 @@ import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import protect.card_locker.databinding.BarcodeSelectorActivityBinding;
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,17 +3,12 @@ package protect.card_locker;
|
||||
public class BarcodeValues {
|
||||
private final String mFormat;
|
||||
private final String mContent;
|
||||
private String mNote;
|
||||
|
||||
public BarcodeValues(String format, String content) {
|
||||
mFormat = format;
|
||||
mContent = content;
|
||||
}
|
||||
|
||||
public void setNote(String note) {
|
||||
mNote = note;
|
||||
}
|
||||
|
||||
public String format() {
|
||||
return mFormat;
|
||||
}
|
||||
@@ -22,5 +17,7 @@ public class BarcodeValues {
|
||||
return mContent;
|
||||
}
|
||||
|
||||
public String note() { return mNote; }
|
||||
}
|
||||
public boolean isEmpty() {
|
||||
return mFormat == null && mContent == null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
public interface BarcodeValuesListDisambiguatorCallback {
|
||||
void onUserChoseBarcode(BarcodeValues barcodeValues);
|
||||
void onUserDismissedSelector();
|
||||
}
|
||||
@@ -15,13 +15,13 @@ import android.service.controls.actions.ControlAction;
|
||||
import android.service.controls.templates.StatelessTemplate;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Flow;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
public class CardsOnPowerScreenService extends ControlsProviderService {
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -14,8 +13,6 @@ 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
|
||||
@@ -33,31 +30,20 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
|
||||
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));
|
||||
}
|
||||
boolean darkMode = Utils.isDarkModeEnabled(this);
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
View decorView = getWindow().getDecorView();
|
||||
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(getWindow(), decorView);
|
||||
wic.setAppearanceLightStatusBars(!darkMode);
|
||||
getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||
} else {
|
||||
// icons are always white back then
|
||||
getWindow().setStatusBarColor(darkMode ? Color.TRANSPARENT : Color.argb(127, 0, 0, 0));
|
||||
}
|
||||
// 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) {
|
||||
|
||||
@@ -8,12 +8,14 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import protect.card_locker.databinding.GroupLayoutBinding;
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.GroupListItemViewHolder> {
|
||||
public final Context mContext;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
@@ -12,24 +14,25 @@ import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import protect.card_locker.async.TaskHandler;
|
||||
import protect.card_locker.databinding.ImportExportActivityBinding;
|
||||
import protect.card_locker.importexport.DataFormat;
|
||||
@@ -123,19 +126,16 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||
builder.setTitle(R.string.exportPassword);
|
||||
|
||||
FrameLayout container = new FrameLayout(ImportExportActivity.this);
|
||||
|
||||
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
|
||||
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setMargins(50, 10, 50, 0);
|
||||
textInputLayout.setLayoutParams(params);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.leftMargin = 50;
|
||||
params.rightMargin = 50;
|
||||
|
||||
final EditText input = new EditText(ImportExportActivity.this);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
input.setLayoutParams(params);
|
||||
input.setHint(R.string.exportPasswordHint);
|
||||
|
||||
textInputLayout.addView(input);
|
||||
container.addView(textInputLayout);
|
||||
container.addView(input);
|
||||
builder.setView(container);
|
||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||
exportPassword = input.getText().toString();
|
||||
@@ -148,6 +148,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
|
||||
builder.show();
|
||||
|
||||
});
|
||||
|
||||
// Check that there is a file manager available
|
||||
@@ -319,21 +320,9 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle(R.string.passwordRequired);
|
||||
|
||||
FrameLayout container = new FrameLayout(ImportExportActivity.this);
|
||||
|
||||
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
|
||||
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setMargins(50, 10, 50, 0);
|
||||
textInputLayout.setLayoutParams(params);
|
||||
|
||||
final EditText input = new EditText(ImportExportActivity.this);
|
||||
final EditText input = new EditText(this);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
input.setHint(R.string.exportPasswordHint);
|
||||
|
||||
textInputLayout.addView(input);
|
||||
container.addView(textInputLayout);
|
||||
builder.setView(container);
|
||||
builder.setView(input);
|
||||
|
||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||
openFileForImport(uri, input.getText().toString().toCharArray());
|
||||
|
||||
@@ -4,13 +4,13 @@ import android.database.Cursor;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Currency;
|
||||
import java.util.Date;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class LoyaltyCard implements Parcelable {
|
||||
public final int id;
|
||||
public final String store;
|
||||
|
||||
@@ -15,13 +15,6 @@ import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.BlendModeColorFilterCompat;
|
||||
import androidx.core.graphics.BlendModeCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.google.android.material.color.MaterialColors;
|
||||
|
||||
@@ -29,6 +22,13 @@ import java.math.BigDecimal;
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.BlendModeColorFilterCompat;
|
||||
import androidx.core.graphics.BlendModeCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import protect.card_locker.databinding.LoyaltyCardLayoutBinding;
|
||||
|
||||
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder> {
|
||||
@@ -123,7 +123,8 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
||||
}
|
||||
|
||||
inputHolder.mCardIcon.setContentDescription(loyaltyCard.store);
|
||||
inputHolder.mIconBackgroundColor = Utils.setIconOrTextWithBackground(mContext, loyaltyCard, icon, inputHolder.mCardIcon, inputHolder.mCardText);
|
||||
Utils.setIconOrTextWithBackground(mContext, loyaltyCard, icon, inputHolder.mCardIcon, inputHolder.mCardText);
|
||||
inputHolder.setIconBackgroundColor(Utils.getHeaderColor(mContext, loyaltyCard));
|
||||
|
||||
inputHolder.toggleCardStateIcon(loyaltyCard.starStatus != 0, loyaltyCard.archiveStatus != 0, itemSelected(inputCursor.getPosition()));
|
||||
|
||||
@@ -338,6 +339,11 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
||||
mArchivedBackground.invalidate();
|
||||
|
||||
}
|
||||
|
||||
public void setIconBackgroundColor(int color) {
|
||||
mIconBackgroundColor = color;
|
||||
mCardIcon.setBackgroundColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
public int dpToPx(int dp, Context mContext) {
|
||||
|
||||
@@ -2,6 +2,8 @@ package protect.card_locker;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -27,34 +29,33 @@ import android.view.WindowManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.DatePicker;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.google.android.material.chip.Chip;
|
||||
import com.google.android.material.chip.ChipGroup;
|
||||
import com.google.android.material.color.MaterialColors;
|
||||
import com.google.android.material.datepicker.CalendarConstraints;
|
||||
import com.google.android.material.datepicker.DateValidatorPointBackward;
|
||||
import com.google.android.material.datepicker.DateValidatorPointForward;
|
||||
import com.google.android.material.datepicker.MaterialDatePicker;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
|
||||
@@ -73,6 +74,7 @@ import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Currency;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -92,7 +94,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
private final String STATE_TAB_INDEX = "savedTab";
|
||||
private final String STATE_TEMP_CARD = "tempLoyaltyCard";
|
||||
private final String STATE_TEMP_CARD_FIELD = "tempLoyaltyCardField";
|
||||
private final String STATE_REQUESTED_IMAGE = "requestedImage";
|
||||
private final String STATE_FRONT_IMAGE_UNSAVED = "frontImageUnsaved";
|
||||
private final String STATE_BACK_IMAGE_UNSAVED = "backImageUnsaved";
|
||||
@@ -104,9 +105,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
private final String STATE_ICON_REMOVED = "iconRemoved";
|
||||
private final String STATE_OPEN_SET_ICON_MENU = "openSetIconMenu";
|
||||
|
||||
private static final String PICK_DATE_REQUEST_KEY = "pick_date_request";
|
||||
private static final String NEWLY_PICKED_DATE_ARGUMENT_KEY = "newly_picked_date";
|
||||
|
||||
private final String TEMP_CAMERA_IMAGE_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_camera_image.jpg";
|
||||
private final String TEMP_CROP_IMAGE_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_crop_image.png";
|
||||
private final Bitmap.CompressFormat TEMP_CROP_IMAGE_FORMAT = Bitmap.CompressFormat.PNG;
|
||||
@@ -184,7 +182,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
HashMap<String, String> currencySymbols = new HashMap<>();
|
||||
|
||||
LoyaltyCard tempLoyaltyCard;
|
||||
LoyaltyCardField tempLoyaltyCardField;
|
||||
|
||||
ActivityResultLauncher<Uri> mPhotoTakerLauncher;
|
||||
ActivityResultLauncher<Intent> mPhotoPickerLauncher;
|
||||
@@ -270,7 +267,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
tabs = binding.tabs;
|
||||
savedInstanceState.putInt(STATE_TAB_INDEX, tabs.getSelectedTabPosition());
|
||||
savedInstanceState.putParcelable(STATE_TEMP_CARD, tempLoyaltyCard);
|
||||
savedInstanceState.putSerializable(STATE_TEMP_CARD_FIELD, tempLoyaltyCardField);
|
||||
savedInstanceState.putInt(STATE_REQUESTED_IMAGE, mRequestedImage);
|
||||
|
||||
Object cardImageFrontObj = cardImageFront.getTag();
|
||||
@@ -306,7 +302,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
onRestoring = true;
|
||||
tempLoyaltyCard = savedInstanceState.getParcelable(STATE_TEMP_CARD);
|
||||
tempLoyaltyCardField = (LoyaltyCardField) savedInstanceState.getSerializable(STATE_TEMP_CARD_FIELD);
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
tabs = binding.tabs;
|
||||
tabs.selectTab(tabs.getTabAt(savedInstanceState.getInt(STATE_TAB_INDEX)));
|
||||
@@ -392,7 +387,20 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
addDateFieldTextChangedListener(expiryField, R.string.never, R.string.chooseExpiryDate, LoyaltyCardField.expiry);
|
||||
|
||||
setMaterialDatePickerResultListener();
|
||||
DatePickerFragment.registerDatePickListener(this, (textFieldToEdit, newDate) -> {
|
||||
switch (textFieldToEdit) {
|
||||
case validFrom:
|
||||
formatDateField(this, validFromField, newDate);
|
||||
updateTempState(LoyaltyCardField.validFrom, newDate);
|
||||
break;
|
||||
case expiry:
|
||||
formatDateField(this, expiryField, newDate);
|
||||
updateTempState(LoyaltyCardField.expiry, newDate);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unexpected field: " + textFieldToEdit);
|
||||
}
|
||||
});
|
||||
|
||||
balanceField.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus && !onResuming && !onRestoring) {
|
||||
@@ -646,22 +654,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
Log.d("barcode card id editor", "barcode and card id editor picker returned without an intent");
|
||||
return;
|
||||
}
|
||||
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, getApplicationContext());
|
||||
|
||||
List<BarcodeValues> barcodeValuesList = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, getApplicationContext());
|
||||
|
||||
Utils.makeUserChooseBarcodeFromList(this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
|
||||
@Override
|
||||
public void onUserChoseBarcode(BarcodeValues barcodeValues) {
|
||||
cardId = barcodeValues.content();
|
||||
barcodeType = barcodeValues.format();
|
||||
barcodeId = "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserDismissedSelector() {
|
||||
|
||||
}
|
||||
});
|
||||
cardId = barcodeValues.content();
|
||||
barcodeType = barcodeValues.format();
|
||||
barcodeId = "";
|
||||
}
|
||||
});
|
||||
|
||||
@@ -708,13 +705,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
});
|
||||
|
||||
mCropperOptions = new UCrop.Options();
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
askBeforeQuitIfChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ucrop 2.2.6 initial aspect ratio is glitched when 0x0 is used as the initial ratio option
|
||||
@@ -788,7 +778,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Override
|
||||
protected void onResume() {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
Log.i(TAG, "To view card: " + loyaltyCardId);
|
||||
@@ -1026,14 +1016,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
if (!lastValue.toString().equals(getString(chooseDateOptionStringId))) {
|
||||
dateField.setText(lastValue);
|
||||
}
|
||||
showDatePicker(
|
||||
DialogFragment datePickerFragment = DatePickerFragment.newInstance(
|
||||
loyaltyCardField,
|
||||
(Date) dateField.getTag(),
|
||||
// if the expiry date is being set, set date picker's minDate to the 'valid from' date
|
||||
loyaltyCardField == LoyaltyCardField.expiry ? (Date) validFromField.getTag() : null,
|
||||
// if the 'valid from' date is being set, set date picker's maxDate to the expiry date
|
||||
loyaltyCardField == LoyaltyCardField.validFrom ? (Date) expiryField.getTag() : null
|
||||
);
|
||||
loyaltyCardField == LoyaltyCardField.validFrom ? (Date) expiryField.getTag() : null);
|
||||
datePickerFragment.show(getSupportFragmentManager(), "datePicker");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1074,6 +1064,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
askBeforeQuitIfChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
@@ -1384,106 +1379,103 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
||||
// Nothing to do, no change made
|
||||
}
|
||||
|
||||
private void showDatePicker(
|
||||
LoyaltyCardField loyaltyCardField,
|
||||
@Nullable Date selectedDate,
|
||||
@Nullable Date minDate,
|
||||
@Nullable Date maxDate
|
||||
) {
|
||||
// Create a new instance of MaterialDatePicker and return it
|
||||
long startDate = minDate != null ? minDate.getTime() : getDefaultMinDateOfDatePicker();
|
||||
long endDate = maxDate != null ? maxDate.getTime() : getDefaultMaxDateOfDatePicker();
|
||||
public static class DatePickerFragment extends DialogFragment
|
||||
implements DatePickerDialog.OnDateSetListener {
|
||||
|
||||
CalendarConstraints.DateValidator dateValidator;
|
||||
switch (loyaltyCardField) {
|
||||
case validFrom:
|
||||
dateValidator = DateValidatorPointBackward.before(endDate);
|
||||
break;
|
||||
case expiry:
|
||||
dateValidator = DateValidatorPointForward.from(startDate);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unexpected field: " + loyaltyCardField);
|
||||
public interface OnDatePickListener {
|
||||
void onDatePicked(@NonNull LoyaltyCardField textFieldToEdit, @NonNull Date newDate);
|
||||
}
|
||||
|
||||
CalendarConstraints calendarConstraints = new CalendarConstraints.Builder()
|
||||
.setValidator(dateValidator)
|
||||
.setStart(startDate)
|
||||
.setEnd(endDate)
|
||||
.build();
|
||||
private static final String TEXT_FIELD_TO_EDIT_ARGUMENT_KEY = "text_field_to_edit";
|
||||
private static final String CURRENT_DATE_ARGUMENT_KEY = "current_date";
|
||||
private static final String MIN_DATE_ARGUMENT_KEY = "min_date";
|
||||
private static final String MAX_DATE_ARGUMENT_KEY = "max_date";
|
||||
private static final String PICK_DATE_REQUEST_KEY = "pick_date_request";
|
||||
private static final String NEWLY_PICKED_DATE_ARGUMENT_KEY = "newly_picked_date";
|
||||
|
||||
// Use the selected date as the default date in the picker
|
||||
final Calendar calendar = Calendar.getInstance();
|
||||
if (selectedDate != null) {
|
||||
calendar.setTime(selectedDate);
|
||||
LoyaltyCardField textFieldEdit;
|
||||
@Nullable
|
||||
Date minDate;
|
||||
@Nullable
|
||||
Date maxDate;
|
||||
|
||||
public static DatePickerFragment newInstance(@NonNull LoyaltyCardField textField, @Nullable Date currentDate, @Nullable Date minDate, @Nullable Date maxDate) {
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable(TEXT_FIELD_TO_EDIT_ARGUMENT_KEY, textField);
|
||||
args.putSerializable(CURRENT_DATE_ARGUMENT_KEY, currentDate);
|
||||
args.putSerializable(MIN_DATE_ARGUMENT_KEY, minDate);
|
||||
args.putSerializable(MAX_DATE_ARGUMENT_KEY, maxDate);
|
||||
DatePickerFragment fragment = new DatePickerFragment();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
MaterialDatePicker<Long> materialDatePicker = MaterialDatePicker.Builder.datePicker()
|
||||
.setSelection(calendar.getTimeInMillis())
|
||||
.setCalendarConstraints(calendarConstraints)
|
||||
.build();
|
||||
public static void registerDatePickListener(@NonNull AppCompatActivity activity, @NonNull OnDatePickListener listener) {
|
||||
activity.getSupportFragmentManager().setFragmentResultListener(
|
||||
PICK_DATE_REQUEST_KEY,
|
||||
activity,
|
||||
(requestKey, result) -> listener.onDatePicked(
|
||||
(LoyaltyCardField) Objects.requireNonNull(result.getSerializable(TEXT_FIELD_TO_EDIT_ARGUMENT_KEY)),
|
||||
(Date) Objects.requireNonNull(result.getSerializable(NEWLY_PICKED_DATE_ARGUMENT_KEY))));
|
||||
}
|
||||
|
||||
// Required to handle configuration changes
|
||||
// See https://github.com/material-components/material-components-android/issues/1688
|
||||
tempLoyaltyCardField = loyaltyCardField;
|
||||
getSupportFragmentManager().addFragmentOnAttachListener((fragmentManager, fragment) -> {
|
||||
if (fragment instanceof MaterialDatePicker && Objects.equals(fragment.getTag(), PICK_DATE_REQUEST_KEY)) {
|
||||
((MaterialDatePicker<Long>) fragment).addOnPositiveButtonClickListener(selection -> {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(NEWLY_PICKED_DATE_ARGUMENT_KEY, selection);
|
||||
getSupportFragmentManager().setFragmentResult(PICK_DATE_REQUEST_KEY, args);
|
||||
});
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Bundle args = requireArguments();
|
||||
textFieldEdit = (LoyaltyCardField) args.getSerializable(TEXT_FIELD_TO_EDIT_ARGUMENT_KEY);
|
||||
minDate = (Date) args.getSerializable(MIN_DATE_ARGUMENT_KEY);
|
||||
maxDate = (Date) args.getSerializable(MAX_DATE_ARGUMENT_KEY);
|
||||
// Use the current date as the default date in the picker
|
||||
final Calendar c = Calendar.getInstance();
|
||||
|
||||
Date date = (Date) args.getSerializable(CURRENT_DATE_ARGUMENT_KEY);
|
||||
if (date != null) {
|
||||
c.setTime(date);
|
||||
}
|
||||
});
|
||||
|
||||
materialDatePicker.show(getSupportFragmentManager(), PICK_DATE_REQUEST_KEY);
|
||||
}
|
||||
int year = c.get(Calendar.YEAR);
|
||||
int month = c.get(Calendar.MONTH);
|
||||
int day = c.get(Calendar.DAY_OF_MONTH);
|
||||
|
||||
// Required to handle configuration changes
|
||||
// See https://github.com/material-components/material-components-android/issues/1688
|
||||
private void setMaterialDatePickerResultListener() {
|
||||
MaterialDatePicker<Long> fragment = (MaterialDatePicker<Long>) getSupportFragmentManager().findFragmentByTag(PICK_DATE_REQUEST_KEY);
|
||||
if (fragment != null) {
|
||||
fragment.addOnPositiveButtonClickListener(selection -> {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(NEWLY_PICKED_DATE_ARGUMENT_KEY, selection);
|
||||
getSupportFragmentManager().setFragmentResult(PICK_DATE_REQUEST_KEY, args);
|
||||
});
|
||||
// Create a new instance of DatePickerDialog and return it
|
||||
DatePickerDialog datePickerDialog = new DatePickerDialog(getActivity(), this, year, month, day);
|
||||
datePickerDialog.getDatePicker().setMinDate(minDate != null ? minDate.getTime() : getDefaultMinDateOfDatePicker());
|
||||
datePickerDialog.getDatePicker().setMaxDate(maxDate != null ? maxDate.getTime() : getDefaultMaxDateOfDatePicker());
|
||||
return datePickerDialog;
|
||||
}
|
||||
|
||||
getSupportFragmentManager().setFragmentResultListener(
|
||||
PICK_DATE_REQUEST_KEY,
|
||||
this,
|
||||
(requestKey, result) -> {
|
||||
long selection = result.getLong(NEWLY_PICKED_DATE_ARGUMENT_KEY);
|
||||
private long getDefaultMinDateOfDatePicker() {
|
||||
Calendar minDateCalendar = Calendar.getInstance();
|
||||
minDateCalendar.set(1970, 0, 1);
|
||||
return minDateCalendar.getTimeInMillis();
|
||||
}
|
||||
|
||||
Date newDate = new Date(selection);
|
||||
switch (tempLoyaltyCardField) {
|
||||
case validFrom:
|
||||
formatDateField(LoyaltyCardEditActivity.this, validFromField, newDate);
|
||||
updateTempState(LoyaltyCardField.validFrom, newDate);
|
||||
break;
|
||||
case expiry:
|
||||
formatDateField(LoyaltyCardEditActivity.this, expiryField, newDate);
|
||||
updateTempState(LoyaltyCardField.expiry, newDate);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unexpected field: " + tempLoyaltyCardField);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
private long getDefaultMaxDateOfDatePicker() {
|
||||
Calendar maxDateCalendar = Calendar.getInstance();
|
||||
maxDateCalendar.set(2100, 11, 31);
|
||||
return maxDateCalendar.getTimeInMillis();
|
||||
}
|
||||
|
||||
private long getDefaultMinDateOfDatePicker() {
|
||||
Calendar minDateCalendar = Calendar.getInstance();
|
||||
minDateCalendar.set(1970, 0, 1);
|
||||
return minDateCalendar.getTimeInMillis();
|
||||
}
|
||||
public void onDateSet(DatePicker view, int year, int month, int day) {
|
||||
Calendar c = new GregorianCalendar();
|
||||
c.set(Calendar.YEAR, year);
|
||||
c.set(Calendar.MONTH, month);
|
||||
c.set(Calendar.DAY_OF_MONTH, day);
|
||||
c.set(Calendar.HOUR_OF_DAY, 0);
|
||||
c.set(Calendar.MINUTE, 0);
|
||||
c.set(Calendar.SECOND, 0);
|
||||
c.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
private long getDefaultMaxDateOfDatePicker() {
|
||||
Calendar maxDateCalendar = Calendar.getInstance();
|
||||
maxDateCalendar.set(2100, 11, 31);
|
||||
return maxDateCalendar.getTimeInMillis();
|
||||
long unixTime = c.getTimeInMillis();
|
||||
|
||||
Date date = new Date(unixTime);
|
||||
|
||||
Bundle result = new Bundle();
|
||||
result.putSerializable(TEXT_FIELD_TO_EDIT_ARGUMENT_KEY, textFieldEdit);
|
||||
result.putSerializable(NEWLY_PICKED_DATE_ARGUMENT_KEY, date);
|
||||
getParentFragmentManager().setFragmentResult(PICK_DATE_REQUEST_KEY, result);
|
||||
}
|
||||
}
|
||||
|
||||
private void doSave() {
|
||||
|
||||
@@ -3,7 +3,6 @@ package protect.card_locker;
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
public class LoyaltyCardLockerApplication extends Application {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.ColorStateList;
|
||||
@@ -19,6 +20,7 @@ import android.text.method.DigitsKeyListener;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.util.Linkify;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -36,17 +38,19 @@ import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.core.graphics.BlendModeColorFilterCompat;
|
||||
import androidx.core.graphics.BlendModeCompat;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
|
||||
import androidx.core.widget.TextViewCompat;
|
||||
|
||||
import com.google.android.material.color.MaterialColors;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
@@ -59,6 +63,7 @@ import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Currency;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
@@ -107,25 +112,22 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
return;
|
||||
}
|
||||
|
||||
ImageType imageType = imageTypes.get(mainImageIndex);
|
||||
|
||||
// If the barcode is shown, switch to fullscreen layout
|
||||
if (imageType == ImageType.BARCODE) {
|
||||
if (imageTypes.get(mainImageIndex) == ImageType.BARCODE) {
|
||||
setFullscreen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is an image, open it in the gallery.
|
||||
openImageInGallery(imageType);
|
||||
openCurrentMainImageInGallery();
|
||||
}
|
||||
|
||||
private void openImageInGallery(ImageType imageType) {
|
||||
private void openCurrentMainImageInGallery() {
|
||||
ImageType wantedImageType = imageTypes.get(mainImageIndex);
|
||||
|
||||
File file = null;
|
||||
|
||||
switch (imageType) {
|
||||
case ICON:
|
||||
file = Utils.retrieveCardImageAsFile(this, loyaltyCardId, ImageLocationType.icon);
|
||||
break;
|
||||
switch (wantedImageType) {
|
||||
case IMAGE_FRONT:
|
||||
file = Utils.retrieveCardImageAsFile(this, loyaltyCardId, ImageLocationType.front);
|
||||
break;
|
||||
@@ -173,7 +175,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
enum ImageType {
|
||||
NONE,
|
||||
ICON,
|
||||
BARCODE,
|
||||
IMAGE_FRONT,
|
||||
IMAGE_BACK
|
||||
@@ -227,9 +228,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
settings = new Settings(this);
|
||||
|
||||
String cardOrientation = settings.getCardViewOrientation();
|
||||
if (cardOrientation.equals(getString(R.string.settings_key_follow_sensor_orientation))) {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
|
||||
} else if (cardOrientation.equals(getString(R.string.settings_key_lock_on_opening_orientation))) {
|
||||
if (cardOrientation.equals(getString(R.string.settings_key_lock_on_opening_orientation))) {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
|
||||
} else if (cardOrientation.equals(getString(R.string.settings_key_portrait_orientation))) {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
@@ -301,13 +300,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
binding.bottomAppBarNextButton.setOnClickListener(view -> prevNextCard(true));
|
||||
binding.bottomAppBarUpdateBalanceButton.setOnClickListener(view -> showBalanceUpdateDialog());
|
||||
|
||||
binding.iconContainer.setOnClickListener(view -> {
|
||||
if (Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.icon) != null) {
|
||||
openImageInGallery(ImageType.ICON);
|
||||
} else {
|
||||
Toast.makeText(LoyaltyCardViewActivity.this, R.string.icon_header_click_text, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
binding.iconContainer.setOnClickListener(view -> Toast.makeText(LoyaltyCardViewActivity.this, R.string.icon_header_click_text, Toast.LENGTH_LONG).show());
|
||||
binding.iconContainer.setOnLongClickListener(view -> {
|
||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
@@ -329,17 +322,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
return true;
|
||||
});
|
||||
binding.fullscreenImage.setOnClickListener(view -> onMainImageTap());
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (isFullscreen) {
|
||||
setFullscreen(false);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private SpannableStringBuilder padSpannableString(SpannableStringBuilder spannableStringBuilder) {
|
||||
@@ -418,11 +400,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
private void showBalanceUpdateDialog() {
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||
|
||||
// Header
|
||||
builder.setTitle(R.string.updateBalanceTitle);
|
||||
|
||||
// Layout
|
||||
FrameLayout container = new FrameLayout(this);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
@@ -440,91 +418,61 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
currentTextview.setText(getString(R.string.currentBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)));
|
||||
layout.addView(currentTextview);
|
||||
|
||||
TextView updateTextView = new TextView(this);
|
||||
updateTextView.setText(getString(R.string.newBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)));
|
||||
layout.addView(updateTextView);
|
||||
|
||||
final TextInputEditText input = new TextInputEditText(this);
|
||||
input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
Context dialogContext = this;
|
||||
input.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
input.setKeyListener(DigitsKeyListener.getInstance("0123456789,."));
|
||||
input.setHint(R.string.updateBalanceHint);
|
||||
input.addTextChangedListener(new SimpleTextWatcher() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
BigDecimal newBalance;
|
||||
try {
|
||||
newBalance = calculateNewBalance(loyaltyCard.balance, loyaltyCard.balanceType, s.toString());
|
||||
} catch (ParseException e) {
|
||||
input.setTag(null);
|
||||
updateTextView.setText(getString(R.string.newBalanceSentence, Utils.formatBalance(dialogContext, loyaltyCard.balance, loyaltyCard.balanceType)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Save new balance into this element
|
||||
input.setTag(newBalance);
|
||||
updateTextView.setText(getString(R.string.newBalanceSentence, Utils.formatBalance(dialogContext, newBalance, loyaltyCard.balanceType)));
|
||||
}
|
||||
});
|
||||
layout.addView(input);
|
||||
layout.setLayoutParams(params);
|
||||
container.addView(layout);
|
||||
|
||||
// Set layout
|
||||
builder.setView(container);
|
||||
|
||||
// Buttons
|
||||
builder.setPositiveButton(R.string.spend, (dialogInterface, i) -> {
|
||||
// Calculate and update balance
|
||||
try {
|
||||
BigDecimal balanceChange = Utils.parseBalance(input.getText().toString(), loyaltyCard.balanceType);
|
||||
BigDecimal newBalance = loyaltyCard.balance.subtract(balanceChange).max(new BigDecimal(0));
|
||||
DBHelper.updateLoyaltyCardBalance(database, loyaltyCardId, newBalance);
|
||||
} catch (ParseException e) {
|
||||
Toast.makeText(getApplicationContext(), R.string.amountParsingFailed, Toast.LENGTH_LONG).show();
|
||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||
// Grab calculated balance from input field
|
||||
BigDecimal newBalance = (BigDecimal) input.getTag();
|
||||
if (newBalance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reload state
|
||||
// Actually update balance
|
||||
DBHelper.updateLoyaltyCardBalance(database, loyaltyCardId, newBalance);
|
||||
// Reload UI
|
||||
this.onResume();
|
||||
|
||||
// Show new balance
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.newBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)), Toast.LENGTH_LONG).show();
|
||||
});
|
||||
builder.setNegativeButton(R.string.receive, (dialogInterface, i) -> {
|
||||
// Calculate and update balance
|
||||
try {
|
||||
BigDecimal balanceChange = Utils.parseBalance(input.getText().toString(), loyaltyCard.balanceType);
|
||||
BigDecimal newBalance = loyaltyCard.balance.add(balanceChange);
|
||||
DBHelper.updateLoyaltyCardBalance(database, loyaltyCardId, newBalance);
|
||||
} catch (ParseException e) {
|
||||
Toast.makeText(getApplicationContext(), R.string.amountParsingFailed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
// Reload state
|
||||
this.onResume();
|
||||
|
||||
// Show new balance
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.newBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)), Toast.LENGTH_LONG).show();
|
||||
});
|
||||
builder.setNeutralButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
||||
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
// Now that the dialog exists, we can bind something that affects the buttons
|
||||
input.addTextChangedListener(new SimpleTextWatcher() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
BigDecimal balanceChange;
|
||||
|
||||
try {
|
||||
balanceChange = Utils.parseBalance(s.toString(), loyaltyCard.balanceType);
|
||||
} catch (ParseException e) {
|
||||
input.setError(getString(R.string.amountParsingFailed));
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
input.setError(null);
|
||||
if (balanceChange.equals(new BigDecimal(0))) {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false);
|
||||
} else {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
|
||||
// Disable buttons (must be done **after** dialog is shown to prevent crash
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false);
|
||||
|
||||
// Set focus on input field
|
||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
input.requestFocus();
|
||||
}
|
||||
|
||||
private BigDecimal calculateNewBalance(BigDecimal currentBalance, Currency currency, String unparsedSubtraction) throws ParseException {
|
||||
BigDecimal subtraction = Utils.parseBalance(unparsedSubtraction, currency);
|
||||
return currentBalance.subtract(subtraction).max(new BigDecimal(0));
|
||||
}
|
||||
|
||||
private void setBottomAppBarButtonState() {
|
||||
if (!loyaltyCard.note.isEmpty() || !loyaltyCardGroups.isEmpty() || hasBalance(loyaltyCard) || loyaltyCard.validFrom != null || loyaltyCard.expiry != null) {
|
||||
binding.bottomAppBarInfoButton.setVisibility(View.VISIBLE);
|
||||
@@ -596,8 +544,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
activityOverridesNavBarColor = true;
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
Log.i(TAG, "To view card: " + loyaltyCardId);
|
||||
@@ -675,11 +622,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
int darkenedColor = ColorUtils.blendARGB(backgroundHeaderColor, Color.BLACK, 0.1f);
|
||||
binding.barcodeScaler.setProgressTintList(ColorStateList.valueOf(darkenedColor));
|
||||
binding.barcodeScaler.setThumbTintList(ColorStateList.valueOf(darkenedColor));
|
||||
|
||||
// Set bottomAppBar and system navigation bar color
|
||||
binding.bottomAppBar.setBackgroundColor(darkenedColor);
|
||||
Utils.setNavigationBarColor(null, window, darkenedColor, Utils.needsDarkForeground(darkenedColor));
|
||||
|
||||
int complementaryColor = Utils.getComplementaryColor(darkenedColor);
|
||||
binding.fabEdit.setBackgroundTintList(ColorStateList.valueOf(complementaryColor));
|
||||
Drawable editButtonIcon = binding.fabEdit.getDrawable();
|
||||
@@ -733,8 +676,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
DBHelper.updateLoyaltyCardLastUsed(database, loyaltyCard.id);
|
||||
|
||||
invalidateOptionsMenu();
|
||||
|
||||
ShortcutHelper.updateShortcuts(this, loyaltyCard);
|
||||
}
|
||||
|
||||
private void setStateBasedOnImageTypes() {
|
||||
@@ -763,6 +704,16 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
imageButton.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(backgroundNeedsDarkIcons ? Color.BLACK : Color.WHITE, BlendModeCompat.SRC_ATOP));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (isFullscreen) {
|
||||
setFullscreen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.card_view_menu, menu);
|
||||
@@ -834,8 +785,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
DBHelper.updateLoyaltyCardArchiveStatus(database, loyaltyCardId, 1);
|
||||
Toast.makeText(LoyaltyCardViewActivity.this, R.string.archived, Toast.LENGTH_LONG).show();
|
||||
|
||||
ShortcutHelper.removeShortcut(LoyaltyCardViewActivity.this, loyaltyCardId);
|
||||
|
||||
// Re-init loyaltyCard with new data from DB
|
||||
onResume();
|
||||
invalidateOptionsMenu();
|
||||
@@ -1106,14 +1055,10 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
// Set Android to fullscreen mode
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
Window window = getWindow();
|
||||
if (window != null) {
|
||||
window.setDecorFitsSystemWindows(false);
|
||||
WindowInsetsController wic = window.getInsetsController();
|
||||
if (wic != null) {
|
||||
wic.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
|
||||
wic.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
}
|
||||
getWindow().setDecorFitsSystemWindows(false);
|
||||
if (getWindow().getInsetsController() != null) {
|
||||
getWindow().getInsetsController().hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
|
||||
getWindow().getInsetsController().setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
}
|
||||
} else {
|
||||
setFullscreenModeSdkLessThan30();
|
||||
@@ -1140,14 +1085,10 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
// Unset fullscreen mode
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
Window window = getWindow();
|
||||
if (window != null) {
|
||||
window.setDecorFitsSystemWindows(true);
|
||||
WindowInsetsController wic = window.getInsetsController();
|
||||
if (wic != null) {
|
||||
wic.show(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
|
||||
wic.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT);
|
||||
}
|
||||
getWindow().setDecorFitsSystemWindows(true);
|
||||
if (getWindow().getInsetsController() != null) {
|
||||
getWindow().getInsetsController().show(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
|
||||
getWindow().getInsetsController().setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT);
|
||||
}
|
||||
} else {
|
||||
unsetFullscreenModeSdkLessThan30();
|
||||
@@ -1159,25 +1100,19 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void unsetFullscreenModeSdkLessThan30() {
|
||||
Window window = getWindow();
|
||||
if (window != null) {
|
||||
window.getDecorView().setSystemUiVisibility(
|
||||
window.getDecorView().getSystemUiVisibility()
|
||||
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
}
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
getWindow().getDecorView().getSystemUiVisibility()
|
||||
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void setFullscreenModeSdkLessThan30() {
|
||||
Window window = getWindow();
|
||||
if (window != null) {
|
||||
window.getDecorView().setSystemUiVisibility(
|
||||
window.getDecorView().getSystemUiVisibility()
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
}
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
getWindow().getDecorView().getSystemUiVisibility()
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@ package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.SearchManager;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.CursorIndexOutOfBoundsException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
@@ -18,7 +22,6 @@ import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@@ -31,6 +34,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -66,7 +70,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
private View mNoGroupCardsText;
|
||||
private TabLayout groupsTabLayout;
|
||||
|
||||
private Runnable mUpdateLoyaltyCardListRunnable;
|
||||
private Runnable mSwapLoyaltyCardListCursor;
|
||||
|
||||
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
|
||||
private ActivityResultLauncher<Intent> mSettingsLauncher;
|
||||
@@ -85,7 +89,35 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode inputMode, MenuItem inputItem) {
|
||||
if (inputItem.getItemId() == R.id.action_share) {
|
||||
if (inputItem.getItemId() == R.id.action_copy_to_clipboard) {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||
|
||||
String clipboardData;
|
||||
int cardCount = mAdapter.getSelectedItemCount();
|
||||
|
||||
if (cardCount == 1) {
|
||||
clipboardData = mAdapter.getSelectedItems().get(0).cardId;
|
||||
} else {
|
||||
StringBuilder cardIds = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
LoyaltyCard loyaltyCard = mAdapter.getSelectedItems().get(i);
|
||||
|
||||
cardIds.append(loyaltyCard.store + ": " + loyaltyCard.cardId);
|
||||
if (i < (cardCount - 1)) {
|
||||
cardIds.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
clipboardData = cardIds.toString();
|
||||
}
|
||||
|
||||
ClipData clip = ClipData.newPlainText(getString(R.string.card_ids_copied), clipboardData);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(MainActivity.this, cardCount > 1 ? R.string.copy_to_clipboard_multiple_toast : R.string.copy_to_clipboard_toast, Toast.LENGTH_LONG).show();
|
||||
inputMode.finish();
|
||||
return true;
|
||||
} else if (inputItem.getItemId() == R.id.action_share) {
|
||||
final ImportURIHelper importURIHelper = new ImportURIHelper(MainActivity.this);
|
||||
try {
|
||||
importURIHelper.startShareIntent(mAdapter.getSelectedItems());
|
||||
@@ -147,7 +179,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||
Log.d(TAG, "Archiving card: " + loyaltyCard.id);
|
||||
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 1);
|
||||
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
|
||||
updateLoyaltyCardList(false);
|
||||
inputMode.finish();
|
||||
invalidateOptionsMenu();
|
||||
@@ -192,13 +223,12 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle inputSavedInstanceState) {
|
||||
extractIntentFields(getIntent());
|
||||
SplashScreen.installSplashScreen(this);
|
||||
super.onCreate(inputSavedInstanceState);
|
||||
|
||||
// We should extract the share intent after we called the super.onCreate as it may need to spawn a dialog window and the app needs to be initialized to not crash
|
||||
extractIntentFields(getIntent());
|
||||
|
||||
binding = MainActivityBinding.inflate(getLayoutInflater());
|
||||
setTitle(R.string.app_name);
|
||||
setContentView(binding.getRoot());
|
||||
setSupportActionBar(binding.toolbar);
|
||||
groupsTabLayout = binding.groups;
|
||||
@@ -206,8 +236,13 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
|
||||
mDatabase = new DBHelper(this).getWritableDatabase();
|
||||
|
||||
mUpdateLoyaltyCardListRunnable = () -> {
|
||||
updateLoyaltyCardList(false);
|
||||
mSwapLoyaltyCardListCursor = () -> {
|
||||
Group group = null;
|
||||
if (mGroup != null) {
|
||||
group = (Group) mGroup;
|
||||
}
|
||||
|
||||
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase, mFilter, group, mOrder, mOrderDirection, mAdapter.showingArchivedCards() ? DBHelper.LoyaltyCardArchiveFilter.All : DBHelper.LoyaltyCardArchiveFilter.Unarchived));
|
||||
};
|
||||
|
||||
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@@ -242,7 +277,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
mNoGroupCardsText = contentMainBinding.noGroupCardsText;
|
||||
mCardList = contentMainBinding.list;
|
||||
|
||||
mAdapter = new LoyaltyCardCursorAdapter(this, null, this, mUpdateLoyaltyCardListRunnable);
|
||||
mAdapter = new LoyaltyCardCursorAdapter(this, null, this, mSwapLoyaltyCardListCursor);
|
||||
mCardList.setAdapter(mAdapter);
|
||||
registerForContextMenu(mCardList);
|
||||
|
||||
@@ -287,11 +322,11 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
}
|
||||
|
||||
Intent intent = result.getData();
|
||||
List<BarcodeValues> barcodeValuesList = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, this);
|
||||
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, this);
|
||||
|
||||
Bundle inputBundle = intent.getExtras();
|
||||
String group = inputBundle != null ? inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
|
||||
processBarcodeValuesList(barcodeValuesList, group, false);
|
||||
processBarcodeValues(barcodeValues, group);
|
||||
});
|
||||
|
||||
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||
@@ -302,17 +337,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mSearchView != null && !mSearchView.isIconified()) {
|
||||
mSearchView.setIconified(true);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -387,6 +411,16 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
addButton.bringToFront();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mSearchView != null && !mSearchView.isIconified()) {
|
||||
mSearchView.setIconified(true);
|
||||
return;
|
||||
}
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
private void displayCardSetupOptions(Menu menu, boolean shouldShow) {
|
||||
for (int id : new int[]{R.id.action_search, R.id.action_display_options, R.id.action_sort}) {
|
||||
menu.findItem(id).setVisible(shouldShow);
|
||||
@@ -398,12 +432,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
}
|
||||
|
||||
private void updateLoyaltyCardList(boolean updateCount) {
|
||||
Group group = null;
|
||||
if (mGroup != null) {
|
||||
group = (Group) mGroup;
|
||||
}
|
||||
|
||||
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase, mFilter, group, mOrder, mOrderDirection, mAdapter.showingArchivedCards() ? DBHelper.LoyaltyCardArchiveFilter.All : DBHelper.LoyaltyCardArchiveFilter.Unarchived));
|
||||
mSwapLoyaltyCardListCursor.run();
|
||||
|
||||
if (updateCount) {
|
||||
updateLoyaltyCardCount();
|
||||
@@ -446,57 +475,63 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
}
|
||||
}
|
||||
|
||||
private void processBarcodeValuesList(List<BarcodeValues> barcodeValuesList, String group, boolean closeAppOnNoBarcode) {
|
||||
if (barcodeValuesList.isEmpty()) {
|
||||
private void processBarcodeValues(BarcodeValues barcodeValues, String group) {
|
||||
if (barcodeValues.isEmpty()) {
|
||||
throw new IllegalArgumentException("barcodesValues may not be empty");
|
||||
}
|
||||
|
||||
Utils.makeUserChooseBarcodeFromList(MainActivity.this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
|
||||
@Override
|
||||
public void onUserChoseBarcode(BarcodeValues barcodeValues) {
|
||||
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
Bundle newBundle = new Bundle();
|
||||
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
|
||||
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
|
||||
if (group != null) {
|
||||
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
|
||||
}
|
||||
newIntent.putExtras(newBundle);
|
||||
startActivity(newIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserDismissedSelector() {
|
||||
if (closeAppOnNoBarcode) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
Bundle newBundle = new Bundle();
|
||||
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
|
||||
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
|
||||
if (group != null) {
|
||||
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
|
||||
}
|
||||
newIntent.putExtras(newBundle);
|
||||
startActivity(newIntent);
|
||||
}
|
||||
|
||||
private void onSharedIntent(Intent intent) {
|
||||
String receivedAction = intent.getAction();
|
||||
String receivedType = intent.getType();
|
||||
|
||||
// Check if an image or file was shared to us
|
||||
// Check if an image was shared to us
|
||||
if (Intent.ACTION_SEND.equals(receivedAction)) {
|
||||
List<BarcodeValues> barcodeValuesList;
|
||||
|
||||
if (receivedType.startsWith("image/")) {
|
||||
barcodeValuesList = Utils.retrieveBarcodesFromImage(this, intent.getParcelableExtra(Intent.EXTRA_STREAM));
|
||||
} else if (receivedType.equals("application/pdf")) {
|
||||
barcodeValuesList = Utils.retrieveBarcodesFromPdf(this, intent.getParcelableExtra(Intent.EXTRA_STREAM));
|
||||
} else {
|
||||
if (!receivedType.startsWith("image/")) {
|
||||
Log.e(TAG, "Wrong mime-type");
|
||||
return;
|
||||
}
|
||||
|
||||
if (barcodeValuesList.isEmpty()) {
|
||||
BarcodeValues barcodeValues;
|
||||
Bitmap bitmap;
|
||||
|
||||
Uri data = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
if (data == null) {
|
||||
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
processBarcodeValuesList(barcodeValuesList, null, true);
|
||||
try {
|
||||
bitmap = Utils.retrieveImageFromUri(this, data);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error getting data from image file");
|
||||
e.printStackTrace();
|
||||
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
barcodeValues = Utils.getBarcodeFromBitmap(bitmap);
|
||||
|
||||
if (barcodeValues.isEmpty()) {
|
||||
Log.i(TAG, "No barcode found in image file");
|
||||
Toast.makeText(this, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
processBarcodeValues(barcodeValues, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +611,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
int id = inputItem.getItemId();
|
||||
|
||||
if (id == android.R.id.home) {
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
onBackPressed();
|
||||
}
|
||||
|
||||
if (id == R.id.action_display_options) {
|
||||
@@ -792,6 +827,8 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
b.putIntegerArrayList("cardList", cardList);
|
||||
intent.putExtras(b);
|
||||
|
||||
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard);
|
||||
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,6 @@ import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
@@ -26,6 +20,11 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import protect.card_locker.databinding.ActivityManageGroupBinding;
|
||||
|
||||
public class ManageGroupActivity extends CatimaAppCompatActivity implements ManageGroupCursorAdapter.CardAdapterListener {
|
||||
@@ -134,13 +133,6 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
|
||||
// this setText is here because content_main.xml is reused from main activity
|
||||
noGroupCardsText.setText(getResources().getText(R.string.noGiftCardsGroup));
|
||||
updateLoyaltyCardList();
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
leaveWithoutSaving();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ArrayList<Integer> adapterStateToIntegerArray(HashMap<Integer, Boolean> adapterState) {
|
||||
@@ -218,9 +210,14 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
leaveWithoutSaving();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,17 +14,17 @@ import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import protect.card_locker.databinding.ManageGroupsActivityBinding;
|
||||
|
||||
public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener {
|
||||
@@ -71,6 +71,11 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
||||
updateGroupList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
private void updateGroupList() {
|
||||
mAdapter.swapCursor(DBHelper.getGroupCursor(mDatabase));
|
||||
|
||||
@@ -243,4 +248,4 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
@@ -62,7 +63,6 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
||||
private static final int COMPAT_SCALE_FACTOR_DIP = 320;
|
||||
|
||||
private static final int PERMISSION_SCAN_ADD_FROM_IMAGE = 100;
|
||||
private static final int PERMISSION_SCAN_ADD_FROM_PDF = 101;
|
||||
|
||||
private CaptureManager capture;
|
||||
private DecoratedBarcodeView barcodeScannerView;
|
||||
@@ -74,7 +74,6 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
||||
private ActivityResultLauncher<Intent> manualAddLauncher;
|
||||
// can't use the pre-made contract because that launches the file manager for image type instead of gallery
|
||||
private ActivityResultLauncher<Intent> photoPickerLauncher;
|
||||
private ActivityResultLauncher<Intent> pdfPickerLauncher;
|
||||
|
||||
static final String STATE_SCANNER_ACTIVE = "scannerActive";
|
||||
private boolean mScannerActive = true;
|
||||
@@ -101,7 +100,6 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
||||
|
||||
manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
|
||||
photoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_IMAGE_FILE, result.getResultCode(), result.getData()));
|
||||
pdfPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_PDF_FILE, result.getResultCode(), result.getData()));
|
||||
customBarcodeScannerBinding.fabOtherOptions.setOnClickListener(view -> {
|
||||
setScannerActive(false);
|
||||
|
||||
@@ -111,8 +109,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
||||
new CharSequence[]{
|
||||
getString(R.string.addWithoutBarcode),
|
||||
getString(R.string.addManually),
|
||||
getString(R.string.addFromImage),
|
||||
getString(R.string.addFromPdfFile)
|
||||
getString(R.string.addFromImage)
|
||||
},
|
||||
(dialogInterface, i) -> {
|
||||
switch (i) {
|
||||
@@ -125,9 +122,6 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
||||
case 2:
|
||||
addFromImage();
|
||||
break;
|
||||
case 3:
|
||||
addFromPdfFile();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown 'Add a card in a different way' dialog option");
|
||||
}
|
||||
@@ -275,24 +269,14 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
||||
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
super.onActivityResult(requestCode, resultCode, intent);
|
||||
|
||||
List<BarcodeValues> barcodeValuesList = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
|
||||
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
|
||||
|
||||
if (barcodeValuesList.isEmpty()) {
|
||||
if (barcodeValues.isEmpty()) {
|
||||
setScannerActive(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Utils.makeUserChooseBarcodeFromList(this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
|
||||
@Override
|
||||
public void onUserChoseBarcode(BarcodeValues barcodeValues) {
|
||||
returnResult(barcodeValues.content(), barcodeValues.format());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserDismissedSelector() {
|
||||
setScannerActive(true);
|
||||
}
|
||||
});
|
||||
returnResult(barcodeValues.content(), barcodeValues.format());
|
||||
}
|
||||
|
||||
private void addWithoutBarcode() {
|
||||
@@ -360,44 +344,32 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
||||
}
|
||||
|
||||
public void addManually() {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ScanActivity.this);
|
||||
builder.setTitle(R.string.add_manually_warning_title);
|
||||
builder.setMessage(R.string.add_manually_warning_message);
|
||||
builder.setPositiveButton(R.string.continue_, (dialog, which) -> {
|
||||
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
|
||||
if (cardId != null) {
|
||||
final Bundle b = new Bundle();
|
||||
b.putString("initialCardId", cardId);
|
||||
i.putExtras(b);
|
||||
}
|
||||
manualAddLauncher.launch(i);
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, (dialog, which) -> setScannerActive(true));
|
||||
builder.setOnCancelListener(dialog -> setScannerActive(true));
|
||||
builder.show();
|
||||
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
|
||||
if (cardId != null) {
|
||||
final Bundle b = new Bundle();
|
||||
b.putString("initialCardId", cardId);
|
||||
i.putExtras(b);
|
||||
}
|
||||
manualAddLauncher.launch(i);
|
||||
}
|
||||
|
||||
public void addFromImage() {
|
||||
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE);
|
||||
}
|
||||
|
||||
public void addFromPdfFile() {
|
||||
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PDF);
|
||||
}
|
||||
|
||||
private void addFromImageOrFileAfterPermission(String mimeType, ActivityResultLauncher<Intent> launcher, int chooserText, int errorMessage) {
|
||||
private void addFromImageAfterPermission() {
|
||||
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
|
||||
photoPickerIntent.setType(mimeType);
|
||||
photoPickerIntent.setType("image/*");
|
||||
Intent contentIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
contentIntent.setType(mimeType);
|
||||
contentIntent.setType("image/*");
|
||||
|
||||
Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(chooserText));
|
||||
Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(R.string.addFromImage));
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { contentIntent });
|
||||
try {
|
||||
launcher.launch(chooserIntent);
|
||||
photoPickerLauncher.launch(chooserIntent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
setScannerActive(true);
|
||||
Toast.makeText(getApplicationContext(), errorMessage, Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, "No activity found to handle intent", e);
|
||||
}
|
||||
}
|
||||
@@ -445,13 +417,9 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
||||
|
||||
if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
|
||||
showCameraPermissionMissingText(!granted);
|
||||
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE || requestCode == PERMISSION_SCAN_ADD_FROM_PDF) {
|
||||
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
|
||||
if (granted) {
|
||||
if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
|
||||
addFromImageOrFileAfterPermission("image/*", photoPickerLauncher, R.string.addFromImage, R.string.failedLaunchingPhotoPicker);
|
||||
} else {
|
||||
addFromImageOrFileAfterPermission("application/pdf", pdfPickerLauncher, R.string.addFromPdfFile, R.string.failedLaunchingFileManager);
|
||||
}
|
||||
addFromImageAfterPermission();
|
||||
} else {
|
||||
setScannerActive(true);
|
||||
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG).show();
|
||||
|
||||
@@ -8,11 +8,6 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -20,6 +15,11 @@ import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
|
||||
class ShortcutHelper {
|
||||
// Android documentation says that no more than 5 shortcuts
|
||||
// are supported. However, that may be too many, as not all
|
||||
@@ -43,11 +43,6 @@ class ShortcutHelper {
|
||||
* used card shortcut is discarded.
|
||||
*/
|
||||
static void updateShortcuts(Context context, LoyaltyCard card) {
|
||||
if (card.archiveStatus == 1) {
|
||||
// Don't add archived card to menu
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedList<ShortcutInfoCompat> list = new LinkedList<>(ShortcutManagerCompat.getDynamicShortcuts(context));
|
||||
|
||||
SQLiteDatabase database = new DBHelper(context).getReadableDatabase();
|
||||
@@ -113,7 +108,18 @@ class ShortcutHelper {
|
||||
* shortcut exists.
|
||||
*/
|
||||
static void removeShortcut(Context context, int cardId) {
|
||||
ShortcutManagerCompat.removeDynamicShortcuts(context, Collections.singletonList(Integer.toString(cardId)));
|
||||
List<ShortcutInfoCompat> list = ShortcutManagerCompat.getDynamicShortcuts(context);
|
||||
|
||||
String shortcutId = Integer.toString(cardId);
|
||||
|
||||
for (int index = 0; index < list.size(); index++) {
|
||||
if (list.get(index).getId().equals(shortcutId)) {
|
||||
list.remove(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ShortcutManagerCompat.setDynamicShortcuts(context, list);
|
||||
}
|
||||
|
||||
static @NotNull
|
||||
|
||||
@@ -7,20 +7,19 @@ import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.google.android.material.color.MaterialColors;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
import com.yalantis.ucrop.UCropActivity;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.view.WindowInsetsControllerCompat;
|
||||
|
||||
import com.google.android.material.color.MaterialColors;
|
||||
import com.google.android.material.textview.MaterialTextView;
|
||||
import com.yalantis.ucrop.UCropActivity;
|
||||
|
||||
public class UCropWrapper extends UCropActivity {
|
||||
public static final String UCROP_TOOLBAR_TYPEFACE_STYLE = "ucop_toolbar_typeface_style";
|
||||
|
||||
@@ -28,18 +27,15 @@ public class UCropWrapper extends UCropActivity {
|
||||
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
boolean darkMode = Utils.isDarkModeEnabled(this);
|
||||
Window window = getWindow();
|
||||
// setup status bar to look like the rest of the app
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
if (window != null) {
|
||||
View decorView = window.getDecorView();
|
||||
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView);
|
||||
wic.setAppearanceLightStatusBars(!darkMode);
|
||||
}
|
||||
View decorView = getWindow().getDecorView();
|
||||
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(getWindow(), decorView);
|
||||
wic.setAppearanceLightStatusBars(!darkMode);
|
||||
} else {
|
||||
// icons are always white back then
|
||||
if (window != null && !darkMode) {
|
||||
window.setStatusBarColor(ColorUtils.compositeColors(Color.argb(127, 0, 0, 0), window.getStatusBarColor()));
|
||||
if (!darkMode) {
|
||||
getWindow().setStatusBarColor(ColorUtils.compositeColors(Color.argb(127, 0, 0, 0), getWindow().getStatusBarColor()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,8 @@ import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ImageDecoder;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.pdf.PdfRenderer;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.Layout;
|
||||
import android.text.Spanned;
|
||||
@@ -24,24 +22,20 @@ import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RawRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.core.view.WindowInsetsControllerCompat;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
import androidx.palette.graphics.Palette;
|
||||
|
||||
import com.google.android.material.color.DynamicColors;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.LuminanceSource;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
@@ -49,8 +43,6 @@ import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.RGBLuminanceSource;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
import com.google.zxing.multi.GenericMultipleBarcodeReader;
|
||||
import com.google.zxing.multi.MultipleBarcodeReader;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -65,13 +57,10 @@ import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Currency;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
@@ -91,13 +80,12 @@ public class Utils {
|
||||
public static final int SELECT_BARCODE_REQUEST = 2;
|
||||
public static final int BARCODE_SCAN = 3;
|
||||
public static final int BARCODE_IMPORT_FROM_IMAGE_FILE = 4;
|
||||
public static final int BARCODE_IMPORT_FROM_PDF_FILE = 5;
|
||||
public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 6;
|
||||
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 7;
|
||||
public static final int CARD_IMAGE_FROM_CAMERA_ICON = 8;
|
||||
public static final int CARD_IMAGE_FROM_FILE_FRONT = 9;
|
||||
public static final int CARD_IMAGE_FROM_FILE_BACK = 10;
|
||||
public static final int CARD_IMAGE_FROM_FILE_ICON = 11;
|
||||
public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 5;
|
||||
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 6;
|
||||
public static final int CARD_IMAGE_FROM_CAMERA_ICON = 7;
|
||||
public static final int CARD_IMAGE_FROM_FILE_FRONT = 8;
|
||||
public static final int CARD_IMAGE_FROM_FILE_BACK = 9;
|
||||
public static final int CARD_IMAGE_FROM_FILE_ICON = 10;
|
||||
|
||||
public static final String CARD_IMAGE_FILENAME_REGEX = "^(card_)(\\d+)(_(?:front|back|icon)\\.png)$";
|
||||
|
||||
@@ -140,91 +128,6 @@ public class Utils {
|
||||
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
|
||||
}
|
||||
|
||||
static public List<BarcodeValues> retrieveBarcodesFromImage(Context context, Uri uri) {
|
||||
Log.i(TAG, "Received image file with possible barcode");
|
||||
|
||||
if (uri == null) {
|
||||
Log.e(TAG, "Uri did not contain any data");
|
||||
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
Bitmap bitmap;
|
||||
try {
|
||||
bitmap = retrieveImageFromUri(context, uri);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error getting data from image file");
|
||||
e.printStackTrace();
|
||||
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<BarcodeValues> barcodesFromBitmap = getBarcodesFromBitmap(bitmap);
|
||||
|
||||
if (barcodesFromBitmap.isEmpty()) {
|
||||
Log.i(TAG, "No barcode found in image file");
|
||||
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
return barcodesFromBitmap;
|
||||
}
|
||||
|
||||
static public List<BarcodeValues> retrieveBarcodesFromPdf(Context context, Uri uri) {
|
||||
Log.i(TAG, "Received PDF file with possible barcode");
|
||||
if (uri == null) {
|
||||
Log.e(TAG, "Uri did not contain any data");
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
ParcelFileDescriptor parcelFileDescriptor = null;
|
||||
PdfRenderer renderer = null;
|
||||
List<BarcodeValues> barcodesFromPdfPages = new ArrayList<>();
|
||||
|
||||
try {
|
||||
parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r");
|
||||
if (parcelFileDescriptor != null) {
|
||||
renderer = new PdfRenderer(parcelFileDescriptor);
|
||||
|
||||
// Loop over all pages to find barcodes
|
||||
Bitmap renderedPage;
|
||||
for (int i = 0; i < renderer.getPageCount(); i++) {
|
||||
PdfRenderer.Page page = renderer.openPage(i);
|
||||
renderedPage = Bitmap.createBitmap(page.getWidth(), page.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
page.render(renderedPage, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
|
||||
page.close();
|
||||
|
||||
List<BarcodeValues> barcodesFromPage = getBarcodesFromBitmap(renderedPage);
|
||||
for (BarcodeValues barcodeValues : barcodesFromPage) {
|
||||
barcodeValues.setNote(String.format(context.getString(R.string.pageWithNumber), i+1));
|
||||
barcodesFromPdfPages.add(barcodeValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error reading PDF file", e);
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
} finally {
|
||||
// Resource handling
|
||||
if (renderer != null) {
|
||||
renderer.close();
|
||||
}
|
||||
if (parcelFileDescriptor != null) {
|
||||
try {
|
||||
parcelFileDescriptor.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error closing ParcelFileDescriptor", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (barcodesFromPdfPages.isEmpty()) {
|
||||
Log.i(TAG, "No barcode found in pdf file");
|
||||
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
return barcodesFromPdfPages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Barcode format and content based on the result of an activity.
|
||||
* It shows toasts to notify the end-user as needed itself and will return an empty
|
||||
@@ -236,20 +139,45 @@ public class Utils {
|
||||
* @param context
|
||||
* @return BarcodeValues
|
||||
*/
|
||||
static public List<BarcodeValues> parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
|
||||
static public BarcodeValues parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
|
||||
String contents;
|
||||
String format;
|
||||
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return new ArrayList<>();
|
||||
return new BarcodeValues(null, null);
|
||||
}
|
||||
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
|
||||
return retrieveBarcodesFromImage(context, intent.getData());
|
||||
}
|
||||
Log.i(TAG, "Received image file with possible barcode");
|
||||
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_PDF_FILE) {
|
||||
return retrieveBarcodesFromPdf(context, intent.getData());
|
||||
Uri data = intent.getData();
|
||||
if (data == null) {
|
||||
Log.e(TAG, "Intent did not contain any data");
|
||||
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||
return new BarcodeValues(null, null);
|
||||
}
|
||||
|
||||
Bitmap bitmap;
|
||||
try {
|
||||
bitmap = retrieveImageFromUri(context, data);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error getting data from image file");
|
||||
e.printStackTrace();
|
||||
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||
return new BarcodeValues(null, null);
|
||||
}
|
||||
|
||||
BarcodeValues barcodeFromBitmap = getBarcodeFromBitmap(bitmap);
|
||||
|
||||
if (barcodeFromBitmap.isEmpty()) {
|
||||
Log.i(TAG, "No barcode found in image file");
|
||||
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
Log.i(TAG, "Read barcode id: " + barcodeFromBitmap.content());
|
||||
Log.i(TAG, "Read format: " + barcodeFromBitmap.format());
|
||||
|
||||
return barcodeFromBitmap;
|
||||
}
|
||||
|
||||
if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) {
|
||||
@@ -265,7 +193,7 @@ public class Utils {
|
||||
Log.i(TAG, "Read barcode id: " + contents);
|
||||
Log.i(TAG, "Read format: " + format);
|
||||
|
||||
return Collections.singletonList(new BarcodeValues(format, contents));
|
||||
return new BarcodeValues(format, contents);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Unknown request code for parseSetBarcodeActivityResult");
|
||||
@@ -285,22 +213,22 @@ public class Utils {
|
||||
return MediaStore.Images.Media.getBitmap(context.getContentResolver(), data);
|
||||
}
|
||||
|
||||
static public List<BarcodeValues> getBarcodesFromBitmap(Bitmap bitmap) {
|
||||
static public BarcodeValues getBarcodeFromBitmap(Bitmap bitmap) {
|
||||
// This function is vulnerable to OOM, so we try again with a smaller bitmap is we get OOM
|
||||
for (int i = 0; i < 10; i++) {
|
||||
try {
|
||||
return Utils.getBarcodesFromBitmapReal(bitmap);
|
||||
return Utils.getBarcodeFromBitmapReal(bitmap);
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.w(TAG, "Ran OOM in getBarcodesFromBitmap! Trying again with smaller picture! Retry " + i + " of 10.");
|
||||
Log.w(TAG, "Ran OOM in getBarcodeFromBitmap! Trying again with smaller picture! Retry " + i + " of 10.");
|
||||
bitmap = Bitmap.createScaledBitmap(bitmap, (int) Math.round(0.75 * bitmap.getWidth()), (int) Math.round(0.75 * bitmap.getHeight()), false);
|
||||
}
|
||||
}
|
||||
|
||||
// Give up
|
||||
return new ArrayList<>();
|
||||
return new BarcodeValues(null, null);
|
||||
}
|
||||
|
||||
static private List<BarcodeValues> getBarcodesFromBitmapReal(Bitmap bitmap) {
|
||||
static private BarcodeValues getBarcodeFromBitmapReal(Bitmap bitmap) {
|
||||
// In order to decode it, the Bitmap must first be converted into a pixel array...
|
||||
int[] intArray = new int[bitmap.getWidth() * bitmap.getHeight()];
|
||||
bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
|
||||
@@ -309,63 +237,15 @@ public class Utils {
|
||||
LuminanceSource source = new RGBLuminanceSource(bitmap.getWidth(), bitmap.getHeight(), intArray);
|
||||
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||
|
||||
List<BarcodeValues> barcodeValuesList = new ArrayList<>();
|
||||
try {
|
||||
MultiFormatReader multiFormatReader = new MultiFormatReader();
|
||||
MultipleBarcodeReader multipleBarcodeReader = new GenericMultipleBarcodeReader(multiFormatReader);
|
||||
Result barcodeResult = new MultiFormatReader().decode(binaryBitmap);
|
||||
|
||||
Result[] barcodeResults = multipleBarcodeReader.decodeMultiple(binaryBitmap);
|
||||
|
||||
for (Result barcodeResult : barcodeResults) {
|
||||
Log.i(TAG, "Read barcode id: " + barcodeResult.getText());
|
||||
Log.i(TAG, "Read format: " + barcodeResult.getBarcodeFormat().name());
|
||||
|
||||
barcodeValuesList.add(new BarcodeValues(barcodeResult.getBarcodeFormat().name(), barcodeResult.getText()));
|
||||
}
|
||||
|
||||
return barcodeValuesList;
|
||||
return new BarcodeValues(barcodeResult.getBarcodeFormat().name(), barcodeResult.getText());
|
||||
} catch (NotFoundException e) {
|
||||
return barcodeValuesList;
|
||||
return new BarcodeValues(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
static public void makeUserChooseBarcodeFromList(Context context, List<BarcodeValues> barcodeValuesList, BarcodeValuesListDisambiguatorCallback callback) {
|
||||
// If there is only one choice, consider it chosen
|
||||
if (barcodeValuesList.size() == 1) {
|
||||
callback.onUserChoseBarcode(barcodeValuesList.get(0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask user to choose a barcode
|
||||
// TODO: This should contain an image of the barcode in question to help users understand the choice they're making
|
||||
CharSequence[] barcodeDescriptions = new CharSequence[barcodeValuesList.size()];
|
||||
for (int i = 0; i < barcodeValuesList.size(); i++) {
|
||||
BarcodeValues barcodeValues = barcodeValuesList.get(i);
|
||||
CatimaBarcode catimaBarcode = CatimaBarcode.fromName(barcodeValues.format());
|
||||
|
||||
String barcodeContent = barcodeValues.content();
|
||||
// Shorten overly long barcodes
|
||||
if (barcodeContent.length() > 22) {
|
||||
barcodeContent = barcodeContent.substring(0, 20) + "…";
|
||||
}
|
||||
|
||||
if (barcodeValues.note() != null) {
|
||||
barcodeDescriptions[i] = String.format("%s: %s (%s)", barcodeValues.note(), catimaBarcode.prettyName(), barcodeContent);
|
||||
} else {
|
||||
barcodeDescriptions[i] = String.format("%s (%s)", catimaBarcode.prettyName(), barcodeContent);
|
||||
}
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
|
||||
builder.setTitle(context.getString(R.string.multipleBarcodesFoundPleaseChooseOne));
|
||||
builder.setItems(
|
||||
barcodeDescriptions,
|
||||
(dialogInterface, i) -> callback.onUserChoseBarcode(barcodeValuesList.get(i))
|
||||
);
|
||||
builder.setOnCancelListener(dialogInterface -> callback.onUserDismissedSelector());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
static public Boolean isNotYetValid(Date validFromDate) {
|
||||
// The note in `hasExpired` does not apply here, since the bug was fixed before this feature was added.
|
||||
return validFromDate.after(getStartOfToday().getTime());
|
||||
@@ -393,7 +273,6 @@ public class Utils {
|
||||
|
||||
static public String formatBalance(Context context, BigDecimal value, Currency currency) {
|
||||
NumberFormat numberFormat = NumberFormat.getInstance();
|
||||
numberFormat.setGroupingUsed(false);
|
||||
|
||||
if (currency == null) {
|
||||
numberFormat.setMaximumFractionDigits(0);
|
||||
@@ -401,7 +280,6 @@ public class Utils {
|
||||
}
|
||||
|
||||
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
|
||||
currencyFormat.setGroupingUsed(false);
|
||||
currencyFormat.setCurrency(currency);
|
||||
currencyFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
|
||||
currencyFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
|
||||
@@ -411,7 +289,6 @@ public class Utils {
|
||||
|
||||
static public String formatBalanceWithoutCurrencySymbol(BigDecimal value, Currency currency) {
|
||||
NumberFormat numberFormat = NumberFormat.getInstance();
|
||||
numberFormat.setGroupingUsed(false);
|
||||
|
||||
if (currency == null) {
|
||||
numberFormat.setMaximumFractionDigits(0);
|
||||
@@ -424,72 +301,19 @@ public class Utils {
|
||||
return numberFormat.format(value);
|
||||
}
|
||||
|
||||
private static final double LargestPreciseDouble = (double) (1l << 53);
|
||||
static{
|
||||
assert (LargestPreciseDouble + 1.0) == LargestPreciseDouble;
|
||||
assert (LargestPreciseDouble - 1.0) != LargestPreciseDouble;
|
||||
}
|
||||
|
||||
private static BigDecimal fromParsed(Number parsed){
|
||||
if(parsed instanceof BigDecimal)
|
||||
return (BigDecimal) parsed;
|
||||
|
||||
final double d = parsed.doubleValue();
|
||||
if(d >= LargestPreciseDouble)
|
||||
return new BigDecimal(parsed.longValue());
|
||||
return new BigDecimal(d);
|
||||
}
|
||||
|
||||
static public BigDecimal parseBalance(String value, Currency currency) throws ParseException {
|
||||
// This function expects the input string to not have any grouping (thousand separators).
|
||||
// It will refuse to work otherwise
|
||||
NumberFormat numberFormat = NumberFormat.getInstance();
|
||||
numberFormat.setGroupingUsed(false);
|
||||
|
||||
if (numberFormat instanceof DecimalFormat) {
|
||||
((DecimalFormat) numberFormat).setParseBigDecimal(true);
|
||||
}
|
||||
|
||||
if (currency == null) {
|
||||
numberFormat.setMaximumFractionDigits(0);
|
||||
} else {
|
||||
int fractionDigits = currency.getDefaultFractionDigits();
|
||||
|
||||
numberFormat.setMinimumFractionDigits(fractionDigits);
|
||||
numberFormat.setMaximumFractionDigits(fractionDigits);
|
||||
|
||||
if (numberFormat instanceof DecimalFormat) {
|
||||
// If the string contains both thousand separators and decimals separators, fail hard
|
||||
DecimalFormatSymbols decimalFormatSymbols = ((DecimalFormat) numberFormat).getDecimalFormatSymbols();
|
||||
char decimalSeparator = decimalFormatSymbols.getDecimalSeparator();
|
||||
|
||||
// Translate all non-digits to decimal separators, failing if we find more than 1.
|
||||
// We loop over the codepoints to make sure eastern arabic numerals are not mistakenly
|
||||
// treated as a separator.
|
||||
boolean separatorFound = false;
|
||||
StringBuilder translatedValue = new StringBuilder();
|
||||
for (int i = 0; i < value.length();) {
|
||||
int character = value.codePointAt(i);
|
||||
|
||||
if (Character.isDigit(character)) {
|
||||
translatedValue.append(value.charAt(i));
|
||||
} else {
|
||||
if (separatorFound) {
|
||||
throw new ParseException("Contains multiple separators", i);
|
||||
}
|
||||
|
||||
separatorFound = true;
|
||||
translatedValue.append(decimalSeparator);
|
||||
}
|
||||
|
||||
i += Character.charCount(character);
|
||||
}
|
||||
|
||||
value = translatedValue.toString();
|
||||
}
|
||||
numberFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
|
||||
numberFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
|
||||
}
|
||||
|
||||
return fromParsed(numberFormat.parse(value));
|
||||
Log.d(TAG, numberFormat.parse(value).toString());
|
||||
|
||||
return new BigDecimal(numberFormat.parse(value).toString());
|
||||
}
|
||||
|
||||
static public byte[] bitmapToByteArray(Bitmap bitmap) {
|
||||
@@ -839,31 +663,13 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
// Force correct color
|
||||
// Fixes OLED dark mode in MainActivity
|
||||
// XXX android 9 and below has issues with patched theme where the background becomes a
|
||||
// rendering mess
|
||||
// use after views are inflated
|
||||
public static void postPatchColors(AppCompatActivity activity) {
|
||||
activity.findViewById(android.R.id.content).setBackgroundColor(resolveBackgroundColor(activity));
|
||||
}
|
||||
|
||||
// Either pass an Activity on which to call getWindow() or an existing Window (may be null) returned by that function.
|
||||
public static void setNavigationBarColor(@Nullable AppCompatActivity activity, @Nullable Window window, int color, boolean useLightBars) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
if (window == null && activity != null) {
|
||||
window = activity.getWindow();
|
||||
}
|
||||
if (window != null) {
|
||||
View decorView = window.getDecorView();
|
||||
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView);
|
||||
wic.setAppearanceLightNavigationBars(useLightBars);
|
||||
window.setNavigationBarColor(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int resolveBackgroundColor(AppCompatActivity activity) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
activity.getTheme().resolveAttribute(android.R.attr.colorBackground, typedValue, true);
|
||||
return typedValue.data;
|
||||
activity.findViewById(android.R.id.content).setBackgroundColor(typedValue.data);
|
||||
}
|
||||
|
||||
public static int getHeaderColorFromImage(Bitmap image, int fallback) {
|
||||
@@ -921,30 +727,23 @@ public class Utils {
|
||||
.replaceAll("(?<!href=\")\\b(https?://[\\w@#%&+=:?/.-]*[\\w@#%&+=:?/-])", "<a href=\"$1\">$1</a>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an icon or text with background on the given ImageView and/or TextView, including background colour.
|
||||
*
|
||||
* @param context Android context
|
||||
* @param loyaltyCard Loyalty Card
|
||||
* @param icon Bitmap of the icon to set, or null
|
||||
* @param backgroundOrIcon ImageView to draw the icon and background on to
|
||||
* @param textWhenNoImage TextView to write the loyalty card name into if icon is null
|
||||
* @return background colour
|
||||
*/
|
||||
public static int setIconOrTextWithBackground(Context context, LoyaltyCard loyaltyCard, Bitmap icon, ImageView backgroundOrIcon, TextView textWhenNoImage) {
|
||||
int headerColor = getHeaderColor(context, loyaltyCard);
|
||||
backgroundOrIcon.setImageBitmap(icon);
|
||||
backgroundOrIcon.setBackgroundColor(headerColor);
|
||||
|
||||
public static void setIconOrTextWithBackground(Context context, LoyaltyCard loyaltyCard, Bitmap icon, ImageView backgroundOrIcon, TextView textWhenNoImage) {
|
||||
if (icon != null) {
|
||||
Log.d("onResume", "setting icon image");
|
||||
textWhenNoImage.setVisibility(View.GONE);
|
||||
|
||||
backgroundOrIcon.setImageBitmap(icon);
|
||||
backgroundOrIcon.setBackgroundColor(Color.TRANSPARENT);
|
||||
} else {
|
||||
textWhenNoImage.setVisibility(View.VISIBLE);
|
||||
|
||||
int headerColor = getHeaderColor(context, loyaltyCard);
|
||||
|
||||
backgroundOrIcon.setImageBitmap(null);
|
||||
backgroundOrIcon.setBackgroundColor(headerColor);
|
||||
textWhenNoImage.setText(loyaltyCard.store);
|
||||
textWhenNoImage.setTextColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||
}
|
||||
|
||||
return headerColor;
|
||||
}
|
||||
|
||||
public static boolean installedFromGooglePlay(Context context) {
|
||||
|
||||
@@ -3,13 +3,12 @@ package protect.card_locker.preferences;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.IntegerRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import protect.card_locker.R;
|
||||
import protect.card_locker.Utils;
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package protect.card_locker.preferences;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
@@ -56,13 +56,6 @@ public class SettingsActivity extends CatimaAppCompatActivity {
|
||||
if (savedInstanceState != null) {
|
||||
fragment.mReloadMain = savedInstanceState.getBoolean(RELOAD_MAIN_STATE);
|
||||
}
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
finishSettingsActivity();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,6 +76,11 @@ public class SettingsActivity extends CatimaAppCompatActivity {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
finishSettingsActivity();
|
||||
}
|
||||
|
||||
private void finishSettingsActivity() {
|
||||
if (fragment.mReloadMain) {
|
||||
Intent intent = new Intent();
|
||||
|
||||
10
app/src/main/res/drawable/ic_copy.xml
Normal file
10
app/src/main/res/drawable/ic_copy.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
|
||||
</vector>
|
||||
@@ -10,8 +10,7 @@
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
@@ -21,12 +20,11 @@
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
android:paddingVertical="8dp"
|
||||
android:clipToPadding="false">
|
||||
android:padding="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -37,9 +35,7 @@
|
||||
android:id="@+id/version_history"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/version_history_main"
|
||||
@@ -78,9 +74,7 @@
|
||||
android:id="@+id/credits"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/credits_main"
|
||||
@@ -119,9 +113,7 @@
|
||||
android:id="@+id/translate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/translate_main"
|
||||
@@ -161,9 +153,7 @@
|
||||
android:id="@+id/license"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/license_main"
|
||||
@@ -203,9 +193,7 @@
|
||||
android:id="@+id/repo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/repo_main"
|
||||
@@ -245,9 +233,7 @@
|
||||
android:id="@+id/privacy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/privacy_main"
|
||||
@@ -287,9 +273,7 @@
|
||||
android:id="@+id/donate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/donate_main"
|
||||
@@ -300,7 +284,6 @@
|
||||
android:text="@string/donate"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
@@ -320,9 +303,7 @@
|
||||
android:id="@+id/rate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/rate_main"
|
||||
@@ -359,12 +340,10 @@
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:padding="8dp"
|
||||
android:id="@+id/report_error"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/report_error_main"
|
||||
@@ -400,5 +379,5 @@
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</ScrollView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -31,6 +31,13 @@
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
style="?attr/toolbarStyle" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/groups"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:tabMode="scrollable" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
@@ -18,11 +17,11 @@
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
@@ -128,7 +127,7 @@
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/importOptionApplicationButton" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
@@ -30,8 +29,7 @@
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent">
|
||||
android:layout_height="wrap_content">
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -48,10 +46,9 @@
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||
<ScrollView android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||
<TableLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -372,110 +369,79 @@
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<!-- Front image -->
|
||||
<LinearLayout
|
||||
android:id="@+id/frontImageHolder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false">
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
android:paddingTop="@dimen/inputPadding">
|
||||
|
||||
<!-- Front image -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/frontImageHolder"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_margin="5dp"
|
||||
style="?attr/materialCardViewElevatedStyle">
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/activity_margin"
|
||||
android:layout_marginTop="@dimen/activity_margin"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:layout_marginBottom="@dimen/activity_margin"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/frontImageConstraint"
|
||||
<ImageView
|
||||
android:id="@+id/frontImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:minHeight="50dp"
|
||||
android:contentDescription="@string/frontImageDescription"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_camera_white"
|
||||
android:background="?attr/colorPrimary" />
|
||||
|
||||
<!-- Back image -->
|
||||
<ImageView
|
||||
android:id="@+id/frontImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:minHeight="50dp"
|
||||
android:contentDescription="@string/backImageDescription"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_camera_white"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:layout_constraintBottom_toTopOf="@id/frontImageDescription"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/frontImageDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/frontImageDescription"
|
||||
android:textAppearance="?attr/textAppearanceHeadlineSmall"
|
||||
android:gravity="center"
|
||||
app:layout_constraintTop_toBottomOf="@id/frontImage"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Back image -->
|
||||
<LinearLayout
|
||||
android:id="@+id/backImageHolder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
android:paddingTop="@dimen/inputPadding">
|
||||
|
||||
<!-- Back image -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_weight="1"
|
||||
android:id="@+id/backImageHolder"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_margin="5dp"
|
||||
style="?attr/materialCardViewElevatedStyle">
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/activity_margin"
|
||||
android:layout_marginTop="@dimen/activity_margin"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:layout_marginBottom="@dimen/activity_margin"
|
||||
android:paddingHorizontal="@dimen/inputPadding"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/backImageConstraint"
|
||||
<ImageView
|
||||
android:id="@+id/backImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:minHeight="50dp"
|
||||
android:contentDescription="@string/backImageDescription"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_camera_white"
|
||||
android:background="?attr/colorPrimary" />
|
||||
|
||||
<!-- Back image -->
|
||||
<ImageView
|
||||
android:id="@+id/backImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:minHeight="50dp"
|
||||
android:contentDescription="@string/backImageDescription"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_camera_white"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:layout_constraintBottom_toTopOf="@id/backImageDescription"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/backImageDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/backImageDescription"
|
||||
android:textAppearance="?attr/textAppearanceHeadlineSmall"
|
||||
android:gravity="center"
|
||||
app:layout_constraintTop_toBottomOf="@id/backImage"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
||||
</TableLayout>
|
||||
</TableLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</ScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/coordinator_layout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
@@ -32,7 +31,6 @@
|
||||
android:id="@+id/groups"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
app:tabMode="scrollable"
|
||||
android:visibility="gone"/>
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -8,8 +9,7 @@
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
android:titleCondensed="@string/editCardTitle"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_copy_to_clipboard"
|
||||
android:icon="@drawable/ic_copy"
|
||||
android:title="@string/copy_to_clipboard"
|
||||
android:titleCondensed="@string/copy_to_clipboard"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_share"
|
||||
android:icon="@drawable/ic_share"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user