Compare commits

...

77 Commits

Author SHA1 Message Date
Sylvia van Os
cd5ef267e8 WIP 2024-05-21 18:49:52 +02:00
Sylvia van Os
4b1d1f4541 Merge pull request #1896 from CatimaLoyalty/dependabot/gradle/com.android.application-8.4.1
Bump com.android.application from 8.4.0 to 8.4.1
2024-05-21 08:25:15 +02:00
dependabot[bot]
801d3fa8cd ---
updated-dependencies:
- dependency-name: com.android.application
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-21 02:19:26 +00:00
Sylvia van Os
d15a46fc6f Fix typo 2024-05-20 20:04:01 +02:00
Sylvia van Os
7f46a267b6 Merge pull request #1895 from CatimaLoyalty/create-pull-request/patch-1716220734
Update Fastlane changelogs
2024-05-20 18:12:04 +02:00
TheLastProject
195cb8d5ee Update Fastlane changelogs 2024-05-20 15:58:53 +00:00
Sylvia van Os
7454a965bc Update CHANGELOG 2024-05-20 17:58:40 +02:00
Sylvia van Os
9ef988c259 Merge pull request #1894 from CatimaLoyalty/feature/showImageTypeInCardIdVield
Show image type on view screen when not viewing barcode
2024-05-20 17:54:09 +02:00
Sylvia van Os
7a2ff0995f Show image type on view screen when not viewing barcode 2024-05-20 17:47:19 +02:00
Sylvia van Os
9b65d3926b Merge pull request #1893 from CatimaLoyalty/create-pull-request/patch-1716210906
Update Fastlane changelogs
2024-05-20 15:20:35 +02:00
TheLastProject
06b3536079 Update Fastlane changelogs 2024-05-20 13:15:06 +00:00
Sylvia van Os
315396fd42 Merge pull request #1892 from CatimaLoyalty/feature/1860
Support for creating a card from shared text
2024-05-20 15:14:53 +02:00
Sylvia van Os
b90c43f667 Support for creating a card from shared text 2024-05-20 14:53:10 +02:00
Aglag257
6d97a29e9c Fix describeContents() in LoyaltyCard Class (#1887) 2024-05-20 13:19:13 +02:00
Sylvia van Os
7f0e2acab9 Merge pull request #1891 from CatimaLoyalty/dependabot/gradle/org.robolectric-robolectric-4.12.2
Bump org.robolectric:robolectric from 4.12.1 to 4.12.2
2024-05-20 11:52:00 +02:00
Sylvia van Os
be35886c92 Merge pull request #1890 from CatimaLoyalty/dependabot/github_actions/actions/checkout-4.1.6
Bump actions/checkout from 4.1.5 to 4.1.6
2024-05-20 11:51:23 +02:00
dependabot[bot]
73ad9c5365 Bump org.robolectric:robolectric from 4.12.1 to 4.12.2
Bumps [org.robolectric:robolectric](https://github.com/robolectric/robolectric) from 4.12.1 to 4.12.2.
- [Release notes](https://github.com/robolectric/robolectric/releases)
- [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.12.1...robolectric-4.12.2)

---
updated-dependencies:
- dependency-name: org.robolectric:robolectric
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 02:34:37 +00:00
dependabot[bot]
818751ffad Bump actions/checkout from 4.1.5 to 4.1.6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.5 to 4.1.6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.5...v4.1.6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 02:12:05 +00:00
Sylvia van Os
d5856e7974 Release Catima 2.29.1 2024-05-19 10:53:43 +02:00
Sylvia van Os
2caf3d42f4 Update fastlane 2024-05-18 10:18:33 +02:00
Sylvia van Os
e766743baa Merge pull request #1885 from CatimaLoyalty/fix/properSeparatorFix
Properly fix decimal separator detection logic
2024-05-14 17:06:20 +02:00
Sylvia van Os
facb23f0a5 Properly fix decimal separator detection logic 2024-05-13 21:17:13 +02:00
Sylvia van Os
a2dff0e6bf Merge pull request #1884 from CatimaLoyalty/dependabot/github_actions/actions/checkout-4.1.5
Bump actions/checkout from 4.1.4 to 4.1.5
2024-05-13 18:42:40 +02:00
dependabot[bot]
60c53ec1d1 Bump actions/checkout from 4.1.4 to 4.1.5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4 to 4.1.5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.4...v4.1.5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 02:55:49 +00:00
Sylvia van Os
e3c903f773 Merge pull request #1883 from CatimaLoyalty/create-pull-request/patch-1715516406
Update Fastlane changelogs
2024-05-12 15:05:00 +02:00
TheLastProject
955764aa18 Update Fastlane changelogs 2024-05-12 12:20:06 +00:00
Sylvia van Os
ed22aa844f Document balance handling improvements 2024-05-12 14:19:36 +02:00
Sylvia van Os
6b8fc50021 Merge pull request #1878 from CatimaLoyalty/feature/acceptBothSeparators
Disable currency grouping and accept thousand separator as decimal separator
2024-05-12 14:17:58 +02:00
Sylvia van Os
1aed5c36b1 Merge pull request #1882 from CatimaLoyalty/create-pull-request/patch-1715487330
Update contributors
2024-05-12 10:33:39 +02:00
TheLastProject
4690f53be7 Update contributors 2024-05-12 04:15:30 +00:00
Sylvia van Os
29a22926aa Merge pull request #1879 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2024-05-10 23:31:27 +02:00
ΣΤΑΥΡΟΣ ΔΑΛΙΑΚΟΠΟΥΛΟΣ
a4da8144c3 Translated using Weblate (Greek)
Currently translated at 100.0% (312 of 312 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2024-05-10 23:24:06 +02:00
Sylvia van Os
84bfa304c4 Disable currency grouping and accept thousand separator as decimal separator 2024-05-10 20:01:39 +02:00
Sylvia van Os
72749015dd Merge pull request #1877 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2024-05-09 21:57:52 +02:00
ΣΤΑΥΡΟΣ ΔΑΛΙΑΚΟΠΟΥΛΟΣ
ae5d656a64 Translated using Weblate (Greek)
Currently translated at 100.0% (1 of 1 strings)

Translation: Catima/Android (Debug)
Translate-URL: https://hosted.weblate.org/projects/catima/android-debug/el/
2024-05-07 16:07:57 +02:00
ΣΤΑΥΡΟΣ ΔΑΛΙΑΚΟΠΟΥΛΟΣ
d8f2cde9de Translated using Weblate (Greek)
Currently translated at 100.0% (312 of 312 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/el/
2024-05-07 16:07:57 +02:00
Sylvia van Os
935a4ae46e Merge pull request #1874 from CatimaLoyalty/create-pull-request/patch-1714882546
Update contributors
2024-05-05 10:37:59 +02:00
TheLastProject
ffecec9cba Update contributors 2024-05-05 04:15:45 +00:00
Sylvia van Os
734845eff0 Merge pull request #1871 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2024-05-03 17:03:36 +02:00
Sébastien Delord
bd6b18eea2 Translated using Weblate (French)
Currently translated at 98.4% (130 of 132 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/fr/
2024-05-03 09:43:43 +02:00
Sylvia van Os
6473bfe84e Merge pull request #1868 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2024-05-03 08:26:25 +02:00
Sylvia van Os
66fff2d3d9 Merge pull request #1870 from CatimaLoyalty/dependabot/gradle/com.google.android.material-material-1.12.0
Bump com.google.android.material:material from 1.11.0 to 1.12.0
2024-05-03 08:20:50 +02:00
dependabot[bot]
ef1b470f0a Bump com.google.android.material:material from 1.11.0 to 1.12.0
Bumps [com.google.android.material:material](https://github.com/material-components/material-components-android) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/material-components/material-components-android/releases)
- [Commits](https://github.com/material-components/material-components-android/compare/1.11.0...1.12.0)

---
updated-dependencies:
- dependency-name: com.google.android.material:material
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-03 02:49:31 +00:00
Milo Ivir
b087d49530 Translated using Weblate (Croatian)
Currently translated at 100.0% (312 of 312 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/catima/catima/hr/
Translation: Catima/Android
2024-05-03 02:09:26 +02:00
Sylvia van Os
cd46cd3f8d Merge pull request #1866 from CatimaLoyalty/dependabot/gradle/com.android.application-8.4.0
Bump com.android.application from 8.3.2 to 8.4.0
2024-05-01 18:38:37 +02:00
dependabot[bot]
c44133942a Bump com.android.application from 8.3.2 to 8.4.0
Bumps com.android.application from 8.3.2 to 8.4.0.

---
updated-dependencies:
- dependency-name: com.android.application
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-01 02:24:02 +00:00
Sylvia van Os
24e40f41a5 Merge pull request #1865 from CatimaLoyalty/dependabot/github_actions/actions/checkout-4.1.4
Bump actions/checkout from 4.1.3 to 4.1.4
2024-04-29 19:22:01 +02:00
Sylvia van Os
43b90587a5 Merge pull request #1864 from CatimaLoyalty/dependabot/github_actions/actions/upload-artifact-4.3.3
Bump actions/upload-artifact from 4.3.2 to 4.3.3
2024-04-29 19:02:36 +02:00
Sylvia van Os
f5b2516492 Merge pull request #1863 from CatimaLoyalty/dependabot/github_actions/peter-evans/create-pull-request-6.0.5
Bump peter-evans/create-pull-request from 6.0.4 to 6.0.5
2024-04-29 19:02:11 +02:00
dependabot[bot]
5bec40eb59 Bump actions/checkout from 4.1.3 to 4.1.4
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.3 to 4.1.4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.3...v4.1.4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 02:12:58 +00:00
dependabot[bot]
6b1cccfdf8 Bump actions/upload-artifact from 4.3.2 to 4.3.3
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.2 to 4.3.3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.3.2...v4.3.3)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 02:12:56 +00:00
dependabot[bot]
ddf28d7542 Bump peter-evans/create-pull-request from 6.0.4 to 6.0.5
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6.0.4 to 6.0.5.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v6.0.4...v6.0.5)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 02:12:53 +00:00
Sylvia van Os
9cf69a3128 Merge pull request #1862 from CatimaLoyalty/create-pull-request/patch-1714277679
Update contributors
2024-04-28 10:22:11 +02:00
Sylvia van Os
8dc62def08 Merge pull request #1861 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2024-04-28 10:21:09 +02:00
TheLastProject
ac8b898ccc Update contributors 2024-04-28 04:14:39 +00:00
Young
97e675e476 Translated using Weblate (Chinese (Traditional))
Currently translated at 98.0% (306 of 312 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/zh_Hant/
2024-04-28 04:07:18 +02:00
Sylvia van Os
4b488ce1f7 Merge pull request #1859 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2024-04-25 17:37:57 +02:00
solokot
7f5fe8831a Translated using Weblate (Russian)
Currently translated at 100.0% (132 of 132 strings)

Translation: Catima/Android (Fastlane)
Translate-URL: https://hosted.weblate.org/projects/catima/fastlane/ru/
2024-04-25 01:07:38 +02:00
Jesus Aguilar-Andrade
f7ff63301b Translated using Weblate (Spanish)
Currently translated at 100.0% (312 of 312 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2024-04-25 01:07:37 +02:00
simosathan9
9bb1602370 Fix balance unexpected rounding and app crashing issue (#1838)
Co-authored-by:  Colette Kerr <colette.m.y.kerr@gmail.com>
2024-04-24 21:41:22 +02:00
Sylvia van Os
40da1d2e16 Merge pull request #1855 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2024-04-24 18:37:33 +02:00
Jesus Aguilar-Andrade
8dd61d026c Translated using Weblate (Spanish)
Currently translated at 100.0% (312 of 312 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/es/
2024-04-24 00:15:47 +02:00
Sylvia van Os
4d0464f729 Merge pull request #1852 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2024-04-22 21:32:33 +02:00
vesp
e0f1410c8b Translated using Weblate (Czech)
Currently translated at 100.0% (312 of 312 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/cs/
2024-04-22 21:07:18 +02:00
Sylvia van Os
2bf7698961 Merge pull request #1847 from CatimaLoyalty/dependabot/github_actions/actions/upload-artifact-4.3.2
Bump actions/upload-artifact from 4.3.1 to 4.3.2
2024-04-22 18:30:47 +02:00
Sylvia van Os
0654a2baef Merge pull request #1846 from CatimaLoyalty/dependabot/github_actions/actions/checkout-4.1.3
Bump actions/checkout from 4.1.1 to 4.1.3
2024-04-22 18:29:56 +02:00
Sylvia van Os
0e785b2ccf Merge pull request #1845 from CatimaLoyalty/dependabot/github_actions/peter-evans/create-pull-request-6.0.4
Bump peter-evans/create-pull-request from 6.0.3 to 6.0.4
2024-04-22 18:28:35 +02:00
dependabot[bot]
46088a48f9 Bump actions/upload-artifact from 4.3.1 to 4.3.2
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.1 to 4.3.2.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.3.1...v4.3.2)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 02:40:35 +00:00
dependabot[bot]
82acaef96f Bump actions/checkout from 4.1.1 to 4.1.3
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.1...v4.1.3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 02:40:32 +00:00
dependabot[bot]
8556f4d586 Bump peter-evans/create-pull-request from 6.0.3 to 6.0.4
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6.0.3 to 6.0.4.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v6.0.3...v6.0.4)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 02:40:29 +00:00
Sylvia van Os
e8308b1069 Merge branch 'main' of github.com:CatimaLoyalty/Android 2024-04-21 12:31:52 +02:00
Sylvia van Os
6516d18cc2 Remove F-Droid buttons
IzzyOnDroid is the recommended source for Catima updates
2024-04-21 12:30:35 +02:00
Sylvia van Os
6dd0105908 Merge pull request #1843 from weblate/weblate-catima-catima
Translations update from Hosted Weblate
2024-04-21 09:54:37 +02:00
Sylvia van Os
3e27b931b5 Merge pull request #1844 from CatimaLoyalty/create-pull-request/patch-1713672853
Update contributors
2024-04-21 09:53:27 +02:00
TheLastProject
73046e56e5 Update contributors 2024-04-21 04:14:13 +00:00
Nicoara Alex
84c350af90 Translated using Weblate (Romanian)
Currently translated at 100.0% (312 of 312 strings)

Translation: Catima/Android
Translate-URL: https://hosted.weblate.org/projects/catima/catima/ro/
2024-04-21 04:07:20 +02:00
Sylvia van Os
30bd8d810f Update fastlane 2024-04-20 18:40:48 +02:00
38 changed files with 751 additions and 499 deletions

View File

@@ -29,7 +29,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.1.6
- name: Fail on bad translations
run: if grep -ri "&lt;xliff" app/src/main/res/values*/strings.xml; then echo "Invalidly escaped translations found"; exit 1; fi
- uses: gradle/actions/wrapper-validation@v3
@@ -48,7 +48,7 @@ jobs:
run: ./gradlew spotbugsRelease
- name: Archive test results
if: always()
uses: actions/upload-artifact@v4.3.1
uses: actions/upload-artifact@v4.3.3
with:
name: test-results
path: app/build/reports

View File

@@ -27,7 +27,7 @@ jobs:
steps:
- name: Checkout repo
id: checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.1.6
- name: Setup Python
uses: actions/setup-python@v5.1.0
with:
@@ -35,7 +35,7 @@ jobs:
- name: Run converter script
run: python .scripts/changelog_to_fastlane.py
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6.0.3
uses: peter-evans/create-pull-request@v6.0.5
with:
title: "Update Fastlane changelogs"
commit-message: "Update Fastlane changelogs"

View File

@@ -25,7 +25,7 @@ jobs:
steps:
- name: Checkout repo
id: checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.1.6
- name: Update contributors
id: update_contributors
uses: TheLastProject/contributors-to-file-action@v3.2.0
@@ -33,7 +33,7 @@ jobs:
file_in_repo: app/src/main/res/raw/contributors.txt
min_commit_count: 5
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6.0.3
uses: peter-evans/create-pull-request@v6.0.5
with:
title: "Update contributors"
commit-message: "Update contributors"

View File

@@ -24,7 +24,7 @@ jobs:
generate-feature-graphic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.1.6
- name: Install requirements
run: |
sudo apt-get update
@@ -38,7 +38,7 @@ jobs:
- name: Generate featureGraphic.png for each language
run: .scripts/generate_feature_graphic/generate_feature_graphic.sh
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6.0.3
uses: peter-evans/create-pull-request@v6.0.5
with:
title: "Update feature graphic"
commit-message: "Update feature graphic"

View File

@@ -21,12 +21,12 @@ jobs:
gradle-update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.1.6
- uses: obfusk/gradle-update-action@v2.0.0
id: gradle-update
- uses: gradle/actions/wrapper-validation@v3
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6.0.3
uses: peter-evans/create-pull-request@v6.0.5
with:
title: "Update Gradle to ${{ steps.gradle-update.outputs.version }}"
commit-message: "Update Gradle to ${{ steps.gradle-update.outputs.version }}"

View File

@@ -25,13 +25,13 @@ jobs:
update-locales:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.1.6
- name: Add new locales
run: .scripts/new-locales.py
- name: Update locales
run: .scripts/locales.py
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6.0.3
uses: peter-evans/create-pull-request@v6.0.5
with:
title: "Update locales"
commit-message: "Update locales"

View File

@@ -1,5 +1,14 @@
# Changelog
## Unreleased - 136
- Support for creating a card when sharing plain text
- Display image type instead of barcode below images
## 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

View File

@@ -1,29 +1,32 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.6)
CFPropertyList (3.0.7)
base64
nkf
rexml
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.884.0)
aws-sdk-core (3.191.0)
aws-partitions (1.931.0)
aws-sdk-core (3.196.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.77.0)
aws-sdk-core (~> 3, >= 3.191.0)
aws-sdk-kms (1.81.0)
aws-sdk-core (~> 3, >= 3.193.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.143.0)
aws-sdk-core (~> 3, >= 3.191.0)
aws-sdk-s3 (1.151.0)
aws-sdk-core (~> 3, >= 3.194.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
@@ -35,7 +38,7 @@ GEM
domain_name (0.6.20240107)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.109.0)
excon (0.110.0)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
@@ -64,15 +67,15 @@ GEM
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.3.0)
fastlane (2.219.0)
fastimage (2.3.1)
fastlane (2.220.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
colored (~> 1.2)
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
@@ -93,10 +96,10 @@ GEM
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (>= 0.1.1)
optparse (>= 0.1.1, < 1.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
security (= 0.1.5)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (~> 3)
@@ -105,7 +108,7 @@ GEM
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
@@ -123,12 +126,12 @@ GEM
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.31.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.6.1)
google-cloud-core (1.7.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.3.1)
google-cloud-errors (1.4.0)
google-cloud-storage (1.47.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
@@ -148,30 +151,33 @@ GEM
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.7.1)
jwt (2.7.1)
json (2.7.2)
jwt (2.8.1)
base64
mini_magick (4.12.0)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.3.0)
multipart-post (2.4.1)
nanaimo (0.3.0)
naturally (2.2.1)
optparse (0.4.0)
nkf (0.2.0)
optparse (0.5.0)
os (1.1.4)
plist (3.7.1)
public_suffix (5.0.4)
rake (13.1.0)
public_suffix (5.0.5)
rake (13.2.1)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.6)
rexml (3.2.8)
strscan (>= 3.0.9)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.18.0)
security (0.1.5)
signet (0.19.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
@@ -179,6 +185,7 @@ GEM
simctl (1.6.10)
CFPropertyList
naturally
strscan (3.1.0)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
@@ -209,4 +216,4 @@ DEPENDENCIES
fastlane
BUNDLED WITH
2.3.26
2.5.9

View File

@@ -21,8 +21,8 @@ android {
applicationId = "me.hackerchick.catima"
minSdk = 21
targetSdk = 34
versionCode = 134
versionName = "2.29.0"
versionCode = 135
versionName = "2.29.1"
vectorDrawables.useSupportLibrary = true
multiDexEnabled = true
@@ -92,8 +92,9 @@ dependencies {
implementation("androidx.exifinterface:exifinterface:1.3.7")
implementation("androidx.palette:palette:1.0.0")
implementation("androidx.preference:preference:1.2.1")
implementation("com.google.android.material:material:1.11.0")
implementation("com.google.android.material:material:1.12.0")
implementation("com.github.yalantis:ucrop:2.2.9")
implementation("androidx.work:work-runtime:2.9.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
// Splash Screen
@@ -112,7 +113,7 @@ dependencies {
// Testing
testImplementation("androidx.test:core:1.5.0")
testImplementation("junit:junit:4.13.2")
testImplementation("org.robolectric:robolectric:4.12.1")
testImplementation("org.robolectric:robolectric:4.12.2")
}
tasks.withType<SpotBugsTask>().configureEach {

View File

@@ -1,2 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<string name="app_name">Αποσφαλμάτωση Catima</string>
</resources>

View File

@@ -12,6 +12,8 @@
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="23" />
<uses-feature
@@ -43,6 +45,7 @@
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
<data android:mimeType="image/*" />
<data android:mimeType="application/pdf" />
</intent-filter>
@@ -187,5 +190,6 @@
<action android:name="android.service.controls.ControlsProviderService" />
</intent-filter>
</service>
<service android:name=".importexport.ImportExportWorker"/>
</application>
</manifest>

View File

@@ -1,8 +1,9 @@
package protect.card_locker;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.text.InputType;
@@ -17,31 +18,31 @@ import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.OutOfQuotaPolicy;
import androidx.work.WorkManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.ImportExportActivityBinding;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.ImportExportResultType;
import protect.card_locker.importexport.ImportExportWorker;
public class ImportExportActivity extends CatimaAppCompatActivity {
private ImportExportActivityBinding binding;
private static final String TAG = "Catima";
private ImportExportTask importExporter;
private String importAlertTitle;
private String importAlertMessage;
private DataFormat importDataFormat;
@@ -51,7 +52,10 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
private ActivityResultLauncher<String> fileOpenLauncher;
private ActivityResultLauncher<Intent> filePickerLauncher;
final private TaskHandler mTasks = new TaskHandler();
private static final int PERMISSION_REQUEST_EXPORT = 100;
private static final int PERMISSION_REQUEST_IMPORT = 101;
private OneTimeWorkRequest mRequestedWorkRequest;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -80,15 +84,20 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
Log.e(TAG, "Activity returned NULL uri");
return;
}
try {
OutputStream writer = getContentResolver().openOutputStream(uri);
Log.e(TAG, "Starting file export with: " + result.toString());
startExport(writer, uri, exportPassword.toCharArray(), true);
} catch (IOException e) {
Log.e(TAG, "Failed to export file: " + result.toString(), e);
onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), uri);
}
Data exportRequestData = new Data.Builder()
.putString(ImportExportWorker.INPUT_URI, uri.toString())
.putString(ImportExportWorker.INPUT_ACTION, ImportExportWorker.ACTION_EXPORT)
.putString(ImportExportWorker.INPUT_FORMAT, DataFormat.Catima.name())
.putString(ImportExportWorker.INPUT_PASSWORD, exportPassword)
.build();
mRequestedWorkRequest = new OneTimeWorkRequest.Builder(ImportExportWorker.class)
.setInputData(exportRequestData)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build();
PermissionUtils.requestPostNotificationsPermission(this, PERMISSION_REQUEST_EXPORT);
});
fileOpenLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> {
if (result == null) {
@@ -159,15 +168,24 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
importApplication.setOnClickListener(v -> chooseImportType(true, null));
}
public static OneTimeWorkRequest buildImportRequest(DataFormat dataFormat, Uri uri, char[] password) {
Data importRequestData = new Data.Builder()
.putString(ImportExportWorker.INPUT_URI, uri.toString())
.putString(ImportExportWorker.INPUT_ACTION, ImportExportWorker.ACTION_IMPORT)
.putString(ImportExportWorker.INPUT_FORMAT, dataFormat.name())
.putString(ImportExportWorker.INPUT_PASSWORD, Arrays.toString(password))
.build();
return new OneTimeWorkRequest.Builder(ImportExportWorker.class)
.setInputData(importRequestData)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build();
}
private void openFileForImport(Uri uri, char[] password) {
try {
InputStream reader = getContentResolver().openInputStream(uri);
Log.e(TAG, "Starting file import with: " + uri.toString());
startImport(reader, uri, importDataFormat, password, true);
} catch (IOException e) {
Log.e(TAG, "Failed to import file: " + uri.toString(), e);
onImportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, e.toString()), uri, importDataFormat);
}
mRequestedWorkRequest = buildImportRequest(importDataFormat, uri, password);
PermissionUtils.requestPostNotificationsPermission(this, PERMISSION_REQUEST_IMPORT);
}
private void chooseImportType(boolean choosePicker,
@@ -232,20 +250,17 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
new MaterialAlertDialogBuilder(this)
.setTitle(importAlertTitle)
.setMessage(importAlertMessage)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
if (choosePicker) {
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
filePickerLauncher.launch(intentPickAction);
} else {
fileOpenLauncher.launch("*/*");
}
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
.setPositiveButton(R.string.ok, (dialog1, which1) -> {
try {
if (choosePicker) {
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
filePickerLauncher.launch(intentPickAction);
} else {
fileOpenLauncher.launch("*/*");
}
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
})
.setNegativeButton(R.string.cancel, null)
@@ -254,60 +269,12 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
builder.show();
}
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password, final boolean closeWhenDone) {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
@Override
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
onImportComplete(result, targetUri, dataFormat);
if (closeWhenDone) {
try {
target.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
dataFormat, target, password, listener);
mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter);
}
private void startExport(final OutputStream target, final Uri targetUri, char[] password, final boolean closeWhenDone) {
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
@Override
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
onExportComplete(result, targetUri);
if (closeWhenDone) {
try {
target.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
DataFormat.Catima, target, password, listener);
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter);
}
@Override
protected void onDestroy() {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
super.onDestroy();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
setResult(RESULT_CANCELED);
finish();
return true;
}
@@ -315,19 +282,19 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
return super.onOptionsItemSelected(item);
}
private void retryWithPassword(DataFormat dataFormat, Uri uri) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
public static void retryWithPassword(Context context, DataFormat dataFormat, Uri uri) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context);
builder.setTitle(R.string.passwordRequired);
FrameLayout container = new FrameLayout(ImportExportActivity.this);
FrameLayout container = new FrameLayout(context);
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
final TextInputLayout textInputLayout = new TextInputLayout(context);
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(50, 10, 50, 0);
textInputLayout.setLayoutParams(params);
final EditText input = new EditText(ImportExportActivity.this);
final EditText input = new EditText(context);
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
input.setHint(R.string.exportPasswordHint);
@@ -336,75 +303,55 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
builder.setView(container);
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
openFileForImport(uri, input.getText().toString().toCharArray());
OneTimeWorkRequest importRequest = ImportExportActivity.buildImportRequest(dataFormat, uri, input.getText().toString().toCharArray());
WorkManager.getInstance(context).enqueueUniqueWork(ImportExportWorker.ACTION_IMPORT, ExistingWorkPolicy.REPLACE, importRequest);
});
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
builder.show();
}
private String buildResultDialogMessage(ImportExportResult result, boolean isImport) {
int messageId;
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (result.resultType() == ImportExportResultType.Success) {
messageId = isImport ? R.string.importSuccessful : R.string.exportSuccessful;
} else {
messageId = isImport ? R.string.importFailed : R.string.exportFailed;
}
StringBuilder messageBuilder = new StringBuilder(getResources().getString(messageId));
if (result.developerDetails() != null) {
messageBuilder.append("\n\n");
messageBuilder.append(getResources().getString(R.string.include_if_asking_support));
messageBuilder.append("\n\n");
messageBuilder.append(result.developerDetails());
}
return messageBuilder.toString();
onMockedRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) {
ImportExportResultType resultType = result.resultType();
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
Integer failureReason = null;
if (resultType == ImportExportResultType.BadPassword) {
retryWithPassword(dataFormat, path);
return;
if (requestCode == PERMISSION_REQUEST_EXPORT) {
if (granted) {
WorkManager.getInstance(this).enqueueUniqueWork(ImportExportWorker.ACTION_EXPORT, ExistingWorkPolicy.REPLACE, mRequestedWorkRequest);
Toast.makeText(this, R.string.exportStartedCheckNotifications, Toast.LENGTH_LONG).show();
// Import/export started
setResult(RESULT_OK);
finish();
return;
}
failureReason = R.string.postNotificationsPermissionRequired;
} else if (requestCode == PERMISSION_REQUEST_IMPORT) {
if (granted) {
WorkManager.getInstance(this).enqueueUniqueWork(ImportExportWorker.ACTION_IMPORT, ExistingWorkPolicy.REPLACE, mRequestedWorkRequest);
// Import/export started
setResult(RESULT_OK);
finish();
return;
}
failureReason = R.string.postNotificationsPermissionRequired;
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.importSuccessfulTitle : R.string.importFailedTitle);
builder.setMessage(buildResultDialogMessage(result, true));
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
builder.create().show();
}
private void onExportComplete(ImportExportResult result, final Uri path) {
ImportExportResultType resultType = result.resultType();
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.exportSuccessfulTitle : R.string.exportFailedTitle);
builder.setMessage(buildResultDialogMessage(result, false));
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
if (resultType == ImportExportResultType.Success) {
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
builder.setPositiveButton(sendLabel, (dialog, which) -> {
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
sendIntent.setType("text/csv");
// set flag to give temporary permission to external app to use the FileProvider
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
sendLabel));
dialog.dismiss();
});
if (failureReason != null) {
Toast.makeText(this, failureReason, Toast.LENGTH_LONG).show();
}
builder.create().show();
}
}

