0

I'm tried to convert angular app to vue 3, but i get some problems. There are:

  1. black window while i call startScan() but android says that camera in using and all permissions allowed
  2. function scan() from google is working and scanning

I'll mean that problems with modal window and its inits or problems with css while i convert ionic to tailwindcss

here original github: https://github.com/robingenz/capacitor-mlkit-plugin-demo/tree/main/src/app/modules/barcode-scanning

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import {
  BarcodeFormat,
  BarcodeScanner,
  LensFacing,
} from '@capacitor-mlkit/barcode-scanning';
import type {
  Barcode,
} from '@capacitor-mlkit/barcode-scanning';

import { FilePicker } from '@capawesome/capacitor-file-picker';
import BarcodeScanningModal from './BarcodeScanningModal.vue';

const GH_URL = 'https://github.com/capawesome-team/capacitor-barcode-scanning';

const formats = ref<BarcodeFormat[]>([]);
const lensFacing = ref<LensFacing>(LensFacing.Back);
const googleBarcodeScannerModuleInstallState = ref(0);
const googleBarcodeScannerModuleInstallProgress = ref<number | undefined>(0);
const barcodes = ref<Barcode[]>([]);
const isSupported = ref(false);
const isPermissionGranted = ref(false);
const showModal = ref(false);

const openOnGithub = () => {
  window.open(GH_URL, '_blank');
};

const startScan = async () => {
    try {
    const status = await BarcodeScanner.checkPermissions();
    if (status.camera !== 'granted') {
      await BarcodeScanner.requestPermissions();
    }
    showModal.value = true;
  } catch (error) {
    console.error('Scan failed:', error);
  }
};

const handleModalClose = (barcode?: Barcode) => {
  showModal.value = false;
  if (barcode) {
    barcodes.value = [barcode];
  }
};

const readBarcodeFromImage = async () => {
  const { files } = await FilePicker.pickImages({ limit: 1 });
  const path = files[0]?.path;
  if (!path) return;

  const { barcodes: scannedBarcodes } = await BarcodeScanner.readBarcodesFromImage({
    path,
    formats: formats.value,
  });
  barcodes.value = scannedBarcodes;
};

const scan = async () => {
  const { barcodes: scannedBarcodes } = await BarcodeScanner.scan({
    formats: formats.value,
  });
  barcodes.value = scannedBarcodes;
};

const openSettings = async () => {
  await BarcodeScanner.openSettings();
};

const installGoogleBarcodeScannerModule = async () => {
  await BarcodeScanner.installGoogleBarcodeScannerModule();
};

const requestPermissions = async () => {
  await BarcodeScanner.requestPermissions();
};

onMounted(() => {
  BarcodeScanner.isSupported().then((result) => {
    isSupported.value = result.supported;
  });

  BarcodeScanner.checkPermissions().then((result) => {
    isPermissionGranted.value = result.camera === 'granted';
  });

  BarcodeScanner.removeAllListeners().then(() => {
    BarcodeScanner.addListener(
      'googleBarcodeScannerModuleInstallProgress',
      (event) => {
        console.log('googleBarcodeScannerModuleInstallProgress', event);
        const { state, progress } = event;
        googleBarcodeScannerModuleInstallState.value = state;
        googleBarcodeScannerModuleInstallProgress.value = progress ;
      }
    );
  });
});
</script>



