diff --git a/android/app/build.gradle b/android/app/build.gradle index 7416c22e..ceb73cbb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -52,6 +52,7 @@ android { dependencies { implementation platform('com.google.firebase:firebase-bom:32.7.4') + implementation 'com.journeyapps:zxing-android-embedded:4.3.0' implementation 'com.google.firebase:firebase-analytics-ktx' implementation 'com.google.firebase:firebase-messaging-ktx' implementation 'com.squareup.okhttp3:okhttp:4.10.0' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index f04d6f26..cf5ef65c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -45,6 +45,11 @@ tools:ignore="LockedOrientationActivity" /> + (R.id.loginApiKeyTextInputLayout) + val apiKeyInput = findViewById(R.id.loginApiKeyTextInput) + + apiKeyInput.setOnClickListener { + startQrCodeScan() + } + + apiKeyInputLayout.setEndIconOnClickListener { + startQrCodeScan() + } + } + + private val barcodeLauncher = registerForActivityResult(ScanContract()) { result -> + if (result.contents != null) { + val apiKeyInput = findViewById(R.id.loginApiKeyTextInput) + apiKeyInput.setText(result.contents) + Toast.makeText(this, "Scanned: ${result.contents}", Toast.LENGTH_LONG).show() + } else { + Toast.makeText(this, "Scan cancelled", Toast.LENGTH_SHORT).show() + } + } + + private fun startQrCodeScan() { + val options = ScanOptions() + options.setPrompt("Scan a QR code") + options.setBeepEnabled(true) + options.setOrientationLocked(false) + options.setCameraId(0) + barcodeLauncher.launch(options) } override fun onStart() { diff --git a/android/app/src/main/res/layout/activity_login.xml b/android/app/src/main/res/layout/activity_login.xml index 0a1f046c..cc4d4eb1 100644 --- a/android/app/src/main/res/layout/activity_login.xml +++ b/android/app/src/main/res/layout/activity_login.xml @@ -41,10 +41,13 @@ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" android:layout_width="match_parent" android:layout_height="wrap_content" - app:errorEnabled="true" android:hint="@string/text_area_api_key" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toTopOf="parent" + app:errorEnabled="true" + app:endIconMode="custom" + app:endIconDrawable="@android:drawable/ic_menu_camera" + app:endIconContentDescription="cameraButton" + app:layout_constraintBottom_toTopOf="@+id/loginPhoneNumberLayoutSIM1" + app:layout_constraintTop_toBottomOf="@+id/textView" tools:layout_editor_absoluteX="16dp"> + + {{ mdiQrcode }} + Show QR Code + + + + API Key QR Code + + + + + + Close + + + { + this.generateQrCode(this.apiKey); + }); + } + }, + }, async mounted() { await Promise.all([ this.$store.dispatch('clearAxiosError'), @@ -818,6 +851,16 @@ export default Vue.extend({ }, methods: { + generateQrCode(text) { + const canvas = this.$refs.qrCodeCanvas; + if (canvas) { + QRCode.toCanvas(canvas, text, { errorCorrectionLevel: 'H' }, (err) => { + if (err) { + console.error(err); + } + }); + } + }, updateEmailNotifications() { this.notificationSettings = { webhook_enabled: diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index b4e47417..926b6825 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: nuxt-highlightjs: specifier: ^1.0.3 version: 1.0.3 + qrcode: + specifier: ^1.5.0 + version: 1.5.0 ufo: specifier: ^1.5.4 version: 1.5.4