View File

@@ -1,143 +0,0 @@
package protect.card_locker;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import protect.card_locker.async.CompatCallable;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.ImportExportResultType;
import protect.card_locker.importexport.MultiFormatExporter;
import protect.card_locker.importexport.MultiFormatImporter;
public class ImportExportTask implements CompatCallable<ImportExportResult> {
private static final String TAG = "Catima";
private Activity activity;
private boolean doImport;
private DataFormat format;
private OutputStream outputStream;
private InputStream inputStream;
private char[] password;
private TaskCompleteListener listener;
private ProgressDialog progress;
/**
* Constructor which will setup a task for exporting to the given file
*/
ImportExportTask(Activity activity, DataFormat format, OutputStream output, char[] password,
TaskCompleteListener listener) {
super();
this.activity = activity;
this.doImport = false;
this.format = format;
this.outputStream = output;
this.password = password;
this.listener = listener;
}
/**
* Constructor which will setup a task for importing from the given InputStream.
*/
ImportExportTask(Activity activity, DataFormat format, InputStream input, char[] password,
TaskCompleteListener listener) {
super();
this.activity = activity;
this.doImport = true;
this.format = format;
this.inputStream = input;
this.password = password;
this.listener = listener;
}
private ImportExportResult performImport(Context context, InputStream stream, SQLiteDatabase database, char[] password) {
ImportExportResult importResult = MultiFormatImporter.importData(context, database, stream, format, password);
Log.i(TAG, "Import result: " + importResult);
return importResult;
}
private ImportExportResult performExport(Context context, OutputStream stream, SQLiteDatabase database, char[] password) {
ImportExportResult result;
try {
OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
result = MultiFormatExporter.exportData(context, database, stream, format, password);
writer.close();
} catch (IOException e) {
result = new ImportExportResult(ImportExportResultType.GenericFailure, e.toString());
Log.e(TAG, "Unable to export file", e);
}
Log.i(TAG, "Export result: " + result);
return result;
}
public void onPreExecute() {
progress = new ProgressDialog(activity);
progress.setTitle(doImport ? R.string.importing : R.string.exporting);
progress.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
ImportExportTask.this.stop();
}
});
progress.show();
}
protected ImportExportResult doInBackground(Void... nothing) {
final SQLiteDatabase database = new DBHelper(activity).getWritableDatabase();
ImportExportResult result;
if (doImport) {
result = performImport(activity.getApplicationContext(), inputStream, database, password);
} else {
result = performExport(activity.getApplicationContext(), outputStream, database, password);
}
database.close();
return result;
}
public void onPostExecute(Object castResult) {
listener.onTaskComplete((ImportExportResult) castResult, format);
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
}
protected void onCancelled() {
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled");
}
protected void stop() {
// Whelp
}
@Override
public ImportExportResult call() {
return doInBackground();
}
interface TaskCompleteListener {
void onTaskComplete(ImportExportResult result, DataFormat format);
}
}

