From 0f9dbc6eaf7e4e1a88d493b0e45feecd9303e5b4 Mon Sep 17 00:00:00 2001 From: Seddik Kadi Date: Tue, 11 Jul 2023 11:07:54 +0200 Subject: [PATCH] new: add QR code to quickly find recipient when preparing a payment --- android/app/capacitor.build.gradle | 2 + android/app/src/main/AndroidManifest.xml | 14 +- .../src/main/assets/capacitor.plugins.json | 8 + android/build.gradle | 2 +- android/capacitor.settings.gradle | 6 + .../gradle/wrapper/gradle-wrapper.properties | 2 +- ios/App/App/Info.plist | 2 + ios/App/Podfile | 2 + package-lock.json | 159 +++++++++++++++--- package.json | 3 + src/App.vue | 5 +- src/components/BankAccountItem.vue | 21 +++ src/components/Modal.vue | 2 + src/components/MoneyTransferModal.vue | 125 +++++++++++--- src/components/QrCodeModal.vue | 84 +++++++++ src/components/ScanQrCode.vue | 66 ++++++++ src/components/TheBankAccountList.vue | 2 + src/i18n/fr-FR/app.po | 78 ++++++--- src/main.ts | 3 +- src/services/QrCodeService.ts | 145 ++++++++++++++++ src/utils/fonts.ts | 4 +- 21 files changed, 654 insertions(+), 81 deletions(-) create mode 100644 src/components/QrCodeModal.vue create mode 100644 src/components/ScanQrCode.vue create mode 100644 src/services/QrCodeService.ts diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index ee3c041d..a54b8cc7 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -9,6 +9,8 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { + implementation project(':lokavaluto-barcode-scanner') + implementation project(':capacitor-camera') implementation project(':capacitor-app') implementation project(':capacitor-filesystem') implementation project(':capacitor-share') diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 05cd7a10..9415a038 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,10 @@ + android:resource="@xml/file_paths" /> @@ -41,5 +43,15 @@ + + + + + + + diff --git a/android/app/src/main/assets/capacitor.plugins.json b/android/app/src/main/assets/capacitor.plugins.json index 6bdc72bb..bd60d869 100644 --- a/android/app/src/main/assets/capacitor.plugins.json +++ b/android/app/src/main/assets/capacitor.plugins.json @@ -1,4 +1,12 @@ [ + { + "pkg": "@lokavaluto/barcode-scanner", + "classpath": "com.getcapacitor.community.barcodescanner.BarcodeScanner" + }, + { + "pkg": "@capacitor/camera", + "classpath": "com.capacitorjs.plugins.camera.CameraPlugin" + }, { "pkg": "@capacitor/app", "classpath": "com.capacitorjs.plugins.app.AppPlugin" diff --git a/android/build.gradle b/android/build.gradle index fe3625ec..f95836a9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:7.4.2' classpath 'com.google.gms:google-services:4.3.13' // NOTE: Do not place your application dependencies here; they belong diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index 91dec0eb..ada8d88f 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -2,6 +2,12 @@ include ':capacitor-android' project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') +include ':lokavaluto-barcode-scanner' +project(':lokavaluto-barcode-scanner').projectDir = new File('../node_modules/@lokavaluto/barcode-scanner/android') + +include ':capacitor-camera' +project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android') + include ':capacitor-app' project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android') diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 92f06b50..2ec77e51 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist index 9f00ebe7..6b6e17c0 100644 --- a/ios/App/App/Info.plist +++ b/ios/App/App/Info.plist @@ -53,5 +53,7 @@ UIStatusBarStyle UIStatusBarStyleLightContent + NSCameraUsageDescription + To be able to scan barcodes diff --git a/ios/App/Podfile b/ios/App/Podfile index 8b3c463f..0ec0a237 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -11,6 +11,8 @@ install! 'cocoapods', :disable_input_output_paths => true def capacitor_pods pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' + pod 'LokavalutoBarcodeScanner', :path => '../../node_modules/@lokavaluto/barcode-scanner' + pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera' pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app' pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem' pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share' diff --git a/package-lock.json b/package-lock.json index c8e8b0ea..aabc99c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,12 @@ "hasInstallScript": true, "dependencies": { "@0k/types-request": "^1.0.1", + "@capacitor/camera": "^4.1.5", "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/vue-fontawesome": "^3.0.3", + "@lokavaluto/barcode-scanner": "^3.0.4", "@lokavaluto/lokapi-backend-comchain": "0.1.0 || >=0.1.1-alpha", "@lokavaluto/lokapi-backend-cyclos": "0.1.0 || >=0.1.1-alpha", "@lokavaluto/lokapi-browser": "0.1.0 || >=0.1.1-alpha", @@ -67,6 +69,7 @@ "mochawesome": "^7.1.3", "prettierx": "0k/prettierx#main", "process": "^0.11.10", + "qrcode.vue": "^3.4.0", "sass": "^1.62.1", "sass-loader": "^13.2.2", "semver": "^7.5.0", @@ -1880,6 +1883,14 @@ "@capacitor/core": "^4.0.0" } }, + "node_modules/@capacitor/camera": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@capacitor/camera/-/camera-4.1.5.tgz", + "integrity": "sha512-E00IRsJiIr1kP3EkJa0OWp//TDFXOx3ifDfmLOmIkPxKt1FfuNheE0ipWBo5b44pKsx9pO64Rm9MkfUhgH5Rfg==", + "peerDependencies": { + "@capacitor/core": "^4.0.0" + } + }, "node_modules/@capacitor/cli": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-4.8.0.tgz", @@ -1916,7 +1927,6 @@ "version": "4.8.0", "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-4.8.0.tgz", "integrity": "sha512-hFgLi1bGmADMmYMFAaitEsVheF3QgoMU4TrHkMgik51NzHwUq3nBdP5+A9oNS9qe3dEDyegoaHF8X9oJc9v8QQ==", - "dev": true, "dependencies": { "tslib": "^2.1.0" } @@ -2504,28 +2514,40 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@lokavaluto/barcode-scanner": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@lokavaluto/barcode-scanner/-/barcode-scanner-3.0.4.tgz", + "integrity": "sha512-a+ofDMp46jI6bc6H9umviq+nDziFyYjV95pnl/hzJvYVenbrL+HMNMQKwoewGyVZEpasCJPVuB6TwjsW86wA7w==", + "dependencies": { + "@zxing/browser": "0.1.1", + "@zxing/library": "^0.19.2" + }, + "peerDependencies": { + "@capacitor/core": "^4.5.0" + } + }, "node_modules/@lokavaluto/lokapi": { - "version": "0.1.1-alpha.202307252142", - "resolved": "https://registry.npmjs.org/@lokavaluto/lokapi/-/lokapi-0.1.1-alpha.202307252142.tgz", - "integrity": "sha512-kQwfU+y15bfrR9p2lwuwd8lr3pzSAxuEkRfr1nDzewbHfSyuDQIKYoLZDryw5bLsQxyPj9k5N6QyiZadOhS9pQ==", + "version": "0.1.1-alpha.202309131812", + "resolved": "https://registry.npmjs.org/@lokavaluto/lokapi/-/lokapi-0.1.1-alpha.202309131812.tgz", + "integrity": "sha512-2bj/2SBzQADhwad5V3tC88XbXRwmZnzkU5acCJ2BgnoJv+x2rVEFw8kDNNMAsiGUCVcmUgP0il3nr28HOR0ZDA==", "dependencies": { "@0k/types-request": "^1.0.0", "qs": "^6.10.1" } }, "node_modules/@lokavaluto/lokapi-backend-comchain": { - "version": "0.1.1-alpha.202307252138", - "resolved": "https://registry.npmjs.org/@lokavaluto/lokapi-backend-comchain/-/lokapi-backend-comchain-0.1.1-alpha.202307252138.tgz", - "integrity": "sha512-E6tdUPyzAMO/JbSpGvBmiLTEDbX70qlRWp3TXBUbLwokGoAJzWqPUBFxP2XmJW2Vg9Jr6AJAL+dggYfDNbnw6A==", + "version": "0.1.1-alpha.202309131842", + "resolved": "https://registry.npmjs.org/@lokavaluto/lokapi-backend-comchain/-/lokapi-backend-comchain-0.1.1-alpha.202309131842.tgz", + "integrity": "sha512-nAL+VnEmZxGZkgPNXv8SpiMVRUKKwQ0Bq4cTFWVA/18NOGBVRTLcyx4IRqkbp9FIrY5JkcAHUbRm4DMcZLDpzA==", "dependencies": { "@com-chain/jsc3l": ">=2.0.1-rc", "@lokavaluto/lokapi": "0.1.0 || >=0.1.1-alpha" } }, "node_modules/@lokavaluto/lokapi-backend-cyclos": { - "version": "0.1.1-alpha.202307252149", - "resolved": "https://registry.npmjs.org/@lokavaluto/lokapi-backend-cyclos/-/lokapi-backend-cyclos-0.1.1-alpha.202307252149.tgz", - "integrity": "sha512-kIJoC3iIfVWVYd+XIRaM1Nd0K541h/pZUCxhoGGBfwqFecjz5uRvVb36x3VGpIPRcK+/8AqaFoZFhLj0BKTIJg==", + "version": "0.1.1-alpha.202309131827", + "resolved": "https://registry.npmjs.org/@lokavaluto/lokapi-backend-cyclos/-/lokapi-backend-cyclos-0.1.1-alpha.202309131827.tgz", + "integrity": "sha512-RajKP1oJMz54WUBgP62PebM9y7Rm1NFHQeRGClBgzcl9yTmFjuft06q6I7o1rQ67j5ZEN4H/nv07A4e4HJF9jw==", "dependencies": { "@0k/types-request": "^1.0.1", "@lokavaluto/lokapi": "0.1.0 || >=0.1.1-alpha" @@ -4243,6 +4265,37 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "node_modules/@zxing/browser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.1.1.tgz", + "integrity": "sha512-fdcHrvvno1NnoiY5++kn/h2b9QnBNyqtK1JqbfY3dfmTdwyXCJr/aT2ZGyRcfe0D3I/THu1I5VJAcpoU0svQMQ==", + "optionalDependencies": { + "@zxing/text-encoding": "^0.9.0" + }, + "peerDependencies": { + "@zxing/library": "^0.19.1" + } + }, + "node_modules/@zxing/library": { + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.19.3.tgz", + "integrity": "sha512-RUv5svewpDoD0ymXleOP8yVTO5BLkR0zn5coGC/Vs1671u0OBJ4xdtR8WVWf08OcvrieEMHdSfQY3ZKtqII/hg==", + "dependencies": { + "ts-custom-error": "^3.2.1" + }, + "engines": { + "node": ">= 10.4.0" + }, + "optionalDependencies": { + "@zxing/text-encoding": "~0.9.0" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -14037,6 +14090,15 @@ "node": ">=6" } }, + "node_modules/qrcode.vue": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.4.0.tgz", + "integrity": "sha512-4XeImbv10Fin16Fl2DArCMhGyAdvIg2jb7vDT+hZiIAMg/6H6mz9nUZr/dR8jBcun5VzNzkiwKhiqOGbloinwA==", + "dev": true, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/qs": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", @@ -16096,6 +16158,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-custom-error": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz", + "integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ts-loader": { "version": "9.4.2", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", @@ -19175,6 +19245,12 @@ "dev": true, "requires": {} }, + "@capacitor/camera": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@capacitor/camera/-/camera-4.1.5.tgz", + "integrity": "sha512-E00IRsJiIr1kP3EkJa0OWp//TDFXOx3ifDfmLOmIkPxKt1FfuNheE0ipWBo5b44pKsx9pO64Rm9MkfUhgH5Rfg==", + "requires": {} + }, "@capacitor/cli": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-4.8.0.tgz", @@ -19204,7 +19280,6 @@ "version": "4.8.0", "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-4.8.0.tgz", "integrity": "sha512-hFgLi1bGmADMmYMFAaitEsVheF3QgoMU4TrHkMgik51NzHwUq3nBdP5+A9oNS9qe3dEDyegoaHF8X9oJc9v8QQ==", - "dev": true, "requires": { "tslib": "^2.1.0" } @@ -19679,28 +19754,37 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "@lokavaluto/barcode-scanner": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@lokavaluto/barcode-scanner/-/barcode-scanner-3.0.4.tgz", + "integrity": "sha512-a+ofDMp46jI6bc6H9umviq+nDziFyYjV95pnl/hzJvYVenbrL+HMNMQKwoewGyVZEpasCJPVuB6TwjsW86wA7w==", + "requires": { + "@zxing/browser": "0.1.1", + "@zxing/library": "^0.19.2" + } + }, "@lokavaluto/lokapi": { - "version": "0.1.1-alpha.202307252142", - "resolved": "https://registry.npmjs.org/@lokavaluto/lokapi/-/lokapi-0.1.1-alpha.202307252142.tgz", - "integrity": "sha512-kQwfU+y15bfrR9p2lwuwd8lr3pzSAxuEkRfr1nDzewbHfSyuDQIKYoLZDryw5bLsQxyPj9k5N6QyiZadOhS9pQ==", + "version": "0.1.1-alpha.202309131812", + "resolved": "https://registry.npmjs.org/@lokavaluto/lokapi/-/lokapi-0.1.1-alpha.202309131812.tgz", + "integrity": "sha512-2bj/2SBzQADhwad5V3tC88XbXRwmZnzkU5acCJ2BgnoJv+x2rVEFw8kDNNMAsiGUCVcmUgP0il3nr28HOR0ZDA==", "requires": { "@0k/types-request": "^1.0.0", "qs": "^6.10.1" } }, "@lokavaluto/lokapi-backend-comchain": { - "version": "0.1.1-alpha.202307252138", - "resolved": "https://registry.npmjs.org/@lokavaluto/lokapi-backend-comchain/-/lokapi-backend-comchain-0.1.1-alpha.202307252138.tgz", - "integrity": "sha512-E6tdUPyzAMO/JbSpGvBmiLTEDbX70qlRWp3TXBUbLwokGoAJzWqPUBFxP2XmJW2Vg9Jr6AJAL+dggYfDNbnw6A==", + "version": "0.1.1-alpha.202309131842", + "resolved": "https://registry.npmjs.org/@lokavaluto/lokapi-backend-comchain/-/lokapi-backend-comchain-0.1.1-alpha.202309131842.tgz", + "integrity": "sha512-nAL+VnEmZxGZkgPNXv8SpiMVRUKKwQ0Bq4cTFWVA/18NOGBVRTLcyx4IRqkbp9FIrY5JkcAHUbRm4DMcZLDpzA==", "requires": { "@com-chain/jsc3l": ">=2.0.1-rc", "@lokavaluto/lokapi": "0.1.0 || >=0.1.1-alpha" } }, "@lokavaluto/lokapi-backend-cyclos": { - "version": "0.1.1-alpha.202307252149", - "resolved": "https://registry.npmjs.org/@lokavaluto/lokapi-backend-cyclos/-/lokapi-backend-cyclos-0.1.1-alpha.202307252149.tgz", - "integrity": "sha512-kIJoC3iIfVWVYd+XIRaM1Nd0K541h/pZUCxhoGGBfwqFecjz5uRvVb36x3VGpIPRcK+/8AqaFoZFhLj0BKTIJg==", + "version": "0.1.1-alpha.202309131827", + "resolved": "https://registry.npmjs.org/@lokavaluto/lokapi-backend-cyclos/-/lokapi-backend-cyclos-0.1.1-alpha.202309131827.tgz", + "integrity": "sha512-RajKP1oJMz54WUBgP62PebM9y7Rm1NFHQeRGClBgzcl9yTmFjuft06q6I7o1rQ67j5ZEN4H/nv07A4e4HJF9jw==", "requires": { "@0k/types-request": "^1.0.1", "@lokavaluto/lokapi": "0.1.0 || >=0.1.1-alpha" @@ -21093,6 +21177,29 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "@zxing/browser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.1.1.tgz", + "integrity": "sha512-fdcHrvvno1NnoiY5++kn/h2b9QnBNyqtK1JqbfY3dfmTdwyXCJr/aT2ZGyRcfe0D3I/THu1I5VJAcpoU0svQMQ==", + "requires": { + "@zxing/text-encoding": "^0.9.0" + } + }, + "@zxing/library": { + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.19.3.tgz", + "integrity": "sha512-RUv5svewpDoD0ymXleOP8yVTO5BLkR0zn5coGC/Vs1671u0OBJ4xdtR8WVWf08OcvrieEMHdSfQY3ZKtqII/hg==", + "requires": { + "@zxing/text-encoding": "~0.9.0", + "ts-custom-error": "^3.2.1" + } + }, + "@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -28404,6 +28511,13 @@ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true }, + "qrcode.vue": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.4.0.tgz", + "integrity": "sha512-4XeImbv10Fin16Fl2DArCMhGyAdvIg2jb7vDT+hZiIAMg/6H6mz9nUZr/dR8jBcun5VzNzkiwKhiqOGbloinwA==", + "dev": true, + "requires": {} + }, "qs": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", @@ -29974,6 +30088,11 @@ "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", "dev": true }, + "ts-custom-error": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz", + "integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==" + }, "ts-loader": { "version": "9.4.2", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", diff --git a/package.json b/package.json index bd2bf488..0e5a5414 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ }, "dependencies": { "@0k/types-request": "^1.0.1", + "@lokavaluto/barcode-scanner": "^3.0.4", + "@capacitor/camera": "^4.1.5", "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", @@ -69,6 +71,7 @@ "mochawesome": "^7.1.3", "prettierx": "0k/prettierx#main", "process": "^0.11.10", + "qrcode.vue": "^3.4.0", "sass": "^1.62.1", "sass-loader": "^13.2.2", "semver": "^7.5.0", diff --git a/src/App.vue b/src/App.vue index 0cd4dac7..f36c4536 100644 --- a/src/App.vue +++ b/src/App.vue @@ -15,14 +15,17 @@ import { Capacitor } from "@capacitor/core" import { StatusBar, Style } from "@capacitor/status-bar" import { App as CapacitorApp } from "@capacitor/app" + import ScanQrCode from "@/components/ScanQrCode.vue" @Options({ - components: { TheNavBar, AuthChallenge, Dialog, Modal }, + components: { TheNavBar, AuthChallenge, Dialog, Modal, ScanQrCode }, async mounted() { if (Capacitor.getPlatform() === "ios") { await StatusBar.setStyle({ style: Style.Light }) } CapacitorApp.addListener("backButton", ({ canGoBack }) => { + // XXXvlab: will need a back button service to insert these + if (this.$qrCode.isActive()) return if (this.$modal.isActive.value) { this.$modal.back() return diff --git a/src/components/BankAccountItem.vue b/src/components/BankAccountItem.vue index 33c57227..24ee706e 100644 --- a/src/components/BankAccountItem.vue +++ b/src/components/BankAccountItem.vue @@ -25,6 +25,19 @@ + + + + + @@ -39,6 +52,8 @@ :isSub="true" class="mt-4 subaccount" @accountSelected="$emit('accountSelected', account)" + :qrcode="false" + :id="id" > @@ -50,6 +65,7 @@ import { mapGetters } from "vuex" import { Options, Vue } from "vue-class-component" import { mapModuleState } from "@/utils/vuex" + import QrCodeModal from "./QrCodeModal.vue" @Options({ name: "BankAccountItem", @@ -62,6 +78,11 @@ active: Boolean, subAccounts: Array, isSub: Boolean, + qrcode: Boolean, + id: Object, + }, + components: { + QrCodeModal, }, computed: { ...mapModuleState("lokapi", ["isMultiCurrency"]), diff --git a/src/components/Modal.vue b/src/components/Modal.vue index 60a24e23..92c7eec7 100644 --- a/src/components/Modal.vue +++ b/src/components/Modal.vue @@ -9,6 +9,7 @@ import AboutModal from "./AboutModal.vue" import TransactionListModal from "./TransactionListModal.vue" import ConfirmPaymentModal from "./ConfirmPaymentModal.vue" + import QrCodeModal from "./QrCodeModal.vue" @Options({ name: "Modal", @@ -18,6 +19,7 @@ AboutModal, TransactionListModal, ConfirmPaymentModal, + QrCodeModal, }, data() { return { diff --git a/src/components/MoneyTransferModal.vue b/src/components/MoneyTransferModal.vue index 848775f7..f6449a55 100644 --- a/src/components/MoneyTransferModal.vue +++ b/src/components/MoneyTransferModal.vue @@ -17,27 +17,32 @@ custom-search-bar " > - + + + + +
import { Options, Vue } from "vue-class-component" + import { mapModuleState } from "@/utils/vuex" import { e as LokapiExc } from "@lokavaluto/lokapi-browser" import Loading from "vue-loading-overlay" import "vue-loading-overlay/dist/css/index.css" import RecipientItem from "@/components/RecipientItem.vue" import BankAccountItem from "@/components/BankAccountItem.vue" + import { Capacitor } from "@capacitor/core" + import { App as CapacitorApp } from "@capacitor/app" + import { Camera, CameraResultType } from "@capacitor/camera" import UseBatchLoading from "@/services/UseBatchLoading" @@ -239,7 +248,7 @@ balance: false, amount: false, }, - account: null, + platform: null, } }, created() { @@ -250,11 +259,12 @@ } else { account = account._obj.parent } - this.account = account - const backend = account.parent + this.selectedBackend = account.parent this.recipientBatchLoader = UseBatchLoading({ - genFactory: backend.searchRecipients.bind(backend), + genFactory: this.selectedBackend.searchRecipients.bind( + this.selectedBackend + ), needMorePredicate: () => this.$refs.recipientsContainer.scrollHeight - (this.$refs.recipientsContainer.scrollTop + @@ -271,10 +281,12 @@ }) }, mounted() { + this.platform = Capacitor.getPlatform() this.setFocus("searchRecipient") this.recipientBatchLoader.newGen("") }, computed: { + ...mapModuleState("lokapi", ["userProfile"]), ownCurrenciesRecipients(): Array { let currencyIds = this.$store.getters.activeVirtualAccounts.map( (a: any) => a.currencyId @@ -285,6 +297,55 @@ }, }, methods: { + async startScan() { + const scanPermission = await this.$qrCode.isPermissionGranted() + if (!scanPermission) { + this.$qrCode.stopScan() + return + } + this.$loading.show() + await this.$qrCode.prepare() + this.$loading.hide() + + let result + try { + result = await this.$qrCode.startScan() + } catch (err) { + console.log("Humm, is that an actual error or did I stop scan ?") + return + } + const { rp, rpb } = JSON.parse(result.content) + if (rp === this.userProfile.id) { + this.$msg.error( + this.$gettext("You can not transfer money to your own account") + ) + this.$qrCode.stopScan() + return + } + if (!result.hasContent) { + this.$qrCode.stopScan() + this.$msg.error(this.$gettext("Unable to read qrcode")) + return + } + let recipient + try { + recipient = await this.selectedBackend.searchRecipientByUri({ + rp, + rpb, + }) + } catch (err) { + this.$msg.error( + this.$gettext("An error occured while searching recipient") + ) + console.log(err) + } + this.$qrCode.stopScan() + if (!recipient) { + this.$msg.error(this.$gettext("Invalid QrCode!")) + return + } + await this.handleClickRecipient(recipient) + }, async handleClickRecipient(recipient: any): Promise { this.selectedRecipient = recipient this.selectedRecipient.currencySymbol = await recipient.getSymbol() @@ -388,7 +449,7 @@ if (result === "later") return try { await this.selectedRecipient.toggleFavorite() - } catch (err: any) { + } catch (err) { // XXXvlab: using ``.then`` makes it trigger outside of // view js grasp. this.$errorHandler(err) @@ -464,10 +525,18 @@ width: 100%; } .custom-search-bar { - width: 89%; margin: auto; } - + .search-bar-container { + width: 75%; + } + .qrcode-icon { + font-size: 1.5em; + opacity: 0.8; + padding: 0.1em; + border: 0.2em solid #e8e8e8; + border-radius: 5px; + } .custom-search-bar input { background: #ffffff; border: 1px solid #e8e8e8; diff --git a/src/components/QrCodeModal.vue b/src/components/QrCodeModal.vue new file mode 100644 index 00000000..d7fb8a29 --- /dev/null +++ b/src/components/QrCodeModal.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/components/ScanQrCode.vue b/src/components/ScanQrCode.vue new file mode 100644 index 00000000..473a37ff --- /dev/null +++ b/src/components/ScanQrCode.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/components/TheBankAccountList.vue b/src/components/TheBankAccountList.vue index b4e3de6f..51a60b2a 100644 --- a/src/components/TheBankAccountList.vue +++ b/src/components/TheBankAccountList.vue @@ -66,6 +66,8 @@ class="mb-5" :class="{ selected: a._obj.internalId === account?._obj?.internalId }" @accountSelected="$emit('accountSelected', a)" + :qrcode="true" + :id="a.id" > diff --git a/src/i18n/fr-FR/app.po b/src/i18n/fr-FR/app.po index 14e61b96..d5deaafe 100644 --- a/src/i18n/fr-FR/app.po +++ b/src/i18n/fr-FR/app.po @@ -22,7 +22,7 @@ msgstr "" msgid "%{ appName } version %{ appVersion }" msgstr "%{ appName } version %{ appVersion }" -#: src/components/MoneyTransferModal.vue:398 +#: src/components/MoneyTransferModal.vue:459 msgid "%{ name } was added to your favorite list" msgstr "%{ name } a bien été ajouté en favori" @@ -55,15 +55,15 @@ msgstr "Demandes de comptes" msgid "Accounts waiting for approval" msgstr "Comptes en attente de validation" -#: src/components/MoneyTransferModal.vue:383 +#: src/components/MoneyTransferModal.vue:444 msgid "Add" msgstr "Ajouter" -#: src/components/MoneyTransferModal.vue:187 +#: src/components/MoneyTransferModal.vue:192 msgid "Add a memo (optional)" msgstr "Ajoutez un texte (optionnel)" -#: src/components/MoneyTransferModal.vue:377 +#: src/components/MoneyTransferModal.vue:438 msgid "Add as favorite" msgstr "Ajouter aux favoris" @@ -80,23 +80,27 @@ msgstr "Toutes les opérations" msgid "Already have an account ?" msgstr "Avez-vous déjà créé un compte ?" -#: src/components/MoneyTransferModal.vue:156 +#: src/components/MoneyTransferModal.vue:161 #: src/components/TransactionListModal.vue:428 #: src/views/admin/PendingCredits.vue:52 msgid "Amount" msgstr "Montant" -#: src/components/MoneyTransferModal.vue:312 +#: src/components/MoneyTransferModal.vue:373 msgid "Amount to send must be greater than 0" msgstr "Le montant à transférer doit être un nombre positif" +#: src/components/MoneyTransferModal.vue:338 +msgid "An error occured while searching recipient" +msgstr "Il y a eu un problème lors de la recherche du destinataire" + #: src/views/admin/PendingCredits.vue:168 msgid "An issue occured upon the approval of the credit request of %{ name }" msgstr "" "Il y a eu un problème lors de la tentative de validation de la demande de " "crédit de %{ name }" -#: src/components/MoneyTransferModal.vue:265 +#: src/components/MoneyTransferModal.vue:275 #: src/components/TransactionListModal.vue:327 msgid "An unexpected issue occured while downloading recipient list" msgstr "" @@ -111,7 +115,7 @@ msgstr "" "Il y a eu un problème lors de la tentative de téléchargement de la liste des " "transactions" -#: src/components/MoneyTransferModal.vue:348 +#: src/components/MoneyTransferModal.vue:409 msgid "" "An unexpected issue occurred during the money transfer. We are sorry for the " "inconvenience." @@ -163,7 +167,7 @@ msgstr "" "Une erreur inattendue est survenue pendant le chargement des portefeuilles. " "Veuillez nous excuser pour le désagrément." -#: src/components/MoneyTransferModal.vue:57 +#: src/components/MoneyTransferModal.vue:62 msgid "" "An unexpected issue occurred while performing recipient lookup. We apologise " "for the inconvenience." @@ -205,6 +209,10 @@ msgstr "Identification biométrique mise en place" msgid "click here" msgstr "cliquez ici" +#: src/components/QrCodeModal.vue:42 +msgid "Close" +msgstr "Fermer" + #: src/components/MoneyCreditModal.vue:170 msgid "Close and refresh" msgstr "Fermer et rafraîchir" @@ -262,7 +270,7 @@ msgid_plural "digits" msgstr[0] "chiffre" msgstr[1] "chiffres" -#: src/components/MoneyTransferModal.vue:378 +#: src/components/MoneyTransferModal.vue:439 msgid "Do you want to add %{ name } to your favorite list ?" msgstr "Voulez vous ajouter %{ name } aux favoris ?" @@ -275,7 +283,7 @@ msgid "Download transactions" msgstr "Télécharger les opérations" #: src/components/MoneyCreditModal.vue:81 -#: src/components/MoneyTransferModal.vue:166 +#: src/components/MoneyTransferModal.vue:171 msgid "e.g. 50" msgstr "ex: 50" @@ -333,9 +341,9 @@ msgstr "Mot de passe oublié ?" #: src/components/ConfirmPaymentModal.vue:59 #: src/components/ConfirmPaymentModal.vue:61 msgid "from" -msgstr "de la part de" +msgstr "De" -#: src/components/MoneyTransferModal.vue:139 +#: src/components/MoneyTransferModal.vue:144 msgid "From" msgstr "Depuis" @@ -355,6 +363,10 @@ msgstr "Identifiant ou mot de passe incorrect" msgid "Invalid email." msgstr "E-mail invalide." +#: src/components/MoneyTransferModal.vue:344 +msgid "Invalid QrCode!" +msgstr "code QR invalide" + #: src/main.ts:223 msgid "Language settings" msgstr "Préférences de langue" @@ -363,7 +375,7 @@ msgstr "Préférences de langue" msgid "Lastname" msgstr "Nom" -#: src/components/MoneyTransferModal.vue:384 +#: src/components/MoneyTransferModal.vue:445 msgid "Later" msgstr "Plus tard" @@ -403,8 +415,8 @@ msgstr "mois" msgid "Mutual credit" msgstr "Crédit mutuel" -#: src/components/MoneyTransferModal.vue:29 -#: src/components/MoneyTransferModal.vue:34 +#: src/components/MoneyTransferModal.vue:30 +#: src/components/MoneyTransferModal.vue:35 msgid "Name, email address or phone number" msgstr "Nom, adresse email ou téléphone" @@ -424,7 +436,7 @@ msgstr "Aucun compte en attente de validation" msgid "No previous transactions in your history." msgstr "Aucune opération dans votre historique" -#: src/components/MoneyTransferModal.vue:97 +#: src/components/MoneyTransferModal.vue:102 msgid "No recipients found" msgstr "Aucun destinataire de paiement à afficher" @@ -559,6 +571,10 @@ msgstr "" "Attention, ce mot de passe n'est pas récupérable, alors veillez à le garder " "dans un endroit sûr et accessible." +#: src/components/QrCodeModal.vue:28 +msgid "Please scan the QR code above to proceed" +msgstr "Veuillez scanner le code QR ci-dessus pour continuer" + #: src/components/CreateAccount.vue:292 msgid "Please try again or contact your administrator" msgstr "Veuillez ré-éssayer ou contacter votre administrateur" @@ -633,12 +649,12 @@ msgstr "Sélectionnez un intervalle de temps :" #: src/components/AuthChallengeDirect.vue:11 #: src/components/AuthChallengeDirect.vue:19 -#: src/components/MoneyTransferModal.vue:194 -#: src/components/MoneyTransferModal.vue:200 +#: src/components/MoneyTransferModal.vue:199 +#: src/components/MoneyTransferModal.vue:205 msgid "Send" msgstr "Envoyer" -#: src/components/MoneyTransferModal.vue:127 +#: src/components/MoneyTransferModal.vue:132 #: src/components/MoneyTransferModal.vue:8 msgid "Send money" msgstr "Envoyer de l'argent" @@ -702,7 +718,7 @@ msgstr "Source" msgid "Target" msgstr "Destination" -#: src/components/MoneyTransferModal.vue:337 +#: src/components/MoneyTransferModal.vue:398 msgid "Target account is inactive." msgstr "Le compte destinataire du tranfert est désactivé." @@ -710,7 +726,7 @@ msgstr "Le compte destinataire du tranfert est désactivé." msgid "Terms of Service" msgstr "CGU" -#: src/components/TheBankAccountList.vue:80 +#: src/components/TheBankAccountList.vue:82 msgid "" "The accounts listed below have been subjected to a creation request. Once " "the creation request is approved, these accounts will become usable." @@ -751,7 +767,7 @@ msgstr "" msgid "to" msgstr "à" -#: src/components/MoneyTransferModal.vue:152 +#: src/components/MoneyTransferModal.vue:157 msgid "To" msgstr "Vers" @@ -823,7 +839,7 @@ msgstr "Opération traitée" msgid "Transaction sent" msgstr "Opération envoyée" -#: src/components/MoneyTransferModal.vue:330 +#: src/components/MoneyTransferModal.vue:391 msgid "Transaction was refused due to insufficient balance" msgstr "Transaction refusée en raison de fonds insuffisants" @@ -831,6 +847,10 @@ msgstr "Transaction refusée en raison de fonds insuffisants" msgid "Transactions" msgstr "Opérations" +#: src/components/MoneyTransferModal.vue:327 +msgid "Unable to read qrcode" +msgstr "Incapable de lire le QR code." + #: src/views/Login.vue:235 msgid "Unexpected issue occurred while saving your credentials" msgstr "" @@ -906,7 +926,11 @@ msgstr "année" msgid "Yes" msgstr "Oui" -#: src/components/MoneyTransferModal.vue:353 +#: src/components/MoneyTransferModal.vue:320 +msgid "You can not transfer money to your own account" +msgstr "Vous ne pouvez pas envoyer de l'argent vers votre propre compte." + +#: src/components/MoneyTransferModal.vue:414 msgid "" "You can try again. If the issue persists, please contact your administrator." msgstr "" @@ -923,7 +947,7 @@ msgstr "" "Vous pouvez essayer de recharger la page, ou contacter votre administrateur " "si l'erreur persiste." -#: src/components/MoneyTransferModal.vue:339 +#: src/components/MoneyTransferModal.vue:400 msgid "You can't send money to this account." msgstr "Vous ne pouvez pas envoyer de l'argent vers ce compte." @@ -947,6 +971,6 @@ msgstr "Vos identifiants seront enregistrés après votre prochaine connexion" msgid "Your password" msgstr "Votre mot de passe" -#: src/components/TheBankAccountList.vue:76 +#: src/components/TheBankAccountList.vue:78 msgid "your pending accounts" msgstr "vos comptes en attente de création" diff --git a/src/main.ts b/src/main.ts index da4ab8a1..776219ec 100644 --- a/src/main.ts +++ b/src/main.ts @@ -33,7 +33,7 @@ import Biometry from "@/services/Biometry" import ToastService from "@/services/toastService" import { LokAPI } from "./services/lokapiService" import mkGettext from "./services/Gettext" - +import QrCodeService from "@/services/QrCodeService" import UseModal from "./services/UseModal" // Components @@ -289,6 +289,7 @@ fetchConfig("config.json").then(async (config: any) => { app.config.globalProperties.$auth = authService app.config.globalProperties.$prefs = prefsService app.config.globalProperties.$export = new ExportService(gettext) + app.config.globalProperties.$qrCode = new QrCodeService(gettext) app.config.globalProperties.$errorHandler = app.config.errorHandler app.config.globalProperties.$appInfo = { appName, appVersion } app.config.globalProperties.$modal = modal diff --git a/src/services/QrCodeService.ts b/src/services/QrCodeService.ts new file mode 100644 index 00000000..50a78971 --- /dev/null +++ b/src/services/QrCodeService.ts @@ -0,0 +1,145 @@ +import { Capacitor } from "@capacitor/core" +import { createApp } from "vue" +import { Camera, CameraResultType } from "@capacitor/camera" +import { BarcodeScanner as BarCodeScanner } from "@lokavaluto/barcode-scanner" +import { App as CapacitorApp } from "@capacitor/app" +import { PluginListenerHandle } from "@capacitor/core" +import { UIError } from "../exception" + +import ScanQrCode from "@/components/ScanQrCode.vue" + +class QrCodeService { + gettext: any + + constructor(gettext: any) { + this.gettext = gettext + } + listener: PluginListenerHandle | null = null + public async startScan(): Promise { + const app = document.getElementById("app") + if (!app) { + throw new Error("No element id 'app' found to insert qrcode ui") + } + if (this.listener) { + console.error("Unexpected qrcode listener still in memory") + return Promise.resolve() + } + + this.listener = await CapacitorApp.addListener( + "backButton", + ({ canGoBack }) => { + if (!canGoBack) { + console.warn("Ignoring unexpected canGoBack false value") + } + + this.stopScan() + } + ) + + const classicContent = document.createElement("div") + classicContent.id = "classic-content" + classicContent.style.display = "none" + classicContent.style.backgroundColor = "black" + while (app.firstChild) { + classicContent.appendChild(app.firstChild) + } + app.style.backgroundColor = "transparent" + app.appendChild(classicContent) + + const qrCodeContent = document.createElement("div") + qrCodeContent.id = "qrcode-content" + + app.appendChild(qrCodeContent) + + const component = createApp(ScanQrCode) + component.config.globalProperties.$qrCode = this + component.mount(qrCodeContent) + qrCodeContent.style.display = "block" + qrCodeContent.style.height = "fit-content" + classicContent.style.backgroundColor = "transparent" + classicContent.style.opacity = "0" + try { + return await BarCodeScanner.startScan() + } catch (err) { + console.log("popo", err) + throw err + } + } + public async prepare() { + await BarCodeScanner.prepare() + } + public isActive() { + return this.listener !== null + } + + public async stopScan() { + if (!this.listener) { + console.error("Unexpected missing qrcode listener") + return + } + this.listener.remove() + this.listener = null + + try { + await BarCodeScanner.stopScan() + } catch (err) { + console.log(err) + throw new UIError(this.gettext("Unexpected error occured"), null) + } + + const app = document.getElementById("app") + const classicContent = document.getElementById("classic-content") + const qrCodeContent = document.getElementById("qrcode-content") + if (!app || !classicContent || !qrCodeContent) { + throw new UIError(this.gettext("Unexpected error occured"), null) + return + } + while (classicContent.firstChild) { + app.appendChild(classicContent.firstChild) + } + app.style.backgroundColor = "" + app.removeChild(classicContent) + app.removeChild(qrCodeContent) + } + + public async isPermissionGranted(): Promise { + const platform = Capacitor.getPlatform() + let permission + let isPermissionGranted + if (platform == "android") { + permission = await Camera.requestPermissions() + isPermissionGranted = permission.camera === "granted" + } else if (platform == "ios") { + permission = await BarCodeScanner.checkPermission({ force: true }) + isPermissionGranted = permission.granted + } else if (platform === "web") { + try { + permission = await navigator.mediaDevices.getUserMedia({ + video: true, + }) + isPermissionGranted = permission.active + } catch (e: any) { + throw new UIError( + "The camera permission is not granted for this website", + e + ) + console.log(e) + return false + } + } + if (platform != "web") { + if (!isPermissionGranted) { + const enablePermission = confirm( + "If you want to grant permission for using your camera, enable it in the app settings." + ) + if (enablePermission) { + BarCodeScanner.openAppSettings() + } + return false + } + } + return isPermissionGranted + } +} + +export default QrCodeService diff --git a/src/utils/fonts.ts b/src/utils/fonts.ts index b37c4f1c..e7a9c68f 100644 --- a/src/utils/fonts.ts +++ b/src/utils/fonts.ts @@ -22,6 +22,7 @@ import { faCircleNotch, faUser, faXmark, + faQrcode, } from "@fortawesome/free-solid-svg-icons" import { faStar as farStar } from "@fortawesome/free-regular-svg-icons" import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome" @@ -48,7 +49,8 @@ library.add( faFingerprint, faCircleNotch, faUser, - faXmark + faXmark, + faQrcode ) export default FontAwesomeIcon