mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2026-01-05 05:27:57 -05:00
Compare commits
1 Commits
feature/as
...
badAttempt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f64449167 |
20
.github/dependabot.yml
vendored
20
.github/dependabot.yml
vendored
@@ -2,29 +2,9 @@ version: 2
|
|||||||
updates:
|
updates:
|
||||||
- package-ecosystem: "gradle"
|
- package-ecosystem: "gradle"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
registries:
|
|
||||||
- google
|
|
||||||
- gradlePluginPortal
|
|
||||||
- jitpack
|
|
||||||
- mavenCentral
|
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
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:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4.0.0
|
||||||
- name: Fail on bad translations
|
- 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
|
run: if grep -ri "<xliff" app/src/main/res/values*/strings.xml; then echo "Invalidly escaped translations found"; exit 1; fi
|
||||||
- uses: gradle/actions/wrapper-validation@v3
|
- uses: gradle/wrapper-validation-action@v1
|
||||||
- name: set up OpenJDK 17
|
- name: set up OpenJDK 17
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
run: ./gradlew spotbugsRelease
|
run: ./gradlew spotbugsRelease
|
||||||
- name: Archive test results
|
- name: Archive test results
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4.3.3
|
uses: actions/upload-artifact@v3.1.3
|
||||||
with:
|
with:
|
||||||
name: test-results
|
name: test-results
|
||||||
path: app/build/reports
|
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:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.0.0
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5.1.0
|
uses: actions/setup-python@v4.7.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Run converter script
|
- name: Run converter script
|
||||||
run: python .scripts/changelog_to_fastlane.py
|
run: python .scripts/changelog_to_fastlane.py
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6.1.0
|
uses: peter-evans/create-pull-request@v5.0.2
|
||||||
with:
|
with:
|
||||||
title: "Update Fastlane changelogs"
|
title: "Update Fastlane changelogs"
|
||||||
commit-message: "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:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.0.0
|
||||||
- name: Update contributors
|
- name: Update contributors
|
||||||
id: update_contributors
|
id: update_contributors
|
||||||
uses: TheLastProject/contributors-to-file-action@v3.2.0
|
uses: TheLastProject/contributors-to-file-action@v3.0.1
|
||||||
with:
|
with:
|
||||||
file_in_repo: app/src/main/res/raw/contributors.txt
|
file_in_repo: app/src/main/res/raw/contributors.txt
|
||||||
min_commit_count: 5
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6.1.0
|
uses: peter-evans/create-pull-request@v5.0.2
|
||||||
with:
|
with:
|
||||||
title: "Update contributors"
|
title: "Update contributors"
|
||||||
commit-message: "Update contributors"
|
commit-message: "Update contributors"
|
||||||
|
|||||||
39
.github/workflows/generate-feature-graphic.yml
vendored
39
.github/workflows/generate-feature-graphic.yml
vendored
@@ -6,7 +6,6 @@ on:
|
|||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- 'fastlane/**/title.txt'
|
- 'fastlane/**/title.txt'
|
||||||
- '.scripts/generate_feature_graphic/**'
|
|
||||||
permissions:
|
permissions:
|
||||||
actions: none
|
actions: none
|
||||||
checks: none
|
checks: none
|
||||||
@@ -25,7 +24,7 @@ jobs:
|
|||||||
generate-feature-graphic:
|
generate-feature-graphic:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4.0.0
|
||||||
- name: Install requirements
|
- name: Install requirements
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -37,9 +36,41 @@ jobs:
|
|||||||
find .scripts/generate_feature_graphic/fonts -name '*.ttf' -exec cp {} "$HOME/.fonts" \;
|
find .scripts/generate_feature_graphic/fonts -name '*.ttf' -exec cp {} "$HOME/.fonts" \;
|
||||||
fc-cache
|
fc-cache
|
||||||
- name: Generate featureGraphic.png for each language
|
- 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
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6.1.0
|
uses: peter-evans/create-pull-request@v5.0.2
|
||||||
with:
|
with:
|
||||||
title: "Update feature graphic"
|
title: "Update feature graphic"
|
||||||
commit-message: "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.7
|
|
||||||
- uses: obfusk/gradle-update-action@v2.0.0
|
|
||||||
id: gradle-update
|
|
||||||
- uses: gradle/actions/wrapper-validation@v3
|
|
||||||
- name: Create Pull Request
|
|
||||||
uses: peter-evans/create-pull-request@v6.1.0
|
|
||||||
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:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- app/src/main/res/values-*/strings.xml
|
|
||||||
- app/src/main/res/values/settings.xml
|
- app/src/main/res/values/settings.xml
|
||||||
permissions:
|
permissions:
|
||||||
actions: none
|
actions: none
|
||||||
@@ -25,13 +24,11 @@ jobs:
|
|||||||
update-locales:
|
update-locales:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4.0.0
|
||||||
- name: Add new locales
|
|
||||||
run: .scripts/new-locales.py
|
|
||||||
- name: Update locales
|
- name: Update locales
|
||||||
run: .scripts/locales.py
|
run: .scripts/locales.py
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6.1.0
|
uses: peter-evans/create-pull-request@v5.0.2
|
||||||
with:
|
with:
|
||||||
title: "Update locales"
|
title: "Update locales"
|
||||||
commit-message: "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
|
*.iml
|
||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
local.properties
|
||||||
#/.idea/caches
|
.idea/
|
||||||
#/.idea/libraries
|
|
||||||
#/.idea/modules.xml
|
|
||||||
#/.idea/workspace.xml
|
|
||||||
#/.idea/navEditor.xml
|
|
||||||
#/.idea/assetWizardSettings.xml
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
build/
|
||||||
/captures
|
captures/
|
||||||
.externalNativeBuild
|
**/release
|
||||||
.cxx
|
**/debug
|
||||||
#local.properties
|
app/*.log
|
||||||
|
|
||||||
# Android extras
|
|
||||||
/app/*.log
|
|
||||||
/app/build
|
|
||||||
/app/release
|
|
||||||
/.idea
|
|
||||||
|
|
||||||
# Bundle
|
# Bundle
|
||||||
/.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 ;;
|
|
||||||
kn-IN) sed -i -e 's/font-size="150"/font-size="100"/' -e "s/Yesteryear/Noto Serif Kannada/" 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
|
|
||||||
@@ -19,12 +19,12 @@ res = ", ".join(f'"{loc}"' for loc in locales)
|
|||||||
sed = [
|
sed = [
|
||||||
"sed",
|
"sed",
|
||||||
"-i",
|
"-i",
|
||||||
f"s/resourceConfigurations .*/resourceConfigurations += listOf({res})/",
|
f"s/resourceConfigurations .*/resourceConfigurations += [{res}]/",
|
||||||
"app/build.gradle.kts"
|
"app/build.gradle"
|
||||||
]
|
]
|
||||||
subprocess.run(sed, check=True)
|
subprocess.run(sed, check=True)
|
||||||
|
|
||||||
with open("app/src/main/res/xml/locales_config.xml", "w", encoding="utf-8") as fh:
|
with open("app/src/main/res/xml/locales_config.xml", "w") as fh:
|
||||||
fh.write('<?xml version="1.0" encoding="utf-8"?>\n')
|
fh.write('<?xml version="1.0" encoding="utf-8"?>\n')
|
||||||
fh.write('<locale-config xmlns:android="http://schemas.android.com/apk/res/android">\n')
|
fh.write('<locale-config xmlns:android="http://schemas.android.com/apk/res/android">\n')
|
||||||
fh.write(' <locale android:name="en-US" />\n')
|
fh.write(' <locale android:name="en-US" />\n')
|
||||||
|
|||||||
@@ -1,132 +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/"
|
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_weblate_langs() -> List[Tuple[str, int]]:
|
|
||||||
url = STATS_URL
|
|
||||||
results = []
|
|
||||||
for _ in range(16): # avoid endless loops just in case
|
|
||||||
r = requests.get(url, timeout=5)
|
|
||||||
r.raise_for_status()
|
|
||||||
data = r.json()
|
|
||||||
for lang in data["results"]:
|
|
||||||
if lang["code"] != "en":
|
|
||||||
code = REPLACE_CODES.get(lang["code"], lang["code"]).replace("_", "-r")
|
|
||||||
results.append((code, round(lang["translated_percent"])))
|
|
||||||
url = data["next"]
|
|
||||||
if not url:
|
|
||||||
return sorted(results)
|
|
||||||
if not url.split("?")[0] == STATS_URL:
|
|
||||||
raise Error(f"Unexpected next URL: {url}")
|
|
||||||
raise Error("Too many pages")
|
|
||||||
|
|
||||||
|
|
||||||
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", encoding="utf-8") 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", encoding="utf-8") 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", encoding="utf-8") 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()
|
|
||||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,37 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## v2.30.0 - 136 (2024-06-18)
|
## Unreleased - 132
|
||||||
|
|
||||||
- Support for creating a card when sharing plain text
|
|
||||||
- Display image type instead of barcode below images
|
|
||||||
- Fix possible crash when trying to import a backup from the Nextcloud app
|
|
||||||
- Improved support for devices without camera
|
|
||||||
|
|
||||||
## v2.29.1 - 135 (2024-05-19)
|
|
||||||
|
|
||||||
- Various fixes and improvements to balance handling
|
|
||||||
|
|
||||||
## v2.29.0 - 134 (2024-04-19)
|
|
||||||
|
|
||||||
- Support for scanning PDF files for barcodes
|
|
||||||
- Support for image files with multiple barcodes
|
|
||||||
- Minor UI fixes
|
|
||||||
|
|
||||||
## v2.28.0 - 133 (2024-03-08)
|
|
||||||
|
|
||||||
- Target Android 14
|
|
||||||
- Open card icon in gallery on touch
|
|
||||||
- Improve design of Photos tab in edit view
|
|
||||||
- Update spending screen to also support receiving
|
|
||||||
|
|
||||||
## v2.27.0 - 132 (2024-01-30)
|
|
||||||
|
|
||||||
- Refine "Add card" workflow
|
- Refine "Add card" workflow
|
||||||
- Validation flow improvements
|
- 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)
|
## 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
|
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
|
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
|
should have a much easier time getting your work merged with the upstream
|
||||||
project.
|
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
|
||||||
|
|
||||||
Translation changes are managed through [Weblate](https://hosted.weblate.org/projects/catima/).
|
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
|
well as instructions on how to test for the bug and verify that it has been
|
||||||
fixed.
|
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
|
### Submit Patch(es) for Review
|
||||||
|
|
||||||
Finally, you will need to submit your patches so that they can be reviewed
|
Finally, you will need to submit your patches so that they can be reviewed
|
||||||
|
|||||||
110
Gemfile.lock
110
Gemfile.lock
@@ -1,32 +1,29 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.7)
|
CFPropertyList (3.0.6)
|
||||||
base64
|
|
||||||
nkf
|
|
||||||
rexml
|
rexml
|
||||||
addressable (2.8.6)
|
addressable (2.8.5)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
artifactory (3.0.17)
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.945.0)
|
aws-partitions (1.824.0)
|
||||||
aws-sdk-core (3.197.1)
|
aws-sdk-core (3.181.1)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.5)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.85.0)
|
aws-sdk-kms (1.71.0)
|
||||||
aws-sdk-core (~> 3, >= 3.197.0)
|
aws-sdk-core (~> 3, >= 3.177.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.152.3)
|
aws-sdk-s3 (1.134.0)
|
||||||
aws-sdk-core (~> 3, >= 3.197.0)
|
aws-sdk-core (~> 3, >= 3.181.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.6)
|
||||||
aws-sigv4 (1.8.0)
|
aws-sigv4 (1.6.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.4)
|
babosa (1.0.4)
|
||||||
base64 (0.2.0)
|
|
||||||
claide (1.1.0)
|
claide (1.1.0)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
@@ -35,10 +32,11 @@ GEM
|
|||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
digest-crc (0.6.5)
|
digest-crc (0.6.5)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
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)
|
dotenv (2.8.1)
|
||||||
emoji_regex (3.2.3)
|
emoji_regex (3.2.3)
|
||||||
excon (0.110.0)
|
excon (0.103.0)
|
||||||
faraday (1.10.3)
|
faraday (1.10.3)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
faraday-em_synchrony (~> 1.0)
|
faraday-em_synchrony (~> 1.0)
|
||||||
@@ -67,15 +65,15 @@ GEM
|
|||||||
faraday-retry (1.0.3)
|
faraday-retry (1.0.3)
|
||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.3.1)
|
fastimage (2.2.7)
|
||||||
fastlane (2.221.1)
|
fastlane (2.215.1)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.8, < 3.0.0)
|
addressable (>= 2.8, < 3.0.0)
|
||||||
artifactory (~> 3.0)
|
artifactory (~> 3.0)
|
||||||
aws-sdk-s3 (~> 1.0)
|
aws-sdk-s3 (~> 1.0)
|
||||||
babosa (>= 1.0.3, < 2.0.0)
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
colored (~> 1.2)
|
colored
|
||||||
commander (~> 4.6)
|
commander (~> 4.6)
|
||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
emoji_regex (>= 0.1, < 4.0)
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
@@ -87,7 +85,6 @@ GEM
|
|||||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
google-apis-androidpublisher_v3 (~> 0.3)
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
google-apis-playcustomapp_v1 (~> 0.1)
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
|
||||||
google-cloud-storage (~> 1.31)
|
google-cloud-storage (~> 1.31)
|
||||||
highline (~> 2.0)
|
highline (~> 2.0)
|
||||||
http-cookie (~> 1.0.5)
|
http-cookie (~> 1.0.5)
|
||||||
@@ -96,10 +93,10 @@ GEM
|
|||||||
mini_magick (>= 4.9.4, < 5.0.0)
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
multipart-post (>= 2.0.0, < 3.0.0)
|
multipart-post (>= 2.0.0, < 3.0.0)
|
||||||
naturally (~> 2.2)
|
naturally (~> 2.2)
|
||||||
optparse (>= 0.1.1, < 1.0.0)
|
optparse (~> 0.1.1)
|
||||||
plist (>= 3.1.0, < 4.0.0)
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
rubyzip (>= 2.0.0, < 3.0.0)
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
security (= 0.1.5)
|
security (= 0.1.3)
|
||||||
simctl (~> 1.6.3)
|
simctl (~> 1.6.3)
|
||||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
terminal-table (~> 3)
|
terminal-table (~> 3)
|
||||||
@@ -108,11 +105,11 @@ GEM
|
|||||||
word_wrap (~> 1.0.0)
|
word_wrap (~> 1.0.0)
|
||||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||||
xcpretty (~> 0.3.0)
|
xcpretty (~> 0.3.0)
|
||||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
xcpretty-travis-formatter (>= 0.0.3)
|
||||||
gh_inspector (1.1.3)
|
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.0, < 2.a)
|
||||||
google-apis-core (0.11.3)
|
google-apis-core (0.11.1)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
httpclient (>= 2.8.1, < 3.a)
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
@@ -120,64 +117,62 @@ GEM
|
|||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.a)
|
retriable (>= 2.0, < 4.a)
|
||||||
rexml
|
rexml
|
||||||
|
webrick
|
||||||
google-apis-iamcredentials_v1 (0.17.0)
|
google-apis-iamcredentials_v1 (0.17.0)
|
||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-playcustomapp_v1 (0.13.0)
|
google-apis-playcustomapp_v1 (0.13.0)
|
||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-storage_v1 (0.31.0)
|
google-apis-storage_v1 (0.19.0)
|
||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-apis-core (>= 0.9.0, < 2.a)
|
||||||
google-cloud-core (1.7.0)
|
google-cloud-core (1.6.0)
|
||||||
google-cloud-env (>= 1.0, < 3.a)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.6.0)
|
google-cloud-env (1.6.0)
|
||||||
faraday (>= 0.17.3, < 3.0)
|
faraday (>= 0.17.3, < 3.0)
|
||||||
google-cloud-errors (1.4.0)
|
google-cloud-errors (1.3.1)
|
||||||
google-cloud-storage (1.47.0)
|
google-cloud-storage (1.44.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-apis-iamcredentials_v1 (~> 0.1)
|
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)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (1.8.1)
|
googleauth (1.8.0)
|
||||||
faraday (>= 0.17.3, < 3.a)
|
faraday (>= 0.17.3, < 3.a)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (>= 0.16, < 2.a)
|
signet (>= 0.16, < 2.a)
|
||||||
highline (2.0.3)
|
highline (2.0.3)
|
||||||
http-cookie (1.0.6)
|
http-cookie (1.0.5)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.7.2)
|
json (2.6.3)
|
||||||
jwt (2.8.2)
|
jwt (2.7.1)
|
||||||
base64
|
mini_magick (4.12.0)
|
||||||
mini_magick (4.13.1)
|
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.4.1)
|
multipart-post (2.3.0)
|
||||||
nanaimo (0.3.0)
|
nanaimo (0.3.0)
|
||||||
naturally (2.2.1)
|
naturally (2.2.1)
|
||||||
nkf (0.2.0)
|
optparse (0.1.1)
|
||||||
optparse (0.5.0)
|
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
plist (3.7.1)
|
plist (3.7.0)
|
||||||
public_suffix (5.1.1)
|
public_suffix (5.0.3)
|
||||||
rake (13.2.1)
|
rake (13.0.6)
|
||||||
representable (3.2.0)
|
representable (3.2.0)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rexml (3.2.9)
|
rexml (3.2.6)
|
||||||
strscan
|
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
security (0.1.5)
|
security (0.1.3)
|
||||||
signet (0.19.0)
|
signet (0.18.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.5, < 3.a)
|
faraday (>= 0.17.5, < 3.a)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
@@ -185,19 +180,22 @@ GEM
|
|||||||
simctl (1.6.10)
|
simctl (1.6.10)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
strscan (3.1.0)
|
|
||||||
terminal-notifier (2.0.0)
|
terminal-notifier (2.0.0)
|
||||||
terminal-table (3.0.2)
|
terminal-table (3.0.2)
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
trailblazer-option (0.1.2)
|
trailblazer-option (0.1.2)
|
||||||
tty-cursor (0.7.1)
|
tty-cursor (0.7.1)
|
||||||
tty-screen (0.8.2)
|
tty-screen (0.8.1)
|
||||||
tty-spinner (0.9.3)
|
tty-spinner (0.9.3)
|
||||||
tty-cursor (~> 0.7)
|
tty-cursor (~> 0.7)
|
||||||
uber (0.1.0)
|
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)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.24.0)
|
xcodeproj (1.22.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
@@ -216,4 +214,4 @@ DEPENDENCIES
|
|||||||
fastlane
|
fastlane
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.5.9
|
2.3.26
|
||||||
|
|||||||
165
app/build.gradle
Normal file
165
app/build.gradle
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import com.github.spotbugs.snom.SpotBugsTask
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
id 'com.github.spotbugs'
|
||||||
|
}
|
||||||
|
|
||||||
|
spotbugs {
|
||||||
|
ignoreFailures = false
|
||||||
|
effort = 'max'
|
||||||
|
excludeFilter = file("./config/spotbugs/exclude.xml")
|
||||||
|
reportsDir = file("$buildDir/reports/spotbugs/")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk 33
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "me.hackerchick.catima"
|
||||||
|
minSdk 21
|
||||||
|
targetSdk 33
|
||||||
|
versionCode 131
|
||||||
|
versionName "2.26.0"
|
||||||
|
|
||||||
|
vectorDrawables.useSupportLibrary true
|
||||||
|
multiDexEnabled true
|
||||||
|
|
||||||
|
resourceConfigurations += ["ar", "bg", "bn", "bn-rIN", "bs", "cs", "da", "de", "el-rGR", "en", "eo", "es", "es-rAR", "fi", "fr", "he-rIL", "hi", "hr", "hu", "in-rID", "is", "it", "ja", "ko", "lt", "lv", "nb-rNO", "nl", "oc", "pl", "pt", "ro-rRO", "ru", "sk", "sl", "sv", "tr", "uk", "zh-rTW", "zh-rCN"]
|
||||||
|
|
||||||
|
//testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
flavorDimensions "version"
|
||||||
|
productFlavors {
|
||||||
|
fdroid {
|
||||||
|
dimension "version"
|
||||||
|
}
|
||||||
|
screengrab {
|
||||||
|
dimension "version"
|
||||||
|
applicationIdSuffix = ".screengrab"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
resValue "string", "app_name", "Catima"
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
applicationIdSuffix ".debug"
|
||||||
|
resValue "string", "app_name", "Catima Debug"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
}
|
||||||
|
|
||||||
|
bundle {
|
||||||
|
language {
|
||||||
|
enableSplit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
encoding "UTF-8"
|
||||||
|
|
||||||
|
// Flag to enable support for the new language APIs
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
|
|
||||||
|
sourceCompatibility JavaVersion.VERSION_11
|
||||||
|
targetCompatibility JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
test {
|
||||||
|
resources.srcDirs += ['src/test/res']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starting with Android Studio 3 Robolectric is unable to find resources.
|
||||||
|
// The following allows it to find the resources.
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
all {
|
||||||
|
testLogging {
|
||||||
|
events 'started', 'passed', 'skipped', 'failed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
includeAndroidResources true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lint {
|
||||||
|
lintConfig file('lint.xml')
|
||||||
|
}
|
||||||
|
namespace 'protect.card_locker'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// AndroidX
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
implementation 'androidx.exifinterface:exifinterface:1.3.6'
|
||||||
|
implementation 'androidx.palette:palette:1.0.0'
|
||||||
|
implementation 'androidx.preference:preference:1.2.0'
|
||||||
|
implementation 'com.google.android.material:material:1.9.0'
|
||||||
|
implementation 'com.github.yalantis:ucrop:2.2.8'
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||||
|
|
||||||
|
// Splash Screen
|
||||||
|
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||||
|
|
||||||
|
// Third-party
|
||||||
|
implementation 'com.journeyapps:zxing-android-embedded:4.3.0@aar'
|
||||||
|
implementation 'com.google.zxing:core:3.5.2'
|
||||||
|
implementation 'org.apache.commons:commons-csv:1.9.0'
|
||||||
|
implementation 'com.jaredrummler:colorpicker:1.1.0'
|
||||||
|
implementation 'net.lingala.zip4j:zip4j:2.11.5'
|
||||||
|
|
||||||
|
// SpotBugs
|
||||||
|
implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0'
|
||||||
|
|
||||||
|
// Testing
|
||||||
|
testImplementation 'androidx.test:core:1.5.0'
|
||||||
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
testImplementation 'org.robolectric:robolectric:4.10.3'
|
||||||
|
|
||||||
|
// Screenshots
|
||||||
|
testImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||||
|
testImplementation 'com.android.support.test:rules:1.0.2'
|
||||||
|
testImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
|
testImplementation 'tools.fastlane:screengrab:2.1.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(SpotBugsTask) {
|
||||||
|
|
||||||
|
description 'Run spotbugs'
|
||||||
|
group 'verification'
|
||||||
|
|
||||||
|
//classes = fileTree('build/intermediates/javac/debug/compileDebugJavaWithJavac/classes')
|
||||||
|
//source = fileTree('src/main/java')
|
||||||
|
//classpath = files()
|
||||||
|
|
||||||
|
reports {
|
||||||
|
xml.enabled = false
|
||||||
|
html.enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('copyRawResFiles', Copy) {
|
||||||
|
from layout.projectDirectory.file("../CHANGELOG.md"),
|
||||||
|
layout.projectDirectory.file("../PRIVACY.md")
|
||||||
|
into layout.projectDirectory.dir("src/main/res/raw")
|
||||||
|
rename { String fileName -> fileName.toLowerCase() }
|
||||||
|
}
|
||||||
|
|
||||||
|
project.afterEvaluate {
|
||||||
|
tasks.each { task ->
|
||||||
|
if (task != copyRawResFiles) {
|
||||||
|
task.dependsOn(copyRawResFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
|
||||||
import com.github.spotbugs.snom.SpotBugsTask
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("com.android.application")
|
|
||||||
id("com.github.spotbugs")
|
|
||||||
}
|
|
||||||
|
|
||||||
spotbugs {
|
|
||||||
ignoreFailures.set(false)
|
|
||||||
setEffort("max")
|
|
||||||
excludeFilter.set(file("./config/spotbugs/exclude.xml"))
|
|
||||||
reportsDir.set(layout.buildDirectory.file("reports/spotbugs/").get().asFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "protect.card_locker"
|
|
||||||
compileSdk = 34
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId = "me.hackerchick.catima"
|
|
||||||
minSdk = 21
|
|
||||||
targetSdk = 34
|
|
||||||
versionCode = 136
|
|
||||||
versionName = "2.30.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-rBR", "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.7.0")
|
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
|
||||||
implementation("androidx.exifinterface:exifinterface:1.3.7")
|
|
||||||
implementation("androidx.palette:palette:1.0.0")
|
|
||||||
implementation("androidx.preference:preference:1.2.1")
|
|
||||||
implementation("com.google.android.material:material:1.12.0")
|
|
||||||
implementation("com.github.yalantis:ucrop:2.2.9")
|
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
|
|
||||||
|
|
||||||
// Splash Screen
|
|
||||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
|
||||||
|
|
||||||
// Third-party
|
|
||||||
implementation("com.journeyapps:zxing-android-embedded:4.3.0@aar")
|
|
||||||
implementation("com.google.zxing:core:3.5.3")
|
|
||||||
implementation("org.apache.commons:commons-csv:1.9.0")
|
|
||||||
implementation("com.jaredrummler:colorpicker:1.1.0")
|
|
||||||
implementation("net.lingala.zip4j:zip4j:2.11.5")
|
|
||||||
|
|
||||||
// SpotBugs
|
|
||||||
implementation("io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0")
|
|
||||||
|
|
||||||
// Testing
|
|
||||||
testImplementation("androidx.test:core:1.6.1")
|
|
||||||
testImplementation("junit:junit:4.13.2")
|
|
||||||
testImplementation("org.robolectric:robolectric:4.12.2")
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
# By default, the flags in this file are appended to flags specified
|
||||||
# in /Users/brarcher/Library/Android/sdk/tools/proguard/proguard-android.txt
|
# in /Users/brarcher/Library/Android/sdk/tools/proguard/proguard-android.txt
|
||||||
# You can edit the include path and order by changing the proguardFiles
|
# 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
|
# For more details, see
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">تصحيح Catima</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Catima Debug</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Catima Debug</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Αποσφαλμάτωση Catima</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Depuración de Catima</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Débogage de Catima</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">कैटिमा डीबग</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Catima Debug</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Catima Debug</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Catima Debug</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Catima Debug</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">ಕ್ಯಾಟಿಮಾ ಡೀಬಗ್</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Catima 디버그</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Catima-avlusing</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Catima-foutopsporing</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Catima Debug</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Depuração do Catima</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,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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
|
||||||
<string name="app_name">Catima Debug</string>
|
|
||||||
</resources>
|
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.camera"
|
android:name="android.hardware.camera"
|
||||||
android:required="false" />
|
android:required="true" />
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.camera.autofocus"
|
android:name="android.hardware.camera.autofocus"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
<application
|
<application
|
||||||
android:name=".LoyaltyCardLockerApplication"
|
android:name=".LoyaltyCardLockerApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
@@ -33,6 +32,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
android:theme="@style/Theme.App.Starting">
|
android:theme="@style/Theme.App.Starting">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
@@ -43,9 +43,7 @@
|
|||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="text/plain" />
|
|
||||||
<data android:mimeType="image/*" />
|
<data android:mimeType="image/*" />
|
||||||
<data android:mimeType="application/pdf" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
@@ -113,12 +111,10 @@
|
|||||||
android:name=".preferences.SettingsActivity"
|
android:name=".preferences.SettingsActivity"
|
||||||
android:label="@string/settings"
|
android:label="@string/settings"
|
||||||
android:theme="@style/AppTheme.NoActionBar" />
|
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
|
<activity
|
||||||
android:name=".ImportExportActivity"
|
android:name=".ImportExportActivity"
|
||||||
android:label="@string/importExport"
|
android:label="@string/importExport"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:screenOrientation="locked"
|
|
||||||
android:theme="@style/AppTheme.NoActionBar">
|
android:theme="@style/AppTheme.NoActionBar">
|
||||||
|
|
||||||
<!-- ZIP Intent Filter -->
|
<!-- ZIP Intent Filter -->
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import android.view.View;
|
|||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
|
|
||||||
import protect.card_locker.databinding.AboutActivityBinding;
|
import protect.card_locker.databinding.AboutActivityBinding;
|
||||||
|
|
||||||
public class AboutActivity extends CatimaAppCompatActivity {
|
public class AboutActivity extends CatimaAppCompatActivity {
|
||||||
@@ -43,7 +43,7 @@ public class AboutActivity extends CatimaAppCompatActivity {
|
|||||||
binding.privacy.setTag("https://catima.app/privacy-policy/");
|
binding.privacy.setTag("https://catima.app/privacy-policy/");
|
||||||
binding.reportError.setTag("https://github.com/CatimaLoyalty/Android/issues");
|
binding.reportError.setTag("https://github.com/CatimaLoyalty/Android/issues");
|
||||||
binding.rate.setTag("https://play.google.com/store/apps/details?id=me.hackerchick.catima");
|
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);
|
boolean installedFromGooglePlay = Utils.installedFromGooglePlay(this);
|
||||||
// Hide Google Play rate button if not on Google Play
|
// Hide Google Play rate button if not on Google Play
|
||||||
@@ -98,7 +98,11 @@ public class AboutActivity extends CatimaAppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showCredits() {
|
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) {
|
private void showHistory(View view) {
|
||||||
@@ -113,7 +117,7 @@ public class AboutActivity extends CatimaAppCompatActivity {
|
|||||||
showHTML(R.string.privacy_policy, content.getPrivacyInfo(), view);
|
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);
|
int dialogContentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
|
||||||
TextView textView = new TextView(this);
|
TextView textView = new TextView(this);
|
||||||
textView.setText(text);
|
textView.setText(text);
|
||||||
@@ -121,21 +125,12 @@ public class AboutActivity extends CatimaAppCompatActivity {
|
|||||||
ScrollView scrollView = new ScrollView(this);
|
ScrollView scrollView = new ScrollView(this);
|
||||||
scrollView.addView(textView);
|
scrollView.addView(textView);
|
||||||
scrollView.setPadding(dialogContentPadding, dialogContentPadding / 2, dialogContentPadding, 0);
|
scrollView.setPadding(dialogContentPadding, dialogContentPadding / 2, dialogContentPadding, 0);
|
||||||
|
new MaterialAlertDialogBuilder(this)
|
||||||
// Create dialog
|
|
||||||
MaterialAlertDialogBuilder materialAlertDialogBuilder = new MaterialAlertDialogBuilder(this);
|
|
||||||
materialAlertDialogBuilder
|
|
||||||
.setTitle(title)
|
.setTitle(title)
|
||||||
.setView(scrollView)
|
.setView(scrollView)
|
||||||
.setPositiveButton(R.string.ok, null);
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.setNeutralButton(R.string.view_online, (dialog, which) -> openExternalBrowser(view))
|
||||||
// Add View online button if an URL is linked to this view
|
.show();
|
||||||
if (view != null && view.getTag() != null) {
|
|
||||||
materialAlertDialogBuilder.setNeutralButton(R.string.view_online, (dialog, which) -> openExternalBrowser(view));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show dialog
|
|
||||||
materialAlertDialogBuilder.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openExternalBrowser(View view) {
|
private void openExternalBrowser(View view) {
|
||||||
|
|||||||
@@ -129,19 +129,19 @@ public class AboutContent {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Spanned getContributorInfo() {
|
public String getContributorInfo() {
|
||||||
StringBuilder contributorInfo = new StringBuilder();
|
StringBuilder contributorInfo = new StringBuilder();
|
||||||
contributorInfo.append(getCopyright());
|
contributorInfo.append(getCopyright());
|
||||||
contributorInfo.append("<br/><br/>");
|
contributorInfo.append("\n\n");
|
||||||
contributorInfo.append(context.getString(R.string.app_copyright_old));
|
contributorInfo.append(context.getString(R.string.app_copyright_old));
|
||||||
contributorInfo.append("<br/><br/>");
|
contributorInfo.append("\n\n");
|
||||||
contributorInfo.append(String.format(context.getString(R.string.app_contributors), getContributors()));
|
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_contributors), getContributors()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||||
contributorInfo.append("<br/><br/>");
|
contributorInfo.append("\n\n");
|
||||||
contributorInfo.append(String.format(context.getString(R.string.app_libraries), getThirdPartyLibraries()));
|
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_libraries), getThirdPartyLibraries()), HtmlCompat.FROM_HTML_MODE_COMPACT));
|
||||||
contributorInfo.append("<br/><br/>");
|
contributorInfo.append("\n\n");
|
||||||
contributorInfo.append(String.format(context.getString(R.string.app_resources), getUsedThirdPartyAssets()));
|
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() {
|
public Spanned getHistoryInfo() {
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ import android.widget.EditText;
|
|||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
import protect.card_locker.databinding.BarcodeSelectorActivityBinding;
|
import protect.card_locker.databinding.BarcodeSelectorActivityBinding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,17 +3,12 @@ package protect.card_locker;
|
|||||||
public class BarcodeValues {
|
public class BarcodeValues {
|
||||||
private final String mFormat;
|
private final String mFormat;
|
||||||
private final String mContent;
|
private final String mContent;
|
||||||
private String mNote;
|
|
||||||
|
|
||||||
public BarcodeValues(String format, String content) {
|
public BarcodeValues(String format, String content) {
|
||||||
mFormat = format;
|
mFormat = format;
|
||||||
mContent = content;
|
mContent = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNote(String note) {
|
|
||||||
mNote = note;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String format() {
|
public String format() {
|
||||||
return mFormat;
|
return mFormat;
|
||||||
}
|
}
|
||||||
@@ -22,5 +17,7 @@ public class BarcodeValues {
|
|||||||
return mContent;
|
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.service.controls.templates.StatelessTemplate;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Flow;
|
import java.util.concurrent.Flow;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
public class CardsOnPowerScreenService extends ControlsProviderService {
|
public class CardsOnPowerScreenService extends ControlsProviderService {
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.graphics.Color;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -14,8 +13,6 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||||||
import androidx.core.view.WindowInsetsControllerCompat;
|
import androidx.core.view.WindowInsetsControllerCompat;
|
||||||
|
|
||||||
public class CatimaAppCompatActivity extends AppCompatActivity {
|
public class CatimaAppCompatActivity extends AppCompatActivity {
|
||||||
protected boolean activityOverridesNavBarColor = false;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context base) {
|
protected void attachBaseContext(Context base) {
|
||||||
// Apply chosen language
|
// Apply chosen language
|
||||||
@@ -33,31 +30,20 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
|
|||||||
super.onPostCreate(savedInstanceState);
|
super.onPostCreate(savedInstanceState);
|
||||||
// material 3 designer does not consider status bar colors
|
// 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
|
// XXX changing this in onCreate causes issues with the splash screen activity, so doing this here
|
||||||
Window window = getWindow();
|
boolean darkMode = Utils.isDarkModeEnabled(this);
|
||||||
if (window != null) {
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
boolean darkMode = Utils.isDarkModeEnabled(this);
|
View decorView = getWindow().getDecorView();
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(getWindow(), decorView);
|
||||||
View decorView = window.getDecorView();
|
wic.setAppearanceLightStatusBars(!darkMode);
|
||||||
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView);
|
getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||||
wic.setAppearanceLightStatusBars(!darkMode);
|
} else {
|
||||||
window.setStatusBarColor(Color.TRANSPARENT);
|
// icons are always white back then
|
||||||
} else {
|
getWindow().setStatusBarColor(darkMode ? Color.TRANSPARENT : Color.argb(127, 0, 0, 0));
|
||||||
// icons are always white back then
|
|
||||||
window.setStatusBarColor(darkMode ? Color.TRANSPARENT : Color.argb(127, 0, 0, 0));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// XXX android 9 and below has a nasty rendering bug if the theme was patched earlier
|
// XXX android 9 and below has a nasty rendering bug if the theme was patched earlier
|
||||||
Utils.postPatchColors(this);
|
Utils.postPatchColors(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
if (!activityOverridesNavBarColor) {
|
|
||||||
Utils.setNavigationBarColor(this, null, Utils.resolveBackgroundColor(this), !Utils.isDarkModeEnabled(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void enableToolbarBackButton() {
|
protected void enableToolbarBackButton() {
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
if (actionBar != null) {
|
if (actionBar != null) {
|
||||||
|
|||||||
@@ -4,24 +4,22 @@ import android.app.Activity;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.core.util.Consumer;
|
|
||||||
|
|
||||||
import com.journeyapps.barcodescanner.CaptureManager;
|
import com.journeyapps.barcodescanner.CaptureManager;
|
||||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
||||||
|
|
||||||
public class CatimaCaptureManager extends CaptureManager {
|
public class CatimaCaptureManager extends CaptureManager {
|
||||||
private final Consumer<String> mErrorCallback;
|
private final Context mContext;
|
||||||
|
|
||||||
public CatimaCaptureManager(Activity activity, DecoratedBarcodeView barcodeView, Consumer<String> errorCallback) {
|
public CatimaCaptureManager(Activity activity, DecoratedBarcodeView barcodeView) {
|
||||||
super(activity, barcodeView);
|
super(activity, barcodeView);
|
||||||
|
|
||||||
mErrorCallback = errorCallback;
|
mContext = activity.getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void displayFrameworkBugMessageAndExit(String message) {
|
protected void displayFrameworkBugMessageAndExit(String message) {
|
||||||
// We don't want to exit, as we also have a enter from card image and add manually button here
|
// We don't want to exit, as we also have a enter from card image and add manually button here
|
||||||
// So, instead, we call our error callback
|
// So we show a toast instead
|
||||||
mErrorCallback.accept(message);
|
Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import protect.card_locker.databinding.GroupLayoutBinding;
|
import protect.card_locker.databinding.GroupLayoutBinding;
|
||||||
|
import protect.card_locker.preferences.Settings;
|
||||||
|
|
||||||
public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.GroupListItemViewHolder> {
|
public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.GroupListItemViewHolder> {
|
||||||
public final Context mContext;
|
public final Context mContext;
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
@@ -12,24 +14,25 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.Toast;
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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.async.TaskHandler;
|
||||||
import protect.card_locker.databinding.ImportExportActivityBinding;
|
import protect.card_locker.databinding.ImportExportActivityBinding;
|
||||||
import protect.card_locker.importexport.DataFormat;
|
import protect.card_locker.importexport.DataFormat;
|
||||||
@@ -80,21 +83,15 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
|||||||
Log.e(TAG, "Activity returned NULL uri");
|
Log.e(TAG, "Activity returned NULL uri");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
|
try {
|
||||||
// FIXME: This is still suboptimal, because showing that the export started is delayed until the network request finishes
|
OutputStream writer = getContentResolver().openOutputStream(uri);
|
||||||
new Thread() {
|
Log.e(TAG, "Starting file export with: " + result.toString());
|
||||||
@Override
|
startExport(writer, uri, exportPassword.toCharArray(), true);
|
||||||
public void run() {
|
} catch (IOException e) {
|
||||||
try {
|
Log.e(TAG, "Failed to export file: " + result.toString(), e);
|
||||||
OutputStream writer = getContentResolver().openOutputStream(uri);
|
onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), uri);
|
||||||
Log.d(TAG, "Starting file export with: " + result);
|
}
|
||||||
startExport(writer, uri, exportPassword.toCharArray(), true);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Failed to export file: " + result, e);
|
|
||||||
onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
});
|
});
|
||||||
fileOpenLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> {
|
fileOpenLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> {
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
@@ -129,19 +126,16 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
|||||||
builder.setTitle(R.string.exportPassword);
|
builder.setTitle(R.string.exportPassword);
|
||||||
|
|
||||||
FrameLayout container = new FrameLayout(ImportExportActivity.this);
|
FrameLayout container = new FrameLayout(ImportExportActivity.this);
|
||||||
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
|
params.leftMargin = 50;
|
||||||
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
|
params.rightMargin = 50;
|
||||||
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(ImportExportActivity.this);
|
||||||
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
|
input.setLayoutParams(params);
|
||||||
input.setHint(R.string.exportPasswordHint);
|
input.setHint(R.string.exportPasswordHint);
|
||||||
|
|
||||||
textInputLayout.addView(input);
|
container.addView(input);
|
||||||
container.addView(textInputLayout);
|
|
||||||
builder.setView(container);
|
builder.setView(container);
|
||||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||||
exportPassword = input.getText().toString();
|
exportPassword = input.getText().toString();
|
||||||
@@ -154,6 +148,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
|||||||
});
|
});
|
||||||
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
|
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
|
||||||
builder.show();
|
builder.show();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check that there is a file manager available
|
// Check that there is a file manager available
|
||||||
@@ -166,21 +161,14 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void openFileForImport(Uri uri, char[] password) {
|
private void openFileForImport(Uri uri, char[] password) {
|
||||||
// Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files
|
try {
|
||||||
// FIXME: This is still suboptimal, because showing that the import started is delayed until the network request finishes
|
InputStream reader = getContentResolver().openInputStream(uri);
|
||||||
new Thread() {
|
Log.e(TAG, "Starting file import with: " + uri.toString());
|
||||||
@Override
|
startImport(reader, uri, importDataFormat, password, true);
|
||||||
public void run() {
|
} catch (IOException e) {
|
||||||
try {
|
Log.e(TAG, "Failed to import file: " + uri.toString(), e);
|
||||||
InputStream reader = getContentResolver().openInputStream(uri);
|
onImportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, e.toString()), uri, importDataFormat);
|
||||||
Log.d(TAG, "Starting file import with: " + uri);
|
}
|
||||||
startImport(reader, uri, importDataFormat, password, true);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Failed to import file: " + uri, e);
|
|
||||||
onImportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, e.toString()), uri, importDataFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void chooseImportType(boolean choosePicker,
|
private void chooseImportType(boolean choosePicker,
|
||||||
@@ -332,21 +320,9 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
|||||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||||
builder.setTitle(R.string.passwordRequired);
|
builder.setTitle(R.string.passwordRequired);
|
||||||
|
|
||||||
FrameLayout container = new FrameLayout(ImportExportActivity.this);
|
final EditText input = new EditText(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);
|
|
||||||
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
input.setHint(R.string.exportPasswordHint);
|
builder.setView(input);
|
||||||
|
|
||||||
textInputLayout.addView(input);
|
|
||||||
container.addView(textInputLayout);
|
|
||||||
builder.setView(container);
|
|
||||||
|
|
||||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||||
openFileForImport(uri, input.getText().toString().toCharArray());
|
openFileForImport(uri, input.getText().toString().toCharArray());
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import android.database.Cursor;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
public class LoyaltyCard implements Parcelable {
|
public class LoyaltyCard implements Parcelable {
|
||||||
public final int id;
|
public final int id;
|
||||||
public final String store;
|
public final String store;
|
||||||
@@ -164,7 +164,7 @@ public class LoyaltyCard implements Parcelable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
return 0;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import android.database.Cursor;
|
|||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.SparseBooleanArray;
|
import android.util.SparseBooleanArray;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
@@ -17,13 +15,6 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
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.card.MaterialCardView;
|
||||||
import com.google.android.material.color.MaterialColors;
|
import com.google.android.material.color.MaterialColors;
|
||||||
|
|
||||||
@@ -31,6 +22,13 @@ import java.math.BigDecimal;
|
|||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.util.ArrayList;
|
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;
|
import protect.card_locker.databinding.LoyaltyCardLayoutBinding;
|
||||||
|
|
||||||
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder> {
|
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder> {
|
||||||
@@ -90,29 +88,9 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
inputHolder.mDivider.setVisibility(View.GONE);
|
inputHolder.mDivider.setVisibility(View.GONE);
|
||||||
|
|
||||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(inputCursor);
|
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(inputCursor);
|
||||||
|
Bitmap icon = Utils.retrieveCardImage(mContext, loyaltyCard.id, ImageLocationType.icon);
|
||||||
|
|
||||||
inputHolder.mCardIcon.setContentDescription(loyaltyCard.store);
|
if (mLoyaltyCardListDisplayOptions.showingNameBelowThumbnail() && icon != null) {
|
||||||
|
|
||||||
// Default header at first, real icon will be retrieved asynchronously if it exists to ensure
|
|
||||||
// smooth scrolling even on slower devices
|
|
||||||
Utils.setIconOrTextWithBackground(mContext, loyaltyCard, null, inputHolder.mCardIcon, inputHolder.mCardText);
|
|
||||||
inputHolder.toggleCardStateIcon(loyaltyCard.starStatus != 0, loyaltyCard.archiveStatus != 0, itemSelected(inputCursor.getPosition()));
|
|
||||||
boolean hasIcon = Utils.retrieveCardImageAsFile(mContext, loyaltyCard.id, ImageLocationType.icon).exists();
|
|
||||||
if (hasIcon) {
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Bitmap icon = Utils.retrieveCardImage(mContext, loyaltyCard.id, ImageLocationType.icon);
|
|
||||||
|
|
||||||
new Handler(Looper.getMainLooper()).post(() -> {
|
|
||||||
inputHolder.mIconBackgroundColor = Utils.setIconOrTextWithBackground(mContext, loyaltyCard, icon, inputHolder.mCardIcon, inputHolder.mCardText);
|
|
||||||
inputHolder.toggleCardStateIcon(loyaltyCard.starStatus != 0, loyaltyCard.archiveStatus != 0, itemSelected(inputHolder.getAdapterPosition()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mLoyaltyCardListDisplayOptions.showingNameBelowThumbnail() && hasIcon) {
|
|
||||||
showDivider = true;
|
showDivider = true;
|
||||||
inputHolder.setStoreField(loyaltyCard.store);
|
inputHolder.setStoreField(loyaltyCard.store);
|
||||||
} else {
|
} else {
|
||||||
@@ -144,6 +122,12 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
inputHolder.setExtraField(inputHolder.mExpiryField, null, null, false);
|
inputHolder.setExtraField(inputHolder.mExpiryField, null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inputHolder.mCardIcon.setContentDescription(loyaltyCard.store);
|
||||||
|
Utils.setIconOrTextWithBackground(mContext, loyaltyCard, icon, inputHolder.mCardIcon, inputHolder.mCardText);
|
||||||
|
inputHolder.setIconBackgroundColor(Utils.getHeaderColor(mContext, loyaltyCard));
|
||||||
|
|
||||||
|
inputHolder.toggleCardStateIcon(loyaltyCard.starStatus != 0, loyaltyCard.archiveStatus != 0, itemSelected(inputCursor.getPosition()));
|
||||||
|
|
||||||
inputHolder.itemView.setActivated(mSelectedItems.get(inputCursor.getPosition(), false));
|
inputHolder.itemView.setActivated(mSelectedItems.get(inputCursor.getPosition(), false));
|
||||||
applyIconAnimation(inputHolder, inputCursor.getPosition());
|
applyIconAnimation(inputHolder, inputCursor.getPosition());
|
||||||
applyClickEvents(inputHolder, inputCursor.getPosition());
|
applyClickEvents(inputHolder, inputCursor.getPosition());
|
||||||
@@ -355,6 +339,11 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
|
|||||||
mArchivedBackground.invalidate();
|
mArchivedBackground.invalidate();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setIconBackgroundColor(int color) {
|
||||||
|
mIconBackgroundColor = color;
|
||||||
|
mCardIcon.setBackgroundColor(color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int dpToPx(int dp, Context mContext) {
|
public int dpToPx(int dp, Context mContext) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package protect.card_locker;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.DatePickerDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -27,34 +29,33 @@ import android.view.WindowManager;
|
|||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.AutoCompleteTextView;
|
import android.widget.AutoCompleteTextView;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.DatePicker;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.activity.OnBackPressedCallback;
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.AppCompatTextView;
|
import androidx.appcompat.widget.AppCompatTextView;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
import androidx.exifinterface.media.ExifInterface;
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
||||||
import com.google.android.material.chip.Chip;
|
import com.google.android.material.chip.Chip;
|
||||||
import com.google.android.material.chip.ChipGroup;
|
import com.google.android.material.chip.ChipGroup;
|
||||||
import com.google.android.material.color.MaterialColors;
|
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.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
|
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
|
||||||
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
|
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
|
||||||
@@ -73,6 +74,7 @@ import java.util.Calendar;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
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_TAB_INDEX = "savedTab";
|
||||||
private final String STATE_TEMP_CARD = "tempLoyaltyCard";
|
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_REQUESTED_IMAGE = "requestedImage";
|
||||||
private final String STATE_FRONT_IMAGE_UNSAVED = "frontImageUnsaved";
|
private final String STATE_FRONT_IMAGE_UNSAVED = "frontImageUnsaved";
|
||||||
private final String STATE_BACK_IMAGE_UNSAVED = "backImageUnsaved";
|
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_ICON_REMOVED = "iconRemoved";
|
||||||
private final String STATE_OPEN_SET_ICON_MENU = "openSetIconMenu";
|
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_CAMERA_IMAGE_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_camera_image.jpg";
|
||||||
private final String TEMP_CROP_IMAGE_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_crop_image.png";
|
private final String TEMP_CROP_IMAGE_NAME = LoyaltyCardEditActivity.class.getSimpleName() + "_crop_image.png";
|
||||||
private final Bitmap.CompressFormat TEMP_CROP_IMAGE_FORMAT = Bitmap.CompressFormat.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<>();
|
HashMap<String, String> currencySymbols = new HashMap<>();
|
||||||
|
|
||||||
LoyaltyCard tempLoyaltyCard;
|
LoyaltyCard tempLoyaltyCard;
|
||||||
LoyaltyCardField tempLoyaltyCardField;
|
|
||||||
|
|
||||||
ActivityResultLauncher<Uri> mPhotoTakerLauncher;
|
ActivityResultLauncher<Uri> mPhotoTakerLauncher;
|
||||||
ActivityResultLauncher<Intent> mPhotoPickerLauncher;
|
ActivityResultLauncher<Intent> mPhotoPickerLauncher;
|
||||||
@@ -270,7 +267,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
|||||||
tabs = binding.tabs;
|
tabs = binding.tabs;
|
||||||
savedInstanceState.putInt(STATE_TAB_INDEX, tabs.getSelectedTabPosition());
|
savedInstanceState.putInt(STATE_TAB_INDEX, tabs.getSelectedTabPosition());
|
||||||
savedInstanceState.putParcelable(STATE_TEMP_CARD, tempLoyaltyCard);
|
savedInstanceState.putParcelable(STATE_TEMP_CARD, tempLoyaltyCard);
|
||||||
savedInstanceState.putSerializable(STATE_TEMP_CARD_FIELD, tempLoyaltyCardField);
|
|
||||||
savedInstanceState.putInt(STATE_REQUESTED_IMAGE, mRequestedImage);
|
savedInstanceState.putInt(STATE_REQUESTED_IMAGE, mRequestedImage);
|
||||||
|
|
||||||
Object cardImageFrontObj = cardImageFront.getTag();
|
Object cardImageFrontObj = cardImageFront.getTag();
|
||||||
@@ -306,7 +302,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
|||||||
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||||
onRestoring = true;
|
onRestoring = true;
|
||||||
tempLoyaltyCard = savedInstanceState.getParcelable(STATE_TEMP_CARD);
|
tempLoyaltyCard = savedInstanceState.getParcelable(STATE_TEMP_CARD);
|
||||||
tempLoyaltyCardField = (LoyaltyCardField) savedInstanceState.getSerializable(STATE_TEMP_CARD_FIELD);
|
|
||||||
super.onRestoreInstanceState(savedInstanceState);
|
super.onRestoreInstanceState(savedInstanceState);
|
||||||
tabs = binding.tabs;
|
tabs = binding.tabs;
|
||||||
tabs.selectTab(tabs.getTabAt(savedInstanceState.getInt(STATE_TAB_INDEX)));
|
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);
|
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) -> {
|
balanceField.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
if (!hasFocus && !onResuming && !onRestoring) {
|
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");
|
Log.d("barcode card id editor", "barcode and card id editor picker returned without an intent");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, getApplicationContext());
|
||||||
|
|
||||||
List<BarcodeValues> barcodeValuesList = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, getApplicationContext());
|
cardId = barcodeValues.content();
|
||||||
|
barcodeType = barcodeValues.format();
|
||||||
Utils.makeUserChooseBarcodeFromList(this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
|
barcodeId = "";
|
||||||
@Override
|
|
||||||
public void onUserChoseBarcode(BarcodeValues barcodeValues) {
|
|
||||||
cardId = barcodeValues.content();
|
|
||||||
barcodeType = barcodeValues.format();
|
|
||||||
barcodeId = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUserDismissedSelector() {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -708,13 +705,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
|||||||
});
|
});
|
||||||
|
|
||||||
mCropperOptions = new UCrop.Options();
|
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
|
// 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")
|
@SuppressLint("DefaultLocale")
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
Log.i(TAG, "To view card: " + loyaltyCardId);
|
Log.i(TAG, "To view card: " + loyaltyCardId);
|
||||||
@@ -1026,14 +1016,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
|||||||
if (!lastValue.toString().equals(getString(chooseDateOptionStringId))) {
|
if (!lastValue.toString().equals(getString(chooseDateOptionStringId))) {
|
||||||
dateField.setText(lastValue);
|
dateField.setText(lastValue);
|
||||||
}
|
}
|
||||||
showDatePicker(
|
DialogFragment datePickerFragment = DatePickerFragment.newInstance(
|
||||||
loyaltyCardField,
|
loyaltyCardField,
|
||||||
(Date) dateField.getTag(),
|
(Date) dateField.getTag(),
|
||||||
// if the expiry date is being set, set date picker's minDate to the 'valid from' date
|
// if the expiry date is being set, set date picker's minDate to the 'valid from' date
|
||||||
loyaltyCardField == LoyaltyCardField.expiry ? (Date) validFromField.getTag() : null,
|
loyaltyCardField == LoyaltyCardField.expiry ? (Date) validFromField.getTag() : null,
|
||||||
// if the 'valid from' date is being set, set date picker's maxDate to the expiry date
|
// 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
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
@@ -1384,106 +1379,103 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
|
|||||||
// Nothing to do, no change made
|
// Nothing to do, no change made
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showDatePicker(
|
public static class DatePickerFragment extends DialogFragment
|
||||||
LoyaltyCardField loyaltyCardField,
|
implements DatePickerDialog.OnDateSetListener {
|
||||||
@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();
|
|
||||||
|
|
||||||
CalendarConstraints.DateValidator dateValidator;
|
public interface OnDatePickListener {
|
||||||
switch (loyaltyCardField) {
|
void onDatePicked(@NonNull LoyaltyCardField textFieldToEdit, @NonNull Date newDate);
|
||||||
case validFrom:
|
|
||||||
dateValidator = DateValidatorPointBackward.before(endDate);
|
|
||||||
break;
|
|
||||||
case expiry:
|
|
||||||
dateValidator = DateValidatorPointForward.from(startDate);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new AssertionError("Unexpected field: " + loyaltyCardField);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CalendarConstraints calendarConstraints = new CalendarConstraints.Builder()
|
private static final String TEXT_FIELD_TO_EDIT_ARGUMENT_KEY = "text_field_to_edit";
|
||||||
.setValidator(dateValidator)
|
private static final String CURRENT_DATE_ARGUMENT_KEY = "current_date";
|
||||||
.setStart(startDate)
|
private static final String MIN_DATE_ARGUMENT_KEY = "min_date";
|
||||||
.setEnd(endDate)
|
private static final String MAX_DATE_ARGUMENT_KEY = "max_date";
|
||||||
.build();
|
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
|
LoyaltyCardField textFieldEdit;
|
||||||
final Calendar calendar = Calendar.getInstance();
|
@Nullable
|
||||||
if (selectedDate != null) {
|
Date minDate;
|
||||||
calendar.setTime(selectedDate);
|
@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()
|
public static void registerDatePickListener(@NonNull AppCompatActivity activity, @NonNull OnDatePickListener listener) {
|
||||||
.setSelection(calendar.getTimeInMillis())
|
activity.getSupportFragmentManager().setFragmentResultListener(
|
||||||
.setCalendarConstraints(calendarConstraints)
|
PICK_DATE_REQUEST_KEY,
|
||||||
.build();
|
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
|
@NonNull
|
||||||
// See https://github.com/material-components/material-components-android/issues/1688
|
@Override
|
||||||
tempLoyaltyCardField = loyaltyCardField;
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
getSupportFragmentManager().addFragmentOnAttachListener((fragmentManager, fragment) -> {
|
Bundle args = requireArguments();
|
||||||
if (fragment instanceof MaterialDatePicker && Objects.equals(fragment.getTag(), PICK_DATE_REQUEST_KEY)) {
|
textFieldEdit = (LoyaltyCardField) args.getSerializable(TEXT_FIELD_TO_EDIT_ARGUMENT_KEY);
|
||||||
((MaterialDatePicker<Long>) fragment).addOnPositiveButtonClickListener(selection -> {
|
minDate = (Date) args.getSerializable(MIN_DATE_ARGUMENT_KEY);
|
||||||
Bundle args = new Bundle();
|
maxDate = (Date) args.getSerializable(MAX_DATE_ARGUMENT_KEY);
|
||||||
args.putLong(NEWLY_PICKED_DATE_ARGUMENT_KEY, selection);
|
// Use the current date as the default date in the picker
|
||||||
getSupportFragmentManager().setFragmentResult(PICK_DATE_REQUEST_KEY, args);
|
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
|
// Create a new instance of DatePickerDialog and return it
|
||||||
// See https://github.com/material-components/material-components-android/issues/1688
|
DatePickerDialog datePickerDialog = new DatePickerDialog(getActivity(), this, year, month, day);
|
||||||
private void setMaterialDatePickerResultListener() {
|
datePickerDialog.getDatePicker().setMinDate(minDate != null ? minDate.getTime() : getDefaultMinDateOfDatePicker());
|
||||||
MaterialDatePicker<Long> fragment = (MaterialDatePicker<Long>) getSupportFragmentManager().findFragmentByTag(PICK_DATE_REQUEST_KEY);
|
datePickerDialog.getDatePicker().setMaxDate(maxDate != null ? maxDate.getTime() : getDefaultMaxDateOfDatePicker());
|
||||||
if (fragment != null) {
|
return datePickerDialog;
|
||||||
fragment.addOnPositiveButtonClickListener(selection -> {
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putLong(NEWLY_PICKED_DATE_ARGUMENT_KEY, selection);
|
|
||||||
getSupportFragmentManager().setFragmentResult(PICK_DATE_REQUEST_KEY, args);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getSupportFragmentManager().setFragmentResultListener(
|
private long getDefaultMinDateOfDatePicker() {
|
||||||
PICK_DATE_REQUEST_KEY,
|
Calendar minDateCalendar = Calendar.getInstance();
|
||||||
this,
|
minDateCalendar.set(1970, 0, 1);
|
||||||
(requestKey, result) -> {
|
return minDateCalendar.getTimeInMillis();
|
||||||
long selection = result.getLong(NEWLY_PICKED_DATE_ARGUMENT_KEY);
|
}
|
||||||
|
|
||||||
Date newDate = new Date(selection);
|
private long getDefaultMaxDateOfDatePicker() {
|
||||||
switch (tempLoyaltyCardField) {
|
Calendar maxDateCalendar = Calendar.getInstance();
|
||||||
case validFrom:
|
maxDateCalendar.set(2100, 11, 31);
|
||||||
formatDateField(LoyaltyCardEditActivity.this, validFromField, newDate);
|
return maxDateCalendar.getTimeInMillis();
|
||||||
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 getDefaultMinDateOfDatePicker() {
|
public void onDateSet(DatePicker view, int year, int month, int day) {
|
||||||
Calendar minDateCalendar = Calendar.getInstance();
|
Calendar c = new GregorianCalendar();
|
||||||
minDateCalendar.set(1970, 0, 1);
|
c.set(Calendar.YEAR, year);
|
||||||
return minDateCalendar.getTimeInMillis();
|
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() {
|
long unixTime = c.getTimeInMillis();
|
||||||
Calendar maxDateCalendar = Calendar.getInstance();
|
|
||||||
maxDateCalendar.set(2100, 11, 31);
|
Date date = new Date(unixTime);
|
||||||
return maxDateCalendar.getTimeInMillis();
|
|
||||||
|
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() {
|
private void doSave() {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package protect.card_locker;
|
|||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
|
|
||||||
import protect.card_locker.preferences.Settings;
|
import protect.card_locker.preferences.Settings;
|
||||||
|
|
||||||
public class LoyaltyCardLockerApplication extends Application {
|
public class LoyaltyCardLockerApplication extends Application {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package protect.card_locker;
|
package protect.card_locker;
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
@@ -19,6 +20,7 @@ import android.text.method.DigitsKeyListener;
|
|||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.util.Linkify;
|
import android.text.util.Linkify;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -36,17 +38,19 @@ import android.widget.SeekBar;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.activity.OnBackPressedCallback;
|
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
import androidx.core.graphics.BlendModeColorFilterCompat;
|
import androidx.core.graphics.BlendModeColorFilterCompat;
|
||||||
import androidx.core.graphics.BlendModeCompat;
|
import androidx.core.graphics.BlendModeCompat;
|
||||||
import androidx.core.graphics.ColorUtils;
|
import androidx.core.graphics.ColorUtils;
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
|
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
|
||||||
|
import androidx.core.widget.TextViewCompat;
|
||||||
|
|
||||||
import com.google.android.material.color.MaterialColors;
|
import com.google.android.material.color.MaterialColors;
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
@@ -59,6 +63,7 @@ import java.text.DateFormat;
|
|||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Currency;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@@ -107,25 +112,22 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageType imageType = imageTypes.get(mainImageIndex);
|
|
||||||
|
|
||||||
// If the barcode is shown, switch to fullscreen layout
|
// If the barcode is shown, switch to fullscreen layout
|
||||||
if (imageType == ImageType.BARCODE) {
|
if (imageTypes.get(mainImageIndex) == ImageType.BARCODE) {
|
||||||
setFullscreen(true);
|
setFullscreen(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is an image, open it in the gallery.
|
// 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;
|
File file = null;
|
||||||
|
|
||||||
switch (imageType) {
|
switch (wantedImageType) {
|
||||||
case ICON:
|
|
||||||
file = Utils.retrieveCardImageAsFile(this, loyaltyCardId, ImageLocationType.icon);
|
|
||||||
break;
|
|
||||||
case IMAGE_FRONT:
|
case IMAGE_FRONT:
|
||||||
file = Utils.retrieveCardImageAsFile(this, loyaltyCardId, ImageLocationType.front);
|
file = Utils.retrieveCardImageAsFile(this, loyaltyCardId, ImageLocationType.front);
|
||||||
break;
|
break;
|
||||||
@@ -173,7 +175,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
|
|
||||||
enum ImageType {
|
enum ImageType {
|
||||||
NONE,
|
NONE,
|
||||||
ICON,
|
|
||||||
BARCODE,
|
BARCODE,
|
||||||
IMAGE_FRONT,
|
IMAGE_FRONT,
|
||||||
IMAGE_BACK
|
IMAGE_BACK
|
||||||
@@ -227,9 +228,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
settings = new Settings(this);
|
settings = new Settings(this);
|
||||||
|
|
||||||
String cardOrientation = settings.getCardViewOrientation();
|
String cardOrientation = settings.getCardViewOrientation();
|
||||||
if (cardOrientation.equals(getString(R.string.settings_key_follow_sensor_orientation))) {
|
if (cardOrientation.equals(getString(R.string.settings_key_lock_on_opening_orientation))) {
|
||||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
|
|
||||||
} else if (cardOrientation.equals(getString(R.string.settings_key_lock_on_opening_orientation))) {
|
|
||||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
|
||||||
} else if (cardOrientation.equals(getString(R.string.settings_key_portrait_orientation))) {
|
} else if (cardOrientation.equals(getString(R.string.settings_key_portrait_orientation))) {
|
||||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||||
@@ -301,13 +300,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
binding.bottomAppBarNextButton.setOnClickListener(view -> prevNextCard(true));
|
binding.bottomAppBarNextButton.setOnClickListener(view -> prevNextCard(true));
|
||||||
binding.bottomAppBarUpdateBalanceButton.setOnClickListener(view -> showBalanceUpdateDialog());
|
binding.bottomAppBarUpdateBalanceButton.setOnClickListener(view -> showBalanceUpdateDialog());
|
||||||
|
|
||||||
binding.iconContainer.setOnClickListener(view -> {
|
binding.iconContainer.setOnClickListener(view -> Toast.makeText(LoyaltyCardViewActivity.this, R.string.icon_header_click_text, Toast.LENGTH_LONG).show());
|
||||||
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.setOnLongClickListener(view -> {
|
binding.iconContainer.setOnLongClickListener(view -> {
|
||||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
@@ -329,17 +322,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
binding.fullscreenImage.setOnClickListener(view -> onMainImageTap());
|
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) {
|
private SpannableStringBuilder padSpannableString(SpannableStringBuilder spannableStringBuilder) {
|
||||||
@@ -418,11 +400,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
|
|
||||||
private void showBalanceUpdateDialog() {
|
private void showBalanceUpdateDialog() {
|
||||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||||
|
|
||||||
// Header
|
|
||||||
builder.setTitle(R.string.updateBalanceTitle);
|
builder.setTitle(R.string.updateBalanceTitle);
|
||||||
|
|
||||||
// Layout
|
|
||||||
FrameLayout container = new FrameLayout(this);
|
FrameLayout container = new FrameLayout(this);
|
||||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
@@ -440,91 +418,61 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
currentTextview.setText(getString(R.string.currentBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)));
|
currentTextview.setText(getString(R.string.currentBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)));
|
||||||
layout.addView(currentTextview);
|
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);
|
final TextInputEditText input = new TextInputEditText(this);
|
||||||
input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
Context dialogContext = this;
|
||||||
|
input.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||||
input.setKeyListener(DigitsKeyListener.getInstance("0123456789,."));
|
input.setKeyListener(DigitsKeyListener.getInstance("0123456789,."));
|
||||||
input.setHint(R.string.updateBalanceHint);
|
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.addView(input);
|
||||||
layout.setLayoutParams(params);
|
layout.setLayoutParams(params);
|
||||||
container.addView(layout);
|
container.addView(layout);
|
||||||
|
|
||||||
// Set layout
|
|
||||||
builder.setView(container);
|
builder.setView(container);
|
||||||
|
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||||
// Buttons
|
// Grab calculated balance from input field
|
||||||
builder.setPositiveButton(R.string.spend, (dialogInterface, i) -> {
|
BigDecimal newBalance = (BigDecimal) input.getTag();
|
||||||
// Calculate and update balance
|
if (newBalance == null) {
|
||||||
try {
|
return;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload state
|
// Actually update balance
|
||||||
|
DBHelper.updateLoyaltyCardBalance(database, loyaltyCardId, newBalance);
|
||||||
|
// Reload UI
|
||||||
this.onResume();
|
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) -> {
|
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
|
||||||
// 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());
|
|
||||||
AlertDialog dialog = builder.create();
|
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();
|
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);
|
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||||
input.requestFocus();
|
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() {
|
private void setBottomAppBarButtonState() {
|
||||||
if (!loyaltyCard.note.isEmpty() || !loyaltyCardGroups.isEmpty() || hasBalance(loyaltyCard) || loyaltyCard.validFrom != null || loyaltyCard.expiry != null) {
|
if (!loyaltyCard.note.isEmpty() || !loyaltyCardGroups.isEmpty() || hasBalance(loyaltyCard) || loyaltyCard.validFrom != null || loyaltyCard.expiry != null) {
|
||||||
binding.bottomAppBarInfoButton.setVisibility(View.VISIBLE);
|
binding.bottomAppBarInfoButton.setVisibility(View.VISIBLE);
|
||||||
@@ -596,8 +544,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
public void onResume() {
|
||||||
activityOverridesNavBarColor = true;
|
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
Log.i(TAG, "To view card: " + loyaltyCardId);
|
Log.i(TAG, "To view card: " + loyaltyCardId);
|
||||||
@@ -651,15 +598,10 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
cardIdString = loyaltyCard.cardId;
|
cardIdString = loyaltyCard.cardId;
|
||||||
barcodeIdString = loyaltyCard.barcodeId;
|
barcodeIdString = loyaltyCard.barcodeId;
|
||||||
|
|
||||||
binding.mainImageDescription.setText(loyaltyCard.cardId);
|
binding.cardIdView.setText(loyaltyCard.cardId);
|
||||||
|
|
||||||
// Display full text on click in case it doesn't fit in a single line
|
// Display full text on click in case it doesn't fit in a single line
|
||||||
binding.mainImageDescription.setOnClickListener(v -> {
|
binding.cardIdView.setOnClickListener(v -> {
|
||||||
if (mainImageIndex != 0) {
|
|
||||||
// Don't show cardId dialog, we're displaying something else
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TextView cardIdView = new TextView(LoyaltyCardViewActivity.this);
|
TextView cardIdView = new TextView(LoyaltyCardViewActivity.this);
|
||||||
cardIdView.setText(loyaltyCard.cardId);
|
cardIdView.setText(loyaltyCard.cardId);
|
||||||
cardIdView.setTextIsSelectable(true);
|
cardIdView.setTextIsSelectable(true);
|
||||||
@@ -680,11 +622,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
int darkenedColor = ColorUtils.blendARGB(backgroundHeaderColor, Color.BLACK, 0.1f);
|
int darkenedColor = ColorUtils.blendARGB(backgroundHeaderColor, Color.BLACK, 0.1f);
|
||||||
binding.barcodeScaler.setProgressTintList(ColorStateList.valueOf(darkenedColor));
|
binding.barcodeScaler.setProgressTintList(ColorStateList.valueOf(darkenedColor));
|
||||||
binding.barcodeScaler.setThumbTintList(ColorStateList.valueOf(darkenedColor));
|
binding.barcodeScaler.setThumbTintList(ColorStateList.valueOf(darkenedColor));
|
||||||
|
|
||||||
// Set bottomAppBar and system navigation bar color
|
|
||||||
binding.bottomAppBar.setBackgroundColor(darkenedColor);
|
binding.bottomAppBar.setBackgroundColor(darkenedColor);
|
||||||
Utils.setNavigationBarColor(null, window, darkenedColor, Utils.needsDarkForeground(darkenedColor));
|
|
||||||
|
|
||||||
int complementaryColor = Utils.getComplementaryColor(darkenedColor);
|
int complementaryColor = Utils.getComplementaryColor(darkenedColor);
|
||||||
binding.fabEdit.setBackgroundTintList(ColorStateList.valueOf(complementaryColor));
|
binding.fabEdit.setBackgroundTintList(ColorStateList.valueOf(complementaryColor));
|
||||||
Drawable editButtonIcon = binding.fabEdit.getDrawable();
|
Drawable editButtonIcon = binding.fabEdit.getDrawable();
|
||||||
@@ -738,8 +676,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
DBHelper.updateLoyaltyCardLastUsed(database, loyaltyCard.id);
|
DBHelper.updateLoyaltyCardLastUsed(database, loyaltyCard.id);
|
||||||
|
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
|
|
||||||
ShortcutHelper.updateShortcuts(this, loyaltyCard);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setStateBasedOnImageTypes() {
|
private void setStateBasedOnImageTypes() {
|
||||||
@@ -768,6 +704,16 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
imageButton.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(backgroundNeedsDarkIcons ? Color.BLACK : Color.WHITE, BlendModeCompat.SRC_ATOP));
|
imageButton.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(backgroundNeedsDarkIcons ? Color.BLACK : Color.WHITE, BlendModeCompat.SRC_ATOP));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (isFullscreen) {
|
||||||
|
setFullscreen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.card_view_menu, menu);
|
getMenuInflater().inflate(R.menu.card_view_menu, menu);
|
||||||
@@ -839,8 +785,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
DBHelper.updateLoyaltyCardArchiveStatus(database, loyaltyCardId, 1);
|
DBHelper.updateLoyaltyCardArchiveStatus(database, loyaltyCardId, 1);
|
||||||
Toast.makeText(LoyaltyCardViewActivity.this, R.string.archived, Toast.LENGTH_LONG).show();
|
Toast.makeText(LoyaltyCardViewActivity.this, R.string.archived, Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
ShortcutHelper.removeShortcut(LoyaltyCardViewActivity.this, loyaltyCardId);
|
|
||||||
|
|
||||||
// Re-init loyaltyCard with new data from DB
|
// Re-init loyaltyCard with new data from DB
|
||||||
onResume();
|
onResume();
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
@@ -932,9 +876,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
if (imageTypes.isEmpty()) {
|
if (imageTypes.isEmpty()) {
|
||||||
barcodeRenderTarget.setVisibility(View.GONE);
|
barcodeRenderTarget.setVisibility(View.GONE);
|
||||||
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
||||||
binding.mainImageDescription.setTextColor(MaterialColors.getColor(binding.mainImageDescription, com.google.android.material.R.attr.colorOnSurfaceVariant));
|
binding.cardIdView.setTextColor(MaterialColors.getColor(binding.cardIdView, com.google.android.material.R.attr.colorOnSurfaceVariant));
|
||||||
|
|
||||||
binding.mainImageDescription.setText(loyaltyCard.cardId);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -943,7 +885,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
if (wantedImageType == ImageType.BARCODE) {
|
if (wantedImageType == ImageType.BARCODE) {
|
||||||
barcodeRenderTarget.setBackgroundColor(Color.WHITE);
|
barcodeRenderTarget.setBackgroundColor(Color.WHITE);
|
||||||
binding.mainCardView.setCardBackgroundColor(Color.WHITE);
|
binding.mainCardView.setCardBackgroundColor(Color.WHITE);
|
||||||
binding.mainImageDescription.setTextColor(getResources().getColor(R.color.md_theme_light_onSurfaceVariant));
|
binding.cardIdView.setTextColor(getResources().getColor(R.color.md_theme_light_onSurfaceVariant));
|
||||||
|
|
||||||
if (waitForResize) {
|
if (waitForResize) {
|
||||||
redrawBarcodeAfterResize(!isFullscreen);
|
redrawBarcodeAfterResize(!isFullscreen);
|
||||||
@@ -951,23 +893,18 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
drawBarcode(!isFullscreen);
|
drawBarcode(!isFullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.mainImageDescription.setText(loyaltyCard.cardId);
|
|
||||||
barcodeRenderTarget.setContentDescription(getString(R.string.barcodeImageDescriptionWithType, format.prettyName()));
|
barcodeRenderTarget.setContentDescription(getString(R.string.barcodeImageDescriptionWithType, format.prettyName()));
|
||||||
} else if (wantedImageType == ImageType.IMAGE_FRONT) {
|
} else if (wantedImageType == ImageType.IMAGE_FRONT) {
|
||||||
barcodeRenderTarget.setImageBitmap(frontImageBitmap);
|
barcodeRenderTarget.setImageBitmap(frontImageBitmap);
|
||||||
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
|
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
|
||||||
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
||||||
binding.mainImageDescription.setTextColor(MaterialColors.getColor(binding.mainImageDescription, com.google.android.material.R.attr.colorOnSurfaceVariant));
|
binding.cardIdView.setTextColor(MaterialColors.getColor(binding.cardIdView, com.google.android.material.R.attr.colorOnSurfaceVariant));
|
||||||
|
|
||||||
binding.mainImageDescription.setText(getString(R.string.frontImageDescription));
|
|
||||||
barcodeRenderTarget.setContentDescription(getString(R.string.frontImageDescription));
|
barcodeRenderTarget.setContentDescription(getString(R.string.frontImageDescription));
|
||||||
} else if (wantedImageType == ImageType.IMAGE_BACK) {
|
} else if (wantedImageType == ImageType.IMAGE_BACK) {
|
||||||
barcodeRenderTarget.setImageBitmap(backImageBitmap);
|
barcodeRenderTarget.setImageBitmap(backImageBitmap);
|
||||||
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
|
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
|
||||||
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
||||||
binding.mainImageDescription.setTextColor(MaterialColors.getColor(binding.mainImageDescription, com.google.android.material.R.attr.colorOnSurfaceVariant));
|
binding.cardIdView.setTextColor(MaterialColors.getColor(binding.cardIdView, com.google.android.material.R.attr.colorOnSurfaceVariant));
|
||||||
|
|
||||||
binding.mainImageDescription.setText(getString(R.string.backImageDescription));
|
|
||||||
barcodeRenderTarget.setContentDescription(getString(R.string.backImageDescription));
|
barcodeRenderTarget.setContentDescription(getString(R.string.backImageDescription));
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Unknown image type: " + wantedImageType);
|
throw new IllegalArgumentException("Unknown image type: " + wantedImageType);
|
||||||
@@ -1118,14 +1055,10 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
|
|
||||||
// Set Android to fullscreen mode
|
// Set Android to fullscreen mode
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
Window window = getWindow();
|
getWindow().setDecorFitsSystemWindows(false);
|
||||||
if (window != null) {
|
if (getWindow().getInsetsController() != null) {
|
||||||
window.setDecorFitsSystemWindows(false);
|
getWindow().getInsetsController().hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
|
||||||
WindowInsetsController wic = window.getInsetsController();
|
getWindow().getInsetsController().setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||||
if (wic != null) {
|
|
||||||
wic.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
|
|
||||||
wic.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setFullscreenModeSdkLessThan30();
|
setFullscreenModeSdkLessThan30();
|
||||||
@@ -1152,14 +1085,10 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
|
|
||||||
// Unset fullscreen mode
|
// Unset fullscreen mode
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
Window window = getWindow();
|
getWindow().setDecorFitsSystemWindows(true);
|
||||||
if (window != null) {
|
if (getWindow().getInsetsController() != null) {
|
||||||
window.setDecorFitsSystemWindows(true);
|
getWindow().getInsetsController().show(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
|
||||||
WindowInsetsController wic = window.getInsetsController();
|
getWindow().getInsetsController().setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT);
|
||||||
if (wic != null) {
|
|
||||||
wic.show(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
|
|
||||||
wic.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unsetFullscreenModeSdkLessThan30();
|
unsetFullscreenModeSdkLessThan30();
|
||||||
@@ -1171,25 +1100,19 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
|||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private void unsetFullscreenModeSdkLessThan30() {
|
private void unsetFullscreenModeSdkLessThan30() {
|
||||||
Window window = getWindow();
|
getWindow().getDecorView().setSystemUiVisibility(
|
||||||
if (window != null) {
|
getWindow().getDecorView().getSystemUiVisibility()
|
||||||
window.getDecorView().setSystemUiVisibility(
|
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||||
window.getDecorView().getSystemUiVisibility()
|
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||||
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
);
|
||||||
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private void setFullscreenModeSdkLessThan30() {
|
private void setFullscreenModeSdkLessThan30() {
|
||||||
Window window = getWindow();
|
getWindow().getDecorView().setSystemUiVisibility(
|
||||||
if (window != null) {
|
getWindow().getDecorView().getSystemUiVisibility()
|
||||||
window.getDecorView().setSystemUiVisibility(
|
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||||
window.getDecorView().getSystemUiVisibility()
|
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||||
| 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.Activity;
|
||||||
import android.app.SearchManager;
|
import android.app.SearchManager;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.CursorIndexOutOfBoundsException;
|
import android.database.CursorIndexOutOfBoundsException;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -18,7 +22,6 @@ import android.view.View;
|
|||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.activity.OnBackPressedCallback;
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
@@ -31,10 +34,10 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@@ -67,7 +70,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
private View mNoGroupCardsText;
|
private View mNoGroupCardsText;
|
||||||
private TabLayout groupsTabLayout;
|
private TabLayout groupsTabLayout;
|
||||||
|
|
||||||
private Runnable mUpdateLoyaltyCardListRunnable;
|
private Runnable mSwapLoyaltyCardListCursor;
|
||||||
|
|
||||||
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
|
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
|
||||||
private ActivityResultLauncher<Intent> mSettingsLauncher;
|
private ActivityResultLauncher<Intent> mSettingsLauncher;
|
||||||
@@ -86,7 +89,35 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onActionItemClicked(ActionMode inputMode, MenuItem inputItem) {
|
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);
|
final ImportURIHelper importURIHelper = new ImportURIHelper(MainActivity.this);
|
||||||
try {
|
try {
|
||||||
importURIHelper.startShareIntent(mAdapter.getSelectedItems());
|
importURIHelper.startShareIntent(mAdapter.getSelectedItems());
|
||||||
@@ -148,7 +179,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
|
||||||
Log.d(TAG, "Archiving card: " + loyaltyCard.id);
|
Log.d(TAG, "Archiving card: " + loyaltyCard.id);
|
||||||
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 1);
|
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 1);
|
||||||
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
|
|
||||||
updateLoyaltyCardList(false);
|
updateLoyaltyCardList(false);
|
||||||
inputMode.finish();
|
inputMode.finish();
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
@@ -193,13 +223,12 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle inputSavedInstanceState) {
|
protected void onCreate(Bundle inputSavedInstanceState) {
|
||||||
|
extractIntentFields(getIntent());
|
||||||
SplashScreen.installSplashScreen(this);
|
SplashScreen.installSplashScreen(this);
|
||||||
super.onCreate(inputSavedInstanceState);
|
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());
|
binding = MainActivityBinding.inflate(getLayoutInflater());
|
||||||
|
setTitle(R.string.app_name);
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
setSupportActionBar(binding.toolbar);
|
setSupportActionBar(binding.toolbar);
|
||||||
groupsTabLayout = binding.groups;
|
groupsTabLayout = binding.groups;
|
||||||
@@ -207,8 +236,13 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
|
|
||||||
mDatabase = new DBHelper(this).getWritableDatabase();
|
mDatabase = new DBHelper(this).getWritableDatabase();
|
||||||
|
|
||||||
mUpdateLoyaltyCardListRunnable = () -> {
|
mSwapLoyaltyCardListCursor = () -> {
|
||||||
updateLoyaltyCardList(false);
|
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() {
|
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||||
@@ -243,7 +277,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
mNoGroupCardsText = contentMainBinding.noGroupCardsText;
|
mNoGroupCardsText = contentMainBinding.noGroupCardsText;
|
||||||
mCardList = contentMainBinding.list;
|
mCardList = contentMainBinding.list;
|
||||||
|
|
||||||
mAdapter = new LoyaltyCardCursorAdapter(this, null, this, mUpdateLoyaltyCardListRunnable);
|
mAdapter = new LoyaltyCardCursorAdapter(this, null, this, mSwapLoyaltyCardListCursor);
|
||||||
mCardList.setAdapter(mAdapter);
|
mCardList.setAdapter(mAdapter);
|
||||||
registerForContextMenu(mCardList);
|
registerForContextMenu(mCardList);
|
||||||
|
|
||||||
@@ -288,11 +322,11 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = result.getData();
|
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();
|
Bundle inputBundle = intent.getExtras();
|
||||||
String group = inputBundle != null ? inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
|
String group = inputBundle != null ? inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
|
||||||
processBarcodeValuesList(barcodeValuesList, group, false);
|
processBarcodeValues(barcodeValues, group);
|
||||||
});
|
});
|
||||||
|
|
||||||
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||||
@@ -303,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
|
@Override
|
||||||
@@ -388,6 +411,16 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
addButton.bringToFront();
|
addButton.bringToFront();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (mSearchView != null && !mSearchView.isIconified()) {
|
||||||
|
mSearchView.setIconified(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
private void displayCardSetupOptions(Menu menu, boolean shouldShow) {
|
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}) {
|
for (int id : new int[]{R.id.action_search, R.id.action_display_options, R.id.action_sort}) {
|
||||||
menu.findItem(id).setVisible(shouldShow);
|
menu.findItem(id).setVisible(shouldShow);
|
||||||
@@ -399,12 +432,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateLoyaltyCardList(boolean updateCount) {
|
private void updateLoyaltyCardList(boolean updateCount) {
|
||||||
Group group = null;
|
mSwapLoyaltyCardListCursor.run();
|
||||||
if (mGroup != null) {
|
|
||||||
group = (Group) mGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
mAdapter.swapCursor(DBHelper.getLoyaltyCardCursor(mDatabase, mFilter, group, mOrder, mOrderDirection, mAdapter.showingArchivedCards() ? DBHelper.LoyaltyCardArchiveFilter.All : DBHelper.LoyaltyCardArchiveFilter.Unarchived));
|
|
||||||
|
|
||||||
if (updateCount) {
|
if (updateCount) {
|
||||||
updateLoyaltyCardCount();
|
updateLoyaltyCardCount();
|
||||||
@@ -447,59 +475,63 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processBarcodeValuesList(List<BarcodeValues> barcodeValuesList, String group, boolean closeAppOnNoBarcode) {
|
private void processBarcodeValues(BarcodeValues barcodeValues, String group) {
|
||||||
if (barcodeValuesList.isEmpty()) {
|
if (barcodeValues.isEmpty()) {
|
||||||
throw new IllegalArgumentException("barcodesValues may not be empty");
|
throw new IllegalArgumentException("barcodesValues may not be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils.makeUserChooseBarcodeFromList(MainActivity.this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
|
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||||
@Override
|
Bundle newBundle = new Bundle();
|
||||||
public void onUserChoseBarcode(BarcodeValues barcodeValues) {
|
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
|
||||||
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
|
||||||
Bundle newBundle = new Bundle();
|
if (group != null) {
|
||||||
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
|
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
|
||||||
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
|
}
|
||||||
if (group != null) {
|
newIntent.putExtras(newBundle);
|
||||||
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
|
startActivity(newIntent);
|
||||||
}
|
|
||||||
newIntent.putExtras(newBundle);
|
|
||||||
startActivity(newIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUserDismissedSelector() {
|
|
||||||
if (closeAppOnNoBarcode) {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSharedIntent(Intent intent) {
|
private void onSharedIntent(Intent intent) {
|
||||||
String receivedAction = intent.getAction();
|
String receivedAction = intent.getAction();
|
||||||
String receivedType = intent.getType();
|
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)) {
|
if (Intent.ACTION_SEND.equals(receivedAction)) {
|
||||||
List<BarcodeValues> barcodeValuesList;
|
if (!receivedType.startsWith("image/")) {
|
||||||
|
|
||||||
if (receivedType.equals("text/plain")) {
|
|
||||||
barcodeValuesList = Collections.singletonList(new BarcodeValues(null, intent.getStringExtra(Intent.EXTRA_TEXT)));
|
|
||||||
} else 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 {
|
|
||||||
Log.e(TAG, "Wrong mime-type");
|
Log.e(TAG, "Wrong mime-type");
|
||||||
return;
|
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();
|
finish();
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,7 +611,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
int id = inputItem.getItemId();
|
int id = inputItem.getItemId();
|
||||||
|
|
||||||
if (id == android.R.id.home) {
|
if (id == android.R.id.home) {
|
||||||
getOnBackPressedDispatcher().onBackPressed();
|
onBackPressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id == R.id.action_display_options) {
|
if (id == R.id.action_display_options) {
|
||||||
@@ -795,6 +827,8 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
|||||||
b.putIntegerArrayList("cardList", cardList);
|
b.putIntegerArrayList("cardList", cardList);
|
||||||
intent.putExtras(b);
|
intent.putExtras(b);
|
||||||
|
|
||||||
|
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard);
|
||||||
|
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,6 @@ import android.widget.EditText;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
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.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
@@ -26,6 +20,11 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
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;
|
import protect.card_locker.databinding.ActivityManageGroupBinding;
|
||||||
|
|
||||||
public class ManageGroupActivity extends CatimaAppCompatActivity implements ManageGroupCursorAdapter.CardAdapterListener {
|
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
|
// this setText is here because content_main.xml is reused from main activity
|
||||||
noGroupCardsText.setText(getResources().getText(R.string.noGiftCardsGroup));
|
noGroupCardsText.setText(getResources().getText(R.string.noGiftCardsGroup));
|
||||||
updateLoyaltyCardList();
|
updateLoyaltyCardList();
|
||||||
|
|
||||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
|
||||||
@Override
|
|
||||||
public void handleOnBackPressed() {
|
|
||||||
leaveWithoutSaving();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<Integer> adapterStateToIntegerArray(HashMap<Integer, Boolean> adapterState) {
|
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
|
@Override
|
||||||
public boolean onSupportNavigateUp() {
|
public boolean onSupportNavigateUp() {
|
||||||
getOnBackPressedDispatcher().onBackPressed();
|
onBackPressed();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,17 +14,17 @@ import android.widget.EditText;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
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.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
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;
|
import protect.card_locker.databinding.ManageGroupsActivityBinding;
|
||||||
|
|
||||||
public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener {
|
public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener {
|
||||||
@@ -71,6 +71,11 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
|||||||
updateGroupList();
|
updateGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateGroupList() {
|
private void updateGroupList() {
|
||||||
mAdapter.swapCursor(DBHelper.getGroupCursor(mDatabase));
|
mAdapter.swapCursor(DBHelper.getGroupCursor(mDatabase));
|
||||||
|
|
||||||
@@ -243,4 +248,4 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
|
|||||||
AlertDialog dialog = builder.create();
|
AlertDialog dialog = builder.create();
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
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 COMPAT_SCALE_FACTOR_DIP = 320;
|
||||||
|
|
||||||
private static final int PERMISSION_SCAN_ADD_FROM_IMAGE = 100;
|
private static final int PERMISSION_SCAN_ADD_FROM_IMAGE = 100;
|
||||||
private static final int PERMISSION_SCAN_ADD_FROM_PDF = 101;
|
|
||||||
|
|
||||||
private CaptureManager capture;
|
private CaptureManager capture;
|
||||||
private DecoratedBarcodeView barcodeScannerView;
|
private DecoratedBarcodeView barcodeScannerView;
|
||||||
@@ -74,11 +74,9 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
private ActivityResultLauncher<Intent> manualAddLauncher;
|
private ActivityResultLauncher<Intent> manualAddLauncher;
|
||||||
// can't use the pre-made contract because that launches the file manager for image type instead of gallery
|
// 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> photoPickerLauncher;
|
||||||
private ActivityResultLauncher<Intent> pdfPickerLauncher;
|
|
||||||
|
|
||||||
static final String STATE_SCANNER_ACTIVE = "scannerActive";
|
static final String STATE_SCANNER_ACTIVE = "scannerActive";
|
||||||
private boolean mScannerActive = true;
|
private boolean mScannerActive = true;
|
||||||
private boolean mHasError = false;
|
|
||||||
|
|
||||||
private void extractIntentFields(Intent intent) {
|
private void extractIntentFields(Intent intent) {
|
||||||
final Bundle b = intent.getExtras();
|
final Bundle b = intent.getExtras();
|
||||||
@@ -102,7 +100,6 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
|
|
||||||
manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
|
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()));
|
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 -> {
|
customBarcodeScannerBinding.fabOtherOptions.setOnClickListener(view -> {
|
||||||
setScannerActive(false);
|
setScannerActive(false);
|
||||||
|
|
||||||
@@ -112,8 +109,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
new CharSequence[]{
|
new CharSequence[]{
|
||||||
getString(R.string.addWithoutBarcode),
|
getString(R.string.addWithoutBarcode),
|
||||||
getString(R.string.addManually),
|
getString(R.string.addManually),
|
||||||
getString(R.string.addFromImage),
|
getString(R.string.addFromImage)
|
||||||
getString(R.string.addFromPdfFile)
|
|
||||||
},
|
},
|
||||||
(dialogInterface, i) -> {
|
(dialogInterface, i) -> {
|
||||||
switch (i) {
|
switch (i) {
|
||||||
@@ -126,9 +122,6 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
case 2:
|
case 2:
|
||||||
addFromImage();
|
addFromImage();
|
||||||
break;
|
break;
|
||||||
case 3:
|
|
||||||
addFromPdfFile();
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown 'Add a card in a different way' dialog option");
|
throw new IllegalArgumentException("Unknown 'Add a card in a different way' dialog option");
|
||||||
}
|
}
|
||||||
@@ -142,7 +135,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
|
|
||||||
// Even though we do the actual decoding with the barcodeScannerView
|
// Even though we do the actual decoding with the barcodeScannerView
|
||||||
// CaptureManager needs to be running to show the camera and scanning bar
|
// CaptureManager needs to be running to show the camera and scanning bar
|
||||||
capture = new CatimaCaptureManager(this, barcodeScannerView, this::onCaptureManagerError);
|
capture = new CatimaCaptureManager(this, barcodeScannerView);
|
||||||
Intent captureIntent = new Intent();
|
Intent captureIntent = new Intent();
|
||||||
Bundle captureIntentBundle = new Bundle();
|
Bundle captureIntentBundle = new Bundle();
|
||||||
captureIntentBundle.putBoolean(Intents.Scan.BEEP_ENABLED, false);
|
captureIntentBundle.putBoolean(Intents.Scan.BEEP_ENABLED, false);
|
||||||
@@ -179,14 +172,9 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
capture.onResume();
|
capture.onResume();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Utils.deviceHasCamera(this)) {
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
||||||
showCameraError(getString(R.string.noCameraFoundGuideText), false);
|
showCameraPermissionMissingText(false);
|
||||||
} else if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
showCameraPermissionMissingText();
|
|
||||||
} else {
|
|
||||||
hideCameraError();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scaleScreen();
|
scaleScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,24 +269,14 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
|
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
super.onActivityResult(requestCode, resultCode, 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);
|
setScannerActive(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils.makeUserChooseBarcodeFromList(this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
|
returnResult(barcodeValues.content(), barcodeValues.format());
|
||||||
@Override
|
|
||||||
public void onUserChoseBarcode(BarcodeValues barcodeValues) {
|
|
||||||
returnResult(barcodeValues.content(), barcodeValues.format());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUserDismissedSelector() {
|
|
||||||
setScannerActive(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addWithoutBarcode() {
|
private void addWithoutBarcode() {
|
||||||
@@ -366,79 +344,42 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addManually() {
|
public void addManually() {
|
||||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ScanActivity.this);
|
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
|
||||||
builder.setTitle(R.string.add_manually_warning_title);
|
if (cardId != null) {
|
||||||
builder.setMessage(R.string.add_manually_warning_message);
|
final Bundle b = new Bundle();
|
||||||
builder.setPositiveButton(R.string.continue_, (dialog, which) -> {
|
b.putString("initialCardId", cardId);
|
||||||
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
|
i.putExtras(b);
|
||||||
if (cardId != null) {
|
}
|
||||||
final Bundle b = new Bundle();
|
manualAddLauncher.launch(i);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addFromImage() {
|
public void addFromImage() {
|
||||||
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE);
|
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addFromPdfFile() {
|
private void addFromImageAfterPermission() {
|
||||||
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PDF);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addFromImageOrFileAfterPermission(String mimeType, ActivityResultLauncher<Intent> launcher, int chooserText, int errorMessage) {
|
|
||||||
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
|
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
|
||||||
photoPickerIntent.setType(mimeType);
|
photoPickerIntent.setType("image/*");
|
||||||
Intent contentIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
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 });
|
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { contentIntent });
|
||||||
try {
|
try {
|
||||||
launcher.launch(chooserIntent);
|
photoPickerLauncher.launch(chooserIntent);
|
||||||
} catch (ActivityNotFoundException e) {
|
} catch (ActivityNotFoundException e) {
|
||||||
setScannerActive(true);
|
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);
|
Log.e(TAG, "No activity found to handle intent", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onCaptureManagerError(String errorMessage) {
|
private void showCameraPermissionMissingText(boolean show) {
|
||||||
if (mHasError) {
|
customBarcodeScannerBinding.cameraPermissionDeniedLayout.cameraPermissionDeniedClickableArea.setOnClickListener(show ? v -> {
|
||||||
// We're already showing an error, ignore this new error
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showCameraError(errorMessage, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showCameraPermissionMissingText() {
|
|
||||||
showCameraError(getString(R.string.noCameraPermissionDirectToSystemSetting), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showCameraError(String message, boolean setOnClick) {
|
|
||||||
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorMessage.setText(message);
|
|
||||||
|
|
||||||
setCameraErrorState(true, setOnClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hideCameraError() {
|
|
||||||
setCameraErrorState(false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCameraErrorState(boolean visible, boolean setOnClick) {
|
|
||||||
mHasError = visible;
|
|
||||||
|
|
||||||
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorClickableArea.setOnClickListener(visible && setOnClick ? v -> {
|
|
||||||
navigateToSystemPermissionSetting();
|
navigateToSystemPermissionSetting();
|
||||||
} : null);
|
} : null);
|
||||||
customBarcodeScannerBinding.cardInputContainer.setBackgroundColor(visible ? obtainThemeAttribute(com.google.android.material.R.attr.colorSurface) : Color.TRANSPARENT);
|
customBarcodeScannerBinding.cardInputContainer.setBackgroundColor(show ? obtainThemeAttribute(com.google.android.material.R.attr.colorSurface) : Color.TRANSPARENT);
|
||||||
customBarcodeScannerBinding.cameraErrorLayout.getRoot().setVisibility(visible ? View.VISIBLE : View.GONE);
|
customBarcodeScannerBinding.cameraPermissionDeniedLayout.getRoot().setVisibility(show ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scaleScreen() {
|
private void scaleScreen() {
|
||||||
@@ -448,8 +389,8 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
|
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
|
||||||
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
|
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
|
||||||
|
|
||||||
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
|
customBarcodeScannerBinding.cameraPermissionDeniedLayout.cameraPermissionDeniedIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
|
||||||
customBarcodeScannerBinding.cameraErrorLayout.cameraErrorTitle.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
|
customBarcodeScannerBinding.cameraPermissionDeniedLayout.cameraPermissionDeniedTitle.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int obtainThemeAttribute(int attribute) {
|
private int obtainThemeAttribute(int attribute) {
|
||||||
@@ -475,18 +416,10 @@ public class ScanActivity extends CatimaAppCompatActivity {
|
|||||||
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||||
|
|
||||||
if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
|
if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
|
||||||
|
showCameraPermissionMissingText(!granted);
|
||||||
|
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
|
||||||
if (granted) {
|
if (granted) {
|
||||||
hideCameraError();
|
addFromImageAfterPermission();
|
||||||
} else {
|
|
||||||
showCameraPermissionMissingText();
|
|
||||||
}
|
|
||||||
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE || requestCode == PERMISSION_SCAN_ADD_FROM_PDF) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setScannerActive(true);
|
setScannerActive(true);
|
||||||
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG).show();
|
||||||
|
|||||||
@@ -8,11 +8,6 @@ import android.graphics.Canvas;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
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 org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -20,6 +15,11 @@ import java.util.Comparator;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
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 {
|
class ShortcutHelper {
|
||||||
// Android documentation says that no more than 5 shortcuts
|
// Android documentation says that no more than 5 shortcuts
|
||||||
// are supported. However, that may be too many, as not all
|
// are supported. However, that may be too many, as not all
|
||||||
@@ -43,11 +43,6 @@ class ShortcutHelper {
|
|||||||
* used card shortcut is discarded.
|
* used card shortcut is discarded.
|
||||||
*/
|
*/
|
||||||
static void updateShortcuts(Context context, LoyaltyCard card) {
|
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));
|
LinkedList<ShortcutInfoCompat> list = new LinkedList<>(ShortcutManagerCompat.getDynamicShortcuts(context));
|
||||||
|
|
||||||
SQLiteDatabase database = new DBHelper(context).getReadableDatabase();
|
SQLiteDatabase database = new DBHelper(context).getReadableDatabase();
|
||||||
@@ -113,7 +108,18 @@ class ShortcutHelper {
|
|||||||
* shortcut exists.
|
* shortcut exists.
|
||||||
*/
|
*/
|
||||||
static void removeShortcut(Context context, int cardId) {
|
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
|
static @NotNull
|
||||||
|
|||||||
@@ -7,20 +7,19 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
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.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.AppCompatImageView;
|
import androidx.appcompat.widget.AppCompatImageView;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.graphics.ColorUtils;
|
import androidx.core.graphics.ColorUtils;
|
||||||
import androidx.core.view.WindowInsetsControllerCompat;
|
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 class UCropWrapper extends UCropActivity {
|
||||||
public static final String UCROP_TOOLBAR_TYPEFACE_STYLE = "ucop_toolbar_typeface_style";
|
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) {
|
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onPostCreate(savedInstanceState);
|
super.onPostCreate(savedInstanceState);
|
||||||
boolean darkMode = Utils.isDarkModeEnabled(this);
|
boolean darkMode = Utils.isDarkModeEnabled(this);
|
||||||
Window window = getWindow();
|
|
||||||
// setup status bar to look like the rest of the app
|
// setup status bar to look like the rest of the app
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
if (window != null) {
|
View decorView = getWindow().getDecorView();
|
||||||
View decorView = window.getDecorView();
|
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(getWindow(), decorView);
|
||||||
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView);
|
wic.setAppearanceLightStatusBars(!darkMode);
|
||||||
wic.setAppearanceLightStatusBars(!darkMode);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// icons are always white back then
|
// icons are always white back then
|
||||||
if (window != null && !darkMode) {
|
if (!darkMode) {
|
||||||
window.setStatusBarColor(ColorUtils.compositeColors(Color.argb(127, 0, 0, 0), window.getStatusBarColor()));
|
getWindow().setStatusBarColor(ColorUtils.compositeColors(Color.argb(127, 0, 0, 0), getWindow().getStatusBarColor()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,8 @@ import android.graphics.BitmapFactory;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.ImageDecoder;
|
import android.graphics.ImageDecoder;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.graphics.pdf.PdfRenderer;
|
|
||||||
import android.hardware.camera2.CameraAccessException;
|
|
||||||
import android.hardware.camera2.CameraManager;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
@@ -26,24 +22,20 @@ import android.util.Log;
|
|||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.RawRes;
|
import androidx.annotation.RawRes;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.core.graphics.ColorUtils;
|
import androidx.core.graphics.ColorUtils;
|
||||||
import androidx.core.os.LocaleListCompat;
|
import androidx.core.os.LocaleListCompat;
|
||||||
import androidx.core.view.WindowInsetsControllerCompat;
|
|
||||||
import androidx.exifinterface.media.ExifInterface;
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
import androidx.palette.graphics.Palette;
|
import androidx.palette.graphics.Palette;
|
||||||
|
|
||||||
import com.google.android.material.color.DynamicColors;
|
import com.google.android.material.color.DynamicColors;
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|
||||||
import com.google.zxing.BinaryBitmap;
|
import com.google.zxing.BinaryBitmap;
|
||||||
import com.google.zxing.LuminanceSource;
|
import com.google.zxing.LuminanceSource;
|
||||||
import com.google.zxing.MultiFormatReader;
|
import com.google.zxing.MultiFormatReader;
|
||||||
@@ -51,8 +43,6 @@ import com.google.zxing.NotFoundException;
|
|||||||
import com.google.zxing.RGBLuminanceSource;
|
import com.google.zxing.RGBLuminanceSource;
|
||||||
import com.google.zxing.Result;
|
import com.google.zxing.Result;
|
||||||
import com.google.zxing.common.HybridBinarizer;
|
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.BufferedReader;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -67,13 +57,10 @@ import java.math.BigDecimal;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.text.DecimalFormatSymbols;
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
@@ -93,13 +80,12 @@ public class Utils {
|
|||||||
public static final int SELECT_BARCODE_REQUEST = 2;
|
public static final int SELECT_BARCODE_REQUEST = 2;
|
||||||
public static final int BARCODE_SCAN = 3;
|
public static final int BARCODE_SCAN = 3;
|
||||||
public static final int BARCODE_IMPORT_FROM_IMAGE_FILE = 4;
|
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 = 5;
|
||||||
public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 6;
|
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 6;
|
||||||
public static final int CARD_IMAGE_FROM_CAMERA_BACK = 7;
|
public static final int CARD_IMAGE_FROM_CAMERA_ICON = 7;
|
||||||
public static final int CARD_IMAGE_FROM_CAMERA_ICON = 8;
|
public static final int CARD_IMAGE_FROM_FILE_FRONT = 8;
|
||||||
public static final int CARD_IMAGE_FROM_FILE_FRONT = 9;
|
public static final int CARD_IMAGE_FROM_FILE_BACK = 9;
|
||||||
public static final int CARD_IMAGE_FROM_FILE_BACK = 10;
|
public static final int CARD_IMAGE_FROM_FILE_ICON = 10;
|
||||||
public static final int CARD_IMAGE_FROM_FILE_ICON = 11;
|
|
||||||
|
|
||||||
public static final String CARD_IMAGE_FILENAME_REGEX = "^(card_)(\\d+)(_(?:front|back|icon)\\.png)$";
|
public static final String CARD_IMAGE_FILENAME_REGEX = "^(card_)(\\d+)(_(?:front|back|icon)\\.png)$";
|
||||||
|
|
||||||
@@ -142,91 +128,6 @@ public class Utils {
|
|||||||
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
|
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public List<BarcodeValues> retrieveBarcodesFromImage(Context context, Uri uri) {
|
|
||||||
Log.i(TAG, "Received image file with possible barcode");
|
|
||||||
|
|
||||||
if (uri == null) {
|
|
||||||
Log.e(TAG, "Uri did not contain any data");
|
|
||||||
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Bitmap bitmap;
|
|
||||||
try {
|
|
||||||
bitmap = retrieveImageFromUri(context, uri);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Error getting data from image file");
|
|
||||||
e.printStackTrace();
|
|
||||||
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<BarcodeValues> barcodesFromBitmap = getBarcodesFromBitmap(bitmap);
|
|
||||||
|
|
||||||
if (barcodesFromBitmap.isEmpty()) {
|
|
||||||
Log.i(TAG, "No barcode found in image file");
|
|
||||||
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
return barcodesFromBitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
static public List<BarcodeValues> retrieveBarcodesFromPdf(Context context, Uri uri) {
|
|
||||||
Log.i(TAG, "Received PDF file with possible barcode");
|
|
||||||
if (uri == null) {
|
|
||||||
Log.e(TAG, "Uri did not contain any data");
|
|
||||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
ParcelFileDescriptor parcelFileDescriptor = null;
|
|
||||||
PdfRenderer renderer = null;
|
|
||||||
List<BarcodeValues> barcodesFromPdfPages = new ArrayList<>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r");
|
|
||||||
if (parcelFileDescriptor != null) {
|
|
||||||
renderer = new PdfRenderer(parcelFileDescriptor);
|
|
||||||
|
|
||||||
// Loop over all pages to find barcodes
|
|
||||||
Bitmap renderedPage;
|
|
||||||
for (int i = 0; i < renderer.getPageCount(); i++) {
|
|
||||||
PdfRenderer.Page page = renderer.openPage(i);
|
|
||||||
renderedPage = Bitmap.createBitmap(page.getWidth(), page.getHeight(), Bitmap.Config.ARGB_8888);
|
|
||||||
page.render(renderedPage, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
|
|
||||||
page.close();
|
|
||||||
|
|
||||||
List<BarcodeValues> barcodesFromPage = getBarcodesFromBitmap(renderedPage);
|
|
||||||
for (BarcodeValues barcodeValues : barcodesFromPage) {
|
|
||||||
barcodeValues.setNote(String.format(context.getString(R.string.pageWithNumber), i+1));
|
|
||||||
barcodesFromPdfPages.add(barcodeValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Error reading PDF file", e);
|
|
||||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
|
||||||
} finally {
|
|
||||||
// Resource handling
|
|
||||||
if (renderer != null) {
|
|
||||||
renderer.close();
|
|
||||||
}
|
|
||||||
if (parcelFileDescriptor != null) {
|
|
||||||
try {
|
|
||||||
parcelFileDescriptor.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Error closing ParcelFileDescriptor", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (barcodesFromPdfPages.isEmpty()) {
|
|
||||||
Log.i(TAG, "No barcode found in pdf file");
|
|
||||||
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
return barcodesFromPdfPages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the Barcode format and content based on the result of an activity.
|
* 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
|
* It shows toasts to notify the end-user as needed itself and will return an empty
|
||||||
@@ -238,20 +139,45 @@ public class Utils {
|
|||||||
* @param context
|
* @param context
|
||||||
* @return BarcodeValues
|
* @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 contents;
|
||||||
String format;
|
String format;
|
||||||
|
|
||||||
if (resultCode != Activity.RESULT_OK) {
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
return new ArrayList<>();
|
return new BarcodeValues(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
|
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) {
|
Uri data = intent.getData();
|
||||||
return retrieveBarcodesFromPdf(context, 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) {
|
if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) {
|
||||||
@@ -267,7 +193,7 @@ public class Utils {
|
|||||||
Log.i(TAG, "Read barcode id: " + contents);
|
Log.i(TAG, "Read barcode id: " + contents);
|
||||||
Log.i(TAG, "Read format: " + format);
|
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");
|
throw new UnsupportedOperationException("Unknown request code for parseSetBarcodeActivityResult");
|
||||||
@@ -287,22 +213,22 @@ public class Utils {
|
|||||||
return MediaStore.Images.Media.getBitmap(context.getContentResolver(), data);
|
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
|
// 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++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
try {
|
try {
|
||||||
return Utils.getBarcodesFromBitmapReal(bitmap);
|
return Utils.getBarcodeFromBitmapReal(bitmap);
|
||||||
} catch (OutOfMemoryError e) {
|
} 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);
|
bitmap = Bitmap.createScaledBitmap(bitmap, (int) Math.round(0.75 * bitmap.getWidth()), (int) Math.round(0.75 * bitmap.getHeight()), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give up
|
// 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...
|
// In order to decode it, the Bitmap must first be converted into a pixel array...
|
||||||
int[] intArray = new int[bitmap.getWidth() * bitmap.getHeight()];
|
int[] intArray = new int[bitmap.getWidth() * bitmap.getHeight()];
|
||||||
bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
|
bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
|
||||||
@@ -311,63 +237,15 @@ public class Utils {
|
|||||||
LuminanceSource source = new RGBLuminanceSource(bitmap.getWidth(), bitmap.getHeight(), intArray);
|
LuminanceSource source = new RGBLuminanceSource(bitmap.getWidth(), bitmap.getHeight(), intArray);
|
||||||
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
|
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||||
|
|
||||||
List<BarcodeValues> barcodeValuesList = new ArrayList<>();
|
|
||||||
try {
|
try {
|
||||||
MultiFormatReader multiFormatReader = new MultiFormatReader();
|
Result barcodeResult = new MultiFormatReader().decode(binaryBitmap);
|
||||||
MultipleBarcodeReader multipleBarcodeReader = new GenericMultipleBarcodeReader(multiFormatReader);
|
|
||||||
|
|
||||||
Result[] barcodeResults = multipleBarcodeReader.decodeMultiple(binaryBitmap);
|
return new BarcodeValues(barcodeResult.getBarcodeFormat().name(), barcodeResult.getText());
|
||||||
|
|
||||||
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;
|
|
||||||
} catch (NotFoundException e) {
|
} 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) {
|
static public Boolean isNotYetValid(Date validFromDate) {
|
||||||
// The note in `hasExpired` does not apply here, since the bug was fixed before this feature was added.
|
// The note in `hasExpired` does not apply here, since the bug was fixed before this feature was added.
|
||||||
return validFromDate.after(getStartOfToday().getTime());
|
return validFromDate.after(getStartOfToday().getTime());
|
||||||
@@ -395,7 +273,6 @@ public class Utils {
|
|||||||
|
|
||||||
static public String formatBalance(Context context, BigDecimal value, Currency currency) {
|
static public String formatBalance(Context context, BigDecimal value, Currency currency) {
|
||||||
NumberFormat numberFormat = NumberFormat.getInstance();
|
NumberFormat numberFormat = NumberFormat.getInstance();
|
||||||
numberFormat.setGroupingUsed(false);
|
|
||||||
|
|
||||||
if (currency == null) {
|
if (currency == null) {
|
||||||
numberFormat.setMaximumFractionDigits(0);
|
numberFormat.setMaximumFractionDigits(0);
|
||||||
@@ -403,7 +280,6 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
|
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
|
||||||
currencyFormat.setGroupingUsed(false);
|
|
||||||
currencyFormat.setCurrency(currency);
|
currencyFormat.setCurrency(currency);
|
||||||
currencyFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
|
currencyFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
|
||||||
currencyFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
|
currencyFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
|
||||||
@@ -413,7 +289,6 @@ public class Utils {
|
|||||||
|
|
||||||
static public String formatBalanceWithoutCurrencySymbol(BigDecimal value, Currency currency) {
|
static public String formatBalanceWithoutCurrencySymbol(BigDecimal value, Currency currency) {
|
||||||
NumberFormat numberFormat = NumberFormat.getInstance();
|
NumberFormat numberFormat = NumberFormat.getInstance();
|
||||||
numberFormat.setGroupingUsed(false);
|
|
||||||
|
|
||||||
if (currency == null) {
|
if (currency == null) {
|
||||||
numberFormat.setMaximumFractionDigits(0);
|
numberFormat.setMaximumFractionDigits(0);
|
||||||
@@ -426,72 +301,19 @@ public class Utils {
|
|||||||
return numberFormat.format(value);
|
return numberFormat.format(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final double LargestPreciseDouble = (double) (1l << 53);
|
|
||||||
static{
|
|
||||||
assert (LargestPreciseDouble + 1.0) == LargestPreciseDouble;
|
|
||||||
assert (LargestPreciseDouble - 1.0) != LargestPreciseDouble;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BigDecimal fromParsed(Number parsed){
|
|
||||||
if(parsed instanceof BigDecimal)
|
|
||||||
return (BigDecimal) parsed;
|
|
||||||
|
|
||||||
final double d = parsed.doubleValue();
|
|
||||||
if(d >= LargestPreciseDouble)
|
|
||||||
return new BigDecimal(parsed.longValue());
|
|
||||||
return new BigDecimal(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
static public BigDecimal parseBalance(String value, Currency currency) throws ParseException {
|
static public BigDecimal parseBalance(String value, Currency currency) throws ParseException {
|
||||||
// This function expects the input string to not have any grouping (thousand separators).
|
|
||||||
// It will refuse to work otherwise
|
|
||||||
NumberFormat numberFormat = NumberFormat.getInstance();
|
NumberFormat numberFormat = NumberFormat.getInstance();
|
||||||
numberFormat.setGroupingUsed(false);
|
|
||||||
|
|
||||||
if (numberFormat instanceof DecimalFormat) {
|
|
||||||
((DecimalFormat) numberFormat).setParseBigDecimal(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currency == null) {
|
if (currency == null) {
|
||||||
numberFormat.setMaximumFractionDigits(0);
|
numberFormat.setMaximumFractionDigits(0);
|
||||||
} else {
|
} else {
|
||||||
int fractionDigits = currency.getDefaultFractionDigits();
|
numberFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
|
||||||
|
numberFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
|
||||||
numberFormat.setMinimumFractionDigits(fractionDigits);
|
|
||||||
numberFormat.setMaximumFractionDigits(fractionDigits);
|
|
||||||
|
|
||||||
if (numberFormat instanceof DecimalFormat) {
|
|
||||||
// If the string contains both thousand separators and decimals separators, fail hard
|
|
||||||
DecimalFormatSymbols decimalFormatSymbols = ((DecimalFormat) numberFormat).getDecimalFormatSymbols();
|
|
||||||
char decimalSeparator = decimalFormatSymbols.getDecimalSeparator();
|
|
||||||
|
|
||||||
// Translate all non-digits to decimal separators, failing if we find more than 1.
|
|
||||||
// We loop over the codepoints to make sure eastern arabic numerals are not mistakenly
|
|
||||||
// treated as a separator.
|
|
||||||
boolean separatorFound = false;
|
|
||||||
StringBuilder translatedValue = new StringBuilder();
|
|
||||||
for (int i = 0; i < value.length();) {
|
|
||||||
int character = value.codePointAt(i);
|
|
||||||
|
|
||||||
if (Character.isDigit(character)) {
|
|
||||||
translatedValue.append(value.charAt(i));
|
|
||||||
} else {
|
|
||||||
if (separatorFound) {
|
|
||||||
throw new ParseException("Contains multiple separators", i);
|
|
||||||
}
|
|
||||||
|
|
||||||
separatorFound = true;
|
|
||||||
translatedValue.append(decimalSeparator);
|
|
||||||
}
|
|
||||||
|
|
||||||
i += Character.charCount(character);
|
|
||||||
}
|
|
||||||
|
|
||||||
value = translatedValue.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fromParsed(numberFormat.parse(value));
|
Log.d(TAG, numberFormat.parse(value).toString());
|
||||||
|
|
||||||
|
return new BigDecimal(numberFormat.parse(value).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
static public byte[] bitmapToByteArray(Bitmap bitmap) {
|
static public byte[] bitmapToByteArray(Bitmap bitmap) {
|
||||||
@@ -841,31 +663,13 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force correct color
|
// XXX android 9 and below has issues with patched theme where the background becomes a
|
||||||
// Fixes OLED dark mode in MainActivity
|
// rendering mess
|
||||||
|
// use after views are inflated
|
||||||
public static void postPatchColors(AppCompatActivity activity) {
|
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();
|
TypedValue typedValue = new TypedValue();
|
||||||
activity.getTheme().resolveAttribute(android.R.attr.colorBackground, typedValue, true);
|
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) {
|
public static int getHeaderColorFromImage(Bitmap image, int fallback) {
|
||||||
@@ -923,30 +727,23 @@ public class Utils {
|
|||||||
.replaceAll("(?<!href=\")\\b(https?://[\\w@#%&+=:?/.-]*[\\w@#%&+=:?/-])", "<a href=\"$1\">$1</a>");
|
.replaceAll("(?<!href=\")\\b(https?://[\\w@#%&+=:?/.-]*[\\w@#%&+=:?/-])", "<a href=\"$1\">$1</a>");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static void setIconOrTextWithBackground(Context context, LoyaltyCard loyaltyCard, Bitmap icon, ImageView backgroundOrIcon, TextView textWhenNoImage) {
|
||||||
* 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);
|
|
||||||
|
|
||||||
if (icon != null) {
|
if (icon != null) {
|
||||||
|
Log.d("onResume", "setting icon image");
|
||||||
textWhenNoImage.setVisibility(View.GONE);
|
textWhenNoImage.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
backgroundOrIcon.setImageBitmap(icon);
|
||||||
|
backgroundOrIcon.setBackgroundColor(Color.TRANSPARENT);
|
||||||
} else {
|
} else {
|
||||||
textWhenNoImage.setVisibility(View.VISIBLE);
|
textWhenNoImage.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
int headerColor = getHeaderColor(context, loyaltyCard);
|
||||||
|
|
||||||
|
backgroundOrIcon.setImageBitmap(null);
|
||||||
|
backgroundOrIcon.setBackgroundColor(headerColor);
|
||||||
textWhenNoImage.setText(loyaltyCard.store);
|
textWhenNoImage.setText(loyaltyCard.store);
|
||||||
textWhenNoImage.setTextColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
textWhenNoImage.setTextColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return headerColor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean installedFromGooglePlay(Context context) {
|
public static boolean installedFromGooglePlay(Context context) {
|
||||||
@@ -1017,12 +814,4 @@ public class Utils {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean deviceHasCamera(Context context) {
|
|
||||||
try {
|
|
||||||
return ((CameraManager) context.getSystemService(Context.CAMERA_SERVICE)).getCameraIdList().length > 0;
|
|
||||||
} catch (CameraAccessException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ package protect.card_locker.preferences;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import androidx.annotation.IntegerRes;
|
import androidx.annotation.IntegerRes;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import protect.card_locker.R;
|
import protect.card_locker.R;
|
||||||
import protect.card_locker.Utils;
|
import protect.card_locker.Utils;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package protect.card_locker.preferences;
|
package protect.card_locker.preferences;
|
||||||
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import androidx.activity.OnBackPressedCallback;
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
@@ -56,13 +56,6 @@ public class SettingsActivity extends CatimaAppCompatActivity {
|
|||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
fragment.mReloadMain = savedInstanceState.getBoolean(RELOAD_MAIN_STATE);
|
fragment.mReloadMain = savedInstanceState.getBoolean(RELOAD_MAIN_STATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
|
||||||
@Override
|
|
||||||
public void handleOnBackPressed() {
|
|
||||||
finishSettingsActivity();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -83,6 +76,11 @@ public class SettingsActivity extends CatimaAppCompatActivity {
|
|||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
finishSettingsActivity();
|
||||||
|
}
|
||||||
|
|
||||||
private void finishSettingsActivity() {
|
private void finishSettingsActivity() {
|
||||||
if (fragment.mReloadMain) {
|
if (fragment.mReloadMain) {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
|
|||||||
10
app/src/main/res/drawable/ic_copy.xml
Normal file
10
app/src/main/res/drawable/ic_copy.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
|
||||||
|
</vector>
|
||||||
@@ -10,8 +10,7 @@
|
|||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
@@ -21,12 +20,11 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="?attr/actionBarSize"
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
android:paddingVertical="8dp"
|
android:padding="10dp">
|
||||||
android:clipToPadding="false">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -37,9 +35,7 @@
|
|||||||
android:id="@+id/version_history"
|
android:id="@+id/version_history"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingVertical="8dp"
|
android:padding="8dp">
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:background="?android:selectableItemBackground">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/version_history_main"
|
android:id="@+id/version_history_main"
|
||||||
@@ -78,9 +74,7 @@
|
|||||||
android:id="@+id/credits"
|
android:id="@+id/credits"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingVertical="8dp"
|
android:padding="8dp">
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:background="?android:selectableItemBackground">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/credits_main"
|
android:id="@+id/credits_main"
|
||||||
@@ -119,9 +113,7 @@
|
|||||||
android:id="@+id/translate"
|
android:id="@+id/translate"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingVertical="8dp"
|
android:padding="8dp">
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:background="?android:selectableItemBackground">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/translate_main"
|
android:id="@+id/translate_main"
|
||||||
@@ -161,9 +153,7 @@
|
|||||||
android:id="@+id/license"
|
android:id="@+id/license"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingVertical="8dp"
|
android:padding="8dp">
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:background="?android:selectableItemBackground">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/license_main"
|
android:id="@+id/license_main"
|
||||||
@@ -203,9 +193,7 @@
|
|||||||
android:id="@+id/repo"
|
android:id="@+id/repo"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingVertical="8dp"
|
android:padding="8dp">
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:background="?android:selectableItemBackground">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/repo_main"
|
android:id="@+id/repo_main"
|
||||||
@@ -245,9 +233,7 @@
|
|||||||
android:id="@+id/privacy"
|
android:id="@+id/privacy"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingVertical="8dp"
|
android:padding="8dp">
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:background="?android:selectableItemBackground">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/privacy_main"
|
android:id="@+id/privacy_main"
|
||||||
@@ -287,9 +273,7 @@
|
|||||||
android:id="@+id/donate"
|
android:id="@+id/donate"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingVertical="8dp"
|
android:padding="8dp">
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:background="?android:selectableItemBackground">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/donate_main"
|
android:id="@+id/donate_main"
|
||||||
@@ -300,7 +284,6 @@
|
|||||||
android:text="@string/donate"
|
android:text="@string/donate"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -320,9 +303,7 @@
|
|||||||
android:id="@+id/rate"
|
android:id="@+id/rate"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingVertical="8dp"
|
android:padding="8dp">
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:background="?android:selectableItemBackground">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/rate_main"
|
android:id="@+id/rate_main"
|
||||||
@@ -359,12 +340,10 @@
|
|||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:padding="8dp"
|
||||||
android:id="@+id/report_error"
|
android:id="@+id/report_error"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:paddingVertical="8dp"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:background="?android:selectableItemBackground">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/report_error_main"
|
android:id="@+id/report_error_main"
|
||||||
@@ -400,5 +379,5 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</ScrollView>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -31,6 +31,13 @@
|
|||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
style="?attr/toolbarStyle" />
|
style="?attr/toolbarStyle" />
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/groups"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tabMode="scrollable" />
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,20 +9,20 @@
|
|||||||
tools:showIn="@layout/custom_barcode_scanner">
|
tools:showIn="@layout/custom_barcode_scanner">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/camera_error_clickable_area"
|
android:id="@+id/camera_permission_denied_clickable_area"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/camera_error_icon"
|
android:id="@+id/camera_permission_denied_icon"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="84dp"
|
android:layout_height="84dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:src="@drawable/camera_error" />
|
android:src="@drawable/camera_permission_denied" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/camera_error_title"
|
android:id="@+id/camera_permission_denied_title"
|
||||||
style="@style/TextAppearance.Material3.HeadlineLarge"
|
style="@style/TextAppearance.Material3.HeadlineLarge"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -30,12 +30,12 @@
|
|||||||
android:text="@string/cameraPermissionDeniedTitle" />
|
android:text="@string/cameraPermissionDeniedTitle" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/camera_error_message"
|
android:id="@+id/camera_permission_denied_message"
|
||||||
style="@style/AppTheme.TextView.NoData"
|
style="@style/AppTheme.TextView.NoData"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/zxing_msg_camera_framework_bug" />
|
android:text="@string/noCameraPermissionDirectToSystemSetting" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
@@ -34,8 +34,8 @@
|
|||||||
android:padding="@dimen/activity_scanner_padding">
|
android:padding="@dimen/activity_scanner_padding">
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/camera_error_layout"
|
android:id="@+id/camera_permission_denied_layout"
|
||||||
layout="@layout/camera_error_layout"
|
layout="@layout/camera_permission_failed_layout"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
@@ -18,11 +17,11 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fillViewport="true"
|
android:fillViewport="true"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
<LinearLayout android:orientation="vertical"
|
<LinearLayout android:orientation="vertical"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
@@ -128,7 +127,7 @@
|
|||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:text="@string/importOptionApplicationButton" />
|
android:text="@string/importOptionApplicationButton" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -18,8 +18,7 @@
|
|||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
@@ -30,8 +29,7 @@
|
|||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/tabs"
|
android:id="@+id/tabs"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:background="@android:color/transparent">
|
|
||||||
<com.google.android.material.tabs.TabItem
|
<com.google.android.material.tabs.TabItem
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -48,10 +46,9 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<ScrollView android:layout_width="fill_parent"
|
||||||
android:layout_width="fill_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
|
||||||
<TableLayout
|
<TableLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -372,110 +369,79 @@
|
|||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<!-- Front image -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/frontImageHolder"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:baselineAligned="false">
|
android:paddingHorizontal="@dimen/inputPadding"
|
||||||
|
android:paddingTop="@dimen/inputPadding">
|
||||||
|
|
||||||
<!-- Front image -->
|
<!-- Front image -->
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/frontImageHolder"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_margin="5dp"
|
android:layout_marginStart="@dimen/activity_margin"
|
||||||
style="?attr/materialCardViewElevatedStyle">
|
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
|
<ImageView
|
||||||
android:id="@+id/frontImageConstraint"
|
android:id="@+id/frontImage"
|
||||||
android:layout_width="match_parent"
|
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>
|
</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 -->
|
<!-- Back image -->
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:layout_weight="1"
|
android:layout_width="match_parent"
|
||||||
android:id="@+id/backImageHolder"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_margin="5dp"
|
android:layout_marginStart="@dimen/activity_margin"
|
||||||
style="?attr/materialCardViewElevatedStyle">
|
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
|
<ImageView
|
||||||
android:id="@+id/backImageConstraint"
|
android:id="@+id/backImage"
|
||||||
android:layout_width="match_parent"
|
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>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:contentDescription="@string/thumbnailDescription"
|
android:contentDescription="@string/thumbnailDescription"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@mipmap/ic_launcher"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user