diff --git a/src/core/features/native/native.module.ts b/src/core/features/native/native.module.ts index dce3324fca1..218e3c96127 100644 --- a/src/core/features/native/native.module.ts +++ b/src/core/features/native/native.module.ts @@ -31,7 +31,7 @@ import { Keyboard } from '@awesome-cordova-plugins/keyboard/ngx'; import { LocalNotifications } from '@awesome-cordova-plugins/local-notifications/ngx'; import { MediaCapture } from '@awesome-cordova-plugins/media-capture/ngx'; import { Push } from '@features/native/plugins/push'; -import { QRScanner } from '@ionic-native/qr-scanner/ngx'; +import { QRScanner } from './plugins/qrscanner'; import { SplashScreen } from '@awesome-cordova-plugins/splash-screen/ngx'; import { SQLite } from '@awesome-cordova-plugins/sqlite/ngx'; import { StatusBar } from '@awesome-cordova-plugins/status-bar/ngx'; @@ -80,7 +80,6 @@ export const CORE_NATIVE_SERVICES = [ Keyboard, LocalNotifications, MediaCapture, - QRScanner, SplashScreen, SQLite, StatusBar, diff --git a/src/core/features/native/plugins/index.ts b/src/core/features/native/plugins/index.ts index 539ad0901fa..ee4d403d87a 100644 --- a/src/core/features/native/plugins/index.ts +++ b/src/core/features/native/plugins/index.ts @@ -16,7 +16,9 @@ import { makeSingleton } from '@singletons'; import { Chooser as ChooserService } from './chooser'; import { Push as PushService } from './push'; import { Zip as ZipService } from './zip'; +import { QRScanner as QRScannerService } from './qrscanner'; export const Chooser = makeSingleton(ChooserService); export const Push = makeSingleton(PushService); export const Zip = makeSingleton(ZipService); +export const QRScanner = makeSingleton(QRScannerService); diff --git a/src/core/features/native/plugins/qrscanner.ts b/src/core/features/native/plugins/qrscanner.ts new file mode 100644 index 00000000000..4b39310479a --- /dev/null +++ b/src/core/features/native/plugins/qrscanner.ts @@ -0,0 +1,187 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { QRScannerError, QRScannerStatus } from '@moodlehq/cordova-plugin-qrscanner'; + +/** + * QR Scanner plugin wrapper + */ +@Injectable({ providedIn: 'root' }) +export class QRScanner { + + /** + * Destroy the scanner instance. + * + * @returns QR scanner status. + */ + destroy(): Promise { + return new Promise((resolve) => window.QRScanner.destroy(resolve)); + } + + /** + * Request permission to use QR scanner. + * + * @returns QR scanner status. + */ + prepare(): Promise { + return new Promise((resolve, reject) => { + window.QRScanner.prepare((error: QRScannerError, status: QRScannerStatus) => { + error ? reject(error) : resolve(status); + }); + }); + } + + /** + * Configures the native webview to have a transparent background, then sets the background of the and DOM + * elements to transparent, allowing the webview to re-render with the transparent background. + * + * @returns QR scanner status. + */ + show(): Promise { + return new Promise(resolve => window.QRScanner.show((status) => resolve(status))); + } + + /** + * Call this method to enable scanning. You must then call the `show` method to make the camera preview visible. + * + * @returns Observable that emits the scanned text. Unsubscribe from the observable to stop scanning. + */ + scan(): Observable { + return new Observable(observer => { + window.QRScanner.scan((error: QRScannerError, text: string) => { + error ? observer.error(error) : observer.next(text); + }); + + return () => { + window.QRScanner.cancelScan(); + }; + }); + } + + /** + * Configures the native webview to be opaque with a white background, covering the video preview. + * + * @returns QR scanner status. + */ + hide(): Promise { + return new Promise((resolve) => window.QRScanner.hide(resolve)); + } + + /** + * Enable the device's light (for scanning in low-light environments). + * + * @returns QR scanner status. + */ + enableLight(): Promise { + return new Promise((resolve, reject) => { + window.QRScanner.enableLight((error: QRScannerError, status: QRScannerStatus) => { + error ? reject(error) : resolve(status); + }); + }); + } + + /** + * Disable the device's light. + * + * @returns QR scanner status. + */ + disableLight(): Promise { + return new Promise((resolve, reject) => { + window.QRScanner.disableLight((error: QRScannerError, status: QRScannerStatus) => { + error ? reject(error) : resolve(status); + }); + }); + } + + /** + * Use front camera. + * + * @returns QR scanner status. + */ + useFrontCamera(): Promise { + return new Promise((resolve, reject) => { + window.QRScanner.useFrontCamera((error: QRScannerError, status: QRScannerStatus) => { + error ? reject(error) : resolve(status); + }); + }); + } + + /** + * Use back camera. + * + * @returns QR scanner status. + */ + useBackCamera(): Promise { + return new Promise((resolve, reject) => { + window.QRScanner.useBackCamera((error: QRScannerError, status: QRScannerStatus) => { + error ? reject(error) : resolve(status); + }); + }); + } + + /** + * Disable the device's light. + * + * @param camera Provide `0` for back camera, and `1` for front camera. + * @returns QR scanner status. + */ + useCamera(camera: QRScannerCamera): Promise { + return new Promise((resolve, reject) => { + window.QRScanner.useCamera(camera, (error: QRScannerError, status: QRScannerStatus) => { + error ? reject(error) : resolve(status); + }); + }); + } + + /** + * Pauses the video preview on the current frame and pauses scanning. + * + * @returns QR scanner status. + */ + pausePreview(): Promise { + return new Promise((resolve) => window.QRScanner.pausePreview(resolve)); + } + + /** + * Resume the video preview and resumes scanning. + * + * @returns QR scanner status. + */ + resumePreview(): Promise { + return new Promise((resolve) => window.QRScanner.resumePreview(resolve)); + } + + /** + * Returns permission status. + * + * @returns QR scanner status. + */ + getStatus(): Promise { + return new Promise((resolve) => window.QRScanner.getStatus(resolve)); + } + + /** + * Opens settings to edit app permissions. + */ + openSettings(): void { + window.QRScanner.openSettings(); + } + +} + +export enum QRScannerCamera { + FRONT_CAMERA = 0, + BACK_CAMERA = 1, +} diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index 2e92b29732f..86c90ea9bc1 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -16,7 +16,6 @@ import { Injectable } from '@angular/core'; import { InAppBrowserObject, InAppBrowserOptions } from '@awesome-cordova-plugins/in-app-browser'; import { FileEntry } from '@awesome-cordova-plugins/file/ngx'; import { Subscription } from 'rxjs'; - import { CoreEvents } from '@singletons/events'; import { CoreFile } from '@services/file'; import { CoreLang } from '@services/lang'; @@ -24,7 +23,7 @@ import { CoreWS } from '@services/ws'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreTextUtils } from '@services/utils/text'; -import { makeSingleton, Clipboard, InAppBrowser, FileOpener, WebIntent, QRScanner, Translate, NgZone } from '@singletons'; +import { makeSingleton, Clipboard, InAppBrowser, FileOpener, WebIntent, Translate, NgZone } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreViewerQRScannerComponent } from '@features/viewer/components/qr-scanner/qr-scanner'; import { CoreCanceledError } from '@classes/errors/cancelederror'; @@ -40,6 +39,7 @@ import { CoreSites } from '@services/sites'; import { CoreCancellablePromise } from '@classes/cancellable-promise'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreUrlUtils } from './url'; +import { QRScanner } from '@features/native/plugins'; export type TreeNode = T & { children: TreeNode[] }; diff --git a/src/core/singletons/index.ts b/src/core/singletons/index.ts index e14c219f45f..894929b381b 100644 --- a/src/core/singletons/index.ts +++ b/src/core/singletons/index.ts @@ -52,7 +52,6 @@ import { WebView as WebViewService } from '@awesome-cordova-plugins/ionic-webvie import { Keyboard as KeyboardService } from '@awesome-cordova-plugins/keyboard/ngx'; import { LocalNotifications as LocalNotificationsService } from '@awesome-cordova-plugins/local-notifications/ngx'; import { MediaCapture as MediaCaptureService } from '@awesome-cordova-plugins/media-capture/ngx'; -import { QRScanner as QRScannerService } from '@ionic-native/qr-scanner/ngx'; import { StatusBar as StatusBarService } from '@awesome-cordova-plugins/status-bar/ngx'; import { SplashScreen as SplashScreenService } from '@awesome-cordova-plugins/splash-screen/ngx'; import { SQLite as SQLiteService } from '@awesome-cordova-plugins/sqlite/ngx'; @@ -183,7 +182,6 @@ export const Keyboard = makeSingleton(KeyboardService); export const LocalNotifications = makeSingleton(LocalNotificationsService); export const MediaCapture = makeSingleton(MediaCaptureService); export const NativeHttp = makeSingleton(HTTP); -export const QRScanner = makeSingleton(QRScannerService); export const StatusBar = makeSingleton(StatusBarService); export const SplashScreen = makeSingleton(SplashScreenService); export const SQLite = makeSingleton(SQLiteService); diff --git a/src/types/cordova-plugin-qrscanner.d.ts b/src/types/cordova-plugin-qrscanner.d.ts new file mode 100644 index 00000000000..a2ca2a58944 --- /dev/null +++ b/src/types/cordova-plugin-qrscanner.d.ts @@ -0,0 +1,68 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Types for qr scanner plugin. + * + * @see https://github.com/moodlemobile/cordova-plugin-qrscanner + */ + +type IQRScannerStatus = { + authorized: boolean; + denied: boolean; + restricted: boolean; + prepared: boolean; + scanning: boolean; + previewing: boolean; + showing: boolean; + lightEnabled: boolean; + canOpenSettings: boolean; + canEnableLight: boolean; + canChangeCamera: boolean; + currentCamera: number; +}; + +type IQRScannerError = { + name: string; + code: number; + _message: string; // eslint-disable-line @typescript-eslint/naming-convention +}; + +interface Window { + + // eslint-disable-next-line @typescript-eslint/naming-convention + QRScanner: { + prepare(onDone?: (error: IQRScannerError, status: IQRScannerStatus) => void): void; + destroy(onDone?: (status: IQRScannerStatus) => void): void; + scan(onDone: (error: IQRScannerError, text: string) => void): void; + cancelScan(onDone?: (status: IQRScannerStatus) => void): void; + show(onDone?: (status: IQRScannerStatus) => void): void; + hide(onDone?: (status: IQRScannerStatus) => void): void; + pausePreview(onDone?: (status: IQRScannerStatus) => void): void; + resumePreview(onDone?: (status: IQRScannerStatus) => void): void; + enableLight(onDone?: (error: IQRScannerError, status: IQRScannerStatus) => void): void; + disableLight(onDone?: (error: IQRScannerError, status: IQRScannerStatus) => void): void; + useCamera(camera: number, onDone?: (error: IQRScannerError, status: IQRScannerStatus) => void): void; + useFrontCamera(onDone?: (error: IQRScannerError, status: IQRScannerStatus) => void): void; + useBackCamera(onDone?: (error: IQRScannerError, status: IQRScannerStatus) => void): void; + openSettings(onDone?: (error: IQRScannerError, status: IQRScannerStatus) => void): void; + getStatus(onDone: (status: IQRScannerStatus) => void): void; + }; + +} + +declare module '@moodlehq/cordova-plugin-qrscanner' { + export type QRScannerStatus = IQRScannerStatus; + export type QRScannerError = IQRScannerError; +}