From 0bbeb991aefc90e6c05e75c66238aef8e27e4dfc Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 9 Mar 2021 13:25:27 +0100 Subject: [PATCH] MOBILE-3712 core: Implement and use QR scanner --- .../rich-text-editor/rich-text-editor.ts | 2 +- .../login/pages/credentials/credentials.ts | 56 ++------- .../login/pages/reconnect/reconnect.html | 8 ++ .../login/pages/reconnect/reconnect.ts | 17 +++ src/core/features/login/pages/site/site.ts | 114 ++++++++++++++---- .../features/login/services/login-helper.ts | 110 ++++++++++++++++- src/core/features/mainmenu/pages/more/more.ts | 31 ++++- .../viewer/components/components.module.ts | 3 + .../components/qr-scanner/qr-scanner.html | 12 ++ .../components/qr-scanner/qr-scanner.ts | 80 ++++++++++++ src/core/services/sites.ts | 3 + src/core/services/utils/text.ts | 30 +++-- src/core/services/utils/utils.ts | 30 +++-- src/theme/theme.base.scss | 11 ++ 14 files changed, 408 insertions(+), 99 deletions(-) create mode 100644 src/core/features/viewer/components/qr-scanner/qr-scanner.html create mode 100644 src/core/features/viewer/components/qr-scanner/qr-scanner.ts diff --git a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts index 31024970e..638d4ac38 100644 --- a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts +++ b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts @@ -1018,9 +1018,9 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn const text = await CoreUtils.scanQR(); if (text) { + this.editorElement?.focus(); // Make sure the editor is focused. document.execCommand('insertText', false, text); } - // this.content.resize(); // Resize content, otherwise the content height becomes 1 for some reason. } /** diff --git a/src/core/features/login/pages/credentials/credentials.ts b/src/core/features/login/pages/credentials/credentials.ts index 803f4354d..ea27e9a0a 100644 --- a/src/core/features/login/pages/credentials/credentials.ts +++ b/src/core/features/login/pages/credentials/credentials.ts @@ -18,8 +18,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { CoreApp } from '@services/app'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreUtils } from '@services/utils/utils'; -import { CoreLoginHelper, CoreLoginHelperProvider } from '@features/login/services/login-helper'; +import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreConstants } from '@/core/constants'; import { Translate } from '@singletons'; import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site'; @@ -50,7 +49,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { isBrowserSSO = false; isFixedUrlSet = false; showForgottenPassword = true; - showScanQR: boolean; + showScanQR = false; protected siteConfig?: CoreSitePublicConfigResponse; protected eventThrown = false; @@ -60,19 +59,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { constructor( protected fb: FormBuilder, - ) { - - const canScanQR = CoreUtils.canScanQR(); - if (canScanQR) { - if (typeof CoreConstants.CONFIG.displayqroncredentialscreen == 'undefined') { - this.showScanQR = CoreLoginHelper.isFixedUrlSet(); - } else { - this.showScanQR = !!CoreConstants.CONFIG.displayqroncredentialscreen; - } - } else { - this.showScanQR = false; - } - } + ) {} /** * Initialize the component. @@ -91,6 +78,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { this.logoUrl = !CoreConstants.CONFIG.forceLoginLogo && CoreNavigator.getRouteParam('logoUrl') || undefined; this.siteConfig = CoreNavigator.getRouteParam('siteConfig'); this.urlToOpen = CoreNavigator.getRouteParam('urlToOpen'); + this.showScanQR = CoreLoginHelper.displayQRInCredentialsScreen(); this.credForm = this.fb.group({ username: [CoreNavigator.getRouteParam('username') || '', Validators.required], @@ -285,37 +273,17 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { /** * Show instructions and scan QR code. - */ - showInstructionsAndScanQR(): void { - // Show some instructions first. - CoreDomUtils.showAlertWithOptions({ - header: Translate.instant('core.login.faqwhereisqrcode'), - message: Translate.instant( - 'core.login.faqwhereisqrcodeanswer', - { $image: CoreLoginHelperProvider.FAQ_QRCODE_IMAGE_HTML }, - ), - buttons: [ - { - text: Translate.instant('core.cancel'), - role: 'cancel', - }, - { - text: Translate.instant('core.next'), - handler: (): void => { - this.scanQR(); - }, - }, - ], - }); - } - - /** - * Scan a QR code and put its text in the URL input. * * @return Promise resolved when done. */ - async scanQR(): Promise { - // @todo Scan for a QR code. + async showInstructionsAndScanQR(): Promise { + try { + await CoreLoginHelper.showScanQRInstructions(); + + await CoreLoginHelper.scanQR(); + } catch { + // Ignore errors. + } } /** diff --git a/src/core/features/login/pages/reconnect/reconnect.html b/src/core/features/login/pages/reconnect/reconnect.html index 6fefa9052..7a9786d43 100644 --- a/src/core/features/login/pages/reconnect/reconnect.html +++ b/src/core/features/login/pages/reconnect/reconnect.html @@ -61,6 +61,14 @@ + + +
{{ 'core.login.or' | translate }}
+ + + {{ 'core.scanqr' | translate }} + +
diff --git a/src/core/features/login/pages/reconnect/reconnect.ts b/src/core/features/login/pages/reconnect/reconnect.ts index 25424c0d6..3016f7984 100644 --- a/src/core/features/login/pages/reconnect/reconnect.ts +++ b/src/core/features/login/pages/reconnect/reconnect.ts @@ -51,6 +51,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { isOAuth = false; isLoggedOut: boolean; siteId!: string; + showScanQR = false; protected page?: string; protected pageParams?: Params; @@ -82,6 +83,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { this.siteUrl = siteId; this.page = CoreNavigator.getRouteParam('pageName'); this.pageParams = CoreNavigator.getRouteParam('pageParams'); + this.showScanQR = CoreLoginHelper.displayQRInSiteScreen() || CoreLoginHelper.displayQRInCredentialsScreen(); try { const site = await CoreSites.getSite(this.siteId); @@ -246,4 +248,19 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { } } + /** + * Show instructions and scan QR code. + * + * @return Promise resolved when done. + */ + async showInstructionsAndScanQR(): Promise { + try { + await CoreLoginHelper.showScanQRInstructions(); + + await CoreLoginHelper.scanQR(); + } catch { + // Ignore errors. + } + } + } diff --git a/src/core/features/login/pages/site/site.ts b/src/core/features/login/pages/site/site.ts index 3eaaf10d8..9ea6a324d 100644 --- a/src/core/features/login/pages/site/site.ts +++ b/src/core/features/login/pages/site/site.ts @@ -30,6 +30,8 @@ import { CoreUrlUtils } from '@services/utils/url'; import { CoreLoginSiteHelpComponent } from '@features/login/components/site-help/site-help'; import { CoreLoginSiteOnboardingComponent } from '@features/login/components/site-onboarding/site-onboarding'; import { CoreNavigator } from '@services/navigator'; +import { CoreCustomURLSchemes, CoreCustomURLSchemesHandleError } from '@services/urlschemes'; +import { CoreTextUtils } from '@services/utils/text'; /** * Page that displays a "splash screen" while the app is being initialized. @@ -82,8 +84,7 @@ export class CoreLoginSitePage implements OnInit { this.initOnboarding(); } - this.showScanQR = CoreUtils.canScanQR() && (typeof CoreConstants.CONFIG.displayqronsitescreen == 'undefined' || - !!CoreConstants.CONFIG.displayqronsitescreen); + this.showScanQR = CoreLoginHelper.displayQRInSiteScreen(); this.siteForm = this.formBuilder.group({ siteUrl: [url, this.moodleUrlValidator()], @@ -464,28 +465,17 @@ export class CoreLoginSitePage implements OnInit { /** * Show instructions and scan QR code. + * + * @return Promise resolved when done. */ - showInstructionsAndScanQR(): void { - // Show some instructions first. - CoreDomUtils.showAlertWithOptions({ - header: Translate.instant('core.login.faqwhereisqrcode'), - message: Translate.instant( - 'core.login.faqwhereisqrcodeanswer', - { $image: CoreLoginHelperProvider.FAQ_QRCODE_IMAGE_HTML }, - ), - buttons: [ - { - text: Translate.instant('core.cancel'), - role: 'cancel', - }, - { - text: Translate.instant('core.next'), - handler: (): void => { - this.scanQR(); - }, - }, - ], - }); + async showInstructionsAndScanQR(): Promise { + try { + await CoreLoginHelper.showScanQRInstructions(); + + await this.scanQR(); + } catch { + // Ignore errors. + } } /** @@ -497,9 +487,83 @@ export class CoreLoginSitePage implements OnInit { // Scan for a QR code. const text = await CoreUtils.scanQR(); - if (text) { - // @todo + if (!text) { + return; } + + if (CoreCustomURLSchemes.isCustomURL(text)) { + try { + await CoreCustomURLSchemes.handleCustomURL(text); + } catch (error) { + if (error && error.data && error.data.isAuthenticationURL && error.data.siteUrl) { + // An error ocurred, but it's an authentication URL and we have the site URL. + this.treatErrorInAuthenticationCustomURL(text, error); + } else { + CoreCustomURLSchemes.treatHandleCustomURLError(error); + } + } + + return; + } + + // Not a custom URL scheme, check if it's a URL scheme to another app. + const scheme = CoreUrlUtils.getUrlProtocol(text); + + if (scheme && scheme != 'http' && scheme != 'https') { + CoreDomUtils.showErrorModal(Translate.instant('core.errorurlschemeinvalidscheme', { $a: text })); + } else if (CoreLoginHelper.isSiteUrlAllowed(text)) { + // Put the text in the field (if present). + this.siteForm.controls.siteUrl.setValue(text); + + this.connect(new Event('click'), text); + } else { + CoreDomUtils.showErrorModal('core.errorurlschemeinvalidsite', true); + } + } + + /** + * Treat an error while handling a custom URL meant to perform an authentication. + * If the site doesn't use SSO, the user will be sent to the credentials screen. + * + * @param customURL Custom URL handled. + * @param error Error data. + * @return Promise resolved when done. + */ + protected async treatErrorInAuthenticationCustomURL(customURL: string, error: CoreCustomURLSchemesHandleError): Promise { + const siteUrl = error.data?.siteUrl || ''; + const modal = await CoreDomUtils.showModalLoading(); + + // Set the site URL in the input. + this.siteForm.controls.siteUrl.setValue(siteUrl); + + try { + // Check if site uses SSO. + const response = await CoreSites.checkSite(siteUrl); + + await CoreSites.checkApplication(response); + + if (!CoreLoginHelper.isSSOLoginNeeded(response.code)) { + // No SSO, go to credentials page. + await CoreNavigator.navigate('/login/credentials', { + params: { + siteUrl: response.siteUrl, + siteConfig: response.config, + }, + }); + } + } catch (error) { + // Ignore errors. + } finally { + modal.dismiss(); + } + + // Now display the error. + error.error = CoreTextUtils.addTextToError( + error.error, + '

' + Translate.instant('core.login.youcanstillconnectwithcredentials'), + ); + + CoreCustomURLSchemes.treatHandleCustomURLError(error); } } diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index a22ca2109..6a66670ba 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -33,6 +33,8 @@ import { makeSingleton, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreUrl } from '@singletons/url'; import { CoreNavigator } from '@services/navigator'; +import { CoreCanceledError } from '@classes/errors/cancelederror'; +import { CoreCustomURLSchemes } from '@services/urlschemes'; /** * Helper provider that provides some common features regarding authentication. @@ -53,7 +55,7 @@ export class CoreLoginHelperProvider { protected isSSOConfirmShown = false; protected isOpenEditAlertShown = false; protected isOpeningReconnect = false; - waitingForBrowser = false; + protected waitingForBrowser = false; constructor() { this.logger = CoreLogger.getInstance('CoreLoginHelper'); @@ -319,7 +321,7 @@ export class CoreLoginHelperProvider { * @param params Params. * @return OAuth ID. */ - getOAuthIdFromParams(params: CoreUrlParams): number | undefined { + getOAuthIdFromParams(params?: CoreUrlParams): number | undefined { return params && typeof params.oauthsso != 'undefined' ? Number(params.oauthsso) : undefined; } @@ -1221,6 +1223,110 @@ export class CoreLoginHelperProvider { } } + /** + * Return whether the app is waiting for browser. + * + * @return Whether the app is waiting for browser. + */ + isWaitingForBrowser(): boolean { + return this.waitingForBrowser; + } + + /** + * Set whether the app is waiting for browser. + * + * @param value New value. + */ + setWaitingForBrowser(value: boolean): void { + this.waitingForBrowser = value; + } + + /** + * Check whether the QR reader should be displayed in site screen. + * + * @return Whether the QR reader should be displayed in site screen. + */ + displayQRInSiteScreen(): boolean { + return CoreUtils.canScanQR() && (typeof CoreConstants.CONFIG.displayqronsitescreen == 'undefined' || + !!CoreConstants.CONFIG.displayqronsitescreen); + } + + /** + * Check whether the QR reader should be displayed in credentials screen. + * + * @return Whether the QR reader should be displayed in credentials screen. + */ + displayQRInCredentialsScreen(): boolean { + if (!CoreUtils.canScanQR()) { + return false; + } + + return (CoreConstants.CONFIG.displayqroncredentialscreen === undefined && this.isFixedUrlSet()) || + (CoreConstants.CONFIG.displayqroncredentialscreen !== undefined && !!CoreConstants.CONFIG.displayqroncredentialscreen); + } + + /** + * Show instructions to scan QR code. + * + * @return Promise resolved if the user accepts to scan QR. + */ + showScanQRInstructions(): Promise { + const deferred = CoreUtils.promiseDefer(); + + // Show some instructions first. + CoreDomUtils.showAlertWithOptions({ + header: Translate.instant('core.login.faqwhereisqrcode'), + message: Translate.instant( + 'core.login.faqwhereisqrcodeanswer', + { $image: CoreLoginHelperProvider.FAQ_QRCODE_IMAGE_HTML }, + ), + buttons: [ + { + text: Translate.instant('core.cancel'), + role: 'cancel', + handler: (): void => { + deferred.reject(new CoreCanceledError()); + }, + }, + { + text: Translate.instant('core.next'), + handler: (): void => { + deferred.resolve(); + }, + }, + ], + }); + + return deferred.promise; + } + + /** + * Scan a QR code and tries to authenticate the user using custom URL scheme. + * + * @return Promise resolved when done. + */ + async scanQR(): Promise { + // Scan for a QR code. + const text = await CoreUtils.scanQR(); + + if (text && CoreCustomURLSchemes.isCustomURL(text)) { + try { + await CoreCustomURLSchemes.handleCustomURL(text); + } catch (error) { + CoreCustomURLSchemes.treatHandleCustomURLError(error); + } + } else if (text) { + // Not a custom URL scheme, check if it's a URL scheme to another app. + const scheme = CoreUrlUtils.getUrlProtocol(text); + + if (scheme && scheme != 'http' && scheme != 'https') { + CoreDomUtils.showErrorModal(Translate.instant('core.errorurlschemeinvalidscheme', { $a: text })); + } else { + CoreDomUtils.showErrorModal('core.login.errorqrnoscheme', true); + } + } + } + } export const CoreLoginHelper = makeSingleton(CoreLoginHelperProvider); diff --git a/src/core/features/mainmenu/pages/more/more.ts b/src/core/features/mainmenu/pages/more/more.ts index 506855dca..83e5703f3 100644 --- a/src/core/features/mainmenu/pages/more/more.ts +++ b/src/core/features/mainmenu/pages/more/more.ts @@ -23,6 +23,10 @@ import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../services/ma import { CoreMainMenu, CoreMainMenuCustomItem } from '../../services/mainmenu'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreNavigator } from '@services/navigator'; +import { CoreCustomURLSchemes } from '@services/urlschemes'; +import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; +import { CoreTextUtils } from '@services/utils/text'; +import { Translate } from '@singletons'; /** * Page that displays the main menu of the app. @@ -154,10 +158,31 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { */ async scanQR(): Promise { // Scan for a QR code. - // @todo - // eslint-disable-next-line no-console - console.error('scanQR not implemented'); + const text = await CoreUtils.scanQR(); + if (!text) { + return; + } + + if (CoreCustomURLSchemes.isCustomURL(text)) { + // Is a custom URL scheme, handle it. + CoreCustomURLSchemes.handleCustomURL(text).catch((error) => { + CoreCustomURLSchemes.treatHandleCustomURLError(error); + }); + } else if (/^[^:]{2,}:\/\/[^ ]+$/i.test(text)) { // Check if it's a URL. + // Check if the app can handle the URL. + const treated = await CoreContentLinksHelper.handleLink(text, undefined, true, true); + + if (!treated) { + // Can't handle it, open it in browser. + CoreSites.getCurrentSite()?.openInBrowserWithAutoLoginIfSameSite(text); + } + } else { + // It's not a URL, open it in a modal so the user can see it and copy it. + CoreTextUtils.viewText(Translate.instant('core.qrscanner'), text, { + displayCopyButton: true, + }); + } } /** diff --git a/src/core/features/viewer/components/components.module.ts b/src/core/features/viewer/components/components.module.ts index cb1428d05..d47cd558b 100644 --- a/src/core/features/viewer/components/components.module.ts +++ b/src/core/features/viewer/components/components.module.ts @@ -16,11 +16,13 @@ import { NgModule } from '@angular/core'; import { CoreSharedModule } from '@/core/shared.module'; import { CoreViewerImageComponent } from './image/image'; +import { CoreViewerQRScannerComponent } from './qr-scanner/qr-scanner'; import { CoreViewerTextComponent } from './text/text'; @NgModule({ declarations: [ CoreViewerImageComponent, + CoreViewerQRScannerComponent, CoreViewerTextComponent, ], imports: [ @@ -28,6 +30,7 @@ import { CoreViewerTextComponent } from './text/text'; ], exports: [ CoreViewerImageComponent, + CoreViewerQRScannerComponent, CoreViewerTextComponent, ], }) diff --git a/src/core/features/viewer/components/qr-scanner/qr-scanner.html b/src/core/features/viewer/components/qr-scanner/qr-scanner.html new file mode 100644 index 000000000..49cc60562 --- /dev/null +++ b/src/core/features/viewer/components/qr-scanner/qr-scanner.html @@ -0,0 +1,12 @@ + + + {{ title }} + + + + + + + + + diff --git a/src/core/features/viewer/components/qr-scanner/qr-scanner.ts b/src/core/features/viewer/components/qr-scanner/qr-scanner.ts new file mode 100644 index 000000000..318e3eb09 --- /dev/null +++ b/src/core/features/viewer/components/qr-scanner/qr-scanner.ts @@ -0,0 +1,80 @@ +// (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 { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreUtils } from '@services/utils/utils'; +import { ModalController, Translate } from '@singletons'; + +/** + * Page to scan a QR code. + */ +@Component({ + selector: 'core-viewer-qr-scanner', + templateUrl: 'qr-scanner.html', +}) +export class CoreViewerQRScannerComponent implements OnInit, OnDestroy { + + @Input() title?: string; // Page title. + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + this.title = this.title || Translate.instant('core.scanqr'); + + try { + + let text = await CoreUtils.startScanQR(); + + // Text captured, return it. + text = typeof text == 'string' ? text.trim() : ''; + + this.closeModal(text); + } catch (error) { + if (!CoreDomUtils.isCanceledError(error)) { + // Show error and stop scanning. + CoreDomUtils.showErrorModalDefault(error, 'An error occurred.'); + CoreUtils.stopScanQR(); + } + + this.closeModal(); + } + } + + /** + * Cancel scanning. + */ + cancel(): void { + CoreUtils.stopScanQR(); + } + + /** + * Close modal. + * + * @param text The text to return (if any). + */ + closeModal(text?: string): void { + ModalController.dismiss(text); + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + // If this code is reached and scan hasn't been stopped yet it means the user clicked the back button, cancel. + CoreUtils.stopScanQR(); + } + +} diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index 8d62529a8..5a5d4ecd1 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -498,6 +498,9 @@ export class CoreSitesProvider { this.currentSite = candidateSite; // Store session. this.login(siteId); + } else if (this.currentSite && this.currentSite.getId() == siteId) { + // Current site has just been updated, trigger the event. + CoreEvents.trigger(CoreEvents.SITE_UPDATED, info, siteId); } CoreEvents.trigger(CoreEvents.SITE_ADDED, info, siteId); diff --git a/src/core/services/utils/text.ts b/src/core/services/utils/text.ts index ec36410a5..462c28f69 100644 --- a/src/core/services/utils/text.ts +++ b/src/core/services/utils/text.ts @@ -118,21 +118,29 @@ export class CoreTextUtilsProvider { * @param text Text to add. * @return Modified error. */ - addTextToError(error: string | CoreTextErrorObject, text: string): string | CoreTextErrorObject { + addTextToError(error: string | CoreError | CoreTextErrorObject | undefined | null, text: string): string | CoreTextErrorObject { if (typeof error == 'string') { return error + text; } - if (error) { - if (typeof error.message == 'string') { - error.message += text; - } else if (typeof error.error == 'string') { - error.error += text; - } else if (typeof error.content == 'string') { - error.content += text; - } else if (typeof error.body == 'string') { - error.body += text; - } + if (error instanceof CoreError) { + error.message += text; + + return error; + } + + if (!error) { + return text; + } + + if (typeof error.message == 'string') { + error.message += text; + } else if (typeof error.error == 'string') { + error.error += text; + } else if (typeof error.content == 'string') { + error.content += text; + } else if (typeof error.body == 'string') { + error.body += text; } return error; diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index 859d46162..3833530be 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -26,9 +26,11 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreTextUtils } from '@services/utils/text'; import { CoreWSError } from '@classes/errors/wserror'; -import { makeSingleton, Clipboard, InAppBrowser, FileOpener, WebIntent, QRScanner, Translate } from '@singletons'; +import { makeSingleton, Clipboard, InAppBrowser, FileOpener, WebIntent, QRScanner, Translate, ModalController } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreFileSizeSum } from '@services/plugin-file-delegate'; +import { CoreViewerQRScannerComponent } from '@features/viewer/components/qr-scanner/qr-scanner'; +import { CoreCanceledError } from '@classes/errors/cancelederror'; type TreeNode = T & { children: TreeNode[] }; @@ -1489,12 +1491,20 @@ export class CoreUtilsProvider { * @param title Title of the modal. Defaults to "QR reader". * @return Promise resolved with the captured text or undefined if cancelled or error. */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - scanQR(title?: string): Promise { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - return new Promise((resolve, reject): void => { - // @todo + async scanQR(title?: string): Promise { + const modal = await ModalController.create({ + component: CoreViewerQRScannerComponent, + cssClass: 'core-modal-fullscreen', + componentProps: { + title, + }, }); + + await modal.present(); + + const result = await modal.onWillDismiss(); + + return result.data; } /** @@ -1503,12 +1513,6 @@ export class CoreUtilsProvider { * @return Promise resolved with the QR string, rejected if error or cancelled. */ async startScanQR(): Promise { - try { - return this.startScanQR(); - } catch (error) { - // do nothing - } - if (!CoreApp.isMobile()) { return Promise.reject('QRScanner isn\'t available in browser.'); } @@ -1580,7 +1584,7 @@ export class CoreUtilsProvider { } else if (typeof data != 'undefined') { this.qrScanData.deferred.resolve(data as string); } else { - this.qrScanData.deferred.reject(CoreDomUtils.createCanceledError()); + this.qrScanData.deferred.reject(new CoreCanceledError()); } delete this.qrScanData; diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index 26a0cb984..bf6558671 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -454,3 +454,14 @@ ion-button.core-button-select { .core-iframe-offline-disabled { display: none !important; } + +.core-scanning-qr { + .ion-page, .modal-wrapper { + background-color: transparent !important; + --background: transparent; + } + + ion-content, ion-backdrop, ion-modal:not(.core-modal-fullscreen), ion-tabs { + display: none !important; + } +}