172 Commits

Author SHA1 Message Date
3dac1b205b Update plugin org.jetbrains.kotlin.android to v2.2.0 2025-06-30 02:07:53 +00:00
d31a4c4c69 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.2.0 2025-06-29 02:08:41 +00:00
974f8a6738 Update plugin com.android.library to v8.11.0 2025-06-29 02:02:44 +00:00
934215723b Update plugin com.autonomousapps.dependency-analysis to v2.19.0 2025-06-28 02:02:52 +00:00
546fbb55d6 Update plugin com.android.application to v8.11.0 2025-06-25 02:07:16 +00:00
e1d05ba16f Update dependency gradle to v8.14.2 2025-06-06 02:02:40 +00:00
0ec542ef25 Update plugin com.android.library to v8.10.1 2025-06-01 02:02:15 +00:00
b9203a4659 Update plugin com.autonomousapps.dependency-analysis to v2.18.0 2025-05-31 02:03:20 +00:00
c6ca8ef754 Update plugin com.android.application to v8.10.1 2025-05-29 02:01:28 +00:00
2bc3372db1 Update dependency gradle to v8.14.1 2025-05-23 02:02:41 +00:00
76cbdb7832 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.1.21 2025-05-17 02:08:38 +00:00
87ea1df4cd Update plugin org.jetbrains.kotlin.android to v2.1.21 2025-05-14 02:06:23 +00:00
ceffc79929 Update plugin com.android.library to v8.10.0 2025-05-10 02:04:59 +00:00
0914ebe475 Update plugin com.android.application to v8.10.0 2025-05-07 02:02:27 +00:00
f57bb2b935 Update plugin com.autonomousapps.dependency-analysis to v2.17.0 2025-04-28 02:01:20 +00:00
d896124765 Update plugin com.android.library to v8.9.2 2025-04-27 02:03:32 +00:00
a3e06eea84 Update dependency gradle to v8.14 2025-04-27 02:03:15 +00:00
01d206fb2b Update plugin com.android.application to v8.9.2 2025-04-26 02:03:24 +00:00
d6824843f0 Update dependency com.google.code.gson:gson to v2.13.1 2025-04-24 02:02:17 +00:00
7635266a78 Update plugin com.autonomousapps.dependency-analysis to v2.16.0 2025-04-12 02:02:24 +00:00
3b27d27c02 Update dependency com.google.code.gson:gson to v2.13.0 2025-04-12 02:02:13 +00:00
571f7d60cd Update plugin com.autonomousapps.dependency-analysis to v2.14.0 2025-04-05 02:03:18 +00:00
ce23372932 Update plugin com.android.library to v8.9.1 2025-03-31 02:01:36 +00:00
1024b8f4aa Update dependency androidx.camera:camera-view to v1.4.2 2025-03-30 02:03:18 +00:00
73e2e20398 Update plugin com.android.application to v8.9.1 2025-03-30 02:03:08 +00:00
a95c6951f2 Update dependency androidx.camera:camera-lifecycle to v1.4.2 2025-03-29 02:03:22 +00:00
0966aa5054 Update dependency androidx.camera:camera-camera2 to v1.4.2 2025-03-27 02:02:29 +00:00
8ca461ee0a Update plugin com.autonomousapps.dependency-analysis to v2.13.0 2025-03-23 02:02:32 +00:00
6c7b1e2675 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.1.20 2025-03-22 02:07:21 +00:00
2359fdca81 Update plugin org.jetbrains.kotlin.android to v2.1.20 2025-03-21 02:06:26 +00:00
998ed5abc0 Update plugin com.autonomousapps.dependency-analysis to v2.12.0 2025-03-15 02:02:51 +00:00
e4452160f6 Update plugin com.android.library to v8.9.0 2025-03-09 02:02:18 +00:00
8e67f2d885 Update plugin com.android.application to v8.9.0 2025-03-08 02:03:19 +00:00
e3943b33ff Update dependency gradle to v8.13 2025-03-02 02:03:37 +00:00
e26055afe7 Update plugin com.android.application to v8.8.2 2025-03-01 02:02:59 +00:00
587607f7dc Update dependency com.android.tools:desugar_jdk_libs_nio to v2.1.5 2025-02-26 02:02:34 +00:00
262dc08881 Update plugin com.autonomousapps.dependency-analysis to v2.10.1 2025-02-22 02:02:24 +00:00
cea9c27d57 Update plugin com.android.library to v8.8.1 2025-02-16 02:03:07 +00:00
17a6526b29 Update plugin com.autonomousapps.dependency-analysis to v2.8.2 2025-02-15 02:04:23 +00:00
a931335a2b Update plugin com.android.application to v8.8.1 2025-02-14 02:02:36 +00:00
aafd4f76d6 Update plugin com.autonomousapps.dependency-analysis to v2.8.1 2025-02-08 02:03:27 +00:00
5d464826dc Update dependency com.google.code.gson:gson to v2.12.1 2025-02-02 02:02:30 +00:00
6eddd15d81 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.1.10 2025-02-01 02:06:06 +00:00
6fee471dec Update plugin org.jetbrains.kotlin.android to v2.1.10 2025-01-28 02:05:47 +00:00
38727a239d Update dependency gradle to v8.12.1 2025-01-25 02:04:09 +00:00
83e2cf733e Update plugin com.autonomousapps.dependency-analysis to v2.7.0 2025-01-12 01:02:04 +00:00
3214d772b2 Update plugin com.android.library to v8.8.0 2025-01-11 01:02:20 +00:00
40c3f39c49 Update plugin com.android.application to v8.8.0 2025-01-10 01:01:23 +00:00
6215ffa7b6 Update dependency com.android.tools:desugar_jdk_libs_nio to v2.1.4 2024-12-21 01:03:13 +00:00
bf073da67b Update dependency gradle to v8.12 2024-12-21 01:03:03 +00:00
49fe5ca037 Update dependency androidx.camera:camera-view to v1.4.1 2024-12-15 01:02:20 +00:00
62f854db27 Update plugin com.autonomousapps.dependency-analysis to v2.6.1 2024-12-15 01:02:08 +00:00
aea6fa6c69 Update dependency androidx.camera:camera-lifecycle to v1.4.1 2024-12-14 07:46:37 +00:00
029a1fcde7 Update dependency androidx.camera:camera-camera2 to v1.4.1 2024-12-12 01:01:07 +00:00
92c99bec22 Update plugin com.android.library to v8.7.3 2024-12-08 01:02:45 +00:00
663c1236a4 Update plugin com.autonomousapps.dependency-analysis to v2.6.0 2024-12-07 01:05:26 +00:00
a9582ffb05 Update plugin com.android.application to v8.7.3 2024-12-03 03:27:24 +00:00
b11fb89bd9 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.1.0 2024-11-30 01:05:21 +00:00
1e6bebe853 Update plugin org.jetbrains.kotlin.android to v2.1.0 2024-11-28 01:05:00 +00:00
96904bce79 Update plugin com.autonomousapps.dependency-analysis to v2.5.0 2024-11-23 01:05:21 +00:00
08675a5fc3 Update dependency gradle to v8.11.1 2024-11-21 01:02:54 +00:00
019046474c Update dependency gradle to v8.11 2024-11-12 01:03:36 +00:00
0e63b6a50d Update plugin com.autonomousapps.dependency-analysis to v2.4.2 2024-11-09 01:05:30 +00:00
608ff610d8 Update dependency com.android.tools:desugar_jdk_libs_nio to v2.1.3 2024-11-08 01:02:13 +00:00
a009ce0c15 Merge pull request 'Update dependency androidx.camera:camera-view to v1.4.0' (#64) from renovate/androidx.camera-camera-view-1.x into main
Reviewed-on: #64
2024-11-07 07:56:07 +01:00
1ba95c54a2 Update dependency androidx.camera:camera-view to v1.4.0 2024-11-04 01:01:06 +00:00
51987f54e1 Update dependency androidx.camera:camera-lifecycle to v1.4.0 2024-11-03 01:05:52 +00:00
efb3a436c4 Update plugin com.android.library to v8.7.2 2024-11-03 01:05:40 +00:00
8a22d3b66e Update dependency androidx.camera:camera-camera2 to v1.4.0 2024-11-02 01:06:00 +00:00
c404d498d5 Update plugin com.android.application to v8.7.2 2024-11-01 01:01:30 +00:00
10d35956b3 Update plugin com.autonomousapps.dependency-analysis to v2.3.0 2024-10-25 00:01:26 +00:00
1de639dc46 Update plugin com.android.library to v8.7.1 2024-10-20 00:04:05 +00:00
a297988d33 Update plugin com.autonomousapps.dependency-analysis to v2.2.0 2024-10-19 00:04:49 +00:00
e788d064a5 Update plugin com.android.application to v8.7.1 2024-10-15 00:01:19 +00:00
d6692b5b7c Update plugin org.jetbrains.kotlin.plugin.serialization to v2.0.21 2024-10-12 00:08:35 +00:00
5ac4ba1d43 Update plugin org.jetbrains.kotlin.android to v2.0.21 2024-10-11 00:05:14 +00:00
50573a37c4 Update plugin com.android.application to v8.7.0 2024-10-05 00:06:34 +00:00
2528b7df5d Update plugin com.autonomousapps.dependency-analysis to v2.1.4 2024-10-05 00:06:20 +00:00
e6159f6f42 Update plugin com.autonomousapps.dependency-analysis to v2.1.1 2024-09-28 00:06:00 +00:00
f2982be549 Update dependency gradle to v8.10.2 2024-09-24 00:05:38 +00:00
0c5f7a658f Update plugin com.android.library to v8.6.1 2024-09-21 00:06:47 +00:00
9b90057f85 Update plugin com.android.application to v8.6.1 2024-09-18 00:03:41 +00:00
a9192314de Update plugin com.autonomousapps.dependency-analysis to v2.0.2 2024-09-14 00:05:24 +00:00
aa08418109 Update dependency gradle to v8.10.1 2024-09-10 00:04:35 +00:00
5b239ace83 Update dependency com.android.tools:desugar_jdk_libs_nio to v2.1.2 2024-09-05 00:02:36 +00:00
555cd8ada2 Update plugin com.android.library to v8.6.0 2024-09-01 00:03:21 +00:00
b188313eb9 Update plugin com.autonomousapps.dependency-analysis to v2 2024-09-01 00:03:09 +00:00
74ea62e8cd Update plugin com.android.application to v8.6.0 2024-08-31 00:06:45 +00:00
a59d79aa0e Update dependency com.android.tools:desugar_jdk_libs_nio to v2.1.1 2024-08-29 00:05:45 +00:00
e8021f37dd Update plugin org.jetbrains.kotlin.plugin.serialization to v2.0.20 2024-08-24 00:06:43 +00:00
4e179d8698 Update plugin org.jetbrains.kotlin.android to v2.0.20 2024-08-23 00:05:23 +00:00
a91f8545b0 Update dependency gradle to v8.10 2024-08-15 00:03:43 +00:00
94642047fb Update plugin org.jetbrains.kotlin.android to v2.0.10 2024-08-11 00:06:35 +00:00
e99f615fcd Update plugin com.android.application to v8.5.2 2024-08-11 00:06:26 +00:00
3ba61e87f9 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.0.10 2024-08-10 00:07:09 +00:00
b798200883 Update plugin com.android.library to v8.5.2 2024-08-10 00:06:58 +00:00
2998362518 Update plugin com.autonomousapps.dependency-analysis to v1.33.0 2024-07-28 00:04:38 +00:00
73e3add4a8 Update plugin com.android.library to v8.5.1 2024-07-14 00:05:29 +00:00
5b43db3ebd Update plugin com.android.application to v8.5.1 2024-07-13 00:06:16 +00:00
f9535fe2da Update dependency gradle to v8.9 2024-07-12 00:04:09 +00:00
aa20ec5a06 Version bump 2024-06-28 22:35:59 +02:00
917a01b2ed Updates and fixes 2024-06-28 22:21:02 +02:00
e7f55c2be2 Update plugin com.android.library to v8.5.0 2024-06-17 00:02:36 +00:00
eb765e09e7 Update dependency androidx.camera:camera-view to v1.3.4 2024-06-16 00:03:56 +00:00
1cf4a6bc36 Update plugin com.android.application to v8.5.0 2024-06-16 00:03:49 +00:00
7f0212fc5d Update dependency androidx.camera:camera-lifecycle to v1.3.4 2024-06-15 00:05:06 +00:00
413d8bd7bf Update dependency androidx.camera:camera-camera2 to v1.3.4 2024-06-13 00:02:43 +00:00
931adbb4dd Update dependency gradle to v8.8 2024-06-01 00:06:27 +00:00
e6b2dfe37a Update plugin org.jetbrains.kotlin.android to v2 2024-05-26 00:07:56 +00:00
f73f9b5acf Update plugin org.jetbrains.kotlin.plugin.serialization to v2 2024-05-25 13:50:32 +00:00
ba2d0ac024 Update plugin com.android.library to v8.4.1 2024-05-25 13:47:04 +00:00
f33b4672b0 Update plugin com.autonomousapps.dependency-analysis to v1.32.0 2024-05-25 00:05:43 +00:00
b6b69587fa Update plugin com.android.application to v8.4.1 2024-05-21 00:02:49 +00:00
06a006c0a2 Merge pull request 'Update dependency com.google.code.gson:gson to v2.11.0' (#18) from renovate/com.google.code.gson-gson-2.x into main
Reviewed-on: #18
2024-05-20 10:34:47 +02:00
8ae38f4250 Update dependency com.google.code.gson:gson to v2.11.0 2024-05-20 00:02:26 +00:00
3e1252cc0a Update plugin com.android.library to v8.4.0 2024-05-12 00:11:36 +00:00
e4357a66e0 Update plugin org.jetbrains.kotlin.plugin.serialization to v1.9.24 2024-05-11 00:11:39 +00:00
21f2c0d69f Update plugin org.jetbrains.kotlin.android to v1.9.24 2024-05-08 00:09:57 +00:00
340789989c Update plugin com.android.application to v8.4.0 2024-05-04 00:04:58 +00:00
2428f4e50b Update dependency com.google.android.material:material to v1.12.0 2024-05-03 00:02:26 +00:00
f4c9eddd22 Merge pull request 'Update dependency androidx.camera:camera-view to v1.3.3' (#12) from renovate/androidx.camera-camera-view-1.x into main
Reviewed-on: #12
2024-04-21 02:36:39 +02:00
6a5e971619 Update dependency androidx.camera:camera-view to v1.3.3 2024-04-21 00:04:40 +00:00
5829f18908 Update dependency androidx.camera:camera-lifecycle to v1.3.3 2024-04-20 00:04:38 +00:00
426d94ba81 Update dependency androidx.camera:camera-camera2 to v1.3.3 2024-04-17 19:25:16 +00:00
a58d208d49 Merge pull request 'Update plugin com.android.application to v8.3.2' (#7) from renovate/com.android.application-8.x into main
Reviewed-on: #7
2024-04-13 01:08:03 +02:00
069edaf6a2 Update plugin com.android.application to v8.3.2 2024-04-12 23:07:08 +00:00
d0aa2fbeb9 Merge pull request 'Update plugin com.android.library to v8.3.2' (#8) from renovate/com.android.library-8.x into main
Reviewed-on: #8
2024-04-13 01:05:24 +02:00
17c75f27bc Merge pull request 'Update gradle/wrapper-validation-action action to v3' (#9) from renovate/gradle-wrapper-validation-action-3.x into main
Reviewed-on: #9
2024-04-13 01:05:09 +02:00
1738664f83 Update gradle/wrapper-validation-action action to v3 2024-04-12 23:02:34 +00:00
a5b55fe214 Update plugin com.android.library to v8.3.2 2024-04-11 00:01:50 +00:00
0cd90413d1 Update app/src/main/AndroidManifest.xml 2024-04-06 19:26:33 +02:00
653ee1ccc1 Fix camera crash & autosave 2024-04-06 19:24:59 +02:00
9b6a69e227 Update plugin com.autonomousapps.dependency-analysis to v1.31.0 2024-04-05 00:02:16 +00:00
e40305b680 Updated icon and permissions 2024-03-29 15:49:10 +01:00
668e9d653f Scaled icon down 2024-03-29 13:47:22 +01:00
bcfcb85121 Update README.md 2024-03-29 13:29:40 +01:00
49c650c8a9 Update README.md 2024-03-29 13:29:19 +01:00
b5f52c7e13 Release 1.2a prep 2024-03-29 13:28:30 +01:00
d81922d2c9 File Scanner & Metadata 2024-03-29 13:27:27 +01:00
84b2c2c455 Update README.md 2024-03-25 07:13:58 +01:00
841c3dea24 Update README.md 2024-03-24 15:45:28 +01:00
6596f347a1 Updated icon 2024-03-24 15:41:26 +01:00
a2dd009533 Fixed versioning 2024-03-24 13:29:13 +01:00
4104104b16 minor release 2024-03-24 13:17:09 +01:00
0d838b6209 Removed missing encoder 2024-03-24 13:11:15 +01:00
1d41671fb6 prep 1.1 2024-03-24 13:01:46 +01:00
84ebdcc9a8 Support more formats & cleanup 2024-03-24 13:00:22 +01:00
8181da421e Deleted GoogleML usage 2024-03-24 12:40:33 +01:00
af97381bdb Fix Readme 2024-03-24 11:37:28 +01:00
ba94beab37 Fix Readme 2024-03-24 11:36:35 +01:00
19b0f91852 Update README.md 2024-03-24 11:32:34 +01:00
3b0bf2097e Github images 2024-03-24 11:16:26 +01:00
455c95021b LICENSE 2024-03-24 10:45:37 +01:00
78ad7f983f CI Fix 2024-03-24 10:34:02 +01:00
0a0b55294f Fix CI 2024-03-24 10:28:30 +01:00
040b9c3af4 Fix CI 2024-03-24 10:26:31 +01:00
796c76f36c CI Signing 2024-03-24 10:23:30 +01:00
517f0240e3 CI release 2024-03-23 15:12:15 +01:00
b02920ca41 CI Autorelease 2024-03-23 15:03:09 +01:00
b289648260 CI Artifact 2024-03-23 14:25:04 +01:00
b6de7ca409 CI fix tag 2024-03-23 14:13:37 +01:00
12fb04974e CI on tag 2024-03-23 14:09:54 +01:00
cdd4d6db1b Fix CI 2024-03-23 13:59:01 +01:00
a403bdea61 CI testing 2024-03-23 13:50:35 +01:00
657489e255 Merge pull request 'Update plugin com.android.library to v8.3.1' (#4) from renovate/com.android.library-8.x into main
Reviewed-on: #4
2024-03-23 13:32:22 +01:00
95f609bc53 Merge pull request 'Update dependency gradle to v8.7' (#5) from renovate/gradle-8.x into main
Reviewed-on: #5
2024-03-23 13:32:06 +01:00
f14929cdf6 Prepare R1.0 2024-03-23 13:21:24 +01:00
ab5c32f2db Update dependency gradle to v8.7 2024-03-23 01:05:44 +00:00
cd8a84531e Update plugin com.android.library to v8.3.1 2024-03-23 01:05:33 +00:00
baee52c69b Update plugin com.android.application to v8.3.1 2024-03-23 01:05:27 +00:00
5edff2f5d3 Cleaning 2024-03-22 07:27:27 +01:00
f691b20952 Added clearing list 2024-03-22 07:27:27 +01:00
72 changed files with 1280 additions and 1116 deletions

BIN
.github/images/apk.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
.github/images/izzy.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

64
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,64 @@
#file: noinspection SpellCheckingInspection
name: CI-Android APK
env:
main_project_module: app
playstore_name: KeepassFidelity
on:
push:
branches: [ main ]
tags:
- '**'
pull_request:
branches: [ main ]
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: set up secrets
run: |
echo "${{ secrets.RELEASE_KEYSTORE }}" > keystore.asc
echo "${{ secrets.RELEASE_KEY}}" > key.asc
gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch keystore.asc > app/keystore.properties
gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch key.asc > app/key.jks
- uses: gradle/wrapper-validation-action@v3
- name: create and checkout branch
if: github.event_name == 'pull_request'
env:
BRANCH: ${{ github.head_ref }}
run: git checkout -B "$BRANCH"
- name: set up JDK
uses: actions/setup-java@v4
with:
java-version: 17
distribution: "temurin"
cache: 'gradle'
- name: Build APK
run: ./gradlew assemble
# - name: Upload APK
# uses: actions/upload-artifact@v4
# with:
# name: app.apk
# path: app/build/outputs/apk/release/app-release.apk
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
app/build/outputs/apk/release/app-release.apk

4
.gitignore vendored
View File

@ -7,7 +7,11 @@ local.properties/
.DS_Store
build/
app/build/
app/debug/
app/release/
captures/
.externalNativeBuild
.cxx
local.properties
keystore.properties
key.jks

24
LICENSE Normal file
View File

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>

View File

@ -0,0 +1,73 @@
<!--suppress ALL -->
<div align="center">
<h1>Keepass Fidelity</h1>
<img width="100px" src="./metadata/en-US/images/icon.png" alt="Logo">
<p>A minimalist fidelity/loyalty card plugin</p>
<img src="https://forthebadge.com/images/badges/built-for-android.svg" alt="Built for Android">
<img src="https://forthebadge.com/images/badges/built-with-love.svg" alt="Built with love">
<br>
<a href="https://github.com/choelzl/keepass-fidelity/actions/workflows/build.yml">
<img src="https://github.com/choelzl/keepass-fidelity/actions/workflows/build.yml/badge.svg?branch=main" alt="Build Status">
</a>
</div>
## 🌄 Screenshots
<div align="center">
<table>
<tr>
<td style="width: 33%; height: 100px;"><img src="./metadata/en-US/images/phoneScreenshots/launcher.jpg" alt="Launcher" style="width: 100%; height: 100%;"></td>
<td style="width: 33%; height: 100px;"><img src="./metadata/en-US/images/phoneScreenshots/view.jpg" alt="View" style="width: 100%; height: 100%;"></td>
<td style="width: 33%; height: 100px;"><img src="./metadata/en-US/images/phoneScreenshots/edit.jpg" alt="Edit" style="width: 100%; height: 100%;"></td>
</tr>
</table>
</div>
## ⭐ Features
- Search entries in [Keepass2Android](https://github.com/PhilippC/keepass2android/)
- Scan & Create entries
- Recently used history for fast access
- Protect entries from caching
- Minimalist design and features
- Supported Formats: CODE_39, CODE_93, CODE_128, EAN_8, EAN_13, UPC_A, UPC_E, CODE_QR, PDF_417, AZTEC, CODABAR, DATA_MATRIX, ITF
## 📳 Installation
<div style="display: flex; justify-content: center; align-items: center; flex-direction: row;">
<a href="https://apt.izzysoft.de/fdroid/index/apk/net.helcel.fidelity">
<img width="200" height="80" alt="Izzy Download" src=".github/images/izzy.png">
</a>
<a href="https://github.com/choelzl/keepass-fidelity/releases/latest">
<img width="200" height="84" alt="APK Download" src=".github/images/apk.png">
</a>
</div>
## ⚙️ Permissions
- `CAMERA`: necessary for importing barcodes from camera
- `READ_MEDIA_VISUAL_USER_SELECTED`: necessary for the importing barcode from images
## 📝 Contribute
Keepass-Fidelity is a user-driven project. We welcome any contribution, big or small.
- **🖥️ Development:** Fix bugs, implement features, or research issues. Open a PR for review.
- **🍥 Design:** Improve interfaces, including accessibility and usability.
- **📂 Issue Reporting:** Report bugs and edge cases with relevant info.
- **🌍 Localization:** Translate if it doesn't support your language.
## ✏️ Acknowledgements
Thanks to all contributors, the developers of our dependencies, and our users.
## 📝 License
```
Copyright (C) 2024 Helcel
Licensed under the Unlicense
For more information, please refer to <https://unlicense.org>
```

View File

@ -1,54 +1,84 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.23'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.0'
}
def keystorePropertiesFile = rootProject.file("app/keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
namespace 'net.helcel.fidelity'
compileSdk 34
defaultConfig {
applicationId 'net.helcel.fidelity'
resValue "string", "app_name", "Keepass Fidelity"
minSdk 28
targetSdk 34
versionCode 1
versionName "1.0"
}
signingConfigs {
create("release") {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
debug {
debuggable true
signingConfig = signingConfigs.getByName("release")
}
release {
minifyEnabled false
minifyEnabled true
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig = signingConfigs.getByName("release")
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
encoding 'utf-8'
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = JavaVersion.VERSION_17
}
buildFeatures {
viewBinding true
}
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
implementation 'androidx.camera:camera-camera2:1.3.2'
implementation 'androidx.camera:camera-lifecycle:1.3.2'
implementation 'androidx.camera:camera-view:1.3.2'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'com.google.zxing:core:3.5.3'
implementation 'com.google.mlkit:barcode-scanning:17.2.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.5'
implementation 'androidx.camera:camera-lifecycle:1.4.2'
implementation 'androidx.camera:camera-view:1.4.2'
runtimeOnly 'androidx.camera:camera-camera2:1.4.2'
implementation 'com.google.code.gson:gson:2.13.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'com.google.zxing:core:3.5.3'
}

13
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,13 @@
# Gson uses generic type information stored in a class file when working with
# fields. Proguard removes such information by default, keep it.
-keepattributes Signature
# This is also needed for R8 in compat mode since multiple
# optimizations will remove the generic signature such as class
# merging and argument removal. See:
# https://r8.googlesource.com/r8/+/refs/heads/main/compatibility-faq.md#troubleshooting-gson-gson
-keep class com.google.gson.reflect.TypeToken { *; }
-keep class * extends com.google.gson.reflect.TypeToken
# Optional. For using GSON @Expose annotation
-keepattributes AnnotationDefault,RuntimeVisibleAnnotations

View File

@ -1,12 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0">
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="8"
android:versionName="1.2c">
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<application
android:icon="@drawable/logo"
android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:supportsRtl="true">
<activity
@ -20,30 +24,14 @@
</activity>
<receiver
android:name=".pluginSDK.PluginAccessReceiver"
android:exported="true">
android:name=".pluginSDK.PluginAccessBroadcastReceiver"
android:exported="true"
tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="keepass2android.ACTION_TRIGGER_REQUEST_ACCESS" />
<action android:name="keepass2android.ACTION_RECEIVE_ACCESS" />
<action android:name="keepass2android.ACTION_REVOKE_ACCESS" />
</intent-filter>
</receiver>
<receiver
android:name=".pluginSDK.PluginActionBroadcastReceiver"
android:exported="true">
<intent-filter>
<action android:name="keepass2android.ACTION_OPEN_ENTRY" />
<action android:name="keepass2android.ACTION_CLOSE_ENTRY_VIEW" />
<action android:name="keepass2android.ACTION_ENTRY_ACTION_SELECTED" />
<action android:name="keepass2android.ACTION_LOCK_DATABASE" />
<action android:name="keepass2android.ACTION_UNLOCK_DATABASE" />
<action android:name="keepass2android.ACTION_CLOSE_DATABASE" />
<action android:name="keepass2android.ACTION_OPEN_DATABASE" />
</intent-filter>
</receiver>
</application>
</manifest>

BIN
app/src/main/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,19 +1,25 @@
package net.helcel.fidelity.activity
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.os.Bundle
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity
import net.helcel.fidelity.R
import net.helcel.fidelity.activity.fragment.Launcher
import net.helcel.fidelity.activity.fragment.ViewEntry
import net.helcel.fidelity.databinding.ActMainBinding
import net.helcel.fidelity.pluginSDK.Kp2aControl.getEntryFieldsFromIntent
import net.helcel.fidelity.tools.CacheManager
import net.helcel.fidelity.tools.KeepassWrapper.bundleCreate
import net.helcel.fidelity.tools.KeepassWrapper.entryExtract
@SuppressLint("SourceLockedOrientationActivity")
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActMainBinding
private lateinit var sharedPreferences: SharedPreferences
@ -23,25 +29,36 @@ class MainActivity : AppCompatActivity() {
this.getSharedPreferences(CacheManager.PREF_NAME, Context.MODE_PRIVATE)
CacheManager.loadFidelity(sharedPreferences)
binding = ActMainBinding.inflate(layoutInflater)
setContentView(binding.root)
onBackPressedDispatcher.addCallback(this) {
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStackImmediate()
loadLauncher()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
} else {
finish()
}
}
if (savedInstanceState == null)
loadLauncher()
if (intent.extras != null)
loadViewEntry()
else if (savedInstanceState == null)
loadLauncher()
}
private fun loadLauncher() {
supportFragmentManager.beginTransaction()
.add(R.id.container, Launcher())
.replace(R.id.container, Launcher())
.commit()
}
private fun loadViewEntry() {
val viewEntry = ViewEntry()
val data = getEntryFieldsFromIntent(intent)
viewEntry.arguments = bundleCreate(entryExtract(data))
supportFragmentManager.beginTransaction()
.replace(R.id.container, viewEntry)
.commit()
}
}

View File

@ -37,7 +37,6 @@ class FidelityListAdapter(
inner class FidelityViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(triple: Triple<String?, String?, String?>) {
val text = "${triple.first}"
binding.textView.text = text

View File

@ -7,9 +7,11 @@ import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.ArrayAdapter
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import com.google.android.material.textfield.TextInputEditText
import com.google.zxing.FormatException
import net.helcel.fidelity.R
import net.helcel.fidelity.databinding.FragCreateEntryBinding
@ -26,7 +28,7 @@ class CreateEntry : Fragment() {
private val handler = Handler(Looper.getMainLooper())
private lateinit var binding: FragCreateEntryBinding
private val resultLauncherAdd = KeepassWrapper.resultLauncherAdd(this) {
private val resultLauncherAdd = KeepassWrapper.resultLauncher(this) {
val r = KeepassWrapper.entryExtract(it)
if (!KeepassWrapper.isProtected(it)) {
CacheManager.addFidelity(r)
@ -34,7 +36,7 @@ class CreateEntry : Fragment() {
startViewEntry(r.first, r.second, r.third)
}
private var isValid: Boolean = false
private var isValidBarcode: Boolean = false
override fun onCreateView(
inflater: LayoutInflater,
@ -51,41 +53,14 @@ class CreateEntry : Fragment() {
binding.editTextCode.setText(res.second)
binding.editTextFormat.setText(res.third, false)
val changeListener = {
isValid = false
handler.removeCallbacksAndMessages(null)
handler.postDelayed({
updatePreview()
}, DEBOUNCE_DELAY)
}
binding.editTextCode.addTextChangedListener { changeListener() }
binding.editTextFormat.addTextChangedListener { changeListener() }
binding.editTextFormat.addTextChangedListener { binding.editTextFormat.error = null }
binding.btnSave.setOnClickListener {
if (!isValid() || !isValid) {
ErrorToaster.formIncomplete(requireActivity())
binding.btnSave.setOnClickListener { submit() }
binding.editTextTitle.onDone { submit() }
binding.editTextCode.onDone { submit() }
} else {
val kpentry = KeepassWrapper.entryCreate(
this,
binding.editTextTitle.text.toString(),
binding.editTextCode.text.toString(),
binding.editTextFormat.text.toString(),
binding.checkboxProtected.isChecked,
)
try {
resultLauncherAdd.launch(
Kp2aControl.getAddEntryIntent(
kpentry.first,
kpentry.second
)
)
} catch (e: ActivityNotFoundException) {
ErrorToaster.noKP2AFound(requireActivity())
}
}
}
updatePreview()
return binding.root
@ -99,7 +74,7 @@ class CreateEntry : Fragment() {
600
)
binding.imageViewPreview.setImageBitmap(barcodeBitmap)
isValid = true
isValidBarcode = true
} catch (e: FormatException) {
binding.imageViewPreview.setImageBitmap(null)
binding.editTextCode.error = "Invalid format"
@ -108,25 +83,23 @@ class CreateEntry : Fragment() {
binding.editTextCode.error = e.message
} catch (e: Exception) {
binding.imageViewPreview.setImageBitmap(null)
println(e.javaClass)
println(e.message)
e.printStackTrace()
}
}
private fun isValid(): Boolean {
private fun isValidForm(): Boolean {
var valid = true
if (binding.editTextTitle.text!!.isEmpty()) {
if (binding.editTextFormat.text.isNullOrEmpty()) {
valid = false
binding.editTextTitle.error = "Title cannot be empty"
binding.editTextFormat.error = "Format cannot be empty"
}
if (binding.editTextCode.text!!.isEmpty()) {
if (binding.editTextCode.text.isNullOrEmpty()) {
valid = false
binding.editTextCode.error = "Code cannot be empty"
}
if (binding.editTextFormat.text!!.isEmpty()) {
if (binding.editTextTitle.text.isNullOrEmpty()) {
valid = false
binding.editTextFormat.error = "Format cannot be empty"
binding.editTextTitle.error = "Title cannot be empty"
}
return valid
}
@ -140,4 +113,55 @@ class CreateEntry : Fragment() {
.replace(R.id.container, viewEntryFragment).commit()
}
private fun changeListener() {
isValidBarcode = false
handler.removeCallbacksAndMessages(null)
handler.postDelayed({
updatePreview()
}, DEBOUNCE_DELAY)
}
private fun TextInputEditText.onDone(callback: () -> Unit) {
setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
callback.invoke()
return@setOnEditorActionListener true
}
false
}
}
private fun submit() {
if (!isValidForm() || !isValidBarcode) {
ErrorToaster.formIncomplete(context)
} else {
val kpEntry = KeepassWrapper.entryCreate(
this,
binding.editTextTitle.text.toString(),
binding.editTextCode.text.toString(),
binding.editTextFormat.text.toString(),
binding.checkboxProtected.isChecked,
)
try {
resultLauncherAdd.launch(
Kp2aControl.getAddEntryIntent(
kpEntry.first,
kpEntry.second
)
)
} catch (e: ActivityNotFoundException) {
ErrorToaster.noKP2AFound(context)
} catch (e: Exception) {
e.printStackTrace()
}
if (!binding.checkboxProtected.isChecked) {
val r = KeepassWrapper.entryExtract(kpEntry.first)
CacheManager.addFidelity(r)
}
activity?.supportFragmentManager?.popBackStack()
}
}
}

View File

@ -0,0 +1,107 @@
package net.helcel.fidelity.activity.fragment
import android.Manifest
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import net.helcel.fidelity.R
import net.helcel.fidelity.tools.BarcodeScanner
import net.helcel.fidelity.tools.ErrorToaster
import net.helcel.fidelity.tools.KeepassWrapper
import java.io.FileNotFoundException
class FileScanner : Fragment() {
private var code: String = ""
private var fmt: String = ""
private val resultPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
resultLauncherOpenMediaPick.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
}
private val resultLauncherOpenMediaBase =
registerForActivityResult(ActivityResultContracts.GetContent()) {
loadUri(it)
}
private val resultLauncherOpenMediaPick =
registerForActivityResult(ActivityResultContracts.PickVisualMedia()) {
loadUri(it)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
println(Build.VERSION.SDK_INT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
resultPermission.launch(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
} else {
// resultLauncherOpenMediaBase.launch("image/*")
resultLauncherOpenMediaPick.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
}
return View(context)
}
private fun startCreateEntry() {
val createEntryFragment = CreateEntry()
createEntryFragment.arguments =
KeepassWrapper.bundleCreate(null, this.code, this.fmt)
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.container, createEntryFragment)
.commit()
}
private fun scannerResult(code: String?, format: String?) {
if (!code.isNullOrEmpty() && !format.isNullOrEmpty()) {
this.code = code
this.fmt = format
}
val isDone = this.code.isNotEmpty() && this.fmt.isNotEmpty()
requireActivity().runOnUiThread {
if (isDone) {
startCreateEntry()
} else {
parentFragmentManager.popBackStack()
ErrorToaster.nothingFound(context)
}
}
}
private fun loadUri(it: Uri?) {
try {
run {
require(it != null)
val file = requireContext().contentResolver.openInputStream(it)
val image = BitmapFactory.decodeStream(file)
BarcodeScanner.bitmapUseCase(image) { code, format ->
scannerResult(code, format)
}
}
} catch (e: FileNotFoundException) {
e.printStackTrace()
println(e.message)
println(it)
ErrorToaster.noPermission(context)
parentFragmentManager.popBackStack()
} catch (e: IllegalArgumentException) {
ErrorToaster.nothingFound(context)
parentFragmentManager.popBackStack()
} catch (e: SecurityException) {
ErrorToaster.noPermission(context)
parentFragmentManager.popBackStack()
}
}
}

View File

@ -23,7 +23,7 @@ class Launcher : Fragment() {
private lateinit var binding: FragLauncherBinding
private lateinit var fidelityListAdapter: FidelityListAdapter
private val resultLauncherQuery = KeepassWrapper.resultLauncherQuery(this) {
private val resultLauncherQuery = KeepassWrapper.resultLauncher(this) {
val r = KeepassWrapper.entryExtract(it)
if (!KeepassWrapper.isProtected(it)) {
CacheManager.addFidelity(r)
@ -49,6 +49,11 @@ class Launcher : Fragment() {
startScanner()
hideMenuAdd()
}
binding.btnOpen.setOnClickListener {
startFileScanner()
hideMenuAdd()
}
binding.btnManual.setOnClickListener {
startCreateEntry()
@ -80,7 +85,7 @@ class Launcher : Fragment() {
private fun startGetFromKeepass() {
try {
this.resultLauncherQuery.launch(Kp2aControl.queryEntryIntentForOwnPackage)
this.resultLauncherQuery.launch(Kp2aControl.getQueryEntryForOwnPackageIntent())
} catch (e: ActivityNotFoundException) {
ErrorToaster.noKP2AFound(requireActivity())
}
@ -96,6 +101,10 @@ class Launcher : Fragment() {
startFragment(Scanner())
}
private fun startFileScanner() {
startFragment(FileScanner())
}
private fun startCreateEntry() {
startFragment(CreateEntry())
}

View File

@ -2,32 +2,40 @@ package net.helcel.fidelity.activity.fragment
import android.Manifest
import android.content.ContentValues
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.camera.core.CameraSelector
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import net.helcel.fidelity.R
import net.helcel.fidelity.databinding.FragScannerBinding
import net.helcel.fidelity.tools.BarcodeScanner.getAnalysisUseCase
import net.helcel.fidelity.tools.BarcodeScanner.analysisUseCase
import net.helcel.fidelity.tools.ErrorToaster
import net.helcel.fidelity.tools.KeepassWrapper
private const val CAMERA_PERMISSION_REQUEST_CODE = 1
class Scanner : Fragment() {
private lateinit var binding: FragScannerBinding
private var code: String = ""
private var fmt: String = ""
private var valid: Boolean = false
private val resultPermissionRequest =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (it) {
bindCameraUseCases()
} else {
parentFragmentManager.popBackStack()
ErrorToaster.noPermission(context)
}
}
override fun onCreateView(
inflater: LayoutInflater,
@ -35,16 +43,15 @@ class Scanner : Fragment() {
savedInstanceState: Bundle?
): View {
binding = FragScannerBinding.inflate(layoutInflater)
binding.bottomText.setOnClickListener {
binding.btnScanDone.setOnClickListener {
startCreateEntry()
}
when (hasCameraPermission()) {
true -> bindCameraUseCases()
else -> requestPermission()
}
binding.btnScanDone.isEnabled = false
resultPermissionRequest.launch(Manifest.permission.CAMERA)
return binding.root
}
private fun startCreateEntry() {
val createEntryFragment = CreateEntry()
createEntryFragment.arguments =
@ -54,26 +61,16 @@ class Scanner : Fragment() {
.commit()
}
private fun hasCameraPermission() =
ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
private fun requestPermission() {
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(Manifest.permission.CAMERA),
CAMERA_PERMISSION_REQUEST_CODE
)
ActivityCompat.OnRequestPermissionsResultCallback { c, p, i ->
require(c == CAMERA_PERMISSION_REQUEST_CODE)
require(p.contains(Manifest.permission.CAMERA))
val el = i[p.indexOf(Manifest.permission.CAMERA)]
if (el != PackageManager.PERMISSION_GRANTED) {
startCreateEntry()
private fun scannerResult(code: String?, format: String?) {
if (!code.isNullOrEmpty() && !format.isNullOrEmpty()) {
this.code = code
this.fmt = format
}
val isDone = this.code.isNotEmpty() && this.fmt.isNotEmpty()
activity?.runOnUiThread {
binding.btnScanDone.isEnabled = isDone
binding.ScanActive.isEnabled = !isDone
}
}
@ -88,14 +85,8 @@ class Scanner : Fragment() {
it.setSurfaceProvider(binding.cameraView.surfaceProvider)
}
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
val analysisUseCase = getAnalysisUseCase { code, format ->
if (code != null && format != null) {
this.code = code
this.fmt = format
this.valid = true
} else {
this.valid = false
}
val analysisUseCase = analysisUseCase { code, format ->
scannerResult(code, format)
}
try {
cameraProvider.bindToLifecycle(
@ -111,6 +102,4 @@ class Scanner : Fragment() {
}
}, ContextCompat.getMainExecutor(requireContext()))
}
}

View File

@ -1,10 +1,14 @@
package net.helcel.fidelity.activity.fragment
import android.annotation.SuppressLint
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
import androidx.fragment.app.Fragment
import com.google.zxing.FormatException
import net.helcel.fidelity.databinding.FragViewEntryBinding
@ -12,16 +16,14 @@ import net.helcel.fidelity.tools.BarcodeGenerator.generateBarcode
import net.helcel.fidelity.tools.ErrorToaster
import net.helcel.fidelity.tools.KeepassWrapper
@SuppressLint("SourceLockedOrientationActivity")
class ViewEntry : Fragment() {
private lateinit var binding: FragViewEntryBinding
private var title: String? = null
private var code: String? = null
private var fmt: String? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -33,8 +35,15 @@ class ViewEntry : Fragment() {
code = res.second
fmt = res.third
adjustLayout()
updatePreview()
updateLayout()
binding.imageViewPreview.setOnClickListener {
requireActivity().requestedOrientation =
if (isLandscape()) ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
else ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
return binding.root
}
@ -42,7 +51,7 @@ class ViewEntry : Fragment() {
binding.title.text = title
try {
val barcodeBitmap = generateBarcode(
code!!, fmt!!, 1024
code, fmt, 1024
)
binding.imageViewPreview.setImageBitmap(barcodeBitmap)
} catch (e: FormatException) {
@ -53,23 +62,25 @@ class ViewEntry : Fragment() {
ErrorToaster.invalidFormat(requireActivity())
} catch (e: Exception) {
binding.imageViewPreview.setImageBitmap(null)
println(e.javaClass)
println(e.message)
e.printStackTrace()
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
adjustLayout()
}
private fun adjustLayout() {
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
private fun updateLayout() {
if (isLandscape()) {
binding.title.visibility = View.GONE
setScreenBrightness(BRIGHTNESS_OVERRIDE_FULL)
} else {
binding.title.visibility = View.VISIBLE
setScreenBrightness(BRIGHTNESS_OVERRIDE_NONE)
}
}
private fun isLandscape(): Boolean {
return (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
}
private fun setScreenBrightness(brightness: Float) {
requireActivity().window?.attributes?.screenBrightness = brightness
}
}

View File

@ -0,0 +1,45 @@
package net.helcel.fidelity.activity.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.util.AttributeSet
import android.view.View
class ScannerView : View {
private val overlayPaint = Paint().apply {
color = Color.parseColor("#80000000") // Semi-transparent black
style = Paint.Style.FILL
}
private val clearPaint = Paint().apply {
xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), overlayPaint)
val centerX = width / 2f
val centerY = height / 2f
val squareSize = 0.75f * width.coerceAtMost(height)
canvas.drawRect(
centerX - squareSize / 2, centerY - squareSize / 2,
centerX + squareSize / 2, centerY + squareSize / 2, clearPaint
)
}
}

View File

@ -2,141 +2,84 @@ package net.helcel.fidelity.pluginSDK
import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.text.TextUtils
import android.util.Log
import org.json.JSONArray
import org.json.JSONException
object AccessManager {
private const val _tag = "Kp2aPluginSDK"
private const val PREF_KEY_SCOPE = "scope"
private const val PREF_KEY_TOKEN = "token"
private fun stringArrayToString(values: ArrayList<String?>): String? {
if (values.isEmpty()) return null
val a = JSONArray()
for (i in values.indices) {
a.put(values[i])
}
return if (values.isNotEmpty()) {
a.toString()
} else {
null
}
values.forEach { a.put(it) }
return a.toString()
}
private fun stringToStringArray(s: String?): ArrayList<String> {
val strings = ArrayList<String>()
if (!TextUtils.isEmpty(s)) {
if (s.isNullOrEmpty()) return strings
try {
val a = JSONArray(s)
for (i in 0 until a.length()) {
val url = a.optString(i)
strings.add(url)
}
for (i in 0 until a.length())
strings.add(a.optString(i))
} catch (e: JSONException) {
e.printStackTrace()
}
}
return strings
}
fun storeAccessToken(
ctx: Context,
hostPackage: String,
accessToken: String,
hostPackage: String?,
accessToken: String?,
scopes: ArrayList<String?>
) {
val prefs = getPrefsForHost(ctx, hostPackage)
val edit = prefs.edit()
edit.putString(PREF_KEY_TOKEN, accessToken)
val scopesString = stringArrayToString(scopes)
edit.putString(PREF_KEY_SCOPE, scopesString)
edit.apply()
Log.d(
_tag,
"stored access token " + accessToken.substring(
0,
4
) + "... for " + scopes.size + " scopes (" + scopesString + ")."
)
val hostPrefs = ctx.getSharedPreferences("KP2A.PluginAccess.hosts", Context.MODE_PRIVATE)
if (!hostPrefs.contains(hostPackage)) {
if (!hostPrefs.contains(hostPackage))
hostPrefs.edit().putString(hostPackage, "").apply()
}
}
fun preparePopup(popupMenu: Any) {
try {
val fields = popupMenu.javaClass.declaredFields
for (field in fields) {
if ("mPopup" == field.name) {
field.isAccessible = true
val menuPopupHelper = field[popupMenu]
val classPopupHelper = Class.forName(
menuPopupHelper
.javaClass.name
)
val setForceIcons = classPopupHelper.getMethod(
"setForceShowIcon", Boolean::class.javaPrimitiveType
)
setForceIcons.invoke(menuPopupHelper, true)
break
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun getPrefsForHost(
ctx: Context,
hostPackage: String
hostPackage: String?
): SharedPreferences {
val prefs = ctx.getSharedPreferences("KP2A.PluginAccess.$hostPackage", Context.MODE_PRIVATE)
return prefs
return ctx.getSharedPreferences("KP2A.PluginAccess.$hostPackage", Context.MODE_PRIVATE)
}
fun tryGetAccessToken(ctx: Context, hostPackage: String, scopes: ArrayList<String?>): String? {
if (TextUtils.isEmpty(hostPackage)) {
Log.d(_tag, "hostPackage is empty!")
return null
}
Log.d(_tag, "trying to find prefs for $hostPackage")
fun tryGetAccessToken(ctx: Context, hostPackage: String?, scopes: ArrayList<String?>): String? {
if (hostPackage.isNullOrEmpty()) return null
val prefs = getPrefsForHost(ctx, hostPackage)
val scopesString = prefs.getString(PREF_KEY_SCOPE, "")
Log.d(_tag, "available scopes: $scopesString")
val currentScope = stringToStringArray(scopesString)
if (isSubset(scopes, currentScope)) {
return prefs.getString(PREF_KEY_TOKEN, null)
} else {
Log.d(_tag, "looks like scope changed. Access token invalid.")
if (!isSubset(scopes, currentScope))
return null
}
return prefs.getString(PREF_KEY_TOKEN, null)
}
private fun isSubset(
requiredScopes: ArrayList<String?>,
availableScopes: ArrayList<String>
): Boolean {
for (r in requiredScopes) {
if (availableScopes.indexOf(r) < 0) {
Log.d(_tag, "Scope " + r + " not available. " + availableScopes.size)
return false
}
}
return true
return availableScopes.containsAll(requiredScopes)
}
fun removeAccessToken(
ctx: Context, hostPackage: String,
accessToken: String
ctx: Context, hostPackage: String?,
accessToken: String?
) {
val prefs = getPrefsForHost(ctx, hostPackage)
Log.d(_tag, "removing AccessToken.")
if (prefs.getString(PREF_KEY_TOKEN, "") == accessToken) {
val edit = prefs.edit()
edit.clear()
@ -148,32 +91,4 @@ object AccessManager {
hostPrefs.edit().remove(hostPackage).apply()
}
}
fun getAllHostPackages(ctx: Context): Set<String> {
val prefs = ctx.getSharedPreferences("KP2A.PluginAccess.hosts", Context.MODE_PRIVATE)
val result: MutableSet<String> = HashSet()
for (host in prefs.all.keys) {
try {
val info = ctx.packageManager.getPackageInfo(host, PackageManager.GET_META_DATA)
//if we get here, the package is still there
result.add(host)
} catch (e: PackageManager.NameNotFoundException) {
//host gone. ignore.
}
}
return result
}
/**
* Returns a valid access token or throws PluginAccessException
*/
fun getAccessToken(
context: Context, hostPackage: String,
scopes: ArrayList<String?>
): String {
val accessToken = tryGetAccessToken(context, hostPackage, scopes)
?: throw PluginAccessException(hostPackage, scopes)
return accessToken
}
}

View File

@ -0,0 +1,9 @@
package net.helcel.fidelity.pluginSDK
@Suppress("unused")
object KeepassDef {
var TitleField: String = "Title"
var UserNameField: String = "UserName"
var PasswordField: String = "Password"
var UrlField: String = "URL"
}

View File

@ -1,45 +0,0 @@
package net.helcel.fidelity.pluginSDK
object KeepassDefs {
/// <summary>
/// Default identifier string for the title field. Should not contain
/// spaces, tabs or other whitespace.
/// </summary>
var TitleField: String = "Title"
/// <summary>
/// Default identifier string for the user name field. Should not contain
/// spaces, tabs or other whitespace.
/// </summary>
private var UserNameField: String = "UserName"
/// <summary>
/// Default identifier string for the password field. Should not contain
/// spaces, tabs or other whitespace.
/// </summary>
private var PasswordField: String = "Password"
/// <summary>
/// Default identifier string for the URL field. Should not contain
/// spaces, tabs or other whitespace.
/// </summary>
var UrlField: String = "URL"
/// <summary>
/// Default identifier string for the notes field. Should not contain
/// spaces, tabs or other whitespace.
/// </summary>
private var NotesField: String = "Notes"
fun IsStandardField(strFieldName: String?): Boolean {
if (strFieldName == null) return false
if (strFieldName == TitleField) return true
if (strFieldName == UserNameField) return true
if (strFieldName == PasswordField) return true
if (strFieldName == UrlField) return true
if (strFieldName == NotesField) return true
return false
}
}

View File

@ -1,99 +1,41 @@
package net.helcel.fidelity.pluginSDK
import android.content.Intent
import android.text.TextUtils
import org.json.JSONException
import org.json.JSONObject
object Kp2aControl {
/**
* Creates and returns an intent to launch Keepass2Android for adding an entry with the given fields.
* @param fields Key/Value pairs of the field values. See KeepassDefs for standard keys.
* @param protectedFields List of keys of the protected fields.
* @return Intent to start Keepass2Android.
* @throws JSONException
*/
fun getAddEntryIntent(
fields: HashMap<String?, String?>?,
protectedFields: ArrayList<String?>?
): Intent {
return getAddEntryIntent(JSONObject((fields as Map<*, *>?)!!).toString(), protectedFields)
}
private fun getAddEntryIntent(
outputData: String?,
protectedFields: ArrayList<String?>?
fun getAddEntryIntent(
fields: HashMap<String, String>,
protectedFields: ArrayList<String>?
): Intent {
val outputData = JSONObject((fields as Map<*, *>)).toString()
val startKp2aIntent = Intent(Strings.ACTION_START_WITH_TASK)
startKp2aIntent.addCategory(Intent.CATEGORY_DEFAULT)
startKp2aIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
startKp2aIntent.putExtra("KP2A_APPTASK", "CreateEntryThenCloseTask")
startKp2aIntent.putExtra("ShowUserNotifications", "false") //KP2A expects a StringExtra
startKp2aIntent.putExtra("ShowUserNotifications", "true")
startKp2aIntent.putExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA, outputData)
if (protectedFields != null) startKp2aIntent.putStringArrayListExtra(
if (protectedFields != null)
startKp2aIntent.putStringArrayListExtra(
Strings.EXTRA_PROTECTED_FIELDS_LIST,
protectedFields
)
return startKp2aIntent
}
/**
* Creates an intent to open a Password Entry matching searchText
* @param searchText queryString
* @param showUserNotifications if true, the notifications (copy to clipboard, keyboard) are displayed
* @param closeAfterOpen if true, the entry is opened and KP2A is immediately closed
* @return Intent to start KP2A with
*/
fun getOpenEntryIntent(
searchText: String?,
showUserNotifications: Boolean,
closeAfterOpen: Boolean
): Intent {
val startKp2aIntent = Intent(Strings.ACTION_START_WITH_TASK)
startKp2aIntent.addCategory(Intent.CATEGORY_DEFAULT)
startKp2aIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
startKp2aIntent.putExtra("KP2A_APPTASK", "SearchUrlTask")
startKp2aIntent.putExtra("ShowUserNotifications", showUserNotifications.toString())
startKp2aIntent.putExtra("CloseAfterCreate", closeAfterOpen.toString())
startKp2aIntent.putExtra("UrlToSearch", searchText)
return startKp2aIntent
fun getQueryEntryForOwnPackageIntent(): Intent {
return Intent(Strings.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE)
}
/**
* Creates an intent to query a password entry from KP2A. The credentials are returned as Activity result.
* @param searchText Text to search for. Should be a URL or "androidapp://com.my.package."
* @return an Intent to start KP2A with
*/
fun getQueryEntryIntent(searchText: String?): Intent {
val i = Intent(Strings.ACTION_QUERY_CREDENTIALS)
if (!TextUtils.isEmpty(searchText)) i.putExtra(Strings.EXTRA_QUERY_STRING, searchText)
return i
}
val queryEntryIntentForOwnPackage: Intent
/**
* Creates an intent to query a password entry from KP2A, matching to the current app's package .
* The credentials are returned as Activity result.
* This requires SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE.
* @return an Intent to start KP2A with
*/
get() = Intent(Strings.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE)
/**
* Converts the entry fields returned in an intent from a query to a hashmap.
* @param intent data received in onActivityResult after getQueryEntryIntent(ForOwnPackage)
* @return HashMap with keys = field names (see KeepassDefs for standard keys) and values = values
*/
fun getEntryFieldsFromIntent(intent: Intent): HashMap<String, String> {
fun getEntryFieldsFromIntent(intent: Intent?): HashMap<String, String> {
val res = HashMap<String, String>()
try {
val json = JSONObject(intent.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA)!!)
val iter = json.keys()
while (iter.hasNext()) {
val key = iter.next()
val json = JSONObject(intent?.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA) ?: "")
val itr = json.keys()
while (itr.hasNext()) {
val key = itr.next()
val value = json[key].toString()
res[key] = value
}

View File

@ -3,35 +3,10 @@ package net.helcel.fidelity.pluginSDK
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
/**
* Broadcast flow between Host and Plugin
* ======================================
*
* The host is responsible for deciding when to initiate the session. It
* should initiate the session as soon as plugins are required or when a plugin
* has been updated through the OS.
* It will then send a broadcast to request the currently required scope.
* The plugin then sends a broadcast to the app which scope is required. If an
* access token is already available, it's sent along with the requset.
*
* If a previous permission has been revoked (or the app settings cleared or the
* permissions have been extended or the token is invalid for any other reason)
* the host will answer with a Revoked-Permission broadcast (i.e. the plugin is
* unconnected.)
*
* Unconnected plugins must be permitted by the user (requiring user action).
* When the user grants access, the plugin will receive an access token for
* the host. This access token is valid for the requested scope. If the scope
* changes (e.g after an update of the plugin), the access token becomes invalid.
*
*/
abstract class PluginAccessBroadcastReceiver : BroadcastReceiver() {
class PluginAccessBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(ctx: Context, intent: Intent) {
val action = intent.action
Log.d(_tag, "received broadcast with action=$action")
if (action == null) return
val action = intent.action ?: return
when (action) {
Strings.ACTION_TRIGGER_REQUEST_ACCESS -> requestAccess(ctx, intent)
Strings.ACTION_RECEIVE_ACCESS -> receiveAccess(ctx, intent)
@ -40,18 +15,17 @@ abstract class PluginAccessBroadcastReceiver : BroadcastReceiver() {
}
}
private fun revokeAccess(ctx: Context, intent: Intent) {
val senderPackage = intent.getStringExtra(Strings.EXTRA_SENDER)
val accessToken = intent.getStringExtra(Strings.EXTRA_ACCESS_TOKEN)
AccessManager.removeAccessToken(ctx, senderPackage!!, accessToken!!)
AccessManager.removeAccessToken(ctx, senderPackage, accessToken)
}
private fun receiveAccess(ctx: Context, intent: Intent) {
val senderPackage = intent.getStringExtra(Strings.EXTRA_SENDER)
val accessToken = intent.getStringExtra(Strings.EXTRA_ACCESS_TOKEN)
AccessManager.storeAccessToken(ctx, senderPackage!!, accessToken!!, scopes)
AccessManager.storeAccessToken(ctx, senderPackage, accessToken, scopes)
}
private fun requestAccess(ctx: Context, intent: Intent) {
@ -62,21 +36,16 @@ abstract class PluginAccessBroadcastReceiver : BroadcastReceiver() {
rpi.putExtra(Strings.EXTRA_SENDER, ctx.packageName)
rpi.putExtra(Strings.EXTRA_REQUEST_TOKEN, requestToken)
val token: String? = AccessManager.tryGetAccessToken(ctx, senderPackage!!, scopes)
val token: String? = AccessManager.tryGetAccessToken(ctx, senderPackage, scopes)
rpi.putExtra(Strings.EXTRA_ACCESS_TOKEN, token)
rpi.putStringArrayListExtra(Strings.EXTRA_SCOPES, scopes)
Log.d(_tag, "requesting access for " + scopes.size + " tokens.")
ctx.sendBroadcast(rpi)
}
/**
*
* @return the list of required scopes for this plugin.
*/
abstract val scopes: ArrayList<String?>
companion object {
private const val _tag = "Kp2aPluginSDK"
}
private val scopes: ArrayList<String?> = ArrayList(
listOf(
Strings.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE,
)
)
}

View File

@ -1,14 +0,0 @@
package net.helcel.fidelity.pluginSDK
class PluginAccessException : Exception {
constructor(what: String?) : super(what)
constructor(hostPackage: String?, scopes: ArrayList<String?>)
companion object {
/**
*
*/
private const val serialVersionUID = 1L
}
}

View File

@ -1,16 +0,0 @@
package net.helcel.fidelity.pluginSDK
import kotlin.collections.ArrayList
class PluginAccessReceiver : PluginAccessBroadcastReceiver() {
override val scopes: ArrayList<String?> = ArrayList()
init {
this.scopes.add(Strings.SCOPE_DATABASE_ACTIONS)
this.scopes.add(Strings.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE)
}
}

View File

@ -1,224 +0,0 @@
package net.helcel.fidelity.pluginSDK
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
class PluginActionBroadcastReceiver : BroadcastReceiver() {
open class PluginActionBase
(var context: Context, protected var _intent: Intent) {
val hostPackage: String?
get() = _intent.getStringExtra(Strings.EXTRA_SENDER)
}
open class PluginEntryActionBase(context: Context, intent: Intent) :
PluginActionBase(context, intent) {
protected val entryFieldsFromIntent: HashMap<String, String>
get() {
val res = HashMap<String, String>()
try {
val json = JSONObject(_intent.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA)!!)
val iter = json.keys()
while (iter.hasNext()) {
val key = iter.next()
val value = json[key].toString()
res[key] = value
}
} catch (e: JSONException) {
e.printStackTrace()
}
return res
}
protected val protectedFieldsListFromIntent: Array<String?>?
get() {
try {
val json =
JSONArray(_intent.getStringExtra(Strings.EXTRA_PROTECTED_FIELDS_LIST))
val res = arrayOfNulls<String>(json.length())
for (i in 0 until json.length()) res[i] = json.getString(i)
return res
} catch (e: JSONException) {
e.printStackTrace()
return null
}
}
open val entryId: String?
get() = _intent.getStringExtra(Strings.EXTRA_ENTRY_ID)
@Throws(PluginAccessException::class)
fun setEntryField(fieldId: String?, fieldValue: String?, isProtected: Boolean) {
val i = Intent(Strings.ACTION_SET_ENTRY_FIELD)
val scope = ArrayList<String?>()
scope.add(Strings.SCOPE_CURRENT_ENTRY)
i.putExtra(
Strings.EXTRA_ACCESS_TOKEN, AccessManager.getAccessToken(
context, hostPackage!!, scope
)
)
i.setPackage(hostPackage)
i.putExtra(Strings.EXTRA_SENDER, context.packageName)
i.putExtra(Strings.EXTRA_FIELD_VALUE, fieldValue)
i.putExtra(Strings.EXTRA_ENTRY_ID, entryId)
i.putExtra(Strings.EXTRA_FIELD_ID, fieldId)
i.putExtra(Strings.EXTRA_FIELD_PROTECTED, isProtected)
context.sendBroadcast(i)
}
}
private inner class ActionSelectedAction(ctx: Context, intent: Intent) :
PluginEntryActionBase(ctx, intent) {
val actionData: Bundle?
/**
*
* @return the Bundle associated with the action. This bundle can be set in OpenEntry.add(Entry)FieldAction
*/
get() = _intent.getBundleExtra(Strings.EXTRA_ACTION_DATA)
private val fieldId: String?
/**
*
* @return the field id which was selected. null if an entry action (in the options menu) was selected.
*/
get() = _intent.getStringExtra(Strings.EXTRA_FIELD_ID)
val isEntryAction: Boolean
/**
*
* @return true if an entry action, i.e. an option from the options menu, was selected. False if an option
* in a popup menu for a certain field was selected.
*/
get() = fieldId == null
val entryFields: HashMap<String, String>
/**
*
* @return a hashmap containing the entry fields in key/value form
*/
get() = entryFieldsFromIntent
val protectedFieldsList: Array<String?>?
/**
*
* @return an array with the keys of all protected fields in the entry
*/
get() = protectedFieldsListFromIntent
}
private inner class CloseEntryViewAction(context: Context, intent: Intent) :
PluginEntryActionBase(context, intent) {
override val entryId: String?
get() = _intent.getStringExtra(Strings.EXTRA_ENTRY_ID)
}
private open inner class OpenEntryAction(context: Context, intent: Intent) :
PluginEntryActionBase(context, intent) {
val entryFields: HashMap<String, String>
get() = entryFieldsFromIntent
val protectedFieldsList: Array<String?>?
/**
*
* @return an array with the keys of all protected fields in the entry
*/
get() = protectedFieldsListFromIntent
@Throws(PluginAccessException::class)
fun addEntryAction(
actionDisplayText: String?,
actionIconResourceId: Int,
actionData: Bundle?
) {
addEntryFieldAction(null, null, actionDisplayText, actionIconResourceId, actionData)
}
@Throws(PluginAccessException::class)
fun addEntryFieldAction(
actionId: String?,
fieldId: String?,
actionDisplayText: String?,
actionIconResourceId: Int,
actionData: Bundle?
) {
val i = Intent(Strings.ACTION_ADD_ENTRY_ACTION)
val scope = ArrayList<String?>()
scope.add(Strings.SCOPE_CURRENT_ENTRY)
i.putExtra(
Strings.EXTRA_ACCESS_TOKEN, AccessManager.getAccessToken(
context, hostPackage!!, scope
)
)
i.setPackage(hostPackage)
i.putExtra(Strings.EXTRA_SENDER, context.packageName)
i.putExtra(Strings.EXTRA_ACTION_DATA, actionData)
i.putExtra(Strings.EXTRA_ACTION_DISPLAY_TEXT, actionDisplayText)
i.putExtra(Strings.EXTRA_ACTION_ICON_RES_ID, actionIconResourceId)
i.putExtra(Strings.EXTRA_ENTRY_ID, entryId)
i.putExtra(Strings.EXTRA_FIELD_ID, fieldId)
i.putExtra(Strings.EXTRA_ACTION_ID, actionId)
context.sendBroadcast(i)
}
}
private inner class DatabaseAction(context: Context, intent: Intent) :
PluginActionBase(context, intent) {
val fileDisplayName: String?
get() = _intent.getStringExtra(Strings.EXTRA_DATABASE_FILE_DISPLAYNAME)
val filePath: String?
get() = _intent.getStringExtra(Strings.EXTRA_DATABASE_FILEPATH)
val action: String?
get() = _intent.action
}
//EntryOutputModified is very similar to OpenEntry because it receives the same
//data (+ the field id which was modified)
private inner class EntryOutputModifiedAction(context: Context, intent: Intent) :
OpenEntryAction(context, intent) {
val modifiedFieldId: String?
get() = _intent.getStringExtra(Strings.EXTRA_FIELD_ID)
}
override fun onReceive(ctx: Context, intent: Intent) {
val action = intent.action
Log.d(
"KP2A.pluginsdk",
"received broadcast in PluginActionBroadcastReceiver with action=$action"
)
if (action == null) return
if (action == Strings.ACTION_OPEN_ENTRY) {
openEntry(OpenEntryAction(ctx, intent))
} else if (action == Strings.ACTION_CLOSE_ENTRY_VIEW) {
closeEntryView(CloseEntryViewAction(ctx, intent))
} else if (action == Strings.ACTION_ENTRY_ACTION_SELECTED) {
actionSelected(ActionSelectedAction(ctx, intent))
} else if (action == Strings.ACTION_ENTRY_OUTPUT_MODIFIED) {
entryOutputModified(EntryOutputModifiedAction(ctx, intent))
} else if (action == Strings.ACTION_LOCK_DATABASE || action == Strings.ACTION_UNLOCK_DATABASE || action == Strings.ACTION_OPEN_DATABASE || action == Strings.ACTION_CLOSE_DATABASE) {
dbAction(DatabaseAction(ctx, intent))
} else {
//TODO handle unexpected action
}
}
private fun closeEntryView(closeEntryView: CloseEntryViewAction?) {}
private fun actionSelected(actionSelected: ActionSelectedAction?) {}
private fun openEntry(oe: OpenEntryAction?) {}
private fun entryOutputModified(eom: EntryOutputModifiedAction?) {}
private fun dbAction(db: DatabaseAction?) {}
}

View File

@ -1,195 +1,31 @@
package net.helcel.fidelity.pluginSDK
@Suppress("unused")
object Strings {
/**
* Plugin is notified about actions like open/close/update a database.
*/
const val SCOPE_DATABASE_ACTIONS = "keepass2android.SCOPE_DATABASE_ACTIONS"
/**
* Plugin is notified when an entry is opened.
*/
const val SCOPE_CURRENT_ENTRY = "keepass2android.SCOPE_CURRENT_ENTRY"
/**
* Plugin may query credentials for its own package
*/
const val SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE =
"keepass2android.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE"
/**
* Plugin may query credentials for a deliberate package
*/
const val SCOPE_QUERY_CREDENTIALS = "keepass2android.SCOPE_QUERY_CREDENTIALS"
/**
* Extra key to transfer a (json serialized) list of scopes
*/
const val EXTRA_SCOPES = "keepass2android.EXTRA_SCOPES"
const val EXTRA_PLUGIN_PACKAGE = "keepass2android.EXTRA_PLUGIN_PACKAGE"
/**
* Extra key for sending the package name of the sender of a broadcast.
* Should be set in every broadcast.
*/
const val EXTRA_SENDER = "keepass2android.EXTRA_SENDER"
/**
* Extra key for sending a request token. The request token is passed from
* KP2A to the plugin. It's used in the authorization process.
*/
const val EXTRA_REQUEST_TOKEN = "keepass2android.EXTRA_REQUEST_TOKEN"
/**
* Action to start KP2A with an AppTask
*/
const val ACTION_START_WITH_TASK = "keepass2android.ACTION_START_WITH_TASK"
/**
* Action sent from KP2A to the plugin to indicate that the plugin should request
* access (sending it's scopes)
*/
const val ACTION_TRIGGER_REQUEST_ACCESS = "keepass2android.ACTION_TRIGGER_REQUEST_ACCESS"
/**
* Action sent from the plugin to KP2A including the scopes.
*/
const val ACTION_REQUEST_ACCESS = "keepass2android.ACTION_REQUEST_ACCESS"
/**
* Action sent from the KP2A to the plugin when the user grants access.
* Will contain an access token.
*/
const val ACTION_RECEIVE_ACCESS = "keepass2android.ACTION_RECEIVE_ACCESS"
/**
* Action sent from KP2A to the plugin to indicate that access is not or no longer valid.
*/
const val ACTION_REVOKE_ACCESS = "keepass2android.ACTION_REVOKE_ACCESS"
/**
* Action for startActivity(). Opens an activity in the Plugin Host to edit the plugin settings (i.e. enable it)
*/
const val ACTION_EDIT_PLUGIN_SETTINGS = "keepass2android.ACTION_EDIT_PLUGIN_SETTINGS"
/**
* Action sent from KP2A to the plugin to indicate that an entry was opened.
* The Intent contains the full entry data.
*/
const val ACTION_OPEN_ENTRY = "keepass2android.ACTION_OPEN_ENTRY"
/**
* Action sent from KP2A to the plugin to indicate that an entry output field was modified/added.
* The Intent contains the full new entry data.
*/
const val ACTION_ENTRY_OUTPUT_MODIFIED = "keepass2android.ACTION_ENTRY_OUTPUT_MODIFIED"
/**
* Action sent from KP2A to the plugin to indicate that an entry activity was closed.
*/
const val ACTION_CLOSE_ENTRY_VIEW = "keepass2android.ACTION_CLOSE_ENTRY_VIEW"
/**
* Extra key for a string containing the GUID of the entry.
*/
const val EXTRA_ENTRY_ID = "keepass2android.EXTRA_ENTRY_DATA"
/**
* Json serialized data of the PwEntry (C# class) representing the opened entry.
* currently not implemented.
*/
//const val EXTRA_ENTRY_DATA = "keepass2android.EXTRA_ENTRY_DATA";
/**
* Json serialized list of fields, transformed using the database context (i.e. placeholders are replaced already)
*/
const val EXTRA_ENTRY_OUTPUT_DATA = "keepass2android.EXTRA_ENTRY_OUTPUT_DATA"
/**
* Json serialized lisf of field keys, specifying which field of the EXTRA_ENTRY_OUTPUT_DATA is protected.
*/
const val EXTRA_PROTECTED_FIELDS_LIST = "keepass2android.EXTRA_PROTECTED_FIELDS_LIST"
/**
* Extra key for passing the access token (both ways)
*/
const val EXTRA_ACCESS_TOKEN = "keepass2android.EXTRA_ACCESS_TOKEN"
/**
* Action for an intent from the plugin to KP2A to add menu options regarding the currently open entry.
* Requires SCOPE_CURRENT_ENTRY.
*/
const val ACTION_ADD_ENTRY_ACTION = "keepass2android.ACTION_ADD_ENTRY_ACTION"
const val EXTRA_ACTION_DISPLAY_TEXT = "keepass2android.EXTRA_ACTION_DISPLAY_TEXT"
const val EXTRA_ACTION_ICON_RES_ID = "keepass2android.EXTRA_ACTION_ICON_RES_ID"
const val EXTRA_FIELD_ID = "keepass2android.EXTRA_FIELD_ID"
/**
* Used to pass an id for the action. Each actionId may occur only once per field, otherwise the previous
* action with same id is replaced by the new action.
*/
const val EXTRA_ACTION_ID = "keepass2android.EXTRA_ACTION_ID"
/** Extra for ACTION_ADD_ENTRY_ACTION and ACTION_ENTRY_ACTION_SELECTED to pass data specifying the action parameters.*/
const val EXTRA_ACTION_DATA = "keepass2android.EXTRA_ACTION_DATA"
/**
* Action for an intent from KP2A to the plugin when an action added with ACTION_ADD_ENTRY_ACTION was selected by the user.
*
*/
const val ACTION_ENTRY_ACTION_SELECTED = "keepass2android.ACTION_ENTRY_ACTION_SELECTED"
/**
* Extra key for the string which is used to query the credentials. This should be either a URL for
* a web login (google.com or a full URI) or something in the form "androidapp://com.my.package"
*/
const val EXTRA_QUERY_STRING = "keepass2android.EXTRA_QUERY_STRING"
/**
* Action when plugin wants to query credentials for its own package
*/
const val ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE =
"keepass2android.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE"
/**
* Action when plugin wants to query credentials for a deliberate package
* The query string is passed as intent data
*/
const val ACTION_QUERY_CREDENTIALS = "keepass2android.ACTION_QUERY_CREDENTIALS"
/**
* Action for an intent from the plugin to KP2A to set (i.e. add or update) a field in the entry.
* May be used to update existing or add new fields at any time while the entry is opened.
*/
const val ACTION_SET_ENTRY_FIELD = "keepass2android.ACTION_SET_ENTRY_FIELD"
/** Actions for an intent from KP2A to the plugin to inform that a database was opened, closed, quicklocked or quickunlocked.*/
const val ACTION_OPEN_DATABASE = "keepass2android.ACTION_OPEN_DATABASE"
const val ACTION_CLOSE_DATABASE = "keepass2android.ACTION_CLOSE_DATABASE"
const val ACTION_LOCK_DATABASE = "keepass2android.ACTION_LOCK_DATABASE"
const val ACTION_UNLOCK_DATABASE = "keepass2android.ACTION_UNLOCK_DATABASE"
/** Extra for ACTION_OPEN_DATABASE and ACTION_CLOSE_DATABASE containing a filepath which is used
* by KP2A internally to identify the file. Use only where necessary, might contain credentials
* for accessing the file (on remote storage).*/
const val EXTRA_DATABASE_FILEPATH = "keepass2android.EXTRA_DATABASE_FILEPATH"
/** Extra for ACTION_OPEN_DATABASE and ACTION_CLOSE_DATABASE containing a filepath which can be
* displayed to the user.*/
const val EXTRA_DATABASE_FILE_DISPLAYNAME = "keepass2android.EXTRA_DATABASE_FILE_DISPLAYNAME"
const val EXTRA_FIELD_VALUE = "keepass2android.EXTRA_FIELD_VALUE"
const val EXTRA_FIELD_PROTECTED = "keepass2android.EXTRA_FIELD_PROTECTED"
const val PREFIX_STRING = "STRING_"
const val PREFIX_BINARY = "BINARY_"
}

View File

@ -1,77 +0,0 @@
package net.helcel.fidelity.tools
import android.content.ContentValues
import android.util.Log
import androidx.annotation.OptIn
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import com.google.mlkit.vision.barcode.BarcodeScanner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import net.helcel.fidelity.tools.BarcodeFormatConverter.formatToString
import java.util.concurrent.Executors
@OptIn(ExperimentalGetImage::class)
object BarcodeScanner {
private fun processImageProxy(
barcodeScanner: BarcodeScanner,
imageProxy: ImageProxy,
cb: (String?, String?) -> Unit
) {
imageProxy.image?.let { image ->
val inputImage =
InputImage.fromMediaImage(
image,
imageProxy.imageInfo.rotationDegrees
)
barcodeScanner.process(inputImage)
.addOnSuccessListener { barcodeList ->
println(barcodeList.map { e -> e.displayValue })
println(barcodeList.map { e -> e.format })
val barcode =
barcodeList.getOrNull(0)
if (barcode != null)
cb(barcode.displayValue, formatToString(barcode.format))
}
.addOnFailureListener {
Log.e(ContentValues.TAG, it.message.orEmpty())
}.addOnCompleteListener {
imageProxy.image?.close()
imageProxy.close()
}
}
}
fun getAnalysisUseCase(cb: (String?, String?) -> Unit): ImageAnalysis {
val options = BarcodeScannerOptions.Builder().setBarcodeFormats(
Barcode.FORMAT_CODE_128,
Barcode.FORMAT_CODE_39,
Barcode.FORMAT_CODE_93,
Barcode.FORMAT_EAN_8,
Barcode.FORMAT_EAN_13,
Barcode.FORMAT_QR_CODE,
Barcode.FORMAT_UPC_A,
Barcode.FORMAT_UPC_E,
Barcode.FORMAT_PDF417
).build()
val scanner = BarcodeScanning.getClient(options)
val analysisUseCase = ImageAnalysis.Builder()
.build()
analysisUseCase.setAnalyzer(
Executors.newSingleThreadExecutor()
) { imageProxy ->
processImageProxy(scanner, imageProxy, cb)
}
return analysisUseCase
}
}

View File

@ -1,6 +1,5 @@
package net.helcel.fidelity.tools
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.zxing.BarcodeFormat
object BarcodeFormatConverter {
@ -16,22 +15,39 @@ object BarcodeFormatConverter {
"UPC_A" -> BarcodeFormat.UPC_A
"UPC_E" -> BarcodeFormat.UPC_E
"PDF_417" -> BarcodeFormat.PDF_417
"AZTEC" -> BarcodeFormat.AZTEC
"CODABAR" -> BarcodeFormat.CODABAR
"MAXICODE" -> BarcodeFormat.MAXICODE
"DATA_MATRIX" -> BarcodeFormat.DATA_MATRIX
"ITF" -> BarcodeFormat.ITF
"RSS_14" -> BarcodeFormat.RSS_14
"RSS_EXPANDED" -> BarcodeFormat.RSS_EXPANDED
"UPC_EAN" -> BarcodeFormat.UPC_EAN_EXTENSION
else -> throw Exception("Unsupported Format: $f")
}
}
fun formatToString(f: Int): String {
fun formatToString(f: BarcodeFormat): String {
return when (f) {
Barcode.FORMAT_CODE_128 -> "CODE_128"
Barcode.FORMAT_CODE_39 -> "CODE_39"
Barcode.FORMAT_CODE_93 -> "CODE_93"
Barcode.FORMAT_EAN_8 -> "EAN_8"
Barcode.FORMAT_EAN_13 -> "EAN_13"
Barcode.FORMAT_QR_CODE -> "CODE_QR"
Barcode.FORMAT_UPC_A -> "UPC_A"
Barcode.FORMAT_UPC_E -> "UPC_E"
Barcode.FORMAT_PDF417 -> "PDF_417"
BarcodeFormat.CODE_39 -> "CODE_39"
BarcodeFormat.CODE_93 -> "CODE_93"
BarcodeFormat.CODE_128 -> "CODE_128"
BarcodeFormat.EAN_8 -> "EAN_8"
BarcodeFormat.EAN_13 -> "EAN_13"
BarcodeFormat.QR_CODE -> "CODE_QR"
BarcodeFormat.UPC_A -> "UPC_A"
BarcodeFormat.UPC_E -> "UPC_E"
BarcodeFormat.PDF_417 -> "PDF_417"
BarcodeFormat.AZTEC -> "AZTEC"
BarcodeFormat.CODABAR -> "CODABAR"
BarcodeFormat.MAXICODE -> "MAXICODE"
BarcodeFormat.DATA_MATRIX -> "DATA_MATRIX"
BarcodeFormat.ITF -> "ITF"
BarcodeFormat.RSS_14 -> "RSS_14"
BarcodeFormat.RSS_EXPANDED -> "RSS_EXPANDED"
BarcodeFormat.UPC_EAN_EXTENSION -> "UPC_EAN"
else -> throw Exception("Unsupported Format: $f")
}
}

View File

@ -19,25 +19,25 @@ object BarcodeGenerator {
android.graphics.Color.WHITE
}
fun generateBarcode(content: String, f: String, width: Int): Bitmap? {
if (content.isEmpty() || f.isEmpty()) {
fun generateBarcode(content: String?, f: String?, w: Int): Bitmap? {
if (content.isNullOrEmpty() || f.isNullOrEmpty()) {
return null
}
try {
val format = stringToFormat(f)
val writer = MultiFormatWriter()
val height = (formatToRatio(format) * width).toInt()
val height = (w * formatToRatio(format)).toInt()
val width = (w * 1.0f).toInt()
val bitMatrix: BitMatrix = writer.encode(content, format, width, height)
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
for (x in 0 until width) {
for (y in 0 until height) {
bitmap.setPixel(
x,
y,
getPixelColor(bitMatrix, x, y)
x, y, getPixelColor(bitMatrix, x, y)
)
}
}
return bitmap

View File

@ -0,0 +1,61 @@
package net.helcel.fidelity.tools
import android.graphics.Bitmap
import androidx.annotation.OptIn
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import com.google.zxing.BinaryBitmap
import com.google.zxing.MultiFormatReader
import com.google.zxing.NotFoundException
import com.google.zxing.RGBLuminanceSource
import com.google.zxing.ReaderException
import com.google.zxing.common.HybridBinarizer
import net.helcel.fidelity.tools.BarcodeFormatConverter.formatToString
import java.util.concurrent.Executors
@OptIn(ExperimentalGetImage::class)
object BarcodeScanner {
private fun processImage(
bitmap: Bitmap,
cb: (String?, String?) -> Unit
) {
val binaryBitmap = createBinaryBitmap(bitmap)
val reader = MultiFormatReader()
try {
val result = reader.decode(binaryBitmap)
cb(result.text, formatToString(result.barcodeFormat))
} catch (e: NotFoundException) {
cb(null, null)
} catch (e: ReaderException) {
cb(null, null)
}
}
private fun createBinaryBitmap(bitmap: Bitmap): BinaryBitmap {
val pixels = IntArray(bitmap.width * bitmap.height)
bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
val source =
RGBLuminanceSource(bitmap.width, bitmap.height, pixels)
return BinaryBitmap(HybridBinarizer(source))
}
fun analysisUseCase(cb: (String?, String?) -> Unit): ImageAnalysis {
val analysisUseCase = ImageAnalysis.Builder().build()
analysisUseCase.setAnalyzer(
Executors.newSingleThreadExecutor()
) { imageProxy ->
val bitmap = imageProxy.toBitmap()
imageProxy.close()
bitmapUseCase(bitmap, cb)
}
return analysisUseCase
}
fun bitmapUseCase(bitmap: Bitmap, cb: (String?, String?) -> Unit) {
processImage(bitmap, cb)
}
}

View File

@ -1,22 +1,31 @@
package net.helcel.fidelity.tools
import android.app.Activity
import android.content.Context
import android.widget.Toast
object ErrorToaster {
private fun helper(activity: Activity, message: String, length: Int) {
private fun helper(activity: Context?, message: String, length: Int) {
if (activity != null)
Toast.makeText(activity, message, length).show()
}
fun noKP2AFound(activity: Activity) {
fun noKP2AFound(activity: Context?) {
helper(activity, "KeePass2Android Not Installed", Toast.LENGTH_LONG)
}
fun formIncomplete(activity: Activity) {
fun formIncomplete(activity: Context?) {
helper(activity, "Form Incomplete", Toast.LENGTH_SHORT)
}
fun invalidFormat(activity: Activity) {
fun invalidFormat(activity: Context?) {
helper(activity, "Invalid Format", Toast.LENGTH_SHORT)
}
fun nothingFound(activity: Context?) {
helper(activity, "Nothing Found", Toast.LENGTH_SHORT)
}
fun noPermission(activity: Context?) {
helper(activity, "Missing Permission", Toast.LENGTH_LONG)
}
}

View File

@ -6,7 +6,7 @@ import android.os.Bundle
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import net.helcel.fidelity.pluginSDK.KeepassDefs
import net.helcel.fidelity.pluginSDK.KeepassDef
import net.helcel.fidelity.pluginSDK.Kp2aControl
object KeepassWrapper {
@ -21,12 +21,12 @@ object KeepassWrapper {
code: String,
format: String,
protectCode: Boolean,
): Pair<HashMap<String?, String?>, ArrayList<String?>> {
): Pair<HashMap<String, String>, ArrayList<String>> {
val fields = HashMap<String?, String?>()
val protected = ArrayList<String?>()
fields[KeepassDefs.TitleField] = title
fields[KeepassDefs.UrlField] =
val fields = HashMap<String, String>()
val protected = ArrayList<String>()
fields[KeepassDef.TitleField] = title
fields[KeepassDef.UrlField] =
"androidapp://" + fragment.requireActivity().packageName
fields[CODE_FIELD] = code
fields[FORMAT_FIELD] = format
@ -37,33 +37,13 @@ object KeepassWrapper {
}
fun resultLauncherAdd(
fun resultLauncher(
fragment: Fragment,
callback: (HashMap<String, String>) -> Unit
): ActivityResultLauncher<Intent> {
return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
val credentials = Kp2aControl.getEntryFieldsFromIntent(
data!!
)
println(credentials)
callback(credentials)
}
}
}
fun resultLauncherQuery(
fragment: Fragment,
callback: (HashMap<String, String>) -> Unit
): ActivityResultLauncher<Intent> {
return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
val credentials = Kp2aControl.getEntryFieldsFromIntent(
data!!
)
println(credentials)
val credentials = Kp2aControl.getEntryFieldsFromIntent(result.data)
callback(credentials)
}
}
@ -71,7 +51,7 @@ object KeepassWrapper {
fun entryExtract(map: HashMap<String, String>): Triple<String?, String?, String?> {
return Triple(
map[KeepassDefs.TitleField],
map[KeepassDef.TitleField],
map[CODE_FIELD],
map[FORMAT_FIELD]
)
@ -85,6 +65,10 @@ object KeepassWrapper {
return data
}
fun bundleCreate(triple: Triple<String?, String?, String?>): Bundle {
return bundleCreate(triple.first, triple.second, triple.third)
}
fun bundleExtract(data: Bundle?): Triple<String?, String?, String?> {
return Triple(
data?.getString("title"),

View File

@ -1,21 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="52"
android:viewportHeight="52">
<group
android:translateX="-10"
android:translateY="-10">
<path
android:fillColor="#EA5A47"
android:pathData="M60.7,26.2c0,-7.2 -5.9,-13.1 -13.1,-13.1c-5,0 -9.3,2.8 -11.5,6.9c-2.2,-4.1 -6.6,-6.9 -11.5,-6.9c-7.2,0 -13.1,5.9 -13.1,13.1c0,3.1 1.1,6 2.9,8.2l0,0l21.8,27l21.8,-27l0,0C59.6,32.2 60.7,29.4 60.7,26.2z" />
<path
android:fillColor="#00000000"
android:pathData="M60.7,26.2c0,-7.2 -5.9,-13.1 -13.1,-13.1c-5,0 -9.3,2.8 -11.5,6.9c-2.2,-4.1 -6.6,-6.9 -11.5,-6.9c-7.2,0 -13.1,5.9 -13.1,13.1c0,3.1 1.1,6 2.9,8.2l0,0l21.8,27l21.8,-27l0,0C59.6,32.2 60.7,29.4 60.7,26.2z"
android:strokeWidth="2"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</group>
</vector>

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="128"
android:viewportHeight="128">
<group android:scaleX="1.2833333"
android:scaleY="1.2833333"
android:translateX="-16.612345"
android:translateY="-16.612345">
<group
android:translateX="34"
android:translateY="26">
<group
android:scaleX="0.8"
android:scaleY="1.0"
android:translateX="0"
android:translateY="0">
<path
android:fillColor="@color/blue"
android:pathData="M59.959,52.794H12.041c-0.552,0 -1,-0.448 -1,-1v-29.547c0,-0.552 0.448,-1 1,-1h47.918c0.552,0 1,0.448 1,1v29.547C60.959,52.347 60.511,52.794 59.959,52.794z"
android:strokeWidth="2"
android:strokeColor="#000000" />
</group>
<group
android:scaleX="0.4"
android:scaleY="0.5"
android:translateX="27"
android:translateY="15.75">
<path
android:fillColor="@color/red"
android:pathData="M46.5,56l-10,-11l-10,11l0,-45l20,0z"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="@color/red2"
android:fillAlpha="1.0"
android:pathData="M41.5,11l0,39l5,6l0,-45z"
android:strokeColor="#00000000" />
</group>
<group
android:scaleX="0.75"
android:scaleY="0.75"
android:translateX="6"
android:translateY="10">
<path
android:fillColor="#00000000"
android:pathData="M9,21V52 M12,21V52 M20,21V50 M28,21V50 M15,50V21H17V50H15 M23,50V21H25V50H23 M31,50V21H32V50H31 M35,21V52 M38,21V52"
android:strokeWidth="2"
android:strokeColor="#000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</group>
</group>
</group>
</vector>

View File

@ -1,31 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="52"
android:viewportHeight="52">
<group
android:translateX="-10"
android:translateY="-10">
<path
android:fillColor="#00000000"
android:pathData="M30.735,34.656l-16.432,16.026l0,7.24l7.565,0l0,-4.637l5.125,0l0,-5.857l5.098,0l2.404,-2.404l0,-4.358l2.015,0"
android:strokeWidth="2"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:pathData="M48.52,23.998m-3.952,0a3.952,3.952 0,1 1,7.904 0a3.952,3.952 0,1 1,-7.904 0"
android:strokeWidth="2"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:pathData="M34.226,31.178c-1.43,-4.238 -0.347,-9.221 3.18,-12.695c4.845,-4.772 12.465,-4.889 17.022,-0.263s4.322,12.244 -0.522,17.016c-3.917,3.858 -9.648,4.674 -14.108,2.4"
android:strokeWidth="2"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</group>
</vector>

View File

@ -1,31 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="52"
android:viewportHeight="52">
<group
android:translateX="-10"
android:translateY="-10">
<path
android:pathData="M53,32.25l1.875,0l0,26.875l-38,0l0,-26.875l1.875,0z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M21.375,28.915c0,-8.379 6.415,-16.274 14.318,-16.523c7.97,-0.251 15.41,7.285 14.742,16.523"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M25.548,28.915c0,-6.335 4.576,-12.305 10.212,-12.493c5.684,-0.19 10.991,5.508 10.514,12.493"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:width="128dp"
android:height="128dp"
android:gravity="center"
android:drawable="@drawable/card" />
<item
android:width="64dp"
android:height="64dp"
android:drawable="@drawable/barcode"
android:gravity="center"
android:right="32dp" />
<item
android:width="52dp"
android:height="52dp"
android:drawable="@drawable/bookmark"
android:gravity="center"
android:left="72dp"
android:bottom="20dp" />
</layer-list>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="128"
android:viewportHeight="128">
<group
android:translateX="34"
android:translateY="26">
<group
android:scaleX="0.8"
android:scaleY="1.0"
android:translateX="0"
android:translateY="0">
<path
android:fillColor="@color/blue"
android:pathData="M59.959,52.794H12.041c-0.552,0 -1,-0.448 -1,-1v-29.547c0,-0.552 0.448,-1 1,-1h47.918c0.552,0 1,0.448 1,1v29.547C60.959,52.347 60.511,52.794 59.959,52.794z"
android:strokeWidth="2"
android:strokeColor="#000000" />
</group>
<group
android:scaleX="0.4"
android:scaleY="0.5"
android:translateX="27"
android:translateY="15.75">
<path
android:fillColor="@color/red"
android:pathData="M46.5,56l-10,-11l-10,11l0,-45l20,0z"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="@color/red2"
android:fillAlpha="1.0"
android:pathData="M41.5,11l0,39l5,6l0,-45z"
android:strokeColor="#00000000" />
</group>
<group
android:scaleX="0.75"
android:scaleY="0.75"
android:translateX="6"
android:translateY="10">
<path
android:fillColor="#00000000"
android:pathData="M9,21V52 M12,21V52 M20,21V50 M28,21V50 M15,50V21H17V50H15 M23,50V21H25V50H23 M31,50V21H32V50H31 M35,21V52 M38,21V52"
android:strokeWidth="2"
android:strokeColor="#000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</group>
</group>
</vector>

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="58"
android:viewportHeight="58">
<group android:translateX="-10" android:translateY="-8">
<path
android:pathData="m57.008,20.304v-3.356l-27.338,-0.002c-0.198,0 -0.359,-0.165 -0.359,-0.368l-0.069,-1.517c-0.116,-1.788 -1.34,-3.003 -2.997,-3.003h-11.287c-1.657,0 -3,1.343 -3,3v40.943"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="m17.027,55.568c-0.59,1.954 -2.972,4.139 -4.646,4.394l44.665,0.011c1.657,0 2.323,-0.439 3,-3s7,-31.657 7,-31.657c0,-0.552 -0.448,-1 -1,-1H24.965c-0.552,0 -1,0.448 -1,1 0,0 -6.348,28.299 -6.938,30.253Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@ -0,0 +1,302 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="58"
android:viewportHeight="58">
<group android:translateX="-8" android:translateY="-8">
<path
android:pathData="M20,20h4v4h-4z"
android:fillColor="#000"/>
<path
android:pathData="M20,48h4v4h-4z"
android:fillColor="#000"/>
<path
android:pathData="M48,20h4v4h-4z"
android:fillColor="#000"/>
<path
android:pathData="M18,40m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M16,38m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M20,38m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M34,46m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M40,38m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M40,28m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M32,16m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M46,32m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M52,32m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M52,44m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M54,48m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M56,56m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M32,56m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M44,56m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M46,54m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M44,52m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M16,32m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M40,54m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"
android:fillColor="#000"/>
<path
android:pathData="M12,12h48v48h-48z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"/>
<path
android:pathData="M16,16h12v12h-12z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"/>
<path
android:pathData="M20,20h4v4h-4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"/>
<path
android:pathData="M16,44h12v12h-12z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"/>
<path
android:pathData="M20,48h4v4h-4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"/>
<path
android:pathData="M44,16h12v12h-12z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"/>
<path
android:pathData="M48,20h4v4h-4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"/>
<path
android:pathData="M18,36V34H26"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M20,34V32"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M24,34V40"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M24,38H26"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M38,32V30"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M56,34H54"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M42,42H44V40"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M28,32H30"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M34,32H40"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M38,16V20H36V28"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M36,26H32V28"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M36,20H32"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M36,22H34V18"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M28,36H36"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M30,36V40H28"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M34,36V38"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M32,44V42H38V48H42V46H50V56"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M36,40V44H42M46,40H42V48H44"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M48,34V38H50V42H48V46"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M50,38V36H52"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M52,50H48V52"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M32,52H34V54H36V50"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M56,32V38H54"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M44,36V34"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M56,42V44"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M54,52H56"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M40,22V24"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
@ -13,6 +11,7 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
tools:ignore="MergeRootFrame" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -14,7 +14,6 @@
android:padding="16dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/nameInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
@ -23,7 +22,11 @@
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editTextTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:minLines="1" />
</com.google.android.material.textfield.TextInputLayout>
@ -46,7 +49,11 @@
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editTextCode"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:inputType="text"
android:maxLines="1"
android:minLines="1" />
</com.google.android.material.textfield.TextInputLayout>
@ -64,7 +71,6 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/formatInputLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -77,6 +83,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:focusable="false"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>

View File

@ -3,13 +3,15 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:orientation="vertical"
tools:context=".activity.fragment.Launcher">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/fidelityList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:layout_margin="24dp" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
@ -25,7 +27,6 @@
app:srcCompat="@drawable/search" />
<LinearLayout
android:id="@+id/expandedMenuContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
@ -56,6 +57,16 @@
app:maxImageSize="32dp"
app:srcCompat="@drawable/camera" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btnOpen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:contentDescription="@string/open"
app:fabCustomSize="46dp"
app:maxImageSize="32dp"
app:srcCompat="@drawable/open" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btnManual"
android:layout_width="wrap_content"

View File

@ -11,12 +11,27 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/bottomText"
<net.helcel.fidelity.activity.view.ScannerView
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_height="match_parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btnScanDone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#ffffff"
android:textSize="24sp" />
android:layout_centerHorizontal="true"
android:layout_margin="24dp"
android:contentDescription="@string/manual" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/ScanActive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_margin="28dp"
android:indeterminate="true" />
</RelativeLayout>

View File

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/textViewFeelings"
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp"

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="darkgray">#FF0C1D2E</color>
<color name="gray">#425F7C</color>
<color name="lightgray">#FF93A9BE</color>
<color name="white">#FFF0F3F7</color>
<color name="blue">#7DB9F5</color>
<color name="blue2">#3193F5</color>
<color name="red">#F57D7D</color>
<color name="red2">#F53131</color>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#0C1D2E</color>
</resources>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="kp2aplugin_title" tools:keep="@string/kp2aplugin_title">Fidelity</string>
<string name="kp2aplugin_shortdesc">Stores and Displays fidelity and other cards</string>
<string name="kp2aplugin_author" tools:keep="@string/kp2aplugin_author">[soraefir](soraefir)</string>
<string name="kp2aplugin_shortdesc" tools:keep="@string/kp2aplugin_shortdesc">Fidelity adds an interface to manage fidelity cards and other barcodes to Keepass2Android</string>
<string name="kp2aplugin_author" tools:keep="@string/kp2aplugin_author">Soraefir</string>
<string name="app_name">Keepass Fidelity</string>
@ -15,15 +15,20 @@
<string name="code">Code</string>
<string name="format">Format</string>
<string name="save">Save</string>
<string name="open">Open</string>
<string-array name="format_array">
<item>CODE_128</item>
<item>CODE_39</item>
<item>CODE_93</item>
<item>CODE_128</item>
<item>EAN_8</item>
<item>EAN_13</item>
<item>CODE_QR</item>
<item>UPC_A</item>
<item>UPC_E</item>
<item>PDF_417</item>
<item>AZTEC</item>
<item>CODABAR</item>
<item>DATA_MATRIX</item>
<item>ITF</item>
</string-array>
</resources>

View File

@ -1,3 +0,0 @@
<resources>
</resources>

View File

@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Fidelity" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">?attr/colorAccent</item>
<style name="Theme.Fidelity" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/blue</item>
<item name="colorPrimaryVariant">@color/blue</item>
<item name="colorSecondary">@color/blue</item>
<item name="colorSecondaryVariant">@color/blue</item>
<item name="colorOnPrimary">@color/darkgray</item>
</style>
</resources>

View File

@ -1,7 +1,8 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.3.1' apply false
id 'com.android.library' version '8.3.0' apply false
id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
id 'com.android.application' version '8.11.0' apply false
id 'com.android.library' version '8.11.0' apply false
id 'org.jetbrains.kotlin.android' version '2.2.0' apply false
id 'com.autonomousapps.dependency-analysis' version '2.19.0' apply true
}

View File

@ -15,7 +15,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
android.enableJetifier=true
android.enableJetifier=false
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

12
gradlew vendored
View File

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -112,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@ -203,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

6
gradlew.bat vendored
View File

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@ -68,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@ -0,0 +1 @@
<p><i>Keepass-Fidelity</i> adds an interface to view/save barcodes (QR included) to Keepass through the plugin interface of the Keepass2Android app.</p><p><br></p><ul><li><b>Launcher:</b> view and launch recent entries (a per entry flag can disable this behaviour)</li><li><b>View:</b> view entries from the history or queried from Keepass2Android</li><li><b>Create:</b> add entries from the camera, an image of by filling out a form. The entry is then created in the Keepass2Android app</li><li><b>Data:</b> the app uses the following data Title (entry name), barcode type (QR, UPC, ...), barcode content (number/text content) and a "secure" flag (enable/disable caching the entry).</li></ul>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1 @@
<ul><li><b>CAMERA:</b> necessary for importing barcodes from camera</li><li><b>READ_MEDIA_VISUAL_USER_SELECTED:</b> necessary for the importing barcode from images</li></ul>

View File

@ -0,0 +1 @@
Fidelity (Membership/Loyalty) Card plugin for Keepass2Android

View File

@ -14,5 +14,6 @@ dependencyResolutionManagement {
maven { url 'https://jitpack.io' }
}
}
rootProject.name = "BeenDroid"
rootProject.name = "Fidelity"
include ':app'