diff --git a/apps/browser-extension/src/i18n/locales/fi.json b/apps/browser-extension/src/i18n/locales/fi.json
index 3d270e5b0..f1bbfaef2 100644
--- a/apps/browser-extension/src/i18n/locales/fi.json
+++ b/apps/browser-extension/src/i18n/locales/fi.json
@@ -57,7 +57,7 @@
"next": "Seuraava",
"use": "Käytä",
"delete": "Poista",
- "save": "Save",
+ "save": "Tallenna",
"or": "Tai",
"close": "Sulje",
"copied": "Kopioitu!",
@@ -242,13 +242,13 @@
"enterEmailPrefix": "Syötä sähköpostin etuliite"
},
"totp": {
- "addCode": "Add 2FA Code",
- "instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
- "nameOptional": "Name (optional)",
- "secretKey": "Secret Key",
- "saveToViewCode": "Save to view code",
+ "addCode": "Lisää 2FA TOTP -koodi",
+ "instructions": "Syötä salainen avain, joka näkyy sivustossa, jossa haluat lisätä kaksivaiheisen tunnistautumisen",
+ "nameOptional": "Nimi (valinnainen)",
+ "secretKey": "Salainen avain",
+ "saveToViewCode": "Tallenna nähdäksesi koodin",
"errors": {
- "invalidSecretKey": "Invalid secret key format."
+ "invalidSecretKey": "Virheellinen salatun avaimen muoto."
}
},
"emails": {
diff --git a/apps/browser-extension/src/i18n/locales/pl.json b/apps/browser-extension/src/i18n/locales/pl.json
index ffebabdc6..b816d1c89 100644
--- a/apps/browser-extension/src/i18n/locales/pl.json
+++ b/apps/browser-extension/src/i18n/locales/pl.json
@@ -57,7 +57,7 @@
"next": "Dalej",
"use": "Użyj",
"delete": "Usuń",
- "save": "Save",
+ "save": "Zapisz",
"or": "lub",
"close": "Zamknąć",
"copied": "Skopiowano",
@@ -238,22 +238,22 @@
"publicEmailDescription": "Anonimowa, ale ograniczona prywatność. Treści e-mail są czytelne dla każdego, kto zna adres.",
"useDomainChooser": "Użyj wybierania domen",
"enterCustomDomain": "Wprowadź własną domenę",
- "enterFullEmail": "Wprowadź pełny adres e-mail",
+ "enterFullEmail": "Wprowadź adres e-mail",
"enterEmailPrefix": "Wprowadź prefiks e-mail"
},
"totp": {
- "addCode": "Add 2FA Code",
- "instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
- "nameOptional": "Name (optional)",
- "secretKey": "Secret Key",
- "saveToViewCode": "Save to view code",
+ "addCode": "Dodaj kod 2FA",
+ "instructions": "Wprowadź tajny klucz wyświetlony na stronie internetowej, na której chcesz dodać uwierzytelnianie dwuskładnikowe.",
+ "nameOptional": "Nazwa (opcjonalnie)",
+ "secretKey": "Tajny klucz",
+ "saveToViewCode": "Zapisz, aby wyświetlić kod",
"errors": {
- "invalidSecretKey": "Invalid secret key format."
+ "invalidSecretKey": "Nieprawidłowy format tajnego klucza."
}
},
"emails": {
"title": "Skrzynka odbiorcza",
- "deleteEmailTitle": "Usuń adres e-mail",
+ "deleteEmailTitle": "Usuń e-mail",
"deleteEmailConfirm": "Czy na pewno chcesz trwale usunąć ten e-mail?",
"from": "Od",
"to": "Do",
diff --git a/apps/browser-extension/src/i18n/locales/ru.json b/apps/browser-extension/src/i18n/locales/ru.json
index 41264d8e3..fb20a36b7 100644
--- a/apps/browser-extension/src/i18n/locales/ru.json
+++ b/apps/browser-extension/src/i18n/locales/ru.json
@@ -57,7 +57,7 @@
"next": "Далее",
"use": "Использовать",
"delete": "Удалить",
- "save": "Save",
+ "save": "Сохранить",
"or": "Или",
"close": "Закрыть",
"copied": "Скопировано!",
@@ -242,13 +242,13 @@
"enterEmailPrefix": "Введите префикс электронной почты"
},
"totp": {
- "addCode": "Add 2FA Code",
- "instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
- "nameOptional": "Name (optional)",
- "secretKey": "Secret Key",
- "saveToViewCode": "Save to view code",
+ "addCode": "Добавить код 2FA",
+ "instructions": "Введите секретный ключ, указанный на веб-сайте, где вы хотите добавить двухфакторную аутентификацию.",
+ "nameOptional": "Имя (необязательно)",
+ "secretKey": "Секретный ключ",
+ "saveToViewCode": "Сохранить для просмотра кода",
"errors": {
- "invalidSecretKey": "Invalid secret key format."
+ "invalidSecretKey": "Неверный формат секретного ключа."
}
},
"emails": {
diff --git a/apps/mobile-app/android/app/build.gradle b/apps/mobile-app/android/app/build.gradle
index 0c10410d6..119ecf22e 100644
--- a/apps/mobile-app/android/app/build.gradle
+++ b/apps/mobile-app/android/app/build.gradle
@@ -202,6 +202,9 @@ dependencies {
// Add modern SQLite library with VACUUM INTO and backup API support
implementation("com.github.requery:sqlite-android:3.49.0")
+ // Add ZXing library for QR code scanning (F-Droid compatible)
+ implementation("com.journeyapps:zxing-android-embedded:4.3.0")
+
// Test dependencies
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.0.0'
diff --git a/apps/mobile-app/android/app/src/main/AndroidManifest.xml b/apps/mobile-app/android/app/src/main/AndroidManifest.xml
index d14b7086e..0dd908452 100644
--- a/apps/mobile-app/android/app/src/main/AndroidManifest.xml
+++ b/apps/mobile-app/android/app/src/main/AndroidManifest.xml
@@ -58,6 +58,13 @@
android:theme="@style/PasskeyRegistrationTheme"
android:screenOrientation="portrait" />
+
+
+
diff --git a/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/MainActivity.kt b/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/MainActivity.kt
index dc98c2176..a282af1de 100644
--- a/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/MainActivity.kt
+++ b/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/MainActivity.kt
@@ -113,6 +113,8 @@ class MainActivity : ReactActivity() {
handlePinUnlockResult(resultCode, data)
} else if (requestCode == net.aliasvault.app.nativevaultmanager.NativeVaultManager.PIN_SETUP_REQUEST_CODE) {
handlePinSetupResult(resultCode, data)
+ } else if (requestCode == net.aliasvault.app.nativevaultmanager.NativeVaultManager.QR_SCANNER_REQUEST_CODE) {
+ handleQRScannerResult(resultCode, data)
}
}
@@ -194,4 +196,31 @@ class MainActivity : ReactActivity() {
}
}
}
+
+ /**
+ * Handle QR scanner result.
+ * @param resultCode The result code from the QR scanner activity.
+ * @param data The intent data containing the scanned QR code.
+ */
+ private fun handleQRScannerResult(resultCode: Int, data: Intent?) {
+ val promise = net.aliasvault.app.nativevaultmanager.NativeVaultManager.pendingActivityResultPromise
+ net.aliasvault.app.nativevaultmanager.NativeVaultManager.pendingActivityResultPromise = null
+
+ if (promise == null) {
+ return
+ }
+
+ when (resultCode) {
+ RESULT_OK -> {
+ val scannedData = data?.getStringExtra("SCAN_RESULT")
+ promise.resolve(scannedData)
+ }
+ RESULT_CANCELED -> {
+ promise.resolve(null)
+ }
+ else -> {
+ promise.resolve(null)
+ }
+ }
+ }
}
diff --git a/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/nativevaultmanager/NativeVaultManager.kt b/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/nativevaultmanager/NativeVaultManager.kt
index 0b3b1e44b..f93aa8724 100644
--- a/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/nativevaultmanager/NativeVaultManager.kt
+++ b/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/nativevaultmanager/NativeVaultManager.kt
@@ -21,6 +21,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
+import net.aliasvault.app.qrscanner.QRScannerActivity
import net.aliasvault.app.vaultstore.VaultStore
import net.aliasvault.app.vaultstore.VaultSyncError
import net.aliasvault.app.vaultstore.keystoreprovider.AndroidKeystoreProvider
@@ -63,6 +64,11 @@ class NativeVaultManager(reactContext: ReactApplicationContext) :
*/
const val PIN_SETUP_REQUEST_CODE = 1002
+ /**
+ * Request code for QR scanner activity.
+ */
+ const val QR_SCANNER_REQUEST_CODE = 1003
+
/**
* Static holder for the pending promise from showPinUnlock.
* This allows MainActivity to resolve/reject the promise directly without
@@ -1436,6 +1442,42 @@ class NativeVaultManager(reactContext: ReactApplicationContext) :
* @param subtitle The subtitle for authentication. If null or empty, uses default.
* @param promise The promise to resolve with authentication result.
*/
+ @ReactMethod
+ override fun scanQRCode(prefixes: ReadableArray?, statusText: String?, promise: Promise) {
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ val activity = currentActivity
+ if (activity == null) {
+ promise.reject("NO_ACTIVITY", "No activity available", null)
+ return@launch
+ }
+
+ // Store promise for later resolution by MainActivity
+ pendingActivityResultPromise = promise
+
+ // Launch QR scanner activity with optional prefixes and status text
+ val intent = Intent(activity, QRScannerActivity::class.java)
+ if (prefixes != null && prefixes.size() > 0) {
+ val prefixList = ArrayList()
+ for (i in 0 until prefixes.size()) {
+ val prefix = prefixes.getString(i)
+ if (prefix != null) {
+ prefixList.add(prefix)
+ }
+ }
+ intent.putStringArrayListExtra(QRScannerActivity.EXTRA_PREFIXES, prefixList)
+ }
+ if (statusText != null && statusText.isNotEmpty()) {
+ intent.putExtra(QRScannerActivity.EXTRA_STATUS_TEXT, statusText)
+ }
+ activity.startActivityForResult(intent, QR_SCANNER_REQUEST_CODE)
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to launch QR scanner", e)
+ promise.reject("SCANNER_ERROR", "Failed to launch QR scanner: ${e.message}", e)
+ }
+ }
+ }
+
@ReactMethod
override fun authenticateUser(title: String?, subtitle: String?, promise: Promise) {
CoroutineScope(Dispatchers.Main).launch {
diff --git a/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/qrscanner/QRScannerActivity.kt b/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/qrscanner/QRScannerActivity.kt
new file mode 100644
index 000000000..efa88b29d
--- /dev/null
+++ b/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/qrscanner/QRScannerActivity.kt
@@ -0,0 +1,147 @@
+package net.aliasvault.app.qrscanner
+
+import android.animation.ObjectAnimator
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import com.google.zxing.ResultPoint
+import com.journeyapps.barcodescanner.BarcodeCallback
+import com.journeyapps.barcodescanner.BarcodeResult
+import com.journeyapps.barcodescanner.CaptureManager
+import com.journeyapps.barcodescanner.DecoratedBarcodeView
+
+/**
+ * Activity for scanning QR codes using ZXing.
+ */
+class QRScannerActivity : Activity() {
+ private lateinit var barcodeView: DecoratedBarcodeView
+ private lateinit var capture: CaptureManager
+ private var hasScanned = false
+ private var prefixes: List? = null
+
+ companion object {
+ /** Intent extra key for prefixes. */
+ const val EXTRA_PREFIXES = "EXTRA_PREFIXES"
+
+ /** Intent extra key for status text. */
+ const val EXTRA_STATUS_TEXT = "EXTRA_STATUS_TEXT"
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // Get prefixes from intent if provided
+ prefixes = intent.getStringArrayListExtra(EXTRA_PREFIXES)
+
+ // Get status text from intent, default to "Scan QR code" if not provided
+ val statusText = intent.getStringExtra(EXTRA_STATUS_TEXT)?.takeIf { it.isNotEmpty() } ?: "Scan QR code"
+
+ // Create and configure barcode view
+ barcodeView = DecoratedBarcodeView(this)
+ barcodeView.setStatusText(statusText)
+ setContentView(barcodeView)
+
+ // Initialize capture manager
+ capture = CaptureManager(this, barcodeView)
+ capture.initializeFromIntent(intent, savedInstanceState)
+
+ // Set custom callback to add visual feedback
+ barcodeView.decodeContinuous(object : BarcodeCallback {
+ override fun barcodeResult(result: BarcodeResult?) {
+ if (result != null && !hasScanned) {
+ val scannedText = result.text
+
+ // Check if prefixes filter is enabled
+ if (prefixes != null && prefixes!!.isNotEmpty()) {
+ // Check if the scanned code starts with any of the accepted prefixes
+ val hasValidPrefix = prefixes!!.any { prefix ->
+ scannedText.startsWith(prefix)
+ }
+
+ if (!hasValidPrefix) {
+ // Invalid QR code - continue scanning without setting hasScanned
+ // Note: ZXing library continues scanning automatically
+ return
+ }
+ }
+
+ // Valid QR code
+ hasScanned = true
+
+ // Show success animation
+ showScanSuccessAnimation()
+
+ // Pause scanning
+ barcodeView.pause()
+
+ // Set result and finish after animation
+ val resultIntent = Intent()
+ resultIntent.putExtra("SCAN_RESULT", scannedText)
+ setResult(RESULT_OK, resultIntent)
+
+ // Delay finish to allow animation to complete
+ barcodeView.postDelayed({
+ finish()
+ }, 400) // 400ms delay for animation
+ }
+ }
+
+ override fun possibleResultPoints(resultPoints: List) {
+ // No visualization needed
+ }
+ })
+ }
+
+ /**
+ * Show a success animation when QR code is scanned.
+ */
+ private fun showScanSuccessAnimation() {
+ // Flash animation - fade viewfinder quickly
+ val viewFinder: View? = barcodeView.viewFinder
+ if (viewFinder != null) {
+ // Create flash effect by animating alpha
+ val fadeOut = ObjectAnimator.ofFloat(viewFinder, "alpha", 1f, 0.3f)
+ fadeOut.duration = 100
+
+ val fadeIn = ObjectAnimator.ofFloat(viewFinder, "alpha", 0.3f, 1f)
+ fadeIn.duration = 100
+
+ fadeOut.start()
+ fadeOut.addListener(object : android.animation.AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: android.animation.Animator) {
+ fadeIn.start()
+ }
+ })
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ capture.onResume()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ capture.onPause()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ capture.onDestroy()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ capture.onSaveInstanceState(outState)
+ }
+
+ @Deprecated("Deprecated in Java")
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray,
+ ) {
+ capture.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ }
+}
diff --git a/apps/mobile-app/android/fdroid/.gitignore b/apps/mobile-app/android/fdroid/.gitignore
index 30f68ef89..e8a47d513 100644
--- a/apps/mobile-app/android/fdroid/.gitignore
+++ b/apps/mobile-app/android/fdroid/.gitignore
@@ -1,2 +1,3 @@
fdroiddata
fdroidserver
+net.aliasvault.app.yml
diff --git a/apps/mobile-app/android/fdroid/docker-compose.yml b/apps/mobile-app/android/fdroid/docker-compose.yml
index 3985e118e..52c68d2ab 100644
--- a/apps/mobile-app/android/fdroid/docker-compose.yml
+++ b/apps/mobile-app/android/fdroid/docker-compose.yml
@@ -14,6 +14,8 @@ services:
- ./net.aliasvault.app.yml:/net.aliasvault.app.yml
# Add build script to the container
- ./scripts/build.sh:/build.sh:Z
+ # Bind the outputs directory to capture APK build output
+ - ./outputs:/outputs:rw
# Increase memory limits for Gradle builds
shm_size: '2gb'
mem_limit: 12g
diff --git a/apps/mobile-app/android/fdroid/net.aliasvault.app.yml b/apps/mobile-app/android/fdroid/net.aliasvault.app.yml.template
similarity index 89%
rename from apps/mobile-app/android/fdroid/net.aliasvault.app.yml
rename to apps/mobile-app/android/fdroid/net.aliasvault.app.yml.template
index 897f2d36a..158054e84 100644
--- a/apps/mobile-app/android/fdroid/net.aliasvault.app.yml
+++ b/apps/mobile-app/android/fdroid/net.aliasvault.app.yml.template
@@ -13,9 +13,9 @@ RepoType: git
Repo: https://github.com/aliasvault/aliasvault.git
Builds:
- - versionName: 0.1.0
- versionCode: 1
- commit: main
+ - versionName: __VERSION_NAME__
+ versionCode: __VERSION_CODE__
+ commit: __COMMIT__
subdir: apps/mobile-app/android/app/
sudo:
- sysctl fs.inotify.max_user_watches=524288 || true
@@ -26,7 +26,7 @@ Builds:
init:
- cd ../..
- sed -i -e '/signingConfig /d' android/app/build.gradle
- - npm install --build-from-source
+ - npm install --production --build-from-source
gradle:
- yes
scanignore:
@@ -44,8 +44,6 @@ Builds:
- apps/mobile-app/node_modules/react-native-context-menu-view/android/build.gradle
- apps/mobile-app/node_modules/react-native-get-random-values/android/build.gradle
- apps/mobile-app/node_modules/react-native-svg/android/build.gradle
- - apps/mobile-app/node_modules/expo-dev-launcher/android/build.gradle
- - apps/mobile-app/node_modules/expo-dev-menu/android/build.gradle
scandelete:
- apps/mobile-app/node_modules/
diff --git a/apps/mobile-app/android/fdroid/run.sh b/apps/mobile-app/android/fdroid/run.sh
index 3b843a69d..a5441211d 100755
--- a/apps/mobile-app/android/fdroid/run.sh
+++ b/apps/mobile-app/android/fdroid/run.sh
@@ -2,11 +2,64 @@
set -e # Exit on any error, except where explicitly ignored
trap 'echo "🛑 Interrupted. Exiting..."; exit 130' INT # Handle Ctrl+C cleanly
+# Get the directory where this script is located
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+BUILD_GRADLE="${SCRIPT_DIR}/../app/build.gradle"
+TEMPLATE_FILE="${SCRIPT_DIR}/net.aliasvault.app.yml.template"
+OUTPUT_FILE="${SCRIPT_DIR}/net.aliasvault.app.yml"
+
+# Check if template exists
+if [ ! -f "$TEMPLATE_FILE" ]; then
+ echo "❌ Error: Template file not found: $TEMPLATE_FILE"
+ exit 1
+fi
+
+# Check if build.gradle exists
+if [ ! -f "$BUILD_GRADLE" ]; then
+ echo "❌ Error: build.gradle not found: $BUILD_GRADLE"
+ exit 1
+fi
+
+# Extract version information from build.gradle
+echo "📱 Extracting version information from build.gradle..."
+VERSION_CODE=$(grep -E '^\s*versionCode\s+' "$BUILD_GRADLE" | sed -E 's/.*versionCode\s+([0-9]+).*/\1/')
+VERSION_NAME=$(grep -E '^\s*versionName\s+' "$BUILD_GRADLE" | sed -E 's/.*versionName\s+"([^"]+)".*/\1/')
+
+if [ -z "$VERSION_CODE" ] || [ -z "$VERSION_NAME" ]; then
+ echo "❌ Error: Could not extract version information from build.gradle"
+ echo " versionCode: ${VERSION_CODE:-not found}"
+ echo " versionName: ${VERSION_NAME:-not found}"
+ exit 1
+fi
+
+# Get current git branch
+CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
+
+echo "✅ Version information extracted:"
+echo " versionCode: $VERSION_CODE"
+echo " versionName: $VERSION_NAME"
+echo " commit: $CURRENT_BRANCH"
+
+# Generate the F-Droid metadata file from template
+echo "📝 Generating F-Droid metadata file..."
+sed -e "s/__VERSION_NAME__/$VERSION_NAME/g" \
+ -e "s/__VERSION_CODE__/$VERSION_CODE/g" \
+ -e "s/__COMMIT__/$CURRENT_BRANCH/g" \
+ "$TEMPLATE_FILE" > "$OUTPUT_FILE"
+
+echo "✅ Generated: $OUTPUT_FILE"
+
+# Create outputs bind dir and set correct permissions
+mkdir -p outputs
+sudo chown -R 1000:1000 outputs
+
# Build and run the Docker environment
-echo "Building Docker images..."
+echo "🐳 Building Docker images..."
if ! docker compose build; then
echo "⚠️ Warning: Docker build failed, continuing..."
fi
-echo "Running fdroid-buildserver..."
-docker compose run --rm fdroid-buildserver
\ No newline at end of file
+echo "🚀 Running fdroid-buildserver..."
+docker compose run --rm fdroid-buildserver
+
+echo "✅ F-Droid build completed!"
diff --git a/apps/mobile-app/android/fdroid/scripts/build.sh b/apps/mobile-app/android/fdroid/scripts/build.sh
index 7df37dbd9..8476db36d 100755
--- a/apps/mobile-app/android/fdroid/scripts/build.sh
+++ b/apps/mobile-app/android/fdroid/scripts/build.sh
@@ -22,5 +22,9 @@ cd /home/vagrant/build
fdroid fetchsrclibs net.aliasvault.app --verbose
# Format build receipe
fdroid rewritemeta net.aliasvault.app
+# Lint app
+fdroid lint --verbose net.aliasvault.app
# Build app and scan for any binary files that are prohibited
-fdroid build --verbose --latest --scan-binary --on-server --no-tarball net.aliasvault.app
\ No newline at end of file
+fdroid build --verbose --test --latest --scan-binary --on-server --no-tarball net.aliasvault.app
+# Copy any outputs to the bind mount folder
+rsync -avh /home/vagrant/build/build/net.aliasvault.app/apps/mobile-app/android/app/build/outputs/ /outputs/
diff --git a/apps/mobile-app/android/fdroid/scripts/sign-apk.sh b/apps/mobile-app/android/fdroid/scripts/sign-apk.sh
new file mode 100755
index 000000000..77341f053
--- /dev/null
+++ b/apps/mobile-app/android/fdroid/scripts/sign-apk.sh
@@ -0,0 +1,124 @@
+#!/usr/bin/env bash
+
+# ================================
+# This script is used to sign an unsigned F-Droid APK file with the local debug keystore (on MacOS) for testing purposes.
+# ================================
+# Flow:
+# 1. First do the run.sh / build.sh flow to build the F-Droid APK file on a (Linux) machine with enough memory and CPU power.
+# 2. Extract the unsigned APK file from the local (bind-mounted) outputs directory
+# 3. Then use this script to sign the APK file with the local debug keystore (on MacOS).
+#
+# ================================
+
+set -euo pipefail
+
+# --- Colors ---
+RED="\033[0;31m"
+GREEN="\033[0;32m"
+YELLOW="\033[1;33m"
+CYAN="\033[0;36m"
+RESET="\033[0m"
+
+info() { echo -e "${CYAN}[INFO]${RESET} $1"; }
+ok() { echo -e "${GREEN}[OK]${RESET} $1"; }
+error() { echo -e "${RED}[ERROR]${RESET} $1"; }
+
+echo -e "${YELLOW}=== APK Debug Signer (macOS) ===${RESET}"
+
+# --- Ask for unsigned APK ---
+read -rp "Enter unsigned APK filename (example: app-release-unsigned.apk): " APK_IN
+
+if [[ ! -f "$APK_IN" ]]; then
+ error "File not found: $APK_IN"
+ exit 1
+fi
+
+info "Input APK: $APK_IN"
+
+# --- Detect SDK and build-tools ---
+SDK_ROOT="${ANDROID_SDK_ROOT:-$HOME/Library/Android/sdk}"
+BT_DIR="$SDK_ROOT/build-tools"
+
+if [[ ! -d "$BT_DIR" ]]; then
+ error "build-tools not found in: $BT_DIR"
+ exit 1
+fi
+
+info "Scanning build-tools..."
+
+LATEST_BT="$(ls "$BT_DIR" | sort -V | tail -n 1)"
+
+if [[ -z "$LATEST_BT" ]]; then
+ error "No build-tools found."
+ exit 1
+fi
+
+info "Using build-tools version: ${YELLOW}${LATEST_BT}${RESET}"
+
+ZIPALIGN="$BT_DIR/$LATEST_BT/zipalign"
+APKSIGNER="$BT_DIR/$LATEST_BT/apksigner"
+
+[[ -x "$ZIPALIGN" ]] || { error "zipalign missing: $ZIPALIGN"; exit 1; }
+[[ -x "$APKSIGNER" ]] || { error "apksigner missing: $APKSIGNER"; exit 1; }
+
+# --- Filenames ---
+APK_ALIGNED="${APK_IN%.apk}-aligned-temp.apk"
+APK_SIGNED="${APK_IN%.apk}-signed.apk"
+
+info "Temporary aligned APK: $APK_ALIGNED"
+info "Final signed APK: $APK_SIGNED"
+
+# --- Debug keystore ---
+DEBUG_KEYSTORE="$HOME/.android/debug.keystore"
+DEBUG_ALIAS="androiddebugkey"
+DEBUG_PASS="android"
+
+[[ -f "$DEBUG_KEYSTORE" ]] || {
+ error "Debug keystore missing: $DEBUG_KEYSTORE"
+ exit 1
+}
+
+info "Using debug keystore: $DEBUG_KEYSTORE"
+
+# --- Step 1: zipalign ---
+echo -e "${YELLOW}=== Step 1: zipalign ===${RESET}"
+echo -e "[CMD] \"$ZIPALIGN\" -p -f 4 \"$APK_IN\" \"$APK_ALIGNED\""
+
+"$ZIPALIGN" -p -f 4 "$APK_IN" "$APK_ALIGNED"
+ok "zipalign complete"
+
+# --- Step 2: sign ---
+echo -e "${YELLOW}=== Step 2: apksigner ===${RESET}"
+echo -e "[CMD] \"$APKSIGNER\" sign --ks \"$DEBUG_KEYSTORE\" --out \"$APK_SIGNED\" \"$APK_ALIGNED\""
+
+"$APKSIGNER" sign \
+ --ks "$DEBUG_KEYSTORE" \
+ --ks-key-alias "$DEBUG_ALIAS" \
+ --ks-pass "pass:$DEBUG_PASS" \
+ --key-pass "pass:$DEBUG_PASS" \
+ --out "$APK_SIGNED" \
+ "$APK_ALIGNED"
+
+ok "Signing complete"
+
+# --- Step 3: verify ---
+echo -e "${YELLOW}=== Step 3: Verify ===${RESET}"
+
+"$APKSIGNER" verify --verbose "$APK_SIGNED"
+ok "APK verified"
+
+# --- Step 4: Cleanup ---
+echo -e "${YELLOW}=== Cleanup ===${RESET}"
+
+if [[ -f "$APK_ALIGNED" ]]; then
+ rm -f "$APK_ALIGNED"
+ ok "Removed temporary file: $APK_ALIGNED"
+fi
+
+ok "Cleanup complete"
+
+echo -e "${GREEN}=== DONE ===${RESET}"
+echo -e "Signed APK created → ${YELLOW}$APK_SIGNED${RESET}"
+echo -e "Install with:"
+echo -e " ${CYAN}adb install -r \"$APK_SIGNED\"${RESET}"
+
diff --git a/apps/mobile-app/android/gradle.properties b/apps/mobile-app/android/gradle.properties
index a0a18dd2a..889455a91 100644
--- a/apps/mobile-app/android/gradle.properties
+++ b/apps/mobile-app/android/gradle.properties
@@ -52,6 +52,9 @@ expo.webp.animated=false
# Enable network inspector
EX_DEV_CLIENT_NETWORK_INSPECTOR=true
+# Enable VisionCamera code scanner
+VisionCamera_enableCodeScanner=true
+
# Use legacy packaging to compress native libraries in the resulting APK.
expo.useLegacyPackaging=false
diff --git a/apps/mobile-app/app/(tabs)/settings/qr-scanner.tsx b/apps/mobile-app/app/(tabs)/settings/qr-scanner.tsx
index 68b3b2893..278139892 100644
--- a/apps/mobile-app/app/(tabs)/settings/qr-scanner.tsx
+++ b/apps/mobile-app/app/(tabs)/settings/qr-scanner.tsx
@@ -1,8 +1,6 @@
-import { Ionicons } from '@expo/vector-icons';
-import { CameraView, useCameraPermissions } from 'expo-camera';
import { Href, router, useLocalSearchParams } from 'expo-router';
import { useEffect, useCallback, useRef } from 'react';
-import { View, Alert, StyleSheet } from 'react-native';
+import { View, StyleSheet, Platform, Alert } from 'react-native';
import { useColors } from '@/hooks/useColorScheme';
import { useTranslation } from '@/hooks/useTranslation';
@@ -10,6 +8,7 @@ import { useTranslation } from '@/hooks/useTranslation';
import LoadingIndicator from '@/components/LoadingIndicator';
import { ThemedContainer } from '@/components/themed/ThemedContainer';
import { ThemedText } from '@/components/themed/ThemedText';
+import NativeVaultManager from '@/specs/NativeVaultManager';
// QR Code type prefixes
const QR_CODE_PREFIXES = {
@@ -54,71 +53,73 @@ function parseQRCode(data: string): ScannedQRCode {
export default function QRScannerScreen() : React.ReactNode {
const colors = useColors();
const { t } = useTranslation();
- const [permission, requestPermission] = useCameraPermissions();
const { url } = useLocalSearchParams<{ url?: string }>();
const hasProcessedUrl = useRef(false);
const processedUrls = useRef(new Set());
-
- // Request camera permission on mount
- useEffect(() => {
- /**
- * Request camera permission.
- */
- const requestCameraPermission = async () : Promise => {
- if (!permission) {
- return; // Still loading permission status
- }
-
- if (!permission.granted && permission.canAskAgain) {
- // Request permission
- await requestPermission();
- } else if (!permission.granted && !permission.canAskAgain) {
- // Permission was permanently denied
- Alert.alert(
- t('settings.qrScanner.cameraPermissionTitle'),
- t('settings.qrScanner.cameraPermissionMessage'),
- [{ text: t('common.ok'), /**
- * Go back to the settings tab.
- */
- onPress: (): void => router.back() }]
- );
- }
- };
-
- requestCameraPermission();
- }, [permission, requestPermission, t]);
+ const hasLaunchedScanner = useRef(false);
/*
* Handle barcode scanned - parse and navigate to appropriate page.
- * Only processes AliasVault QR codes, silently ignores others.
+ * Native scanner already filters by prefix, so we only get AliasVault QR codes here.
* Validation is handled by the destination page.
*/
- const handleBarcodeScanned = useCallback(({ data }: { data: string }) : void => {
+ const handleQRCodeScanned = useCallback((data: string) : void => {
// Prevent processing the same URL multiple times
if (processedUrls.current.has(data)) {
return;
}
- // Parse the QR code to determine its type
- const parsedData = parseQRCode(data);
-
- // Silently ignore non-AliasVault QR codes
- if (!parsedData.type) {
- return;
- }
-
// Mark this URL as processed
processedUrls.current.add(data);
+ // Parse the QR code to determine its type
+ const parsedData = parseQRCode(data);
+
/*
* Navigate to the appropriate page based on QR code type
- * Validation will be handled by the destination page
+ * Use push instead of replace to navigate while scanner is still dismissing
+ * This creates a smoother transition without returning to settings first
*/
if (parsedData.type === 'MOBILE_UNLOCK') {
- router.replace(`/(tabs)/settings/mobile-unlock/${parsedData.payload}` as Href);
+ router.push(`/(tabs)/settings/mobile-unlock/${parsedData.payload}` as Href);
}
}, []);
+ /**
+ * Launch the native QR scanner.
+ */
+ const launchScanner = useCallback(async () => {
+ if (hasLaunchedScanner.current) {
+ return;
+ }
+
+ hasLaunchedScanner.current = true;
+
+ try {
+ // Pass prefixes to native scanner for filtering and translated status text
+ const prefixes = Object.values(QR_CODE_PREFIXES);
+ const statusText = t('settings.qrScanner.scanningMessage');
+ const scannedData = await NativeVaultManager.scanQRCode(prefixes, statusText);
+
+ if (scannedData) {
+ handleQRCodeScanned(scannedData);
+ } else {
+ // User cancelled or scan failed, go back
+ router.back();
+ }
+ } catch (error) {
+ console.error('QR scan error:', error);
+ Alert.alert(
+ t('common.error'),
+ 'Failed to scan QR code',
+ [{ text: t('common.ok'), /**
+ * Navigate back.
+ */
+ onPress: (): void => router.back() }]
+ );
+ }
+ }, [handleQRCodeScanned, t]);
+
/**
* Reset hasProcessedUrl when URL changes to allow processing new URLs.
*/
@@ -132,45 +133,24 @@ export default function QRScannerScreen() : React.ReactNode {
useEffect(() => {
if (url && typeof url === 'string' && !hasProcessedUrl.current) {
hasProcessedUrl.current = true;
- handleBarcodeScanned({ data: url });
+ handleQRCodeScanned(url);
}
- }, [url, handleBarcodeScanned]);
+ }, [url, handleQRCodeScanned]);
+
+ /**
+ * Launch scanner when component mounts (Android/iOS only).
+ */
+ useEffect(() => {
+ if (Platform.OS === 'android' || Platform.OS === 'ios') {
+ launchScanner();
+ }
+ }, [launchScanner]);
const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 0,
},
- camera: {
- flex: 1,
- },
- cameraContainer: {
- backgroundColor: colors.black,
- flex: 1,
- },
- cameraOverlay: {
- alignItems: 'center',
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
- bottom: 0,
- justifyContent: 'center',
- left: 0,
- position: 'absolute',
- right: 0,
- top: 0,
- },
- cameraOverlayText: {
- color: colors.white,
- fontSize: 16,
- marginTop: 20,
- paddingHorizontal: 40,
- textAlign: 'center',
- },
- closeButton: {
- position: 'absolute',
- right: 16,
- top: 16,
- zIndex: 10,
- },
loadingContainer: {
alignItems: 'center',
flex: 1,
@@ -179,35 +159,14 @@ export default function QRScannerScreen() : React.ReactNode {
},
});
- // Show permission request screen
- if (!permission || !permission.granted) {
- return (
-
-
-
-
-
- );
- }
-
+ // Show loading while scanner is launching
return (
-
-
-
-
-
- {t('settings.qrScanner.scanningMessage')}
-
-
-
+
+
+
+ {t('settings.qrScanner.scanningMessage')}
+
);
diff --git a/apps/mobile-app/i18n/locales/fi.json b/apps/mobile-app/i18n/locales/fi.json
index 6610d4310..d3cc61ff5 100644
--- a/apps/mobile-app/i18n/locales/fi.json
+++ b/apps/mobile-app/i18n/locales/fi.json
@@ -20,9 +20,9 @@
"notice": "Huomautus",
"enabled": "Otettu käyttöön",
"disabled": "Pois käytöstä",
- "twoFactorAuthentication": "Two-Factor Authentication",
- "deleteItemConfirmTitle": "Delete Item",
- "deleteItemConfirmDescription": "Are you sure you want to delete this item?",
+ "twoFactorAuthentication": "Kaksivaiheinen tunnistautuminen",
+ "deleteItemConfirmTitle": "Poista kohde",
+ "deleteItemConfirmDescription": "Haluatko varmasti poistaa tämän kohteen?",
"errors": {
"unknownError": "Tapahtui tuntematon virhe. Yritä uudelleen.",
"unknownErrorTryAgain": "Tapahtui tuntematon virhe. Yritä uudelleen.",
@@ -207,13 +207,13 @@
"passkeyWillBeDeleted": "Tämä todennusavain poistetaan, kun tallennat tämän käyttäjätiedon."
},
"totp": {
- "addCode": "Add 2FA Code",
- "nameOptional": "Name (optional)",
- "secretKey": "Secret Key",
- "instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
- "saveToViewCode": "Save to view code",
+ "addCode": "Lisää 2FA TOTP -koodi",
+ "nameOptional": "Nimi (valinnainen)",
+ "secretKey": "Salainen avain",
+ "instructions": "Syötä salainen avain, joka näkyy sivustossa, jossa haluat lisätä kaksivaiheisen tunnistautumisen",
+ "saveToViewCode": "Tallenna nähdäksesi koodin",
"errors": {
- "invalidSecretKey": "Invalid secret key format."
+ "invalidSecretKey": "Virheellinen salatun avaimen muoto."
}
},
"settings": {
@@ -328,8 +328,8 @@
"languageDescription": "Aseta kieli, jota käytetään luotaessa uusia henkilöllisyyksiä.",
"genderSection": "Sukupuoli",
"genderDescription": "Aseta oletussukupuoli uusien henkilöllisyyksien luomiseksi. ",
- "ageRangeSection": "Age Range",
- "ageRangeDescription": "Set the age range for generating new identities.",
+ "ageRangeSection": "Ikähaarukka",
+ "ageRangeDescription": "Aseta ikähaarukka uusia henkilöllisyyksien luomisessa",
"genderOptions": {
"random": "Satunnainen",
"male": "Mies",
diff --git a/apps/mobile-app/i18n/locales/fr.json b/apps/mobile-app/i18n/locales/fr.json
index 8fb73bda7..0242ca581 100644
--- a/apps/mobile-app/i18n/locales/fr.json
+++ b/apps/mobile-app/i18n/locales/fr.json
@@ -7,22 +7,22 @@
"yes": "Oui",
"no": "Non",
"ok": "OK",
- "continue": "Continue",
- "loading": "Loading",
- "error": "Error",
- "success": "Success",
- "never": "Never",
- "copied": "Copied to clipboard",
+ "continue": "Continuer",
+ "loading": "Chargement",
+ "error": "Erreur",
+ "success": "Succès",
+ "never": "Jamais",
+ "copied": "Copier dans le presse-papiers",
"loadMore": "Voir plus",
- "use": "Use",
- "confirm": "Confirm",
- "next": "Next",
- "notice": "Notice",
- "enabled": "Enabled",
- "disabled": "Disabled",
- "twoFactorAuthentication": "Two-Factor Authentication",
- "deleteItemConfirmTitle": "Delete Item",
- "deleteItemConfirmDescription": "Are you sure you want to delete this item?",
+ "use": "Utiliser",
+ "confirm": "Confirmer",
+ "next": "Suivant",
+ "notice": "Notification",
+ "enabled": "Activé",
+ "disabled": "Désactivé",
+ "twoFactorAuthentication": "Authentification à deux facteurs",
+ "deleteItemConfirmTitle": "Supprimer l'élement",
+ "deleteItemConfirmDescription": "Êtes-vous certain de vouloir supprimer cet élément?",
"errors": {
"unknownError": "An unknown error occurred. Please try again.",
"unknownErrorTryAgain": "An unknown error occurred. Please try again.",
diff --git a/apps/mobile-app/i18n/locales/pl.json b/apps/mobile-app/i18n/locales/pl.json
index d465a88b2..6b7b89791 100644
--- a/apps/mobile-app/i18n/locales/pl.json
+++ b/apps/mobile-app/i18n/locales/pl.json
@@ -20,9 +20,9 @@
"notice": "Uwaga",
"enabled": "Włączone",
"disabled": "Wyłączone",
- "twoFactorAuthentication": "Two-Factor Authentication",
- "deleteItemConfirmTitle": "Delete Item",
- "deleteItemConfirmDescription": "Are you sure you want to delete this item?",
+ "twoFactorAuthentication": "Uwierzytelnianie dwuskładnikowe",
+ "deleteItemConfirmTitle": "Usuń element",
+ "deleteItemConfirmDescription": "Czy na pewno chcesz usunąć ten element?",
"errors": {
"unknownError": "Wystąpił nieznany błąd. Spróbuj ponownie.",
"unknownErrorTryAgain": "Wystąpił nieznany błąd. Spróbuj ponownie.",
@@ -207,13 +207,13 @@
"passkeyWillBeDeleted": "Ten klucz dostępu zostanie usunięty po zapisaniu tych danych."
},
"totp": {
- "addCode": "Add 2FA Code",
- "nameOptional": "Name (optional)",
- "secretKey": "Secret Key",
- "instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
- "saveToViewCode": "Save to view code",
+ "addCode": "Dodaj kod 2FA",
+ "nameOptional": "Nazwa (opcjonalnie)",
+ "secretKey": "Tajny klucz",
+ "instructions": "Wprowadź tajny klucz wyświetlony na stronie internetowej, na której chcesz dodać uwierzytelnianie dwuskładnikowe.",
+ "saveToViewCode": "Zapisz, aby wyświetlić kod",
"errors": {
- "invalidSecretKey": "Invalid secret key format."
+ "invalidSecretKey": "Nieprawidłowy format tajnego klucza."
}
},
"settings": {
diff --git a/apps/mobile-app/i18n/locales/ru.json b/apps/mobile-app/i18n/locales/ru.json
index 0964e02e7..4cbc03678 100644
--- a/apps/mobile-app/i18n/locales/ru.json
+++ b/apps/mobile-app/i18n/locales/ru.json
@@ -20,9 +20,9 @@
"notice": "Примечание",
"enabled": "Включено",
"disabled": "Отключено",
- "twoFactorAuthentication": "Two-Factor Authentication",
- "deleteItemConfirmTitle": "Delete Item",
- "deleteItemConfirmDescription": "Are you sure you want to delete this item?",
+ "twoFactorAuthentication": "Двухфакторная аутентификация",
+ "deleteItemConfirmTitle": "Удалить элемент",
+ "deleteItemConfirmDescription": "Вы уверены, что хотите удалить этот элемент?",
"errors": {
"unknownError": "Произошла неизвестная ошибка. Пожалуйста, попробуйте снова.",
"unknownErrorTryAgain": "Произошла неизвестная ошибка. Попробуйте снова.",
@@ -207,13 +207,13 @@
"passkeyWillBeDeleted": "Этот ключ доступа будет удален при сохранении этой учетной записи."
},
"totp": {
- "addCode": "Add 2FA Code",
- "nameOptional": "Name (optional)",
- "secretKey": "Secret Key",
- "instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
- "saveToViewCode": "Save to view code",
+ "addCode": "Добавить код 2FA",
+ "nameOptional": "Имя (необязательно)",
+ "secretKey": "Секретный ключ",
+ "instructions": "Введите секретный ключ, указанный на веб-сайте, где вы хотите добавить двухфакторную аутентификацию.",
+ "saveToViewCode": "Сохранить для просмотра кода",
"errors": {
- "invalidSecretKey": "Invalid secret key format."
+ "invalidSecretKey": "Неверный формат секретного ключа."
}
},
"settings": {
diff --git a/apps/mobile-app/ios/AliasVault.xcodeproj/project.pbxproj b/apps/mobile-app/ios/AliasVault.xcodeproj/project.pbxproj
index ffab2b72d..626358c4a 100644
--- a/apps/mobile-app/ios/AliasVault.xcodeproj/project.pbxproj
+++ b/apps/mobile-app/ios/AliasVault.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 60;
+ objectVersion = 70;
objects = {
/* Begin PBXBuildFile section */
@@ -212,7 +212,7 @@
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
- CEE9098F2DA548C7008D568F /* Exceptions for "Autofill" folder in "Autofill" target */ = {
+ CEE9098F2DA548C7008D568F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
@@ -222,84 +222,13 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
- CE59C7602E4F47FD0024A246 /* VaultUITests */ = {
- isa = PBXFileSystemSynchronizedRootGroup;
- exceptions = (
- );
- explicitFileTypes = {
- };
- explicitFolders = (
- );
- path = VaultUITests;
- sourceTree = "";
- };
- CE77825E2EA1822400A75E6F /* VaultUtils */ = {
- isa = PBXFileSystemSynchronizedRootGroup;
- exceptions = (
- );
- explicitFileTypes = {
- };
- explicitFolders = (
- );
- path = VaultUtils;
- sourceTree = "";
- };
- CEE480882DBE86DC00F4A367 /* VaultStoreKit */ = {
- isa = PBXFileSystemSynchronizedRootGroup;
- exceptions = (
- );
- explicitFileTypes = {
- };
- explicitFolders = (
- );
- path = VaultStoreKit;
- sourceTree = "";
- };
- CEE480972DBE86DD00F4A367 /* VaultStoreKitTests */ = {
- isa = PBXFileSystemSynchronizedRootGroup;
- exceptions = (
- );
- explicitFileTypes = {
- };
- explicitFolders = (
- );
- path = VaultStoreKitTests;
- sourceTree = "";
- };
- CEE4816B2DBE8AC800F4A367 /* VaultUI */ = {
- isa = PBXFileSystemSynchronizedRootGroup;
- exceptions = (
- );
- explicitFileTypes = {
- };
- explicitFolders = (
- );
- path = VaultUI;
- sourceTree = "";
- };
- CEE482AB2DBE8EFE00F4A367 /* VaultModels */ = {
- isa = PBXFileSystemSynchronizedRootGroup;
- exceptions = (
- );
- explicitFileTypes = {
- };
- explicitFolders = (
- );
- path = VaultModels;
- sourceTree = "";
- };
- CEE909812DA548C7008D568F /* Autofill */ = {
- isa = PBXFileSystemSynchronizedRootGroup;
- exceptions = (
- CEE9098F2DA548C7008D568F /* Exceptions for "Autofill" folder in "Autofill" target */,
- );
- explicitFileTypes = {
- };
- explicitFolders = (
- );
- path = Autofill;
- sourceTree = "";
- };
+ CE59C7602E4F47FD0024A246 /* VaultUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultUITests; sourceTree = ""; };
+ CE77825E2EA1822400A75E6F /* VaultUtils */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultUtils; sourceTree = ""; };
+ CEE480882DBE86DC00F4A367 /* VaultStoreKit */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultStoreKit; sourceTree = ""; };
+ CEE480972DBE86DD00F4A367 /* VaultStoreKitTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultStoreKitTests; sourceTree = ""; };
+ CEE4816B2DBE8AC800F4A367 /* VaultUI */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultUI; sourceTree = ""; };
+ CEE482AB2DBE8EFE00F4A367 /* VaultModels */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultModels; sourceTree = ""; };
+ CEE909812DA548C7008D568F /* Autofill */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CEE9098F2DA548C7008D568F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Autofill; sourceTree = ""; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@@ -1418,10 +1347,7 @@
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
- OTHER_LDFLAGS = (
- "$(inherited)",
- " ",
- );
+ OTHER_LDFLAGS = "$(inherited) ";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
@@ -1475,10 +1401,7 @@
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = NO;
- OTHER_LDFLAGS = (
- "$(inherited)",
- " ",
- );
+ OTHER_LDFLAGS = "$(inherited) ";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
diff --git a/apps/mobile-app/ios/NativeVaultManager/RCTNativeVaultManager.mm b/apps/mobile-app/ios/NativeVaultManager/RCTNativeVaultManager.mm
index 5abc17116..7152e3128 100644
--- a/apps/mobile-app/ios/NativeVaultManager/RCTNativeVaultManager.mm
+++ b/apps/mobile-app/ios/NativeVaultManager/RCTNativeVaultManager.mm
@@ -291,4 +291,10 @@
[vaultManager authenticateUser:title subtitle:subtitle resolver:resolve rejecter:reject];
}
+// MARK: - QR Code Scanner
+
+- (void)scanQRCode:(NSArray *)prefixes statusText:(NSString *)statusText resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
+ [vaultManager scanQRCode:prefixes statusText:statusText resolver:resolve rejecter:reject];
+}
+
@end
diff --git a/apps/mobile-app/ios/NativeVaultManager/VaultManager.swift b/apps/mobile-app/ios/NativeVaultManager/VaultManager.swift
index 1b4e6699f..535b7a4c7 100644
--- a/apps/mobile-app/ios/NativeVaultManager/VaultManager.swift
+++ b/apps/mobile-app/ios/NativeVaultManager/VaultManager.swift
@@ -5,6 +5,7 @@ import VaultStoreKit
import VaultModels
import SwiftUI
import VaultUI
+import AVFoundation
/**
* This class is used as a bridge to allow React Native to interact with the VaultStoreKit class.
@@ -913,6 +914,42 @@ public class VaultManager: NSObject {
}
}
+ @objc
+ func scanQRCode(_ prefixes: [String]?,
+ statusText: String?,
+ resolver resolve: @escaping RCTPromiseResolveBlock,
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
+ DispatchQueue.main.async {
+ // Get the root view controller from React Native
+ guard let rootVC = RCTPresentedViewController() else {
+ reject("NO_VIEW_CONTROLLER", "No view controller available", nil)
+ return
+ }
+
+ // Create QR scanner view with optional prefix filtering and custom status text
+ let scannerView = QRScannerView(
+ prefixes: prefixes,
+ statusText: statusText,
+ onCodeScanned: { code in
+ // Resolve immediately and dismiss without waiting (matches Android behavior)
+ resolve(code)
+ rootVC.dismiss(animated: true)
+ },
+ onCancel: {
+ // Cancel resolves nil and dismisses
+ resolve(nil)
+ rootVC.dismiss(animated: true)
+ }
+ )
+
+ let hostingController = UIHostingController(rootView: scannerView)
+
+ // Present modally as full screen
+ hostingController.modalPresentationStyle = .fullScreen
+ rootVC.present(hostingController, animated: true)
+ }
+ }
+
@objc
func authenticateUser(_ title: String?,
subtitle: String?,
diff --git a/apps/mobile-app/ios/Podfile.lock b/apps/mobile-app/ios/Podfile.lock
index 42b9df0ad..5923a0124 100644
--- a/apps/mobile-app/ios/Podfile.lock
+++ b/apps/mobile-app/ios/Podfile.lock
@@ -272,10 +272,6 @@ PODS:
- ExpoModulesCore
- ExpoBlur (14.1.5):
- ExpoModulesCore
- - ExpoCamera (16.1.11):
- - ExpoModulesCore
- - ZXingObjC/OneD
- - ZXingObjC/PDF417
- ExpoClipboard (7.1.5):
- ExpoModulesCore
- ExpoDocumentPicker (13.1.6):
@@ -2449,11 +2445,6 @@ PODS:
- SwiftLint (0.59.1)
- SWXMLHash (7.0.2)
- Yoga (0.0.0)
- - ZXingObjC/Core (3.6.9)
- - ZXingObjC/OneD (3.6.9):
- - ZXingObjC/Core
- - ZXingObjC/PDF417 (3.6.9):
- - ZXingObjC/Core
DEPENDENCIES:
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
@@ -2468,7 +2459,6 @@ DEPENDENCIES:
- expo-dev-menu-interface (from `../node_modules/expo-dev-menu-interface/ios`)
- ExpoAsset (from `../node_modules/expo-asset/ios`)
- ExpoBlur (from `../node_modules/expo-blur/ios`)
- - ExpoCamera (from `../node_modules/expo-camera/ios`)
- ExpoClipboard (from `../node_modules/expo-clipboard/ios`)
- ExpoDocumentPicker (from `../node_modules/expo-document-picker/ios`)
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
@@ -2582,7 +2572,6 @@ SPEC REPOS:
- SQLite.swift
- SwiftLint
- SWXMLHash
- - ZXingObjC
EXTERNAL SOURCES:
boost:
@@ -2609,8 +2598,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-asset/ios"
ExpoBlur:
:path: "../node_modules/expo-blur/ios"
- ExpoCamera:
- :path: "../node_modules/expo-camera/ios"
ExpoClipboard:
:path: "../node_modules/expo-clipboard/ios"
ExpoDocumentPicker:
@@ -2820,7 +2807,6 @@ SPEC CHECKSUMS:
expo-dev-menu-interface: 609c35ae8b97479cdd4c9e23c8cf6adc44beea0e
ExpoAsset: ef06e880126c375f580d4923fdd1cdf4ee6ee7d6
ExpoBlur: 3c8885b9bf9eef4309041ec87adec48b5f1986a9
- ExpoCamera: e1879906d41184e84b57d7643119f8509414e318
ExpoClipboard: 436f6de6971f14eb75ae160e076d9cb3b19eb795
ExpoDocumentPicker: b263a279685b6640b8c8bc70d71c83067aeaae55
ExpoFileSystem: 7f92f7be2f5c5ed40a7c9efc8fa30821181d9d63
@@ -2925,7 +2911,6 @@ SPEC CHECKSUMS:
SwiftLint: 3d48e2fb2a3468fdaccf049e5e755df22fb40c2c
SWXMLHash: dd733a457e9c4fe93b1538654057aefae4acb382
Yoga: dc7c21200195acacb62fa920c588e7c2106de45e
- ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5
PODFILE CHECKSUM: ac288e273086bafdd610cafff08ccca0d164f7c3
diff --git a/apps/mobile-app/ios/VaultUI/QRScanner/QRScannerView.swift b/apps/mobile-app/ios/VaultUI/QRScanner/QRScannerView.swift
new file mode 100644
index 000000000..7afa66548
--- /dev/null
+++ b/apps/mobile-app/ios/VaultUI/QRScanner/QRScannerView.swift
@@ -0,0 +1,240 @@
+import SwiftUI
+import AVFoundation
+
+private let locBundle = Bundle.vaultUI
+
+/// SwiftUI view for scanning QR codes using AVFoundation
+public struct QRScannerView: View {
+ let onCodeScanned: (String) -> Void
+ let onCancel: () -> Void
+ let prefixes: [String]?
+ let statusText: String
+
+ @State private var hasScanned = false
+ @State private var showFlash = false
+
+ public init(
+ prefixes: [String]? = nil,
+ statusText: String? = nil,
+ onCodeScanned: @escaping (String) -> Void,
+ onCancel: @escaping () -> Void
+ ) {
+ self.prefixes = prefixes
+ self.statusText = statusText?.isEmpty == false ? statusText! : "Scan QR code"
+ self.onCodeScanned = onCodeScanned
+ self.onCancel = onCancel
+ }
+
+ public var body: some View {
+ ZStack {
+ // Camera preview
+ QRScannerRepresentable(
+ prefixes: prefixes,
+ onCodeScanned: { code in
+ if !hasScanned {
+ hasScanned = true
+ showFlash = true
+
+ // Flash animation then callback
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+ onCodeScanned(code)
+ }
+ }
+ },
+ onCodeRejected: {
+ // Reset hasScanned to allow scanning again
+ hasScanned = false
+ }
+ )
+ .edgesIgnoringSafeArea(.all)
+
+ // Overlay with viewfinder
+ VStack {
+ Spacer()
+
+ // Viewfinder frame
+ Rectangle()
+ .stroke(Color.white, lineWidth: 3)
+ .frame(width: 280, height: 280)
+ .overlay(
+ // Flash effect
+ Rectangle()
+ .fill(Color.white)
+ .opacity(showFlash ? 0.7 : 0)
+ .animation(.easeInOut(duration: 0.2), value: showFlash)
+ )
+
+ Spacer()
+
+ // Status text
+ Text(statusText)
+ .foregroundColor(.white)
+ .padding()
+ .background(Color.black.opacity(0.7))
+ .cornerRadius(10)
+ .padding(.bottom, 50)
+ }
+
+ // Cancel button
+ VStack {
+ HStack {
+ Spacer()
+ Button(action: onCancel) {
+ Image(systemName: "xmark.circle.fill")
+ .font(.system(size: 32))
+ .foregroundColor(.white)
+ .padding()
+ }
+ }
+ Spacer()
+ }
+ }
+ .background(Color.black)
+ }
+}
+
+/// UIViewControllerRepresentable wrapper for AVFoundation camera
+struct QRScannerRepresentable: UIViewControllerRepresentable {
+ let prefixes: [String]?
+ let onCodeScanned: (String) -> Void
+ let onCodeRejected: () -> Void
+
+ func makeUIViewController(context: Context) -> QRScannerViewController {
+ let controller = QRScannerViewController()
+ controller.prefixes = prefixes
+ controller.onCodeScanned = onCodeScanned
+ controller.onCodeRejected = onCodeRejected
+ return controller
+ }
+
+ func updateUIViewController(_ uiViewController: QRScannerViewController, context: Context) {
+ // No updates needed
+ }
+}
+
+/// UIViewController that handles AVFoundation QR code scanning
+class QRScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
+ var captureSession: AVCaptureSession?
+ var previewLayer: AVCaptureVideoPreviewLayer?
+ var prefixes: [String]?
+ var onCodeScanned: ((String) -> Void)?
+ var onCodeRejected: (() -> Void)?
+ private var rejectedQRCodes = Set() // Track rejected QR codes to avoid repeated haptic feedback
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setupCamera()
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+
+ if let session = captureSession, !session.isRunning {
+ DispatchQueue.global(qos: .userInitiated).async {
+ session.startRunning()
+ }
+ }
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+
+ if let session = captureSession, session.isRunning {
+ DispatchQueue.global(qos: .userInitiated).async {
+ session.stopRunning()
+ }
+ }
+ }
+
+ private func setupCamera() {
+ let session = AVCaptureSession()
+
+ guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else {
+ return
+ }
+
+ let videoInput: AVCaptureDeviceInput
+
+ do {
+ videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
+ } catch {
+ return
+ }
+
+ if session.canAddInput(videoInput) {
+ session.addInput(videoInput)
+ } else {
+ return
+ }
+
+ let metadataOutput = AVCaptureMetadataOutput()
+
+ if session.canAddOutput(metadataOutput) {
+ session.addOutput(metadataOutput)
+
+ metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
+ metadataOutput.metadataObjectTypes = [.qr]
+ } else {
+ return
+ }
+
+ let previewLayer = AVCaptureVideoPreviewLayer(session: session)
+ previewLayer.frame = view.layer.bounds
+ previewLayer.videoGravity = .resizeAspectFill
+ view.layer.addSublayer(previewLayer)
+
+ self.captureSession = session
+ self.previewLayer = previewLayer
+
+ DispatchQueue.global(qos: .userInitiated).async {
+ session.startRunning()
+ }
+ }
+
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+ previewLayer?.frame = view.layer.bounds
+ }
+
+ func metadataOutput(
+ _ output: AVCaptureMetadataOutput,
+ didOutput metadataObjects: [AVMetadataObject],
+ from connection: AVCaptureConnection
+ ) {
+ if let metadataObject = metadataObjects.first,
+ let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
+ let stringValue = readableObject.stringValue {
+
+ // Check if prefixes filter is enabled
+ if let prefixes = prefixes, !prefixes.isEmpty {
+ // Check if the scanned code starts with any of the accepted prefixes
+ let hasValidPrefix = prefixes.contains { prefix in
+ stringValue.hasPrefix(prefix)
+ }
+
+ if !hasValidPrefix {
+ // Invalid QR code - only give haptic feedback once per unique code
+ if !rejectedQRCodes.contains(stringValue) {
+ let generator = UINotificationFeedbackGenerator()
+ generator.notificationOccurred(.warning)
+ rejectedQRCodes.insert(stringValue)
+ }
+
+ // Notify that code was rejected (to reset UI state if needed)
+ onCodeRejected?()
+ return
+ }
+ }
+
+ // Valid QR code - stop scanning
+ captureSession?.stopRunning()
+
+ // Success haptic feedback
+ let generator = UINotificationFeedbackGenerator()
+ generator.notificationOccurred(.success)
+
+ // Callback with scanned code
+ onCodeScanned?(stringValue)
+ }
+ }
+}
diff --git a/apps/mobile-app/package-lock.json b/apps/mobile-app/package-lock.json
index ec42f7b2c..9734ec7a8 100644
--- a/apps/mobile-app/package-lock.json
+++ b/apps/mobile-app/package-lock.json
@@ -17,10 +17,8 @@
"@types/jsrsasign": "^10.5.15",
"expo": "^53.0.22",
"expo-blur": "~14.1.5",
- "expo-camera": "^16.1.11",
"expo-clipboard": "~7.1.5",
"expo-constants": "~17.1.7",
- "expo-dev-client": "~5.1.8",
"expo-document-picker": "~13.1.6",
"expo-file-system": "~18.1.11",
"expo-font": "~13.3.2",
@@ -37,8 +35,7 @@
"expo-web-browser": "~14.2.0",
"fbemitter": "^3.0.0",
"i18next": "^25.3.2",
- "jest": "~29.7.0",
- "jest-expo": "~53.0.10",
+ "lodash": "^4.17.21",
"otpauth": "^9.4.0",
"react": "19.0.0",
"react-hook-form": "^7.56.1",
@@ -55,6 +52,7 @@
"react-native-safe-area-context": "5.6.1",
"react-native-screens": "~4.15.4",
"react-native-svg": "15.11.2",
+ "react-native-svg-transformer": "^1.5.0",
"react-native-toast-message": "^2.2.1",
"react-native-webview": "13.13.5",
"secure-remote-password": "github:LinusU/secure-remote-password#73e5f732b6ca0cdbdc19da1a0c5f48cdbad2cbf0",
@@ -77,10 +75,10 @@
"eslint-config-expo": "~9.2.0",
"eslint-plugin-jsdoc": "^55.2.0",
"eslint-plugin-react-native": "^5.0.0",
+ "expo-dev-client": "~5.1.8",
"globals": "^16.3.0",
"jest": "^29.2.1",
"jest-expo": "~53.0.0",
- "react-native-svg-transformer": "^1.5.0",
"typescript": "~5.8.3"
},
"engines": {
@@ -129,7 +127,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3",
@@ -4100,7 +4097,6 @@
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.17.tgz",
"integrity": "sha512-uEcYWi1NV+2Qe1oELfp9b5hTYekqWATv2cuwcOAg5EvsIsUPtzFrKIasgUXLBRGb9P7yR5ifoJ+ug4u6jdqSTQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@react-navigation/core": "^7.12.4",
"escape-string-regexp": "^4.0.0",
@@ -4225,7 +4221,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
"integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@@ -4242,7 +4237,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz",
"integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@@ -4259,7 +4253,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz",
"integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@@ -4276,7 +4269,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz",
"integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@@ -4293,7 +4285,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz",
"integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@@ -4310,7 +4301,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz",
"integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@@ -4327,7 +4317,6 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz",
"integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@@ -4344,7 +4333,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz",
"integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -4361,7 +4349,6 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz",
"integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@svgr/babel-plugin-add-jsx-attribute": "8.0.0",
@@ -4388,9 +4375,7 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz",
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
- "dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/core": "^7.21.3",
"@svgr/babel-preset": "8.1.0",
@@ -4410,7 +4395,6 @@
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -4423,7 +4407,6 @@
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"import-fresh": "^3.3.0",
@@ -4450,7 +4433,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz",
"integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.21.3",
@@ -4468,7 +4450,6 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
@@ -4481,7 +4462,6 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz",
"integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.21.3",
@@ -4504,7 +4484,6 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz",
"integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"cosmiconfig": "^8.1.3",
@@ -4526,7 +4505,6 @@
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"import-fresh": "^3.3.0",
@@ -4563,7 +4541,6 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
- "dev": true,
"license": "ISC",
"engines": {
"node": ">=10.13.0"
@@ -4634,6 +4611,7 @@
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
@@ -4645,6 +4623,7 @@
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
@@ -4775,7 +4754,6 @@
"integrity": "sha512-ixLZ7zG7j1fM0DijL9hDArwhwcCb4vqmePgwtV0GfnkHRSCUEv4LvzarcTdhoqgyMznUx/EhoTUv31CKZzkQlw==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -4893,7 +4871,6 @@
"integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.43.0",
"@typescript-eslint/types": "8.43.0",
@@ -5424,6 +5401,7 @@
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/helper-numbers": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
@@ -5434,21 +5412,24 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.13.2",
@@ -5456,6 +5437,7 @@
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
"@webassemblyjs/helper-api-error": "1.13.2",
@@ -5467,7 +5449,8 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.14.1",
@@ -5475,6 +5458,7 @@
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@@ -5488,6 +5472,7 @@
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
@@ -5498,6 +5483,7 @@
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@xtuc/long": "4.2.2"
}
@@ -5507,7 +5493,8 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.14.1",
@@ -5515,6 +5502,7 @@
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@@ -5532,6 +5520,7 @@
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
@@ -5546,6 +5535,7 @@
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@@ -5559,6 +5549,7 @@
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-api-error": "1.13.2",
@@ -5574,6 +5565,7 @@
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@xtuc/long": "4.2.2"
@@ -5593,14 +5585,16 @@
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true,
- "license": "Apache-2.0"
+ "license": "Apache-2.0",
+ "peer": true
},
"node_modules/abab": {
"version": "2.0.6",
@@ -5649,7 +5643,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -5674,6 +5667,7 @@
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10.13.0"
},
@@ -5753,6 +5747,7 @@
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"ajv": "^8.0.0"
},
@@ -5771,6 +5766,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -5787,7 +5783,8 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/anser": {
"version": "1.4.10",
@@ -6545,7 +6542,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001737",
"electron-to-chromium": "^1.5.211",
@@ -6691,7 +6687,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -6785,6 +6780,7 @@
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6.0"
}
@@ -7277,7 +7273,6 @@
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
"integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"css-tree": "~2.2.0"
@@ -7291,7 +7286,6 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
"integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"mdn-data": "2.0.28",
@@ -7306,7 +7300,6 @@
"version": "2.0.28",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
- "dev": true,
"license": "CC0-1.0"
},
"node_modules/cssom": {
@@ -7717,7 +7710,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
"integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
- "dev": true,
"license": "MIT",
"dependencies": {
"no-case": "^3.0.4",
@@ -7823,6 +7815,7 @@
"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
@@ -8028,7 +8021,8 @@
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
@@ -8144,7 +8138,6 @@
"integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -8329,7 +8322,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -8686,7 +8678,6 @@
"resolved": "https://registry.npmjs.org/expo/-/expo-53.0.22.tgz",
"integrity": "sha512-sJ2I4W/e5iiM4u/wYCe3qmW4D7WPCRqByPDD0hJcdYNdjc9HFFFdO4OAudZVyC/MmtoWZEIH5kTJP1cw9FjzYA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.20.0",
"@expo/cli": "0.24.21",
@@ -8756,26 +8747,6 @@
"react-native": "*"
}
},
- "node_modules/expo-camera": {
- "version": "16.1.11",
- "resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-16.1.11.tgz",
- "integrity": "sha512-etA5ZKoC6nPBnWWqiTmlX//zoFZ6cWQCCIdmpUHTGHAKd4qZNCkhPvBWbi8o32pDe57lix1V4+TPFgEcvPwsaA==",
- "license": "MIT",
- "dependencies": {
- "invariant": "^2.2.4"
- },
- "peerDependencies": {
- "expo": "*",
- "react": "*",
- "react-native": "*",
- "react-native-web": "*"
- },
- "peerDependenciesMeta": {
- "react-native-web": {
- "optional": true
- }
- }
- },
"node_modules/expo-clipboard": {
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/expo-clipboard/-/expo-clipboard-7.1.5.tgz",
@@ -8792,7 +8763,6 @@
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.7.tgz",
"integrity": "sha512-byBjGsJ6T6FrLlhOBxw4EaiMXrZEn/MlUYIj/JAd+FS7ll5X/S4qVRbIimSJtdW47hXMq0zxPfJX6njtA56hHA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@expo/config": "~11.0.12",
"@expo/env": "~1.0.7"
@@ -8806,6 +8776,7 @@
"version": "5.1.8",
"resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.1.8.tgz",
"integrity": "sha512-IopYPgBi3JflksO5ieTphbKsbYHy9iIVdT/d69It++y0iBMSm0oBIoDmUijrHKjE3fV6jnrwrm8luU13/mzIQQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"expo-dev-launcher": "5.1.11",
@@ -8822,6 +8793,7 @@
"version": "5.1.11",
"resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-5.1.11.tgz",
"integrity": "sha512-bN0+nv5H038s8Gzf8i16hwCyD3sWDmHp7vb+QbL1i6B3XNnICCKS/H/3VH6H3PRMvCmoLGPlg+ODDqGlf0nu3g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"ajv": "8.11.0",
@@ -8837,6 +8809,7 @@
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
@@ -8853,12 +8826,14 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true,
"license": "MIT"
},
"node_modules/expo-dev-menu": {
"version": "6.1.10",
"resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-6.1.10.tgz",
"integrity": "sha512-LaI0Bw5zzw5XefjYSX6YaMydzk0YBysjqQoxzj6ufDyKgwAfPmFwOLkZ03DOSerc9naezGLNAGgTEN6QTgMmgQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"expo-dev-menu-interface": "1.10.0"
@@ -8871,6 +8846,7 @@
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/expo-dev-menu-interface/-/expo-dev-menu-interface-1.10.0.tgz",
"integrity": "sha512-NxtM/qot5Rh2cY333iOE87dDg1S8CibW+Wu4WdLua3UMjy81pXYzAGCZGNOeY7k9GpNFqDPNDXWyBSlk9r2pBg==",
+ "dev": true,
"license": "MIT",
"peerDependencies": {
"expo": "*"
@@ -8900,7 +8876,6 @@
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-13.3.2.tgz",
"integrity": "sha512-wUlMdpqURmQ/CNKK/+BIHkDA5nGjMqNlYmW0pJFXY/KE/OG80Qcavdu2sHsL4efAIiNGvYdBS10WztuQYU4X0A==",
"license": "MIT",
- "peer": true,
"dependencies": {
"fontfaceobserver": "^2.1.0"
},
@@ -8922,6 +8897,7 @@
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-0.15.0.tgz",
"integrity": "sha512-duRT6oGl80IDzH2LD2yEFWNwGIC2WkozsB6HF3cDYNoNNdUvFk6uN3YiwsTsqVM/D0z6LEAQ01/SlYvN+Fw0JQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/expo-keep-awake": {
@@ -8950,7 +8926,6 @@
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-7.1.7.tgz",
"integrity": "sha512-ZJaH1RIch2G/M3hx2QJdlrKbYFUTOjVVW4g39hfxrE5bPX9xhZUYXqxqQtzMNl1ylAevw9JkgEfWbBWddbZ3UA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"expo-constants": "~17.1.7",
"invariant": "^2.2.4"
@@ -8989,6 +8964,7 @@
"version": "0.16.6",
"resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-0.16.6.tgz",
"integrity": "sha512-1A+do6/mLUWF9xd3uCrlXr9QFDbjbfqAYmUy8UDLOjof1lMrOhyeC4Yi6WexA/A8dhZEpIxSMCKfn7G4aHAh4w==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@expo/config": "~11.0.12",
@@ -9140,6 +9116,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-1.1.0.tgz",
"integrity": "sha512-DeB+fRe0hUDPZhpJ4X4bFMAItatFBUPjw/TVSbJsaf3Exeami+2qbbJhWkcTMoYHOB73nOIcaYcWXYJnCJXO0w==",
+ "dev": true,
"license": "MIT",
"peerDependencies": {
"expo": "*"
@@ -9231,7 +9208,8 @@
"url": "https://opencollective.com/fastify"
}
],
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/fast-xml-parser": {
"version": "4.5.3",
@@ -9755,7 +9733,8 @@
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true,
- "license": "BSD-2-Clause"
+ "license": "BSD-2-Clause",
+ "peer": true
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.0.2",
@@ -10091,7 +10070,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.27.6"
},
@@ -10165,7 +10143,6 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"parent-module": "^1.0.0",
@@ -10182,7 +10159,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -10937,7 +10913,6 @@
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@jest/core": "^29.7.0",
"@jest/types": "^29.6.3",
@@ -11281,7 +11256,8 @@
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/jest-get-type": {
"version": "29.6.3",
@@ -11954,7 +11930,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
- "devOptional": true,
"license": "MIT"
},
"node_modules/json-schema-traverse": {
@@ -12336,6 +12311,7 @@
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6.11.5"
}
@@ -12359,7 +12335,6 @@
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "dev": true,
"license": "MIT"
},
"node_modules/lodash.debounce": {
@@ -12601,7 +12576,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"tslib": "^2.0.3"
@@ -13382,7 +13356,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"lower-case": "^2.0.2",
@@ -13916,7 +13889,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"callsites": "^3.0.0"
@@ -13939,7 +13911,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.0.0",
@@ -13999,7 +13970,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
"integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==",
- "dev": true,
"license": "MIT"
},
"node_modules/path-exists": {
@@ -14061,7 +14031,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -14476,6 +14445,7 @@
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
@@ -14534,7 +14504,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -14593,7 +14562,6 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
"integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18.0.0"
},
@@ -14642,7 +14610,6 @@
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.79.6.tgz",
"integrity": "sha512-kvIWSmf4QPfY41HC25TR285N7Fv0Pyn3DAEK8qRL9dA35usSaxsJkHfw+VqnonqJjXOaoKCEanwudRAJ60TBGA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@jest/create-cache-key-function": "^29.7.0",
"@react-native/assets-registry": "0.79.6",
@@ -14862,7 +14829,6 @@
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.17.5.tgz",
"integrity": "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/plugin-transform-arrow-functions": "^7.0.0-0",
"@babel/plugin-transform-class-properties": "^7.0.0-0",
@@ -14898,7 +14864,6 @@
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz",
"integrity": "sha512-/wJE58HLEAkATzhhX1xSr+fostLsK8Q97EfpfMDKo8jlOc1QKESSX/FQrhk7HhQH/2uSaox4Y86sNaI02kteiA==",
"license": "MIT",
- "peer": true,
"peerDependencies": {
"react": "*",
"react-native": "*"
@@ -14909,7 +14874,6 @@
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.15.4.tgz",
"integrity": "sha512-aKHPDScUbpQiZEG9eZssHdG5jEQs4yiJ8eMx6g81Ex/xU7DZkv3911enzdCb+v4eJE79X8waizY0ZhauZJQmrw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"react-freeze": "^1.0.0",
"react-native-is-edge-to-edge": "^1.2.1",
@@ -14925,7 +14889,6 @@
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.11.2.tgz",
"integrity": "sha512-+YfF72IbWQUKzCIydlijV1fLuBsQNGMT6Da2kFlo1sh+LE3BIm/2Q7AR1zAAR6L0BFLi1WaQPLfFUC9bNZpOmw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"css-select": "^5.1.0",
"css-tree": "^1.1.3",
@@ -14940,7 +14903,6 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/react-native-svg-transformer/-/react-native-svg-transformer-1.5.1.tgz",
"integrity": "sha512-dFvBNR8A9VPum9KCfh+LE49YiJEF8zUSnEFciKQroR/bEOhlPoZA0SuQ0qNk7m2iZl2w59FYjdRe0pMHWMDl0Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@svgr/core": "^8.1.0",
@@ -14968,7 +14930,6 @@
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.13.5.tgz",
"integrity": "sha512-MfC2B+woL4Hlj2WCzcb1USySKk+SteXnUKmKktOk/H/AQy5+LuVdkPKm8SknJ0/RxaxhZ48WBoTRGaqgR137hw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"escape-string-regexp": "^4.0.0",
"invariant": "2.2.4"
@@ -15536,6 +15497,7 @@
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
@@ -15574,6 +15536,7 @@
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
@@ -15586,7 +15549,8 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/secure-remote-password": {
"version": "0.3.0",
@@ -15696,6 +15660,7 @@
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true,
"license": "BSD-3-Clause",
+ "peer": true,
"dependencies": {
"randombytes": "^2.1.0"
}
@@ -16107,7 +16072,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
"integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"dot-case": "^3.0.4",
@@ -16678,14 +16642,12 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/svgo": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
"integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@trysound/sax": "0.2.0",
@@ -16711,7 +16673,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 10"
@@ -16721,7 +16682,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"mdn-data": "2.0.30",
@@ -16735,7 +16695,6 @@
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
- "dev": true,
"license": "CC0-1.0"
},
"node_modules/symbol-tree": {
@@ -16751,6 +16710,7 @@
"integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6"
},
@@ -16849,6 +16809,7 @@
"integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.25",
"jest-worker": "^27.4.5",
@@ -16884,6 +16845,7 @@
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/node": "*",
"merge-stream": "^2.0.0",
@@ -16899,6 +16861,7 @@
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -17131,7 +17094,6 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "dev": true,
"license": "0BSD"
},
"node_modules/type-check": {
@@ -17266,7 +17228,6 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"devOptional": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -17475,6 +17436,7 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"punycode": "^2.1.0"
@@ -17629,6 +17591,7 @@
"integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
@@ -17662,6 +17625,7 @@
"integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
@@ -17721,6 +17685,7 @@
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true,
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^4.1.1"
@@ -17735,6 +17700,7 @@
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"dev": true,
"license": "BSD-2-Clause",
+ "peer": true,
"engines": {
"node": ">=4.0"
}
diff --git a/apps/mobile-app/package.json b/apps/mobile-app/package.json
index 9e3990b5f..cc4fa0fcf 100644
--- a/apps/mobile-app/package.json
+++ b/apps/mobile-app/package.json
@@ -38,10 +38,8 @@
"@types/jsrsasign": "^10.5.15",
"expo": "^53.0.22",
"expo-blur": "~14.1.5",
- "expo-camera": "^16.1.11",
"expo-clipboard": "~7.1.5",
"expo-constants": "~17.1.7",
- "expo-dev-client": "~5.1.8",
"expo-document-picker": "~13.1.6",
"expo-file-system": "~18.1.11",
"expo-font": "~13.3.2",
@@ -58,8 +56,7 @@
"expo-web-browser": "~14.2.0",
"fbemitter": "^3.0.0",
"i18next": "^25.3.2",
- "jest": "~29.7.0",
- "jest-expo": "~53.0.10",
+ "lodash": "^4.17.21",
"otpauth": "^9.4.0",
"react": "19.0.0",
"react-hook-form": "^7.56.1",
@@ -76,6 +73,7 @@
"react-native-safe-area-context": "5.6.1",
"react-native-screens": "~4.15.4",
"react-native-svg": "15.11.2",
+ "react-native-svg-transformer": "^1.5.0",
"react-native-toast-message": "^2.2.1",
"react-native-webview": "13.13.5",
"secure-remote-password": "github:LinusU/secure-remote-password#73e5f732b6ca0cdbdc19da1a0c5f48cdbad2cbf0",
@@ -98,10 +96,10 @@
"eslint-config-expo": "~9.2.0",
"eslint-plugin-jsdoc": "^55.2.0",
"eslint-plugin-react-native": "^5.0.0",
+ "expo-dev-client": "~5.1.8",
"globals": "^16.3.0",
"jest": "^29.2.1",
"jest-expo": "~53.0.0",
- "react-native-svg-transformer": "^1.5.0",
"typescript": "~5.8.3"
},
"engines": {
diff --git a/apps/mobile-app/specs/NativeVaultManager.ts b/apps/mobile-app/specs/NativeVaultManager.ts
index bec75b1ee..86fa6d072 100644
--- a/apps/mobile-app/specs/NativeVaultManager.ts
+++ b/apps/mobile-app/specs/NativeVaultManager.ts
@@ -105,6 +105,13 @@ export interface Spec extends TurboModule {
// Re-authentication methods
// Authenticate user with biometric or PIN. If title/subtitle are null/empty, defaults to "Unlock Vault" context.
authenticateUser(title: string | null, subtitle: string | null): Promise;
+
+ // QR code scanner
+ // Scan a QR code and return the scanned data. Returns null if cancelled or failed.
+ // If prefixes is provided, only QR codes starting with one of these prefixes will be accepted.
+ // Scanner will keep scanning until a matching code is found or user cancels.
+ // statusText is the message to display on the scanner screen (defaults to "Scan QR code" if null/empty).
+ scanQRCode(prefixes: string[] | null, statusText: string | null): Promise;
}
export default TurboModuleRegistry.getEnforcing('NativeVaultManager');
diff --git a/apps/server/AliasVault.Client/Resources/Pages/Main/Settings/General.fi.resx b/apps/server/AliasVault.Client/Resources/Pages/Main/Settings/General.fi.resx
index 263745c2d..5e553e093 100644
--- a/apps/server/AliasVault.Client/Resources/Pages/Main/Settings/General.fi.resx
+++ b/apps/server/AliasVault.Client/Resources/Pages/Main/Settings/General.fi.resx
@@ -71,11 +71,11 @@
- Identity Generator Settings
+ Henkilöllisyysgeneraattorin asetukset
Title for identity generator settings section
- Language
+ Kieli
Label for alias generation language setting
@@ -83,7 +83,7 @@
Description for alias generation language setting
- Gender
+ Sukupuoli
Label for alias generation gender setting
@@ -103,7 +103,7 @@
Female gender option
- Age range
+ Ikähaarukka
Label for alias generation age range setting
diff --git a/apps/server/AliasVault.Client/Resources/SharedResources.fr.resx b/apps/server/AliasVault.Client/Resources/SharedResources.fr.resx
index b69663136..5eef1a724 100644
--- a/apps/server/AliasVault.Client/Resources/SharedResources.fr.resx
+++ b/apps/server/AliasVault.Client/Resources/SharedResources.fr.resx
@@ -213,7 +213,7 @@
Generic error message
- An unknown error occurred. Please try again.
+ Une erreur inconnue s'est produite. Merci de réessayer.
Generic unknown error message
@@ -289,7 +289,7 @@
- or
+ ou
Divider text between options