View File

@@ -164,7 +164,7 @@ public class LoyaltyCard implements Parcelable {
@Override
public int describeContents() {
return id;
return 0;
}
@NonNull

View File

@@ -1,7 +1,6 @@
package protect.card_locker;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.ColorStateList;
@@ -29,7 +28,6 @@ import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
@@ -61,7 +59,6 @@ import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Currency;
import java.util.Date;
import java.util.List;
import java.util.function.Predicate;
@@ -444,7 +441,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
layout.addView(currentTextview);
final TextInputEditText input = new TextInputEditText(this);
input.setInputType(InputType.TYPE_CLASS_NUMBER);
input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
input.setKeyListener(DigitsKeyListener.getInstance("0123456789,."));
input.setHint(R.string.updateBalanceHint);
@@ -654,10 +651,15 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
cardIdString = loyaltyCard.cardId;
barcodeIdString = loyaltyCard.barcodeId;
binding.cardIdView.setText(loyaltyCard.cardId);
binding.mainImageDescription.setText(loyaltyCard.cardId);
// Display full text on click in case it doesn't fit in a single line
binding.cardIdView.setOnClickListener(v -> {
binding.mainImageDescription.setOnClickListener(v -> {
if (mainImageIndex != 0) {
// Don't show cardId dialog, we're displaying something else
return;
}
TextView cardIdView = new TextView(LoyaltyCardViewActivity.this);
cardIdView.setText(loyaltyCard.cardId);
cardIdView.setTextIsSelectable(true);
@@ -930,7 +932,9 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
if (imageTypes.isEmpty()) {
barcodeRenderTarget.setVisibility(View.GONE);
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
binding.cardIdView.setTextColor(MaterialColors.getColor(binding.cardIdView, com.google.android.material.R.attr.colorOnSurfaceVariant));
binding.mainImageDescription.setTextColor(MaterialColors.getColor(binding.mainImageDescription, com.google.android.material.R.attr.colorOnSurfaceVariant));
binding.mainImageDescription.setText(loyaltyCard.cardId);
return;
}
@@ -939,7 +943,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
if (wantedImageType == ImageType.BARCODE) {
barcodeRenderTarget.setBackgroundColor(Color.WHITE);
binding.mainCardView.setCardBackgroundColor(Color.WHITE);
binding.cardIdView.setTextColor(getResources().getColor(R.color.md_theme_light_onSurfaceVariant));
binding.mainImageDescription.setTextColor(getResources().getColor(R.color.md_theme_light_onSurfaceVariant));
if (waitForResize) {
redrawBarcodeAfterResize(!isFullscreen);
@@ -947,18 +951,23 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
drawBarcode(!isFullscreen);
}
binding.mainImageDescription.setText(loyaltyCard.cardId);
barcodeRenderTarget.setContentDescription(getString(R.string.barcodeImageDescriptionWithType, format.prettyName()));
} else if (wantedImageType == ImageType.IMAGE_FRONT) {
barcodeRenderTarget.setImageBitmap(frontImageBitmap);
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
binding.cardIdView.setTextColor(MaterialColors.getColor(binding.cardIdView, com.google.android.material.R.attr.colorOnSurfaceVariant));
binding.mainImageDescription.setTextColor(MaterialColors.getColor(binding.mainImageDescription, com.google.android.material.R.attr.colorOnSurfaceVariant));
binding.mainImageDescription.setText(getString(R.string.frontImageDescription));
barcodeRenderTarget.setContentDescription(getString(R.string.frontImageDescription));
} else if (wantedImageType == ImageType.IMAGE_BACK) {
barcodeRenderTarget.setImageBitmap(backImageBitmap);
barcodeRenderTarget.setBackgroundColor(Color.TRANSPARENT);
binding.mainCardView.setCardBackgroundColor(Color.TRANSPARENT);
binding.cardIdView.setTextColor(MaterialColors.getColor(binding.cardIdView, com.google.android.material.R.attr.colorOnSurfaceVariant));
binding.mainImageDescription.setTextColor(MaterialColors.getColor(binding.mainImageDescription, com.google.android.material.R.attr.colorOnSurfaceVariant));
binding.mainImageDescription.setText(getString(R.string.backImageDescription));
barcodeRenderTarget.setContentDescription(getString(R.string.backImageDescription));
} else {
throw new IllegalArgumentException("Unknown image type: " + wantedImageType);

View File

@@ -26,20 +26,29 @@ import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.SearchView;
import androidx.core.splashscreen.SplashScreen;
import androidx.recyclerview.widget.RecyclerView;
import androidx.work.Data;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import protect.card_locker.databinding.ContentMainBinding;
import protect.card_locker.databinding.MainActivityBinding;
import protect.card_locker.databinding.SortingOptionBinding;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportWorker;
import protect.card_locker.preferences.SettingsActivity;
public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
@@ -70,6 +79,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
private ActivityResultLauncher<Intent> mSettingsLauncher;
private ActivityResultLauncher<Intent> mImportExportLauncher;
private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback() {
@Override
@@ -303,6 +313,69 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
});
mImportExportLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
// User didn't ask for import or export
if (result.getResultCode() != RESULT_OK) {
return;
}
// Watch for active imports/exports
new Thread(() -> {
WorkManager workManager = WorkManager.getInstance(MainActivity.this);
Snackbar importRunning = Snackbar.make(binding.getRoot(), R.string.importing, Snackbar.LENGTH_INDEFINITE);
while (true) {
try {
List<WorkInfo> activeImports = workManager.getWorkInfosForUniqueWork(ImportExportWorker.ACTION_IMPORT).get();
// We should only have one import running at a time, so it should be safe to always grab the latest
WorkInfo activeImport = activeImports.get(activeImports.size() - 1);
WorkInfo.State importState = activeImport.getState();
if (importState == WorkInfo.State.RUNNING || importState == WorkInfo.State.ENQUEUED || importState == WorkInfo.State.BLOCKED) {
importRunning.show();
} else if (importState == WorkInfo.State.SUCCEEDED) {
importRunning.dismiss();
runOnUiThread(() -> {
Toast.makeText(getApplicationContext(), getString(R.string.importSuccessful), Toast.LENGTH_LONG).show();
updateLoyaltyCardList(true);
});
break;
} else {
importRunning.dismiss();
Data outputData = activeImport.getOutputData();
// FIXME: This dialog will asynchronously be accepted or declined and we don't know the status of it so we can't show the import state
// We want to get back into this function
// A cheap fix would be to keep looping but if the user dismissed the dialog that could mean we're looping forever...
if (Objects.equals(outputData.getString(ImportExportWorker.OUTPUT_ERROR_REASON), ImportExportWorker.ERROR_PASSWORD_REQUIRED)) {
runOnUiThread(() -> ImportExportActivity.retryWithPassword(
MainActivity.this,
DataFormat.valueOf(outputData.getString(ImportExportWorker.INPUT_FORMAT)),
Uri.parse(outputData.getString(ImportExportWorker.INPUT_URI))
));
} else {
runOnUiThread(() -> {
Toast.makeText(getApplicationContext(), getString(R.string.importFailed), Toast.LENGTH_LONG).show();
Toast.makeText(getApplicationContext(), activeImport.getOutputData().getString(ImportExportWorker.OUTPUT_ERROR_REASON), Toast.LENGTH_LONG).show();
Toast.makeText(getApplicationContext(), activeImport.getOutputData().getString(ImportExportWorker.OUTPUT_ERROR_DETAILS), Toast.LENGTH_LONG).show();
});
}
break;
}
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
});
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
@@ -482,7 +555,9 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
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));
@@ -638,7 +713,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
if (id == R.id.action_import_export) {
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
startActivity(i);
mImportExportLauncher.launch(i);
return true;
}

View File

@@ -0,0 +1,63 @@
package protect.card_locker;
import static android.content.Context.NOTIFICATION_SERVICE;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class NotificationHelper {
// Do not change these IDs!
public static final String CHANNEL_IMPORT = "import";
public static final String CHANNEL_EXPORT = "export";
public static final int IMPORT_ID = 100;
public static final int IMPORT_PROGRESS_ID = 101;
public static final int EXPORT_ID = 103;
public static final int EXPORT_PROGRESS_ID = 104;
public static Notification.Builder createNotificationBuilder(@NonNull Context context, @NonNull String channel, @NonNull int icon, @NonNull String title, @Nullable String message) {
Notification.Builder notificationBuilder = new Notification.Builder(context)
.setSmallIcon(icon)
.setTicker(title)
.setContentTitle(title);
if (message != null) {
notificationBuilder.setContentText(message);
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
NotificationChannel notificationChannel = new NotificationChannel(channel, getChannelName(channel), NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(notificationChannel);
notificationBuilder.setChannelId(channel);
}
return notificationBuilder;
}
public static void sendNotification(@NonNull Context context, @NonNull int notificationId, @NonNull Notification notification) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(notificationId, notification);
}
private static String getChannelName(@NonNull String channel) {
switch(channel) {
case CHANNEL_IMPORT:
return "Import";
case CHANNEL_EXPORT:
return "Export";
default:
throw new IllegalArgumentException("Unknown notification channel");
}
}
}

View File

@@ -42,6 +42,16 @@ public class PermissionUtils {
return ContextCompat.checkSelfPermission(activity, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED;
}
/**
* Check if post notifications permission is needed
*
* @param activity
* @return
*/
public static boolean needsPostNotificationsPermission(Activity activity) {
return ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED;
}
/**
* Call onRequestPermissionsResult after storage read permission was granted.
* Mocks a successful grant if a grant is not necessary.
@@ -91,4 +101,37 @@ public class PermissionUtils {
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
}
}
/**
* Call onRequestPermissionsResult after notification permission was granted.
* Mocks a successful grant if a grant is not necessary.
*
* @param activity
* @param requestCode
*/
public static void requestPostNotificationsPermission(CatimaAppCompatActivity activity, int requestCode) {
int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED };
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) {
String[] permissions = new String[0];
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
return;
}
String[] permissions = new String[]{ Manifest.permission.POST_NOTIFICATIONS};
if (needsPostNotificationsPermission(activity)) {
ActivityCompat.requestPermissions(activity, permissions, requestCode);
} else {
// FIXME: This points to onMockedRequestPermissionResult instead of to
// onRequestPermissionResult because onRequestPermissionResult was only introduced in
// Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too.
//
// When minSdk becomes 23, this should point to onRequestPermissionResult directly and
// the activity input variable should be changed from CatimaAppCompatActivity to
// Activity.
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
}
}
}

View File

@@ -65,7 +65,9 @@ import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
@@ -174,16 +176,16 @@ public class Utils {
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++) {
@@ -191,7 +193,7 @@ public class Utils {
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));
@@ -215,7 +217,7 @@ public class Utils {
}
}
}
if (barcodesFromPdfPages.isEmpty()) {
Log.i(TAG, "No barcode found in pdf file");
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
@@ -391,6 +393,7 @@ public class Utils {
static public String formatBalance(Context context, BigDecimal value, Currency currency) {
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setGroupingUsed(false);
if (currency == null) {
numberFormat.setMaximumFractionDigits(0);
@@ -398,6 +401,7 @@ public class Utils {
}
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
currencyFormat.setGroupingUsed(false);
currencyFormat.setCurrency(currency);
currencyFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
currencyFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
@@ -407,6 +411,7 @@ public class Utils {
static public String formatBalanceWithoutCurrencySymbol(BigDecimal value, Currency currency) {
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setGroupingUsed(false);
if (currency == null) {
numberFormat.setMaximumFractionDigits(0);
@@ -419,19 +424,72 @@ public class Utils {
return numberFormat.format(value);
}
private static final double LargestPreciseDouble = (double) (1l << 53);
static{
assert (LargestPreciseDouble + 1.0) == LargestPreciseDouble;
assert (LargestPreciseDouble - 1.0) != LargestPreciseDouble;
}
private static BigDecimal fromParsed(Number parsed){
if(parsed instanceof BigDecimal)
return (BigDecimal) parsed;
final double d = parsed.doubleValue();
if(d >= LargestPreciseDouble)
return new BigDecimal(parsed.longValue());
return new BigDecimal(d);
}
static public BigDecimal parseBalance(String value, Currency currency) throws ParseException {
// This function expects the input string to not have any grouping (thousand separators).
// It will refuse to work otherwise
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setGroupingUsed(false);
if (numberFormat instanceof DecimalFormat) {
((DecimalFormat) numberFormat).setParseBigDecimal(true);
}
if (currency == null) {
numberFormat.setMaximumFractionDigits(0);
} else {
numberFormat.setMinimumFractionDigits(currency.getDefaultFractionDigits());
numberFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
int fractionDigits = currency.getDefaultFractionDigits();
numberFormat.setMinimumFractionDigits(fractionDigits);
numberFormat.setMaximumFractionDigits(fractionDigits);
if (numberFormat instanceof DecimalFormat) {
// If the string contains both thousand separators and decimals separators, fail hard
DecimalFormatSymbols decimalFormatSymbols = ((DecimalFormat) numberFormat).getDecimalFormatSymbols();
char decimalSeparator = decimalFormatSymbols.getDecimalSeparator();
// Translate all non-digits to decimal separators, failing if we find more than 1.
// We loop over the codepoints to make sure eastern arabic numerals are not mistakenly
// treated as a separator.
boolean separatorFound = false;
StringBuilder translatedValue = new StringBuilder();
for (int i = 0; i < value.length();) {
int character = value.codePointAt(i);
if (Character.isDigit(character)) {
translatedValue.append(value.charAt(i));
} else {
if (separatorFound) {
throw new ParseException("Contains multiple separators", i);
}
separatorFound = true;
translatedValue.append(decimalSeparator);
}
i += Character.charCount(character);
}
value = translatedValue.toString();
}
}
Log.d(TAG, numberFormat.parse(value).toString());
return new BigDecimal(numberFormat.parse(value).toString());
return fromParsed(numberFormat.parse(value));
}
static public byte[] bitmapToByteArray(Bitmap bitmap) {

View File

@@ -0,0 +1,212 @@
package protect.card_locker.importexport;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.ForegroundInfo;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import protect.card_locker.DBHelper;
import protect.card_locker.NotificationHelper;
import protect.card_locker.R;
public class ImportExportWorker extends Worker {
private final String TAG = "Catima";
public static final String INPUT_URI = "uri";
public static final String INPUT_ACTION = "action";
public static final String INPUT_FORMAT = "format";
public static final String INPUT_PASSWORD = "password";
public static final String ACTION_IMPORT = "import";
public static final String ACTION_EXPORT = "export";
public static final String OUTPUT_ERROR_REASON = "errorReason";
public static final String ERROR_GENERIC = "errorTypeGeneric";
public static final String ERROR_PASSWORD_REQUIRED = "errorTypePasswordRequired";
public static final String OUTPUT_ERROR_DETAILS = "errorDetails";
public ImportExportWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
Log.e("CATIMA", "Started import/export worker");
Context context = getApplicationContext();
Data inputData = getInputData();
String uriString = inputData.getString(INPUT_URI);
String action = inputData.getString(INPUT_ACTION);
String format = inputData.getString(INPUT_FORMAT);
String password = inputData.getString(INPUT_PASSWORD);
if (action.equals(ACTION_IMPORT)) {
Log.e("CATIMA", "Import requested");
setForegroundAsync(createForegroundInfo(NotificationHelper.CHANNEL_IMPORT, NotificationHelper.IMPORT_PROGRESS_ID, R.string.importing));
ImportExportResult result;
InputStream stream;
try {
stream = context.getContentResolver().openInputStream(Uri.parse(uriString));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
final SQLiteDatabase database = new DBHelper(context).getWritableDatabase();
try {
InputStreamReader writer = new InputStreamReader(stream, StandardCharsets.UTF_8);
result = MultiFormatImporter.importData(context, database, stream, DataFormat.valueOf(format), password.toCharArray());
writer.close();
} catch (IOException e) {
Log.e(TAG, "Unable to import file", e);
NotificationHelper.sendNotification(context, NotificationHelper.IMPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_IMPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.importFailedTitle), e.getLocalizedMessage()).build());
Data failureData = new Data.Builder()
.putString(OUTPUT_ERROR_REASON, ERROR_GENERIC)
.putString(OUTPUT_ERROR_DETAILS, e.getLocalizedMessage())
.putString(INPUT_URI, uriString)
.putString(INPUT_ACTION, action)
.putString(INPUT_FORMAT, format)
.putString(INPUT_PASSWORD, password)
.build();
return Result.failure(failureData);
}
Log.i(TAG, "Import result: " + result);
if (result.resultType() == ImportExportResultType.Success) {
NotificationHelper.sendNotification(context, NotificationHelper.IMPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_IMPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.importSuccessfulTitle), context.getString(R.string.importSuccessful)).build());
return Result.success();
} else if (result.resultType() == ImportExportResultType.BadPassword) {
Log.e(TAG, "Needs password, unhandled for now");
NotificationHelper.sendNotification(context, NotificationHelper.IMPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_IMPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.importing), context.getString(R.string.passwordRequired)).build());
Data failureData = new Data.Builder()
.putString(OUTPUT_ERROR_REASON, ERROR_PASSWORD_REQUIRED)
.putString(OUTPUT_ERROR_DETAILS, result.developerDetails())
.putString(INPUT_URI, uriString)
.putString(INPUT_ACTION, action)
.putString(INPUT_FORMAT, format)
.putString(INPUT_PASSWORD, password)
.build();
return Result.failure(failureData);
} else {
NotificationHelper.sendNotification(context, NotificationHelper.IMPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_IMPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.importFailedTitle), context.getString(R.string.importFailed)).build());
Data failureData = new Data.Builder()
.putString(OUTPUT_ERROR_REASON, ERROR_GENERIC)
.putString(OUTPUT_ERROR_DETAILS, result.developerDetails())
.putString(INPUT_URI, uriString)
.putString(INPUT_ACTION, action)
.putString(INPUT_FORMAT, format)
.putString(INPUT_PASSWORD, password)
.build();
return Result.failure(failureData);
}
} else {
Log.e("CATIMA", "Export requested");
setForegroundAsync(createForegroundInfo(NotificationHelper.CHANNEL_EXPORT, NotificationHelper.EXPORT_PROGRESS_ID, R.string.exporting));
ImportExportResult result;
OutputStream stream;
try {
stream = context.getContentResolver().openOutputStream(Uri.parse(uriString));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
final SQLiteDatabase database = new DBHelper(context).getReadableDatabase();
try {
OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
result = MultiFormatExporter.exportData(context, database, stream, DataFormat.valueOf(format), password.toCharArray());
writer.close();
} catch (IOException e) {
Log.e(TAG, "Unable to export file", e);
NotificationHelper.sendNotification(context, NotificationHelper.EXPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_EXPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.exportFailedTitle), e.getLocalizedMessage()).build());
Data failureData = new Data.Builder()
.putString(OUTPUT_ERROR_REASON, ERROR_GENERIC)
.putString(OUTPUT_ERROR_DETAILS, e.getLocalizedMessage())
.putString(INPUT_URI, uriString)
.putString(INPUT_ACTION, action)
.putString(INPUT_FORMAT, format)
.putString(INPUT_PASSWORD, password)
.build();
return Result.failure(failureData);
}
Log.i(TAG, "Export result: " + result);
if (result.resultType() == ImportExportResultType.Success) {
NotificationHelper.sendNotification(context, NotificationHelper.EXPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_EXPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.exportSuccessfulTitle), context.getString(R.string.exportSuccessful)).build());
return Result.success();
} else {
NotificationHelper.sendNotification(context, NotificationHelper.EXPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_EXPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.exportFailedTitle), context.getString(R.string.exportFailed)).build());
Data failureData = new Data.Builder()
.putString(OUTPUT_ERROR_REASON, ERROR_GENERIC)
.putString(OUTPUT_ERROR_DETAILS, result.developerDetails())
.putString(INPUT_URI, uriString)
.putString(INPUT_ACTION, action)
.putString(INPUT_FORMAT, format)
.putString(INPUT_PASSWORD, password)
.build();
return Result.failure(failureData);
}
}
}
@NonNull
private ForegroundInfo createForegroundInfo(@NonNull String channel, int notificationId, int title) {
Context context = getApplicationContext();
String cancel = context.getString(R.string.cancel);
// This PendingIntent can be used to cancel the worker
PendingIntent intent = WorkManager.getInstance(context)
.createCancelPendingIntent(getId());
Notification.Builder notificationBuilder = NotificationHelper.createNotificationBuilder(context, channel, R.drawable.ic_import_export_white_24dp, context.getString(title), null);
Notification notification = notificationBuilder
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build();
return new ForegroundInfo(notificationId, notification);
}
}