<template>
  <div class="min-h-screen bg-gray-100">
    <header class="bg-white shadow">
      <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8 flex items-center">
        <router-link to="/home" class="mr-4">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            class="h-6 w-6 text-gray-500"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="2"
              d="M10 19l-7-7m0 0l7-7m-7 7h18"
            />
          </svg>
        </router-link>
        <h1 class="text-2xl font-bold text-gray-900">Barcode Scanning</h1>
      </div>
    </header>

    <main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
      <div class="bg-white shadow rounded-lg overflow-hidden mb-6">
        <div class="p-6">
          <h2 class="text-lg font-medium text-gray-900 mb-2">About</h2>
          <p class="text-gray-600">
            ⚡️ Capacitor plugin for scanning barcodes and QR codes.
          </p>
          <div class="flex justify-end mt-4">
            <button
              @click="openOnGithub"
              class="text-indigo-600 hover:text-indigo-800 font-medium"
            >
              GitHub
            </button>
          </div>
        </div>
      </div>

      <div class="bg-white shadow rounded-lg overflow-hidden">
        <div class="p-6">
          <h2 class="text-lg font-medium text-gray-900 mb-4">Demo</h2>
          <form class="space-y-4">
            <div class="grid grid-cols-1 gap-4">
              <div>
                <label class="block text-sm font-medium text-gray-700 mb-1">
                  Formats
                </label>
                <select
                  v-model="formats"
                  multiple
                  class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md border"
                >
                  <option :value="BarcodeFormat.Aztec">Aztec</option>
                  <option :value="BarcodeFormat.Codabar">Codabar</option>
                  <option :value="BarcodeFormat.Code39">Code39</option>
                  <option :value="BarcodeFormat.Code93">Code93</option>
                  <option :value="BarcodeFormat.Code128">Code128</option>
                  <option :value="BarcodeFormat.DataMatrix">DataMatrix</option>
                  <option :value="BarcodeFormat.Ean8">Ean8</option>
                  <option :value="BarcodeFormat.Ean13">Ean13</option>
                  <option :value="BarcodeFormat.Itf">Itf</option>
                  <option :value="BarcodeFormat.Pdf417">Pdf417</option>
                  <option :value="BarcodeFormat.QrCode">QrCode</option>
                  <option :value="BarcodeFormat.UpcA">UpcA</option>
                  <option :value="BarcodeFormat.UpcE">UpcE</option>
                </select>
              </div>

              <div>
                <label class="block text-sm font-medium text-gray-700 mb-1">
                  Lens Facing
                </label>
                <select
                  v-model="lensFacing"
                  class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md border"
                >
                  <option :value="LensFacing.Front">Front</option>
                  <option :value="LensFacing.Back">Back</option>
                </select>
              </div>

              <div>
                <label class="block text-sm font-medium text-gray-700 mb-1">
                  Google Barcode Scanner Module Install State
                </label>
                <input
                  v-model="googleBarcodeScannerModuleInstallState"
                  type="number"
                  readonly
                  class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-gray-100"
                />
              </div>

              <div>
                <label class="block text-sm font-medium text-gray-700 mb-1">
                  Google Barcode Scanner Module Install Progress
                </label>
                <input
                  v-model="googleBarcodeScannerModuleInstallProgress"
                  type="number"
                  readonly
                  class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-gray-100"
                />
              </div>
            </div>

            <div class="space-y-2">
              <button
                @click.prevent="startScan"
                :disabled="!isSupported"
                class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
              >
                Start Scan
              </button>

              <button
                @click.prevent="readBarcodeFromImage"
                :disabled="!isSupported"
                class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
              >
                Read Barcode From Image
              </button>

              <button
                @click.prevent="scan"
                :disabled="!isSupported"
                class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
              >
                Scan
              </button>

              <button
                @click.prevent="openSettings"
                :disabled="!isSupported"
                class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
              >
                Open Settings
              </button>

              <button
                @click.prevent="installGoogleBarcodeScannerModule"
                :disabled="!isSupported"
                class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
              >
                Install Google Barcode Scanner Module
              </button>

              <button
                @click.prevent="requestPermissions"
                :disabled="isPermissionGranted"
                class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
              >
                Request Permissions
              </button>
            </div>
          </form>
        </div>
      </div>

      <div v-for="barcode in barcodes" :key="barcode.rawValue" class="mt-6">
        <div class="bg-white shadow rounded-lg overflow-hidden">
          <div class="p-6 space-y-4">
            <div>
              <label class="block text-sm font-medium text-gray-700 mb-1">
                Bytes
              </label>
              <input
                type="text"
                readonly
                :value="barcode.bytes?.toString() || ''"
                class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-gray-100"
              />
            </div>

            <div>
              <label class="block text-sm font-medium text-gray-700 mb-1">
                Corner Points
              </label>
              <input
                type="text"
                readonly
                :value="barcode.cornerPoints?.toString() || ''"
                class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-gray-100"
              />
            </div>

            <div>
              <label class="block text-sm font-medium text-gray-700 mb-1">
                Display Value
              </label>
              <input
                type="text"
                readonly
                :value="barcode.displayValue"
                class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-gray-100"
              />
            </div>

            <div>
              <label class="block text-sm font-medium text-gray-700 mb-1">
                Format
              </label>
              <input
                type="text"
                readonly
                :value="barcode.format"
                class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-gray-100"
              />
            </div>

            <div>
              <label class="block text-sm font-medium text-gray-700 mb-1">
                Raw Value
              </label>
              <input
                type="text"
                readonly
                :value="barcode.rawValue"
                class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-gray-100"
              />
            </div>

            <div>
              <label class="block text-sm font-medium text-gray-700 mb-1">
                Value Type
              </label>
              <input
                type="text"
                readonly
                :value="barcode.valueType"
                class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-gray-100"
              />
            </div>
          </div>
        </div>
      </div>
    </main>
  </div>

  <BarcodeScanningModal
    v-if="showModal"
    :formats="formats"
    :lensFacing="lensFacing"
    @close="handleModalClose"
  />
