mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2025-12-24 23:57:53 -05:00
Compare commits
1 Commits
pdfImport
...
feature/ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ddb88b45c |
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.1
|
||||
- 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/wrapper-validation-action@v2
|
||||
- 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.1
|
||||
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.1
|
||||
uses: actions/checkout@v4.0.0
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5.0.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.2
|
||||
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.1
|
||||
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.2
|
||||
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.1
|
||||
- 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.2
|
||||
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.1
|
||||
- uses: obfusk/gradle-update-action@v2.0.0
|
||||
id: gradle-update
|
||||
- uses: gradle/wrapper-validation-action@v2
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6.0.2
|
||||
with:
|
||||
title: "Update Gradle to ${{ steps.gradle-update.outputs.version }}"
|
||||
commit-message: "Update Gradle to ${{ steps.gradle-update.outputs.version }}"
|
||||
branch-suffix: timestamp
|
||||
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.1
|
||||
- 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.2
|
||||
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()
|
||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,25 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased - 134
|
||||
|
||||
- Support for scanning PDF files for barcodes
|
||||
- Support for image files with multiple barcodes
|
||||
|
||||
## 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
|
||||
|
||||
75
Gemfile.lock
75
Gemfile.lock
@@ -3,25 +3,25 @@ GEM
|
||||
specs:
|
||||
CFPropertyList (3.0.6)
|
||||
rexml
|
||||
addressable (2.8.6)
|
||||
addressable (2.8.5)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.884.0)
|
||||
aws-sdk-core (3.191.0)
|
||||
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.77.0)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sdk-kms (1.71.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.143.0)
|
||||
aws-sdk-core (~> 3, >= 3.191.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)
|
||||
claide (1.1.0)
|
||||
@@ -32,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.109.0)
|
||||
excon (0.103.0)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
@@ -64,8 +65,8 @@ GEM
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.3.0)
|
||||
fastlane (2.219.0)
|
||||
fastimage (2.2.7)
|
||||
fastlane (2.215.1)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@@ -84,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)
|
||||
@@ -93,7 +93,7 @@ GEM
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (>= 0.1.1)
|
||||
optparse (~> 0.1.1)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
@@ -107,9 +107,9 @@ GEM
|
||||
xcpretty (~> 0.3.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)
|
||||
@@ -117,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.6.1)
|
||||
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.3.1)
|
||||
google-cloud-storage (1.47.0)
|
||||
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)
|
||||
@@ -148,7 +149,7 @@ GEM
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.6.2)
|
||||
json (2.7.1)
|
||||
json (2.6.3)
|
||||
jwt (2.7.1)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
@@ -156,11 +157,11 @@ GEM
|
||||
multipart-post (2.3.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.1)
|
||||
optparse (0.4.0)
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.7.1)
|
||||
public_suffix (5.0.4)
|
||||
rake (13.1.0)
|
||||
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)
|
||||
@@ -184,13 +185,17 @@ GEM
|
||||
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)
|
||||
|
||||
146
app/build.gradle
Normal file
146
app/build.gradle
Normal file
@@ -0,0 +1,146 @@
|
||||
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"]
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
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 = 133
|
||||
versionName = "2.28.0"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
multiDexEnabled = true
|
||||
|
||||
resourceConfigurations += listOf("ar", "bg", "bn", "bn-rIN", "bs", "cs", "da", "de", "el-rGR", "en", "eo", "es", "es-rAR", "fi", "fr", "he-rIL", "hi", "hr", "hu", "in-rID", "is", "it", "ja", "ko", "lt", "lv", "nb-rNO", "nl", "oc", "pl", "pt-rPT", "ro-rRO", "ru", "sk", "sl", "sv", "tr", "uk", "vi", "zh-rCN", "zh-rTW")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
encoding = "UTF-8"
|
||||
|
||||
// Flag to enable support for the new language APIs
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("test") {
|
||||
resources.srcDirs("src/test/res")
|
||||
}
|
||||
}
|
||||
|
||||
// Starting with Android Studio 3 Robolectric is unable to find resources.
|
||||
// The following allows it to find the resources.
|
||||
testOptions.unitTests.isIncludeAndroidResources = true
|
||||
tasks.withType<Test>().configureEach {
|
||||
testLogging {
|
||||
events("started", "passed", "skipped", "failed")
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
lintConfig = file("lint.xml")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// AndroidX
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.exifinterface:exifinterface:1.3.7")
|
||||
implementation("androidx.palette:palette:1.0.0")
|
||||
implementation("androidx.preference:preference:1.2.1")
|
||||
implementation("com.google.android.material:material:1.11.0")
|
||||
implementation("com.github.yalantis:ucrop:2.2.8")
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
|
||||
|
||||
// Splash Screen
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
|
||||
// Third-party
|
||||
implementation("com.journeyapps:zxing-android-embedded:4.3.0@aar")
|
||||
implementation("com.google.zxing:core:3.5.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.11.1")
|
||||
}
|
||||
|
||||
tasks.withType<SpotBugsTask>().configureEach {
|
||||
description = "Run spotbugs"
|
||||
group = "verification"
|
||||
|
||||
//classes = fileTree("build/intermediates/javac/debug/compileDebugJavaWithJavac/classes")
|
||||
//source = fileTree("src/main/java")
|
||||
//classpath = files()
|
||||
|
||||
reports.maybeCreate("xml").required.set(false)
|
||||
reports.maybeCreate("html").required.set(true)
|
||||
}
|
||||
|
||||
tasks.register("copyRawResFiles", Copy::class) {
|
||||
from(
|
||||
layout.projectDirectory.file("../CHANGELOG.md"),
|
||||
layout.projectDirectory.file("../PRIVACY.md")
|
||||
)
|
||||
into(layout.projectDirectory.dir("src/main/res/raw"))
|
||||
rename { it.lowercase() }
|
||||
}.also {
|
||||
tasks.preBuild.dependsOn(it)
|
||||
tasks.getByName<Delete>("clean") {
|
||||
val filesNamesToDelete = listOf("CHANGELOG", "PRIVACY")
|
||||
filesNamesToDelete.forEach { fileName ->
|
||||
delete(layout.projectDirectory.file("src/main/res/raw/${fileName.lowercase()}.md"))
|
||||
}
|
||||
}
|
||||
}
|
||||
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,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Depuración de Catima</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Débogage de Catima</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">कैटिमा डीबग</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">ಕ್ಯಾಟಿಮಾ ಡೀಬಗ್</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima 디버그</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima-avlusing</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima-foutopsporing</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Depuração Catima</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Depanare Catima</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Отладка Catima</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Hata Ayaklama</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Debug</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Gỡ lỗi Catima</string>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima 调试</string>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns: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 {
|
||||
|
||||
@@ -20,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;
|
||||
@@ -29,7 +30,6 @@ import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
@@ -38,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;
|
||||
@@ -110,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;
|
||||
@@ -176,7 +175,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
enum ImageType {
|
||||
NONE,
|
||||
ICON,
|
||||
BARCODE,
|
||||
IMAGE_FRONT,
|
||||
IMAGE_BACK
|
||||
@@ -230,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);
|
||||
@@ -304,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();
|
||||
@@ -332,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) {
|
||||
@@ -421,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,
|
||||
@@ -443,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);
|
||||
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);
|
||||
@@ -599,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);
|
||||
@@ -678,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();
|
||||
@@ -736,8 +676,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
DBHelper.updateLoyaltyCardLastUsed(database, loyaltyCard.id);
|
||||
|
||||
invalidateOptionsMenu();
|
||||
|
||||
ShortcutHelper.updateShortcuts(this, loyaltyCard);
|
||||
}
|
||||
|
||||
private void setStateBasedOnImageTypes() {
|
||||
@@ -766,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);
|
||||
@@ -837,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();
|
||||
@@ -1109,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();
|
||||
@@ -1143,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();
|
||||
@@ -1162,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;
|
||||
@@ -69,7 +61,6 @@ import java.text.NumberFormat;
|
||||
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;
|
||||
@@ -89,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)$";
|
||||
|
||||
@@ -138,80 +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;
|
||||
PdfRenderer renderer;
|
||||
try {
|
||||
parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r");
|
||||
renderer = new PdfRenderer(parcelFileDescriptor);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Could not read file in uri");
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// Loop over all pages to find barcodes
|
||||
List<BarcodeValues> barcodesFromPdfPages = new ArrayList<>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
renderer.close();
|
||||
|
||||
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
|
||||
@@ -223,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) {
|
||||
@@ -252,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");
|
||||
@@ -272,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());
|
||||
@@ -296,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());
|
||||
@@ -770,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) {
|
||||
@@ -852,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>
|
||||
@@ -369,106 +369,75 @@
|
||||
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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,58 +1,283 @@
|
||||
Sylvia van Os
|
||||
Branden Archer
|
||||
J. Lavoie
|
||||
solokot
|
||||
Allan Nordhøy
|
||||
solokot
|
||||
Heimen Stoffels
|
||||
Oğuz Ersen
|
||||
FC (Fay) Stegerman
|
||||
Katharine Chui
|
||||
SlavekB
|
||||
StoyanDimitrov
|
||||
mondstern
|
||||
StoyanDimitrov
|
||||
IllusiveMan196
|
||||
Altonss
|
||||
Michael Moroni
|
||||
GM
|
||||
Eric
|
||||
Gediminas Murauskas
|
||||
Petr Novák
|
||||
Joel A
|
||||
laralem
|
||||
Taco
|
||||
pfaffenrodt
|
||||
Aayush Gupta
|
||||
HudobniVolk
|
||||
Eric
|
||||
Nyatsuki
|
||||
HudobniVolk
|
||||
Samantaz Fox
|
||||
Giovanni Donisi
|
||||
Jiri Grönroos
|
||||
arno-github
|
||||
Cliff Heraldo
|
||||
Sergio Paredes
|
||||
Ankit Tiwari
|
||||
Milan Šalka
|
||||
mdvhimself
|
||||
Sergio Paredes
|
||||
Clxff H3r4ld0
|
||||
Aayush Gupta
|
||||
Balázs Meskó
|
||||
Milo Ivir
|
||||
Skrripy
|
||||
huuhaa
|
||||
大王叫我来巡山
|
||||
Projjal Moitra
|
||||
Quentin PAGÈS
|
||||
ikanakova
|
||||
waffshappen
|
||||
ngocanhtve
|
||||
Ziad OUALHADJ
|
||||
Scrambled777
|
||||
Robin Liu
|
||||
Denis Shilin
|
||||
Giovanni Donisi
|
||||
Alexander Ivanov
|
||||
Miha Frangež
|
||||
Silvério Santos
|
||||
Virginie
|
||||
Michael Gangolf
|
||||
rudy3
|
||||
Kim Seohyun
|
||||
Govind S Nair
|
||||
Freddo espresso
|
||||
arshbeerSingh
|
||||
Denis Shilin
|
||||
Freddo espresso
|
||||
Ziad OUALHADJ
|
||||
Silvério Santos
|
||||
Miha Frangež
|
||||
Eryk Michalak
|
||||
Arnis Jaundžeikars
|
||||
Dan
|
||||
sr093906
|
||||
mdvhimself
|
||||
Jiri Grönroos
|
||||
Katarzyna
|
||||
echo r"0xX4H" | rev
|
||||
Magnitudee
|
||||
Olivia (Zoe)
|
||||
betsythefc
|
||||
waffshappen
|
||||
Robin
|
||||
ati3
|
||||
enolp
|
||||
Evgeniy Khramov
|
||||
Jane Kong
|
||||
Jean Mareilles
|
||||
Jean-Luc Tibaux
|
||||
José Rebelo
|
||||
K. Herbert
|
||||
Lisa A.
|
||||
Mawuena M. KODZO A.
|
||||
rudy3
|
||||
Reza
|
||||
Still / Azaka
|
||||
String E. Fighter
|
||||
Tapu
|
||||
Yurical
|
||||
rr-vesp
|
||||
yangyangdaji
|
||||
丛林意志
|
||||
alajemba-vik
|
||||
/usr/local/ΕΨΗΕΛΩΝ
|
||||
Adolfo Jayme-Barrientos
|
||||
Alessandro Mandelli
|
||||
KovalevArtem
|
||||
Artem M.
|
||||
Astrohops1
|
||||
BMN
|
||||
balaraz
|
||||
BootVirtual
|
||||
Bottan Hermawan
|
||||
zChiip
|
||||
Clonewayx
|
||||
D. Domig
|
||||
Danylo Lystopadov
|
||||
Diego
|
||||
Eudes-alencar
|
||||
Fede Pujol
|
||||
FineFindus
|
||||
francescbassas
|
||||
Jason Li
|
||||
Jesse Davids
|
||||
Kamborio
|
||||
Kis Dominik
|
||||
Lukas Grassauer
|
||||
Luna Jernberg
|
||||
Marnick L'Eau
|
||||
Michalis
|
||||
Michał
|
||||
Milo Ivir
|
||||
Mohamed A. Salah
|
||||
Yatoku
|
||||
the7thNightmare
|
||||
Quang Trung
|
||||
Rishi Agarwal
|
||||
Rosdyana Kusuma
|
||||
Sabri Ünal
|
||||
umoenks
|
||||
Simon Rusinov
|
||||
Siriusmart
|
||||
Mritunjay
|
||||
Tarik Dzambic
|
||||
Thomas Bertels
|
||||
Thomas Cruveilher
|
||||
Tian Jiale
|
||||
Tong Liu
|
||||
Tymofii Lytvynenko
|
||||
Wanath
|
||||
Younes Bouhouche
|
||||
Runner
|
||||
ce i moa
|
||||
enescan201
|
||||
Frablock
|
||||
inesre
|
||||
lgasp
|
||||
notlin4
|
||||
phlostically
|
||||
pokeghost
|
||||
sal0max
|
||||
Ágata Leuck
|
||||
BmBKun
|
||||
NamHyeonjeong
|
||||
Aditya Das
|
||||
asier123123131
|
||||
Kevin Sicong Jiang
|
||||
Tomer Ben-Rachel
|
||||
Tom Sawyer
|
||||
tfuxu
|
||||
Ahmed Saleh
|
||||
Airat
|
||||
Tapwaterisokey
|
||||
Alexandra-Ioana Moroz
|
||||
sNiXx
|
||||
Angela Enogieru
|
||||
Animesh Chatterjee
|
||||
Artūras Kalenda
|
||||
Ashish Yadav
|
||||
Aya Elsaadany
|
||||
Aya
|
||||
Biren
|
||||
Booc Sylvan
|
||||
Brage Nesteby Reitan
|
||||
Cap Amr Karam
|
||||
Carlo Maria Cuoghi Barbagli
|
||||
ChaoticNeutralCzech
|
||||
ChengCheng
|
||||
CherryMonster222
|
||||
Colgrave
|
||||
djcand
|
||||
Mylou53
|
||||
Daniel Sych
|
||||
danieluhrinyi
|
||||
Daniele Tricoli
|
||||
Kasina Dheeraj
|
||||
Donno
|
||||
Reihan
|
||||
Erik Spjelkavik
|
||||
Flav
|
||||
Franciszek Stefan
|
||||
Gael Caraballo
|
||||
Giacomo Alessandroni
|
||||
Govind S Nair
|
||||
Grzegorz
|
||||
gneiss15
|
||||
Hamustra Scans
|
||||
helzubair
|
||||
HowITsDone
|
||||
Hubert Maciejewicz
|
||||
Izzy
|
||||
Jacek
|
||||
Jacopo Gennaro Esposito
|
||||
Jean-Baptiste
|
||||
Kung-chih
|
||||
Karvjorm
|
||||
polar
|
||||
krkk
|
||||
Kristoffer Grundström
|
||||
Laura Ferraz
|
||||
Lionel HANNEQUIN
|
||||
Lucas da Costa
|
||||
almir992
|
||||
Manan Jhaveri
|
||||
Marco
|
||||
BRBsoup
|
||||
Mateo Gomez
|
||||
Mattia
|
||||
Md. Al-Amin
|
||||
Michael Gangolf
|
||||
Milan Šalka
|
||||
3DN1M
|
||||
Minecraft boom
|
||||
Mobashir Raihan
|
||||
Moi Toi
|
||||
DiCeYMaYo
|
||||
DivideEtImpera
|
||||
Nicolas
|
||||
Nosnahc
|
||||
osamaqw
|
||||
pa4k
|
||||
pbeckmann
|
||||
Peer Beckmann
|
||||
vandman
|
||||
Piotr Strebski
|
||||
Piotr Zet
|
||||
Poorva Patidar
|
||||
Quang Nguyen
|
||||
Ratnesh
|
||||
Rohan Babbar
|
||||
Ronak Upadhyay
|
||||
Rose Liverman
|
||||
SKULD
|
||||
Sabrina
|
||||
Salem Malus
|
||||
Samarth Asthan
|
||||
tatyhub
|
||||
Shailendra Maurya
|
||||
SilverFS
|
||||
Simone Dotto
|
||||
Subhashish Anand
|
||||
Subhradeep Bera
|
||||
Swayam Khare
|
||||
SziaTomi
|
||||
Mehedi Hasan
|
||||
Tim Trek
|
||||
Titas Pažereckas
|
||||
atakujonc
|
||||
tkraljevic
|
||||
Tony C
|
||||
Vancha March
|
||||
tyap-lyap-ivprod
|
||||
Waldemar Stoczkowski
|
||||
Wiktor Kwapisiewicz
|
||||
Yevgeny M
|
||||
Yusril A
|
||||
ahmed-awad26
|
||||
Avik Kundu
|
||||
ayuyydev
|
||||
diksha-2911
|
||||
essys
|
||||
gbonaspetti
|
||||
gittyboy-cell
|
||||
huang ivan
|
||||
lassr8
|
||||
liva
|
||||
lucafont2
|
||||
mtrmirez
|
||||
michaelpratana
|
||||
opsik
|
||||
pesta007
|
||||
polarhun
|
||||
pooyanazari
|
||||
psa-jforestier
|
||||
z369369
|
||||
sergio
|
||||
skauVictor
|
||||
080502
|
||||
Marcus
|
||||
techwebpd
|
||||
tjw123hh
|
||||
Truestorybaby
|
||||
tygyh
|
||||
unstartdev
|
||||
wmilan 17
|
||||
MeH762
|
||||
يوسف لطفي
|
||||
しいたけ
|
||||
元气
|
||||
JaeBeom An
|
||||
JungHee Lee
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<string name="confirm">تأكيد</string>
|
||||
<string name="deleteConfirmation">مسح هذة البطاقة نهائيا؟</string>
|
||||
<string name="ok">حسنا</string>
|
||||
<string name="copy_to_clipboard">نسخ البطاقة الشخصية الى الحافظة</string>
|
||||
<string name="share">شارك</string>
|
||||
<string name="sendLabel">ارسل…</string>
|
||||
<string name="editCardTitle">عدل البطاقة</string>
|
||||
@@ -25,6 +26,7 @@
|
||||
<string name="scanCardBarcode">مسح باركود</string>
|
||||
<string name="cardShortcut">اختصار البطاقة</string>
|
||||
<string name="noCardsMessage">اضف بطاقة أولا</string>
|
||||
<string name="card_ids_copied">البطاقة(ات) الشخصية المنسوخة</string>
|
||||
<string name="barcodeImageDescriptionWithType">صورة <xliff:g>%s</xliff:g> باركود</string>
|
||||
<string name="noCardExistsError">لا يمكن العثور على هذه البطاقة</string>
|
||||
<string name="failedParsingImportUriError">لا يمكن تحليل الرابط المستورد</string>
|
||||
@@ -45,6 +47,7 @@
|
||||
<string name="app_license">البرمجيات الحرة متروكة الحقوق, ترخيص +GPLv3</string>
|
||||
<string name="app_libraries">مكتبات الطرف الثالث الحرة: <xliff:g id="app_libraries_list">%s</xliff:g></string>
|
||||
<string name="selectBarcodeTitle">اختار الباركود</string>
|
||||
<string name="copy_to_clipboard_toast">تم نسخ بطاقة الهوية إلى الحافظة</string>
|
||||
<string name="thumbnailDescription">صورة مصغرة</string>
|
||||
<string name="starImage">نجم مفضل</string>
|
||||
<string name="settings">اعدادات</string>
|
||||
@@ -92,6 +95,7 @@
|
||||
<string name="setBarcodeId">قم بتعيين قيمة الباركود</string>
|
||||
<string name="unsupportedBarcodeType">لا يمكن عرض نوع الباركود هذا. قد يكون مدعومًا في إصدار أحدث من التطبيق.</string>
|
||||
<string name="wrongValueForBarcodeType">القيمة غير صالحة لنوع الباركود المحدد</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">تم نسخ بطاقات الهوية إلى الحافظة</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">أريد مشاركة بعض البطاقات معك</string>
|
||||
<string name="frontImageDescription">الصورة الأمامية</string>
|
||||
<string name="backImageDescription">الصورة الخلفية</string>
|
||||
@@ -173,9 +177,9 @@
|
||||
<item quantity="zero">مسح <xliff:g>%d</xliff:g> بطاقة</item>
|
||||
<item quantity="one">مسح <xliff:g>%d</xliff:g> بطاقة</item>
|
||||
<item quantity="two">مسح <xliff:g>%d</xliff:g> بطاقتين</item>
|
||||
<item quantity="few">مسح <xliff:g>%d</xliff:g> بطائق</item>
|
||||
<item quantity="many">مسح <xliff:g>%d</xliff:g> بطاقة</item>
|
||||
<item quantity="other">مسح <xliff:g>%d</xliff:g> بطاقة</item>
|
||||
<item quantity="few">مسح <xliff:g>%d</xliff:g> بطاقات</item>
|
||||
<item quantity="many">مسح <xliff:g>%d</xliff:g> بطاقات</item>
|
||||
<item quantity="other">مسح <xliff:g>%d</xliff:g> بطاقات</item>
|
||||
</plurals>
|
||||
<plurals name="deleteCardsConfirmation">
|
||||
<item quantity="zero">مسح هذه <xliff:g>%d</xliff:g> البطاقة نهائيا؟</item>
|
||||
@@ -199,7 +203,7 @@
|
||||
<string name="groupsList">مجموعات: <xliff:g>%s</xliff:g></string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card">منع قفل الشاشة</string>
|
||||
<string name="leaveWithoutSaveTitle">خروج</string>
|
||||
<string name="editGroup">تعديل المجموعه: <xliff:g>%s</xliff:g></string>
|
||||
<string name="editGroup">مجموعة التعديل: <xliff:g>%s</xliff:g></string>
|
||||
<plurals name="groupCardCount">
|
||||
<item quantity="zero"><xliff:g>%d</xliff:g> بطاقة</item>
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> بطاقة</item>
|
||||
@@ -251,7 +255,7 @@
|
||||
<string name="barcodeLongPressMessage">يمكن فتح صور فقط في تطبيق معرض الصور</string>
|
||||
<string name="failedToOpenUrl">ثبت متصفح ويب أولاً</string>
|
||||
<string name="welcome">مرحبا بك في كاتيما</string>
|
||||
<string name="updateBalanceTitle">كم أنفقت أو استلمت؟</string>
|
||||
<string name="updateBalanceTitle">كم أنفقت؟</string>
|
||||
<string name="currentBalanceSentence">الرصيد الحالي: <xliff:g> %s </xliff:g></string>
|
||||
<plurals name="viewArchivedCardsWithCount">
|
||||
<item quantity="zero">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقة)</item>
|
||||
@@ -302,20 +306,4 @@
|
||||
<string name="settings_category_title_privacy">خصوصية</string>
|
||||
<string name="show_balance">إظهار التوازن</string>
|
||||
<string name="settings_keep_screen_on_summary">تعطيل مهلة الشاشة أثناء عرض البطاقة</string>
|
||||
<string name="balanceParsingFailed">رصيد غير صالح</string>
|
||||
<string name="card_id_must_not_be_empty">يجب ألا يكون معرف البطاقة فارغا</string>
|
||||
<string name="add_a_card_in_a_different_way">أضف بطاقة بطريقة مختلفة</string>
|
||||
<string name="manually_enter_barcode_instructions">أدخل رقم الهوية أو النص الموجود على بطاقتك واضغط على الرمز الشريطي الذي يشبه الموجود على بطاقتك.</string>
|
||||
<string name="action_more_options">خيارات أخرى</string>
|
||||
<string name="enter_card_id">أدخل رقم الهوية أو النص الموجود على بطاقتك</string>
|
||||
<string name="addWithoutBarcode">إضافة بدون باركود</string>
|
||||
<string name="field_must_not_be_empty">يجب ألا يكون الحقل فارغا</string>
|
||||
<string name="app_name">كاتيما</string>
|
||||
<string name="settings_follow_sensor_orientation">التدوير دائمًا ( تجاهل إعدادات النظام)</string>
|
||||
<string name="add_manually_warning_title">الفحص موصى به</string>
|
||||
<string name="continue_">استمر</string>
|
||||
<string name="spend">انفق</string>
|
||||
<string name="receive">استلم</string>
|
||||
<string name="amountParsingFailed">كمية غير صحيحة</string>
|
||||
<string name="add_manually_warning_message">في بعض المتاجر قيمة الباركود تختلف عن الرقم الموجود على البطاقة. لهذا السبب إدخال الباركود يدوياً لن ينجح دائماً. من المستحسن فحص الباركود بكاميرا بدلا من ذالك. هل انت مُصِر على الاستكمال؟</string>
|
||||
</resources>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="storeName">Nome</string>
|
||||
<string name="note">Nota</string>
|
||||
<string name="noMatchingGiftCards">Nun hai nengún resultáu. Prueba a camudar la busca.</string>
|
||||
@@ -24,6 +24,10 @@
|
||||
<string name="save">Guardar</string>
|
||||
<string name="edit">Editar</string>
|
||||
<string name="delete">Desaniciar</string>
|
||||
<plurals name="deleteCardsTitle">
|
||||
<item quantity="one"></item>
|
||||
<item quantity="other"></item>
|
||||
</plurals>
|
||||
<string name="unstar">Quitar de Favoritos</string>
|
||||
<string name="cancel">Encaboxar</string>
|
||||
<string name="importFailed">Nun se pudo facer la importación</string>
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
<string name="addCardTitle">Добавяне на карта</string>
|
||||
<string name="removeImage">Премахване на изображение</string>
|
||||
<string name="takePhoto">Снимане</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">Номерата са копирани</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">Искам да споделя тези карти с вас</string>
|
||||
<string name="wrongValueForBarcodeType">Стойноста е неприемлива за избрания щрихкод</string>
|
||||
<string name="setBarcodeId">Задаване на стойност</string>
|
||||
@@ -88,6 +89,7 @@
|
||||
<string name="settings">Настройки</string>
|
||||
<string name="starImage">Звезда за любимо</string>
|
||||
<string name="thumbnailDescription">Миниатюра</string>
|
||||
<string name="copy_to_clipboard_toast">Номерът е копиран</string>
|
||||
<string name="selectBarcodeTitle">Избиране на щрихкод</string>
|
||||
<string name="importOptionApplicationButton">Избиране чрез приложение</string>
|
||||
<string name="importing">Внасяне…</string>
|
||||
@@ -105,6 +107,7 @@
|
||||
<string name="scanCardBarcode">Снемане на щрихкод</string>
|
||||
<string name="editCardTitle">Редактиране на карта</string>
|
||||
<string name="share">Споделя</string>
|
||||
<string name="copy_to_clipboard">Копира номера</string>
|
||||
<string name="ok">Добре</string>
|
||||
<string name="importSuccessful">Данните са внесени</string>
|
||||
<string name="chooseImportType">Внасяне на данни на</string>
|
||||
@@ -201,6 +204,7 @@
|
||||
<string name="starred">Със звезда</string>
|
||||
<string name="showMoreInfo">Показване на информация</string>
|
||||
<string name="options">Настройки</string>
|
||||
<string name="card_ids_copied">Номерата са копирани</string>
|
||||
<plurals name="balancePoints">
|
||||
<item quantity="one"><xliff:g>%s</xliff:g> точка</item>
|
||||
<item quantity="other"><xliff:g>%s</xliff:g> точки</item>
|
||||
@@ -233,7 +237,7 @@
|
||||
<string name="barcodeLongPressMessage">В приложението галерия могат да бъдат отваряни само изображения</string>
|
||||
<string name="failedToRetrieveImageFile">Не е възможно извличане на изображение</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">За да сканирате щрихкодове с Catima е необходим достъп до камерата. За да промените разрешението докоснете тук.</string>
|
||||
<string name="updateBalanceTitle">Колко е похарчено или получено?</string>
|
||||
<string name="updateBalanceTitle">Каква е промяната\?</string>
|
||||
<string name="updateBalanceHint">Въведете стойност</string>
|
||||
<string name="newBalanceSentence">Нов баланс: <xliff:g>%s</xliff:g></string>
|
||||
<string name="cameraPermissionDeniedTitle">Камерата е недостъпна</string>
|
||||
@@ -282,12 +286,4 @@
|
||||
<string name="enter_card_id">Въведете номера или текста от картата</string>
|
||||
<string name="addWithoutBarcode">Добавяне на карта без щрихкод</string>
|
||||
<string name="field_must_not_be_empty">Полето не трябва да е празно</string>
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="settings_follow_sensor_orientation">Винаги да се завърта (пренебрегва системната настройка)</string>
|
||||
<string name="continue_">Продължаване</string>
|
||||
<string name="add_manually_warning_title">Препоръчително е да сканирате</string>
|
||||
<string name="add_manually_warning_message">Стойностите от щрихкода и отбелязаните на картата числа в някои случаи се различават. По тази причина е при ръчно въвеждане картата може да не работи. Силно препоръчително е да сканирате щрихкода с камерата. Желаете ли да продължите въпреки това?</string>
|
||||
<string name="amountParsingFailed">Неприемлива сума</string>
|
||||
<string name="spend">Похарчено</string>
|
||||
<string name="receive">Получено</string>
|
||||
</resources>
|
||||
@@ -33,6 +33,7 @@
|
||||
<string name="setBarcodeId">বারকোড আইডি সেট করুন</string>
|
||||
<string name="unsupportedBarcodeType">এই বারকোডের টাইপটি এখন দেখানো যাচ্ছে না। অ্যাপের পরের সংস্করণে হয়ত এটি সমর্থন করা যেতে পারে।</string>
|
||||
<string name="wrongValueForBarcodeType">বারকোড টাইপের জন্য ভুল মান</string>
|
||||
<string name="copy_to_clipboard_multiple_toast">আইডিগুলি ক্লিপবোর্ডে কপি হল</string>
|
||||
<string name="intent_import_card_from_url_share_multiple_text">url থেকে ইন্টেন্ট ইম্পোর্ট কার্ড একাধিক টেক্সট শেয়ার করে</string>
|
||||
<string name="frontImageDescription">সামনের চিত্র</string>
|
||||
<string name="backImageDescription">পিছনের চিত্র</string>
|
||||
@@ -67,11 +68,13 @@
|
||||
<string name="reverse">...উল্টো ক্রমে</string>
|
||||
<string name="sort_by">ক্রমানুসার</string>
|
||||
<string name="noCardExistsError">কার্ডটি খুঁজে পাওয়া গেল না</string>
|
||||
<string name="card_ids_copied">আইডি কপি করা হয়েছে</string>
|
||||
<string name="noCardsMessage">কোন কার্ড বার্তা নেই</string>
|
||||
<string name="addCardTitle">কার্ডের শিরোনাম যোগ করুন</string>
|
||||
<string name="editCardTitle">কার্ডের শিরোনাম সম্পাদনা করুন</string>
|
||||
<string name="sendLabel">পাঠান…</string>
|
||||
<string name="share">ভাগ</string>
|
||||
<string name="copy_to_clipboard">নকল করুন ক্লিপবোর্ড এ</string>
|
||||
<string name="deleteConfirmation">এই কার্ডটি চিরকালের জন্য মুছে দেবো\?</string>
|
||||
<string name="confirm">নিশ্চিত করুন</string>
|
||||
<string name="delete">মুছে ফেলুন</string>
|
||||
@@ -122,6 +125,7 @@
|
||||
<string name="importExport">আমদানি/রপ্তানি</string>
|
||||
<string name="cardShortcut">কার্ড শর্টকাট</string>
|
||||
<string name="exportFailed">রপ্তানি করা যাচ্ছে না</string>
|
||||
<string name="copy_to_clipboard_toast">আইডি ক্লিপবোর্ডে নকল করা হল</string>
|
||||
<string name="importExportHelp">নিজের ডেটা অন্য কোথাও সংরক্ষণ করে রাখলে পরে সেটা অন্য ডিভাইসে সরিয়ে নিতে পারবেন।</string>
|
||||
<string name="importFailed">আমদানি করা গেল না</string>
|
||||
<string name="noGiftCardsGroup">কিছু কার্ড বানান আর এই গ্রুপে স্থির করুন।</string>
|
||||
@@ -243,7 +247,7 @@
|
||||
<string name="unarchive">সংরক্ষণাগারমুক্ত করুন</string>
|
||||
<string name="archived">কার্ড সংরক্ষণাগারভুক্ত</string>
|
||||
<string name="welcome">Catima-তে স্বাগতম</string>
|
||||
<string name="updateBalanceTitle">আপনি কত খরচ করেছেন?</string>
|
||||
<string name="updateBalanceTitle">আপনি কত খরচ করেছেন\?</string>
|
||||
<string name="updateBalanceHint">পরিমান লিখুন</string>
|
||||
<string name="currentBalanceSentence">বর্তমান ব্যালেন্স: <xliff:g>%s</xliff:g></string>
|
||||
<string name="show_name_below_image_thumbnail">ছবির থাম্বনেইল এর নিচে নামটি দেখান</string>
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
<item quantity="one">এই <xliff:g>%d</xliff:g> কার্ডটি স্থায়ীভাবে মুছবেন\?</item>
|
||||
<item quantity="other">এই <xliff:g>%d</xliff:g> কার্ডগুলিকে স্থায়ীভাবে মুছবেন\?</item>
|
||||
</plurals>
|
||||
<string name="copy_to_clipboard">ক্লিপবোর্ডে আইডি কপি করুন</string>
|
||||
<string name="share">শেয়ার করুন</string>
|
||||
<string name="editCardTitle">কার্ড সম্পাদনা করুন</string>
|
||||
<string name="addCardTitle">কার্ড যোগ করুন</string>
|
||||
@@ -52,28 +53,5 @@
|
||||
<string name="noGiftCards">একটি কার্ড যোগ করতে + প্লাস বোতামে ক্লিক করুন বা ⋮ মেনু থেকে আমদানি করুন।</string>
|
||||
<string name="cardShortcut">কার্ড শর্টকাট</string>
|
||||
<string name="noCardsMessage">প্রথমে একটি কার্ড যোগ করুন</string>
|
||||
<string name="noCardExistsError">কার্ডটি খুঁজে পাওয়া যায়নি</string>
|
||||
<string name="barcodeImageDescriptionWithType">ছবি <xliff:g>%s</xliff:g> বারকোড</string>
|
||||
<string name="cameraPermissionDeniedTitle">ক্যামেরাটি ব্যবহার করা যাচ্ছে না</string>
|
||||
<string name="failedParsingImportUriError">দেওয়া URL-টি প্রক্রিয়া করা যাচ্ছে না</string>
|
||||
<string name="exporting">রপ্তানি করা হচ্ছে…</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">বারকোড স্ক্যান করার জন্য, ক্যাটিমা কে ক্যামেরাটি ব্যবহার করার অনুমতি দিতে হবে। এইখানে টাচ করে আপনার অনুমতি সেটিংস পালটে নিন।</string>
|
||||
<string name="settings_dark_theme">অন্ধকার</string>
|
||||
<string name="exportOptionExplanation">ডেটাটি আপনার পছন্দের জায়গায় রাখা হবে।</string>
|
||||
<string name="importFailed">আমদানি করা গেল না</string>
|
||||
<string name="permissionReadCardsDescription">সমস্ত ক্যাটিমা কার্ডস এবং তার তথ্য পড়ুন, নোট্স আর ছবি সহ</string>
|
||||
<string name="importFailedTitle">আমদানি ব্যর্থ</string>
|
||||
<string name="importExportHelp">নিজের ডেটা সংরক্ষণ করে রাখলে পরে সেটা অন্য ডিভাইসে সরিয়ে নিতে পারবেন।</string>
|
||||
<string name="importExport">আমদানি/রপ্তানি</string>
|
||||
<string name="importing">আমদানি করা হচ্ছে…</string>
|
||||
<string name="exportFailed">রপ্তানি করা যাচ্ছে না</string>
|
||||
<string name="exportName">আমদানি/রপ্তানি</string>
|
||||
<string name="cameraPermissionRequired">এই কাজটির জন্য ক্যামেরা ব্যবহার করার অনুমতি লাগবে…</string>
|
||||
<string name="importSuccessfulTitle">আমদানি শেষ</string>
|
||||
<string name="exportSuccessfulTitle">রপ্তানি শেষ</string>
|
||||
<string name="permissionReadCardsLabel">কাটিমা কার্ডস পড়ুন</string>
|
||||
<string name="storageReadPermissionRequired">এই কাজটির জন্য ফোনের স্টোরেজ দেখার অনুমতি লাগবে…</string>
|
||||
<string name="exportFailedTitle">রপ্তানি ব্যর্থ</string>
|
||||
<string name="settings_card_orientation">বারকোড অভিমুখ (ওরিয়েন্টেশন)</string>
|
||||
<string name="app_name">ক্যাটিমা</string>
|
||||
<string name="card_ids_copied">আইডি কপি করা হয়েছে</string>
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user