View File

@@ -45,7 +45,7 @@ public class MultiFormatImporter {
break;
}
String error = null;
String error;
if (importer != null) {
File inputFile;
try {

View File

@@ -121,7 +121,7 @@
android:layout_weight="1"/>
<TextView
android:id="@+id/card_id_view"
android:id="@+id/main_image_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_large"

View File

@@ -7,8 +7,8 @@ Heimen Stoffels
Oğuz Ersen
FC (Fay) Stegerman
Katharine Chui
SlavekB
StoyanDimitrov
SlavekB
mondstern
IllusiveMan196
Altonss
@@ -25,27 +25,27 @@ HudobniVolk
Nyatsuki
Giovanni Donisi
Jiri Grönroos
大王叫我来巡山
Samantaz Fox
arno-github
大王叫我来巡山
Cliff Heraldo
Sergio Paredes
Ankit Tiwari
Scrambled777
Milo Ivir
Milan Šalka
mdvhimself
Balázs Meskó
Milo Ivir
Scrambled777
Skrripy
huuhaa
waffshappen
ikanakova
Projjal Moitra
Quentin PAGÈS
ikanakova
waffshappen
ngocanhtve
Ziad OUALHADJ
Robin Liu
Denis Shilin
Robin Liu
Ziad OUALHADJ
ngocanhtve
Alexander Ivanov
Miha Frangež
Viet Nguyen Hoang
@@ -57,3 +57,4 @@ Kim Seohyun
Govind S Nair
Freddo espresso
arshbeerSingh
MisterCosta96

View File

@@ -297,4 +297,9 @@
<string name="spend">Utratit</string>
<string name="receive">Obdržet</string>
<string name="amountParsingFailed">Neplatné množství</string>
<string name="addFromPdfFile">Vybrat soubor PDF</string>
<string name="errorReadingFile">Soubor nelze přečíst</string>
<string name="pageWithNumber">Stránka <xliff:g>%d</xliff:g></string>
<string name="multipleBarcodesFoundPleaseChooseOne">Který z nalezených čárových kódů chcete použít?</string>
<string name="failedLaunchingFileManager">Nelze nalézt podporovaný správce souborů</string>
</resources>

View File

@@ -14,13 +14,13 @@
<string name="sendLabel">Αποστολή…</string>
<string name="editCardTitle">Επεξεργασία Κάρτας</string>
<string name="addCardTitle">Προσθήκη Κάρτας</string>
<string name="scanCardBarcode">Σαρώστε τον γραμμοκώδικα (bardcode)</string>
<string name="scanCardBarcode">Σαρώστε τον γραμμωτό κώδικα</string>
<string name="cardShortcut">Συντόμευση Κάρτας</string>
<string name="noCardsMessage">Προσθέστε μία κάρτα πρώτα</string>
<string name="noCardExistsError">Δεν ήταν δυνατό να εντοπιστεί η κάρτα</string>
<string name="importExport">Εισαγωγή/Εξαγωγή</string>
<string name="exportName">Εξαγωγή</string>
<string name="importExportHelp">Τα αντίγραφα ασφαλείας, σας επιτρέπουν να τα εισάγετε σε άλλη συσκευή.</string>
<string name="importExportHelp">Τα αντίγραφα ασφαλείας σας επιτρέπουν να τα εισάγετε σε άλλη συσκευή.</string>
<string name="importSuccessfulTitle">Εισήχθησαν</string>
<string name="importFailedTitle">Εισαγωγή ανεπιτυχής</string>
<string name="importFailed">Δεν ήταν δυνατή η εισαγωγή</string>
@@ -39,7 +39,7 @@
<string name="app_license">Άδεια χρήσης υπό GPLv3+</string>
<string name="about_title_fmt">Σχετικά με <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Έκδοση: <xliff:g id="version">%s</xliff:g></string>
<string name="selectBarcodeTitle">Επιλέξτε Barcode</string>
<string name="selectBarcodeTitle">Επιλέξτε γραμμωτό κώδικα</string>
<string name="thumbnailDescription">Μικρογραφία</string>
<string name="settings">Ρυθμίσεις</string>
<string name="settings_dark_theme">Σκοτεινό</string>
@@ -64,7 +64,7 @@
<string name="exportOptionExplanation">Τα δεδομένα θα μεταφερθούν σε τοποθεσία της επιλογής σας.</string>
<string name="settings_theme">Θέμα</string>
<string name="groupsList">Ομάδες: <xliff:g>%s</xliff:g></string>
<string name="barcodeId">Τιμή γραμμοκώδικα</string>
<string name="barcodeId">Τιμή γραμμωτού κώδικα</string>
<string name="sort">Ταξινόμηση</string>
<string name="deleteConfirmationGroup">Διαγραφή ομάδας;</string>
<string name="moveDown">Προχώρα κάτω</string>
@@ -91,17 +91,17 @@
<string name="importLoyaltyCardKeychainMessage">Επιλέξτε την <i>LoyaltyCardKeychain.csv</i> εξαγωγή από το Loyalty Card Keychain για εισαγωγή.
\nΔημιουργήστε το από το μενού Εισαγωγής/Εξαγωγής στο Loyalty Card Keychain επιλέγοντας Εξαγωγή.</string>
<string name="importFidme">Εισαγωγή από FidMe</string>
<string name="importFidmeMessage">Επιλέξτε την <i>fidme-export-request-xxxxxx.zip</i> εξαγωγή από το FidMe για εισαγωγή και επιλέξτε χειροκίνητα τους τύπους γραμμοκώδικα μετέπειτα.
<string name="importFidmeMessage">Επιλέξτε την <i>fidme-export-request-xxxxxx.zip</i> εξαγωγή από το FidMe για εισαγωγή και επιλέξτε χειροκίνητα τους τύπους γραμμωτού κώδικα μετέπειτα.
\nΔημιουργήστε το από το FidMe προφίλ επιλέγοντας Προστασία Δεδομένων και διαλέγοντας εξαγωγή δεδομένων.</string>
<string name="setBarcodeId">Επιλέξτε τιμή γραμμοκώδικα</string>
<string name="wrongValueForBarcodeType">Η τιμή δεν είναι έγκυρη για αυτού του τύπου γραμμοκώδικα</string>
<string name="setBarcodeId">Επιλέξτε τιμή γραμμωτού κώδικα</string>
<string name="wrongValueForBarcodeType">Η τιμή δεν είναι έγκυρη για τον επιλεγμένο γραμμωτό κώδικα</string>
<string name="setBackImage">Επιλογή οπίσθιας εικόνας</string>
<string name="removeImage">Αφαίρεση εικόνας</string>
<string name="takePhoto">Τραβήξτε μια φωτογραφία</string>
<string name="updateBarcodeQuestionText">Αλλάξατε τον κωδικό. Θέλετε να ενημερώσετε και τον γραμμοκώδικα στην ίδια τιμή;</string>
<string name="updateBarcodeQuestionText">Αλλάξατε τον κωδικό. Θέλετε να ενημερώσετε και τον γραμμωτό κώδικα στην ίδια τιμή;</string>
<string name="options">Επιλογές</string>
<string name="noGroupCards">Αυτή η ομάδα είναι άδεια</string>
<string name="settings_display_barcode_max_brightness">Επιπλέον φωτισμός γραμμοκώδικα</string>
<string name="settings_display_barcode_max_brightness">Επιπλέον φωτισμός γραμμωτού κώδικα</string>
<string name="group_name_is_empty">Το όνομα της ομάδας δεν πρέπει να είναι κενό</string>
<string name="group_edit">Επεξεργασία ομάδας</string>
<string name="star">Προσθήκη στα αγαπημένα</string>
@@ -114,7 +114,7 @@
<string name="currency">Νόμισμα</string>
<string name="privacy_policy">Πολιτική απορρήτου</string>
<string name="chooseImportType">Εισαγωγή δεδομένων από</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
<string name="app_loyalty_card_keychain">Lοyalty Card Keychain</string>
<string name="privacy_policy_popup_text">Σημείωμα πολιτικής απορρήτου ( υποχρεωτικό σε κάποια \"μαγαζιά\" εφαρμογών)
\n
\nΜΗΔΕΝΙΚΆ ΔΕΔΟΜΈΝΑ ΣΥΛΛΈΓΟΝΤΑΙ, ο οποιοσδήποτε μπορεί να το επιβεβαιώσει μιας και η εφαρμογή είναι ελεύθερο λογισμικό.</string>
@@ -131,21 +131,21 @@
<string name="setFrontImage">Επιλογή μπροστινής εικόνας</string>
<string name="importVoucherVaultMessage">Επιλέξτε την <i>vouchervault.json</i> εξαγωγή από το Voucher Vault για εισαγωγή.
\nΔημιουργήστε το επιλέγοντας Εξαγωγή στο Voucher Vault.</string>
<string name="unsupportedBarcodeType">Ο τύπος γραμμοκώδικα δεν γίνεται να εμφανιστεί ακόμα. Μπορεί να υποστηρίζεται σε μια μελλοντική έκδοση της εφαρμογής.</string>
<string name="unsupportedBarcodeType">Ο τύπος γραμμωτού κώδικα δεν μπορεί να εμφανιστεί ακόμα. Μπορεί να υποστηρίζεται σε μια μελλοντική έκδοση της εφαρμογής.</string>
<string name="frontImageDescription">Μπροστινή εικόνα</string>
<string name="photos">Φωτογραφίες</string>
<string name="backImageDescription">Οπίσθια εικόνα</string>
<string name="updateBarcodeQuestionTitle">Ενημέρωση τιμής γραμμοκώδικα;</string>
<string name="updateBarcodeQuestionTitle">Ενημέρωση τιμής γραμμωτού κώδικα;</string>
<string name="passwordRequired">Παρακαλώ εισάγετε τον κωδικό</string>
<string name="sort_by_most_recently_used">Χρησιμοποιήθηκαν πρόσφατα</string>
<string name="shortcutSelectCard">Επιλέξτε μία κάρτα</string>
<string name="barcodeImageDescriptionWithType">Εικόνα <xliff:g>%s</xliff:g> γραμμοκώδικα</string>
<string name="barcodeImageDescriptionWithType">Εικόνα <xliff:g>%s</xliff:g> γραμμωτού κώδικα</string>
<string name="app_libraries">Ελεύθερες βιβλιοθήκες τρίτων: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="license">Άδεια</string>
<string name="include_if_asking_support">Αν θέλετε να ζητήσετε υποστήριξη, συμπεριλάβετε τις ακόλουθες πληροφορίες:</string>
<string name="importSuccessful">Δεδομένα εισήχθησαν</string>
<string name="moveUp">Προχώρα πάνω</string>
<string name="barcodeType">Τύπος γραμμοκώδικα</string>
<string name="barcodeType">Τύπος γραμμωτού κώδικα</string>
<string name="app_resources">Ελεύθερες πηγές τρίτων: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectColor">Επιλογή χρώματος</string>
<string name="setIcon">Ορισμός εικονιδίου</string>
@@ -162,7 +162,7 @@
<string name="exportSuccessful">Δεδομένα εξήχθησαν</string>
<string name="settings_disable_lockscreen_while_viewing_card">Αποτροπή κλειδώματος οθόνης</string>
<string name="failedLaunchingPhotoPicker">Δεν βρέθηκε υποστηριζόμενη εφαρμογή συλλογής</string>
<string name="noBarcode">Χωρίς barcode</string>
<string name="noBarcode">Χωρίς γραμμωτό κώδικα</string>
<string name="starImage">Αγαπημένο αστέρι</string>
<string name="balanceSentence">Υπόλοιπο: <xliff:g>%s</xliff:g></string>
<string name="failedParsingImportUriError">Δεν ήταν δυνατή η ανάλυση του εισαγόμενου URL</string>
@@ -173,7 +173,7 @@
<string name="app_copyright_old">Βασισμένο στο Loyalty Card Keychain
\nπνευματικά δικαιώματα © 2016-2020 Branden Archer</string>
<string name="settings_follow_system_orientation">Ακολούθηση συστήματος</string>
<string name="settings_card_orientation">Προσανατολισμός γραμμοκώδικα</string>
<string name="settings_card_orientation">Προσανατολισμός γραμμωτού κώδικα</string>
<string name="settings_portrait_orientation">Πορτραίτο</string>
<string name="settings_landscape_orientation">Οριζόντια</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Πνευματικά δικαιώματα © 2019-<xliff:g>%d</xliff:g> Sylvia van Os</string>
@@ -189,15 +189,15 @@
<string name="leaveWithoutSaveConfirmation">Έξοδος χωρίς αποθήκευση;</string>
<string name="expiryStateSentenceExpired">Έληξε: <xliff:g>%s</xliff:g></string>
<string name="card">Κάρτα</string>
<string name="editBarcode">Επεξεργασία γραμμοκώδικα</string>
<string name="editBarcode">Επεξεργασία γραμμωτού κώδικα</string>
<string name="chooseExpiryDate">Επιλέξτε ημερομηνία λήξης</string>
<string name="moveBarcodeToTopOfScreen">Μετακίνηση του γραμμοκώδικα στο πάνω μέρος της οθόνης</string>
<string name="noBarcodeFound">Δεν βρέθηκε γραμμοκώδικας</string>
<string name="moveBarcodeToTopOfScreen">Μετακίνηση του γραμμωτού κώδικα στο πάνω μέρος της οθόνης</string>
<string name="noBarcodeFound">Δεν βρέθηκε γραμμωτός κώδικα</string>
<string name="balance">Υπόλοιπο</string>
<string name="importCatima">Εισαγωγή από Catima</string>
<string name="importStocard">Εισαγωγή από Stocard</string>
<string name="importVoucherVault">Εισαγωγή από Voucher Vault</string>
<string name="sameAsCardId">Ίδιος κωδικός</string>
<string name="sameAsCardId">Όπως ο κωδικός</string>
<string name="exportPassword">Προσθέστε έναν κωδικό για προστασία της εξαγωγής (προαιρετικά)</string>
<string name="exportPasswordHint">Εισαγωγή κωδικού</string>
<string name="failedGeneratingShareURL">Δεν ήταν δυνατή η δημιουργία κοινοποιούμενου URL. Παρακαλώ αναφέρετε το.</string>
@@ -207,7 +207,7 @@
<string name="settings_oled_dark">Απόλυτο μαύρο φόντο για το μαύρο θέμα</string>
<string name="settings_system_locale">Σύστημα</string>
<string name="settings_theme_color">Χρώμα θέματος</string>
<string name="settings_catima_theme">Catima</string>
<string name="settings_catima_theme">Κάτιμα</string>
<string name="settings_pink_theme">Ροζ</string>
<string name="settings_magenta_theme">Φούξια</string>
<string name="settings_violet_theme">Βιολετί</string>
@@ -229,7 +229,7 @@
<string name="nextCard">Επόμενη</string>
<string name="updateBalance">Ενημέρωση υπολοίπου</string>
<string name="barcodeLongPressMessage">Μόνο εικόνες μπορούν να ανοιχτούν στην εφαρμογή φωτογραφιών</string>
<string name="noCameraPermissionDirectToSystemSetting">Για να σκανάρετε γραμμοκώδικες, θα χρειαστεί πρόσβαση στην κάμερα από το Catima. Πατήστε εδώ για να δώσετε πρόσβαση.</string>
<string name="noCameraPermissionDirectToSystemSetting">Για να σκανάρετε γραμμωτούς κώδικες, θα χρειαστεί πρόσβαση στην κάμερα από το Catima. Πατήστε εδώ για να δώσετε πρόσβαση.</string>
<plurals name="viewArchivedCardsWithCount">
<item quantity="one">Προβολή αρχείου (<xliff:g>%1$d</xliff:g> κάρτας)</item>
<item quantity="other">Προβολή αρχείου (<xliff:g>%1$d</xliff:g> καρτών)</item>
@@ -241,19 +241,19 @@
<string name="failedToOpenUrl">Εγκαταστήστε έναν περιηγητή πρώτα</string>
<string name="welcome">Καλώς ήρθατε στο Catima</string>
<string name="settings_disable_lockscreen_while_viewing_card_summary">Απενεργοποιεί το κλείδωμα οθόνης κατά την προβολή μιας κάρτας</string>
<string name="settings_display_barcode_max_brightness_summary">Απαραίτητο για να δουλέψει σε κάποια σκάνερ</string>
<string name="settings_display_barcode_max_brightness_summary">Απαραίτητο για να δουλέψει σε κάποιους σαρωτές</string>
<string name="cameraPermissionRequired">Δικαίωμα πρόσβασης στην κάμερα απαραίτητο γι\' αυτή την ενέργεια…</string>
<string name="settings_allow_content_provider_read_title">Να επιτρέπεται σε άλλες εφαρμογές να έχουν πρόσβαση στα δεδομένα μου</string>
<string name="app_copyright_short">Πνευματικά δικαιώματα © Sylvia van Os και συνεργάτες</string>
<string name="height">Ύψος:</string>
<string name="switchToFrontImage">Μετάβαση στην μπροστινή εικόνα</string>
<string name="switchToBackImage">Μετάβαση στην πίσω εικόνα</string>
<string name="switchToBarcode">Μετάβαση σε barcode</string>
<string name="switchToBackImage">Μετάβαση στην οπίσθια εικόνα</string>
<string name="switchToBarcode">Μετάβαση σε γραμμωτό κώδικα</string>
<string name="validFromSentence">Ισχύει από: <xliff:g>%s</xliff:g></string>
<string name="permissionReadCardsLabel">Διαβάστε τις κάρτες Catima</string>
<string name="openBackImageInGalleryApp">Ανοίξτε την πίσω εικόνα στην εφαρμογή γκαλερί</string>
<string name="openBackImageInGalleryApp">Ανοίξτε την οπίσθια εικόνα στην εφαρμογή γκαλερί</string>
<string name="permissionReadCardsDescription">Διάβασε τις Κάρτες σου Catima και όλες τους τις λεπτομέρειες, συμπεριλαμβανομένων των σημειώσεων και των εικόνων</string>
<string name="donate">Δωρίζω</string>
<string name="donate">Δωρεά</string>
<string name="icon_header_click_text">Πατήστε παρατεταμένα για επεξεργασία του εικονιδίου</string>
<string name="openFrontImageInGalleryApp">Ανοίξτε την μπροστινή εικόνα στην εφαρμογή γκαλερί</string>
<string name="storageReadPermissionRequired">Δικαίωμα ανάγνωσης του χώρου αποθήκευσης απαραίτητο για αυτήν την ενέργεια…</string>
@@ -261,38 +261,38 @@
<string name="validFromDate">Ισχύει από</string>
<string name="anyDate">Οποιαδήποτε ημερομηνία</string>
<string name="chooseValidFromDate">Επιλέξτε έγκυρη ημερομηνία από</string>
<string name="setBarcodeHeight">Ρυθμίστε το ύψος του barcode</string>
<string name="setBarcodeHeight">Ρυθμίστε το ύψος του γραμμωτού κώδικα</string>
<string name="show_name_below_image_thumbnail">Εμφάνιση ονόματος κάτω από το εικονίδιο</string>
<string name="app_name">Catima</string>
<string name="balanceParsingFailed">Μη έγκυρο υπόλοιπο</string>
<string name="continue_">Συνέχεια</string>
<string name="settings_category_title_privacy">Απόρρητο</string>
<string name="addFromPdfFile">Επιλογή PDF αρχείου</string>
<string name="add_manually_warning_message">Για ορισμένα καταστήματα, ο γραμμοκώδικας διαφέρει από τον αριθμό που αναγράφεται πάνω στην κάρτα. Εξαιτίας αυτού, η εισαγωγή γραμμοκώδικα χειροκίνητα ενδέχεται να μην λειτουργεί πάντα. Προτείνεται να σκανάρετε τον γραμμοκώδικα με χρήση της κάμερας. Επιθυμείτε να συνεχίσετε ;</string>
<string name="addFromPdfFile">Επιλογή αρχείου PDF</string>
<string name="add_manually_warning_message">Για ορισμένα καταστήματα, ο γραμμωτός κώδικας διαφέρει από τον αριθμό που αναγράφεται πάνω στην κάρτα. Εξαιτίας αυτού, η εισαγωγή γραμμωτού κώδικα χειροκίνητα ενδέχεται να μην λειτουργεί πάντα. Προτείνεται να σκανάρετε τον γραμμωτό κώδικα με χρήση της κάμερας. Επιθυμείτε να συνεχίσετε ;</string>
<string name="amountParsingFailed">Μη έγκυρο ποσό</string>
<string name="show_balance">Προβολή υπολοίπου</string>
<string name="action_display_options">Επιλογές Εμφάνισης</string>
<string name="action_display_options">Επιλογές εμφάνισης</string>
<string name="settings_category_title_cards">Κάρτες</string>
<string name="settings_category_title_general">Γενικά</string>
<string name="show_archived_cards">Προβολή αρχειοθετημένων καρτών</string>
<string name="addWithoutBarcode">Προσθήκη κάρτας χωρίς γραμμοκώδικα</string>
<string name="addWithoutBarcode">Προσθήκη κάρτας χωρίς γραμμωτό κώδικα</string>
<string name="view_online">Προβολή διαδικτυακά</string>
<string name="errorReadingFile">Δεν ήταν δυνατή η ανάγνωση του αρχείου</string>
<string name="failedLaunchingFileManager">Δεν ήταν δυνατή η εύρεση υποστηριζόμενης διαχείρισης αρχείων</string>
<string name="failedLaunchingFileManager">Δεν ήταν δυνατή η εύρεση υποστηριζόμενου διαχειριστή αρχείων</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Ποιους από τους γραμμωτούς κώδικες που βρέθηκαν θέλετε να χρησιμοποιήσετε;</string>
<string name="pageWithNumber">Σελίδα <xliff:g>%d</xliff:g></string>
<string name="spend">Δαπάνησε</string>
<string name="receive">Λαμβάνω</string>
<string name="receive">Λήψη</string>
<string name="settings_keep_screen_on_summary">Απενεργοποιεί το χρονικό όριο της οθόνης κατά την προβολή μιας κάρτας</string>
<string name="settings_oled_dark_summary">Μειώνει τη χρήση της μπαταρίας στις οθόνες OLED</string>
<string name="show_note">Εμφάνιση σημείωσης</string>
<string name="action_more_options">Περισσότερες επιλογές</string>
<string name="enter_card_id">Εισαγάγετε τον αριθμό ταυτότητας ή το κείμενο στην κάρτα σας</string>
<string name="show_validity">Δείξτε εγκυρότητα</string>
<string name="enter_card_id">Εισαγάγετε τον κωδικό αριθμό ή το κείμενο στην κάρτα σας</string>
<string name="show_validity">Εμφάνιση εγκυρότητας</string>
<string name="add_a_card_in_a_different_way">Προσθέστε μια κάρτα με διαφορετικό τρόπο</string>
<string name="card_id_must_not_be_empty">Ο αναγνωριστικός αριθμός της κάρτας δεν πρέπει να είναι κενός</string>
<string name="card_id_must_not_be_empty">Ο κωδικός αριθμός της κάρτας δεν πρέπει να είναι κενός</string>
<string name="settings_allow_content_provider_read_summary">Οι εφαρμογές θα πρέπει ακόμα να ζητήσουν άδεια για να τους δοθεί πρόσβαση</string>
<string name="field_must_not_be_empty">Το πεδίο δεν πρέπει να είναι κενό</string>
<string name="manually_enter_barcode_instructions">Εισαγάγετε τον αριθμό ταυτότητας ή το κείμενο στην κάρτα σας και πατήστε τον γραμμωτό κώδικα που μοιάζει με αυτόν της κάρτας σας.</string>
<string name="manually_enter_barcode_instructions">Εισαγάγετε τον κωδικό αριθμό ή το κείμενο στην κάρτα σας και πατήστε τον γραμμωτό κώδικα που μοιάζει με αυτόν της κάρτας σας.</string>
<string name="add_manually_warning_title">Συνιστάται η σάρωση</string>
</resources>

View File

@@ -4,7 +4,7 @@
<string name="noGiftCards">Haz clic en el botón + para añadir una tarjeta, o importa desde el menú ⋮.</string>
<string name="storeName">Nombre</string>
<string name="note">Nota</string>
<string name="cardId">ID. de tarjeta</string>
<string name="cardId">ID de tarjeta</string>
<string name="cancel">Cancelar</string>
<string name="save">Guardar</string>
<string name="edit">Editar</string>
@@ -20,7 +20,7 @@
<string name="noCardExistsError">No se ha podido encontrar esa tarjeta</string>
<string name="importExport">Importar/Exportar</string>
<string name="exportName">Exportar</string>
<string name="importExportHelp">Hacer una copia de seguridad de los datos permite trasladarlos a otro dispositivo.</string>
<string name="importExportHelp">Respaldar tus datos permite trasladarlos a otro dispositivo.</string>
<string name="importSuccessfulTitle">Importado</string>
<string name="importFailedTitle">Falló la importación</string>
<string name="importFailed">No se ha podido realizar la importación</string>
@@ -33,9 +33,9 @@
<string name="importOptionFilesystemExplanation">Elegir un archivo concreto del sistema de archivos.</string>
<string name="importOptionFilesystemButton">Desde el sistema de archivos</string>
<string name="importOptionApplicationTitle">Utilizar otra aplicación</string>
<string name="importOptionApplicationExplanation">Use una aplicación o su gestor de archivos favoritos para abrir un archivo.</string>
<string name="importOptionApplicationExplanation">Usa cualquier aplicación o tu administrador de archivos favorito para abrir un archivo.</string>
<string name="importOptionApplicationButton">Utilizar otra aplicación</string>
<string name="about">Acerca de</string>
<string name="about">Información</string>
<string name="app_license">Programa libre con «copyleft», disponible en virtud de la licencia GPLv3+</string>
<string name="about_title_fmt">Acerca de <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versión: <xliff:g id="version">%s</xliff:g></string>
@@ -56,16 +56,16 @@
<string name="barcodeType">Tipo de código de barras</string>
<string name="noMatchingGiftCards">Sin resultados. Intente cambiar su búsqueda.</string>
<string name="action_search">Buscar</string>
<string name="noGroups">Pulse en el botón «+» para añadir grupos de categorización.</string>
<string name="noGroups">Pulse en el botón + para añadir grupos de categorización.</string>
<string name="starImage">Estrella favorita</string>
<string name="thumbnailDescription">Miniatura</string>
<string name="selectBarcodeTitle">Seleccionar el código de barras</string>
<string name="unstar">Eliminar de favoritos</string>
<string name="noBarcode">Sin código de barras</string>
<string name="enter_group_name">Introducir nombre del grupo</string>
<string name="enter_group_name">Escribe el nombre del grupo</string>
<string name="groups">Grupos</string>
<string name="groupsList">Grupos: <xliff:g>%s</xliff:g></string>
<string name="addManually">Introducir manualmente el código de barras</string>
<string name="addManually">Escribe manualmente el código de barras</string>
<string name="leaveWithoutSaveConfirmation">¿Quiere abandonar sin guardar\?</string>
<string name="leaveWithoutSaveTitle">Salir</string>
<string name="moveDown">Bajar</string>
@@ -115,7 +115,7 @@
<string name="removeImage">Quitar imagen</string>
<string name="setFrontImage">Establecer imagen frontal</string>
<string name="photos">Fotos</string>
<string name="backImageDescription">Imagen del reverso</string>
<string name="backImageDescription">Imagen de atrás</string>
<string name="frontImageDescription">Imagen frontal</string>
<string name="wrongValueForBarcodeType">El valor no es válido para el tipo de código de barras seleccionado</string>
<string name="unsupportedBarcodeType">Este tipo de código de barras todavía no se puede visualizar. Es posible que se admita en una futura versión de la aplicación.</string>
@@ -184,10 +184,10 @@
<string name="settings_portrait_orientation">Formato vertical</string>
<string name="group_edit">Editar grupo</string>
<string name="group_updated">Grupo actualizado</string>
<string name="noGiftCardsGroup">Cree algunas tarjetas y luego asígnelas al grupo aquí.</string>
<string name="settings_follow_system_orientation">Seguir el sistema</string>
<string name="noGiftCardsGroup">Crea algunas tarjetas y luego asígnelas al grupo aquí.</string>
<string name="settings_follow_system_orientation">Segue el sistema</string>
<string name="settings_lock_on_opening_orientation">Bloqueo a la orientación utilizada al abrir la tarjeta</string>
<string name="sort_by_most_recently_used">Lo más reciente que se ha utilizado</string>
<string name="sort_by_most_recently_used">Lo más usado recientemente</string>
<string name="sort_by_expiry">Caducidad</string>
<string name="version_history">Historial de versiones</string>
<string name="help_translate_this_app">Ayuda a traducir esta aplicación</string>
@@ -199,7 +199,7 @@
<string name="sort_by_name">Nombre</string>
<string name="license">Licencia</string>
<string name="source_repository">Repositorio de fuente</string>
<string name="on_github">En GitHub</string>
<string name="on_github">en GitHub</string>
<string name="on_google_play">en Google Play</string>
<string name="report_error">Informar de un error</string>
<string name="translate_platform">en Weblate</string>
@@ -230,7 +230,7 @@
<string name="archived">Tarjeta archivada</string>
<string name="unarchived">Tarjeta desarchivada</string>
<string name="exportPassword">Establezca una contraseña para proteger su exportación (opcional)</string>
<string name="failedLaunchingPhotoPicker">No se ha podido encontrar una aplicación de galería compatible</string>
<string name="failedLaunchingPhotoPicker">No se ha encontró una aplicación de galería compatible</string>
<plurals name="groupCardCountWithArchived">
<item quantity="one"><xliff:g>%1$d</xliff:g> tarjeta (archivada)<xliff:g id="archivedCount">%2$d</xliff:g></item>
<item quantity="many"><xliff:g>%1$d</xliff:g> tarjetas (archivadas)<xliff:g id="archivedCount">%2$d</xliff:g></item>
@@ -239,7 +239,7 @@
<string name="starred">Estrellado</string>
<string name="failedToRetrieveImageFile">No se pudo recuperar el archivo de imagen</string>
<string name="barcodeLongPressMessage">Solo se pueden abrir imágenes en la aplicación de galería</string>
<string name="updateBalanceTitle">¿Cuánto ha gastado o recibido?</string>
<string name="updateBalanceTitle">¿Cuánto has gastado o recibido?</string>
<string name="currentBalanceSentence">Saldo actual: <xliff:g>%s</xliff:g></string>
<string name="noCameraPermissionDirectToSystemSetting">Para escanear códigos de barras, Catima necesitará acceso a su cámara. Toque aquí para cambiar la configuración de sus permisos.</string>
<string name="updateBalanceHint">Introduzca el importe</string>

View File

@@ -252,7 +252,7 @@
<string name="shortcutSelectCard">Odaberi karticu</string>
<string name="previousCard">Prethodna</string>
<string name="nextCard">Sljedeća</string>
<string name="updateBalanceTitle">Koliko si potrošio/la?</string>
<string name="updateBalanceTitle">Koliko si potrošio/la ili primio/la?</string>
<string name="updateBalanceHint">Upiši iznos</string>
<string name="about_title_fmt">Informaije o <xliff:g id="app_name">%s</xliff:g></string>
<string name="failedToOpenUrl">Najprije instaliraj web preglednik</string>
@@ -291,4 +291,15 @@
<string name="settings_keep_screen_on_summary">Deaktivira isključivanje ekrana tijekom prikaza kartice</string>
<string name="app_name">Catima</string>
<string name="settings_follow_sensor_orientation">Uvijek rotiraj (ignorira sistemske postavke)</string>
<string name="continue_">Nastavi</string>
<string name="add_manually_warning_message">Za neke trgovine se vrijednost barkoda razlikuje od broja napisanog na kartici. Zbog toga ručni unos barkoda možda neće uvijek funkcionirati. Preporučuje se snimanje barkoda pomoću kamere. Želiš li svejedno nastaviti?</string>
<string name="add_manually_warning_title">Preporučuje se snimanje</string>
<string name="addFromPdfFile">Odaberi PDF datoteku</string>
<string name="errorReadingFile">Neuspjelo čitanje datoteke</string>
<string name="failedLaunchingFileManager">Nije pronađen podržani upravljač datoteka</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Koji pronađeni barkod želiš koristiti?</string>
<string name="pageWithNumber">Stranica <xliff:g>%d</xliff:g></string>
<string name="spend">Potroši</string>
<string name="receive">Primi</string>
<string name="amountParsingFailed">Neispravan iznos</string>
</resources>

View File

@@ -297,4 +297,9 @@
<string name="spend">Cheltuie</string>
<string name="receive">Primește</string>
<string name="amountParsingFailed">Sumă nevalidă</string>
<string name="addFromPdfFile">Selectați un fișier PDF</string>
<string name="errorReadingFile">Nu am putut citi fișierul</string>
<string name="failedLaunchingFileManager">Nu s-a găsit un manager de fișiere suportat</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Pe care dintre codurile de bare găsite dorești să-l folosești?</string>
<string name="pageWithNumber">Pagina <xliff:g>%d</xliff:g></string>
</resources>

View File

@@ -276,4 +276,10 @@
<string name="settings_display_barcode_max_brightness_summary">一些條碼掃描器需要此設定方能運作</string>
<string name="field_must_not_be_empty">欄位不能為空</string>
<string name="settings_follow_sensor_orientation">始終旋轉(忽略系統設定)</string>
<string name="add_manually_warning_title">建議掃描</string>
<string name="failedLaunchingFileManager">找不到支援的檔案管理器</string>
<string name="addFromPdfFile">選擇一個 PDF 檔案</string>
<string name="errorReadingFile">無法讀取此檔案</string>
<string name="receive">接收</string>
<string name="amountParsingFailed">無效的數值</string>
</resources>

View File

@@ -346,4 +346,7 @@
<string name="failedLaunchingFileManager">Could not find a supported file manager</string>
<string name="multipleBarcodesFoundPleaseChooseOne">Which of the found barcodes do you want to use?</string>
<string name="pageWithNumber">Page <xliff:g>%d</xliff:g></string>
<string name="exportStartedCheckNotifications">Export started, check your notifications for the result</string>
<string name="importStartedCheckNotifications">Import started, check your notifications for the result</string>
<string name="postNotificationsPermissionRequired">Permission to show notifications needed for this action…</string>
</resources>

View File

@@ -574,72 +574,6 @@ public class ImportExportTest {
}
}
class TestTaskCompleteListener implements ImportExportTask.TaskCompleteListener {
ImportExportResult result;
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
this.result = result;
}
}
@Test
@LooperMode(LooperMode.Mode.PAUSED)
public void useImportExportTask() throws FileNotFoundException {
final int NUM_CARDS = 10;
final File sdcardDir = Environment.getExternalStorageDirectory();
final File exportFile = new File(sdcardDir, "Catima.csv");
TestHelpers.addLoyaltyCards(mDatabase, NUM_CARDS);
TestTaskCompleteListener listener = new TestTaskCompleteListener();
// Export to the file
final String password = "123456789";
FileOutputStream fileOutputStream = new FileOutputStream(exportFile);
ImportExportTask task = new ImportExportTask(activity, DataFormat.Catima, fileOutputStream, password.toCharArray(), listener);
TaskHandler mTasks = new TaskHandler();
mTasks.executeTask(TaskHandler.TYPE.EXPORT, task);
// Actually run the task to completion
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, false, false, true);
shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(5000));
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
// Check that the listener was executed
assertNotNull(listener.result);
assertEquals(ImportExportResultType.Success, listener.result.resultType());
TestHelpers.getEmptyDb(activity);
// Import everything back from the default location
listener = new TestTaskCompleteListener();
FileInputStream fileStream = new FileInputStream(exportFile);
task = new ImportExportTask(activity, DataFormat.Catima, fileStream, password.toCharArray(), listener);
mTasks.executeTask(TaskHandler.TYPE.IMPORT, task);
// Actually run the task to completion
// I am CONVINCED there must be a better way than to wait on this Queue with a flush.
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, false, false, true);
shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(5000));
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
// Check that the listener was executed
assertNotNull(listener.result);
assertEquals(ImportExportResultType.Success, listener.result.resultType());
assertEquals(NUM_CARDS, DBHelper.getLoyaltyCardCount(mDatabase));
checkLoyaltyCards();
// Clear the database for the next format under test
TestHelpers.getEmptyDb(activity);
}
@Test
public void importWithoutColorsV1() {
InputStream inputStream = getClass().getResourceAsStream("catima_v1_no_colors.csv");

View File

@@ -308,7 +308,7 @@ public class LoyaltyCardViewActivityTest {
final String barcodeId, final String barcodeType,
final Bitmap frontImage, final Bitmap backImage) {
if (mode == ViewMode.VIEW_CARD) {
checkFieldProperties(activity, R.id.card_id_view, View.VISIBLE, cardId, FieldTypeView.TextView);
checkFieldProperties(activity, R.id.main_image_description, View.VISIBLE, cardId, FieldTypeView.TextView);
} else {
int editVisibility = View.VISIBLE;

View File

@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.3.2" apply false
id("com.android.application") version "8.4.1" apply false
id("com.github.spotbugs") version "5.1.4" apply false
}

View File

@@ -4,7 +4,6 @@ Copylefted libre software (GPLv3+) card management app.
[![GitHub Version](https://img.shields.io/github/v/release/TheLastProject/Catima.svg?logo=github&label=GitHub)](https://github.com/TheLastProject/Catima/releases)
[![IzzyOnDroid Version](https://img.shields.io/endpoint?url=https://apt.izzysoft.de/fdroid/api/v1/shield/me.hackerchick.catima)](https://apt.izzysoft.de/fdroid/index/apk/me.hackerchick.catima)
[![F-Droid Version](https://img.shields.io/f-droid/v/me.hackerchick.catima.svg?logo=f-droid&label=F-Droid)](https://f-droid.org/packages/me.hackerchick.catima/)
[![Google Play Store Version](https://img.shields.io/endpoint?color=blue&logo=google-play&url=https%3A%2F%2Fplay.cuzi.workers.dev%2Fplay%3Fi%3Dme.hackerchick.catima%26l%3DGoogle%2520Play%26m%3D%24version)](https://play.google.com/store/apps/details?id=me.hackerchick.catima)
![Android CI](https://github.com/TheLastProject/Catima/workflows/Android%20CI/badge.svg)
@@ -14,8 +13,6 @@ Copylefted libre software (GPLv3+) card management app.
<a href="https://apt.izzysoft.de/fdroid/index/apk/me.hackerchick.catima" target="_blank">
<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" alt="Get it on IzzyOnDroid" height="90"/></a>
<a href="https://f-droid.org/repository/browse/?fdid=me.hackerchick.catima" target="_blank">
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="90"/></a>
<a href="https://play.google.com/store/apps/details?id=me.hackerchick.catima" target="_blank">
<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="90"/></a>
@@ -60,7 +57,7 @@ For FidMe you need to select the barcode type for each entry afterwards.
# Building
Building can either be done through Android Studio (not reproducible!) or the build.sh script in this repository (reproducibly with OpenJDK 17, same way F-Droid builds it). This script can also sign the build.
Building can either be done through Android Studio (not reproducible!) or the build.sh script in this repository (reproducible with OpenJDK 17). This script can also sign the build.
Build without signing:
```

View File

@@ -0,0 +1 @@
- Various fixes and improvements to balance handling

View File

@@ -0,0 +1,2 @@
- Support for creating a card when sharing plain text
- Display image type instead of barcode below images

View File

@@ -13,9 +13,9 @@ Avec cet outil essentiel à emporter au quotidien, vous pouvez remplacer le plas
- Partagez des coupons, des offres exclusives, des codes promotionnels ou des cartes et des codes en utilisant n'importe quelle application.
- Thème sombre et options d'accessibilité pour les utilisateurs malvoyants.
- Fait pour tout le monde par la communauté des logiciels libres.
- Traductions artisanales localisées pour plus de 20 langues.
- Traductions artisanales localisées pour plus de 40 langues.
- Gratis, soutenu par les contributions de la communauté.
- Utilisez, étudiez, modifiez et partagez comme vous le souhaitez ; <i>avec tous</i>.
- Utilisez, étudiez, modifiez et partagez comme vous le souhaitez; <i>avec tous</i>.
- Pas seulement logiciel libre et à code source ouvert <i>logiciel libre à copyleft</i> (GPLv3+) pour la gestion des cartes.
Simplifiez votre vie et vos achats, et ne perdez plus jamais un reçu papier, une carte cadeau de paiement en magasin ou un billet d'avion.

View File

@@ -1,3 +1,3 @@
- Поддержка сканирования PDF-файлов на наличие штрих-кодов
- Поддержка файлов изображений с несколькими штрихкодами
- Поддержка файлов изображений с несколькими штрих-кодами
- Незначительные исправления в интерфейсе