</template>

ModalWindow.vue

  <script setup lang="ts">
    import { ref, onMounted, onUnmounted } from 'vue';
    import {
      BarcodeFormat,
      BarcodeScanner,
      LensFacing,
    } from '@capacitor-mlkit/barcode-scanning';
    import type {
      Barcode,
      StartScanOptions,
    } from '@capacitor-mlkit/barcode-scanning';
    import { Capacitor } from '@capacitor/core';
    
    const props = defineProps<{
      formats: BarcodeFormat[];
      lensFacing: LensFacing;
    }>();
    
    const emit = defineEmits<{
      (e: 'close', barcode?: Barcode): void;
    }>();
    
    const squareElement = ref<HTMLDivElement | null>(null);
    const videoElement = ref<HTMLVideoElement | null>(null);
    const isTorchAvailable = ref(false);
    const isWeb = Capacitor.getPlatform() === 'web';
    const minZoomRatio = ref<number>();
    const maxZoomRatio = ref<number>();
    const isCameraActive = ref(false);
    const errorMessage = ref<string>('');
    
    const closeModal = (barcode?: Barcode) => {
      emit('close', barcode);
    };
    
    const setZoomRatio = (event: Event) => {
      const target = event.target as HTMLInputElement;
      if (!target.value) return;
      BarcodeScanner.setZoomRatio({
        zoomRatio: parseInt(target.value, 10),
      });
    };
    
    const toggleTorch = async () => {
      await BarcodeScanner.toggleTorch();
    };
    
    const startScan = async () => {
      try {
        if (isWeb && !videoElement.value) {
          throw new Error('Video element is not available');
        }
        document.body.classList.add('barcode-scanning-active');
        isCameraActive.value = true;
        errorMessage.value = '';
    
        const options: StartScanOptions = {
          formats: props.formats,
          lensFacing: props.lensFacing,
          videoElement: isWeb ? videoElement.value || undefined : undefined,
        };
    
        const squareElementBoundingClientRect =
          squareElement.value?.getBoundingClientRect();
        const scaledRect = squareElementBoundingClientRect
          ? {
              left: squareElementBoundingClientRect.left * window.devicePixelRatio,
              right: squareElementBoundingClientRect.right * window.devicePixelRatio,
              top: squareElementBoundingClientRect.top * window.devicePixelRatio,
              bottom: squareElementBoundingClientRect.bottom * window.devicePixelRatio,
              width: squareElementBoundingClientRect.width * window.devicePixelRatio,
              height: squareElementBoundingClientRect.height * window.devicePixelRatio,
            }
          : undefined;
        const detectionCornerPoints = scaledRect
          ? [
              [scaledRect.left, scaledRect.top],
              [scaledRect.left + scaledRect.width, scaledRect.top],
              [scaledRect.left + scaledRect.width, scaledRect.top + scaledRect.height],
              [scaledRect.left, scaledRect.top + scaledRect.height],
            ]
          : undefined;
    
        const listener = await BarcodeScanner.addListener(
          'barcodesScanned',
          async (event) => {
            const firstBarcode = event.barcodes[0];
            if (!firstBarcode) return;
    
            const cornerPoints = firstBarcode.cornerPoints;
            if (
              detectionCornerPoints &&
              cornerPoints &&
              Capacitor.getPlatform() !== 'web'
            ) {
              if (
                detectionCornerPoints[0][0] > cornerPoints[0][0] ||
                detectionCornerPoints[0][1] > cornerPoints[0][1] ||
                detectionCornerPoints[1][0] < cornerPoints[1][0] ||
                detectionCornerPoints[1][1] > cornerPoints[1][1] ||
                detectionCornerPoints[2][0] < cornerPoints[2][0] ||
                detectionCornerPoints[2][1] < cornerPoints[2][1] ||
                detectionCornerPoints[3][0] > cornerPoints[3][0] ||
                detectionCornerPoints[3][1] < cornerPoints[3][1]
              ) {
                return;
              }
            }
    
            await listener.remove();
            closeModal(firstBarcode);
          }
        );
    
        await BarcodeScanner.startScan(options);
    
        if (Capacitor.getPlatform() !== 'web') {
          BarcodeScanner.getMinZoomRatio().then((result) => {
            minZoomRatio.value = result.zoomRatio;
          });
          BarcodeScanner.getMaxZoomRatio().then((result) => {
            maxZoomRatio.value = result.zoomRatio;
          });
        }
      } catch (error) {
        console.error('Failed to start scan:', error);
        errorMessage.value = 'Не удалось запустить камеру';
        isCameraActive.value = false;
      }
    };
    
    const stopScan = async () => {
      try {
        document.body.classList.remove('barcode-scanning-active');
        await BarcodeScanner.stopScan();
        isCameraActive.value = false;
      } catch (error) {
        console.error('Failed to stop scan:', error);
      }
    };
    
    const restartCamera = async () => {
      await stopScan();
      setTimeout(async () => {
        await startScan();
      }, 100);
    };
    
    onMounted(async () => {
      try {
        const isSupported = await BarcodeScanner.isSupported();
        if (!isSupported.supported) {
          errorMessage.value = 'Сканирование штрих-кодов не поддерживается на этом устройстве';
          return;
        }
    
        await startScan();
        const torchResult = await BarcodeScanner.isTorchAvailable();
        isTorchAvailable.value = torchResult.available;
      } catch (error) {
        console.error('Failed to initialize scanner:', error);
        errorMessage.value = 'Ошибка инициализации сканера';
      }
    });
    
    onUnmounted(() => {
      stopScan();
    });
    </script>
    
    <template>
      <div class="fixed inset-0 z-50 overflow-hidden barcode-scanning-modal">
        <div class="absolute inset-0 bg-black bg-opacity-30"></div>
    
        <div class="fixed inset-0 flex items-center justify-center">
          <div class="relative w-full h-full">
            <!-- Video element for web -->
            <video
              v-if="isWeb"
              ref="videoElement"
              autoplay
              class="absolute inset-0 w-full h-full object-cover"
            ></video>
    
            <!-- Scanning square overlay -->
            <div
              ref="squareElement"
              class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-48 h-48 rounded-xl border-4 border-white shadow-lg"
            ></div>
    
            <!-- Error message -->
            <div
              v-if="errorMessage"
              class="absolute top-20 left-1/2 transform -translate-x-1/2 bg-red-500 text-white p-4 rounded-lg text-center"
            >
              {{ errorMessage }}
            </div>
    
            <!-- Restart camera button -->
            <button
              v-if="!isCameraActive"
              @click="restartCamera"
              class="absolute left-4 bottom-4 p-3 bg-blue-500 text-white rounded-full shadow-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
            >
              <svg
                xmlns="http://www.w3.org/2000/svg"
                class="h-6 w-6"
                fill="none"
                viewBox="0 0 24 24"
                stroke="currentColor"
              >
                <path
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  stroke-width="2"
                  d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
                />
              </svg>
            </button>
    
            <!-- Zoom controls -->
            <div
              class="absolute left-1/2 bottom-4 transform -translate-x-1/2 w-1/2"
            >
              <input
                type="range"
                :min="minZoomRatio"
                :max="maxZoomRatio"
                :disabled="minZoomRatio === undefined || maxZoomRatio === undefined"
                @input="setZoomRatio"
                class="w-full"
              />
            </div>
    
            <!-- Torch button -->
            <button
              v-if="isTorchAvailable"
              @click="toggleTorch"
              class="absolute right-4 bottom-4 p-3 bg-white rounded-full shadow-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500"
            >
              <svg
                xmlns="http://www.w3.org/2000/svg"
                class="h-6 w-6 text-gray-700"
                fill="none"
                viewBox="0 0 24 24"
                stroke="currentColor"
              >
                <path
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  stroke-width="2"
                  d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"
                />
              </svg>
            </button>
    
            <!-- Close button -->
            <button
              @click="closeModal()"
              class="absolute top-4 right-4 p-2 bg-white rounded-full shadow-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500"
            >
              <svg
                xmlns="http://www.w3.org/2000/svg"
                class="h-6 w-6 text-gray-700"
                fill="none"
                viewBox="0 0 24 24"
                stroke="currentColor"
              >
                <path
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  stroke-width="2"
                  d="M6 18L18 6M6 6l12 12"
                />
              </svg>
            </button>
          </div>
        </div>
      </div>
    </template>
    
    <style scoped>
    .barcode-scanning-modal {
    }
    
    video {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    
    body.barcode-scanning-active {
      background-color: transparent !important;
      visibility: visible !important;
    }
    </style>

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.