forked from jsnbuchanan/crowd-funder-for-time-pwa
De-coupled web and mobile QR scanner views
- Separate scanner views for web and mobile platforms: different libraries, similar layouts - Mobile: QR code overlaid on top of full-screen camera view - Mobile: added framing box + instruction text - Mobile: increased debounce time to compensate for behavior of MLkit scanner - Web: removed Capacitor-related code and platform-specific conditions - Web: adjusted max-size of QR code and camera view to better fit newer iOS device screens - Web + mobile: camera view remains active when a QR scan is triggered
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"appId": "app.timesafari",
|
||||
"appId": "app.trentlarson.timesafari",
|
||||
"appName": "TimeSafari",
|
||||
"webDir": "dist",
|
||||
"bundledWebRuntime": false,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 48;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -123,8 +123,9 @@
|
||||
504EC2FC1FED79650016851F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 920;
|
||||
LastUpgradeCheck = 920;
|
||||
LastUpgradeCheck = 1630;
|
||||
TargetAttributes = {
|
||||
504EC3031FED79650016851F = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
@@ -268,6 +269,7 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -275,8 +277,10 @@
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -286,8 +290,10 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 7XVXYPEQYJ;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
@@ -325,6 +331,7 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -332,8 +339,10 @@
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -343,8 +352,10 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 7XVXYPEQYJ;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
@@ -356,7 +367,8 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
@@ -368,18 +380,22 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 18;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.4.7;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.trentlarson.timesafari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic"; /* allows agvtool to set *_VERSION settings, but new-marketing-version sets MARKETING_VERSION in Info.plist instead */
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -390,17 +406,21 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 18;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.4.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.trentlarson.timesafari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic"; /* allows agvtool to set *_VERSION settings, but new-marketing-version sets MARKETING_VERSION in Info.plist instead */
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -1,24 +1,104 @@
|
||||
<template>
|
||||
<!-- CONTENT -->
|
||||
<section id="Content" class="relativew-[100vw] h-[100vh]">
|
||||
<section id="Content" class="relative w-[100vw] h-[100vh]">
|
||||
<div
|
||||
class="absolute inset-x-0 bottom-0 bg-black/50 p-6 pb-[calc(env(safe-area-inset-bottom)+1.5rem)]"
|
||||
class="p-6 bg-white w-full max-w-[calc((100vh-env(safe-area-inset-top)-env(safe-area-inset-bottom))*0.4)] mx-auto"
|
||||
>
|
||||
<p class="text-center text-white mb-3">
|
||||
Point your camera at a TimeSafari contact QR code to scan it
|
||||
automatically.
|
||||
</p>
|
||||
|
||||
<p v-if="error" class="text-center text-rose-300 mb-3">{{ error }}</p>
|
||||
|
||||
<div class="flex justify-center items-center">
|
||||
<button
|
||||
class="text-center text-slate-600 leading-none bg-white p-2 rounded-full drop-shadow-lg"
|
||||
<div class="mb-4">
|
||||
<h1 class="text-xl text-center font-semibold relative">
|
||||
<!-- Back -->
|
||||
<a
|
||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||
@click="handleBack"
|
||||
>
|
||||
<font-awesome icon="xmark" class="size-6"></font-awesome>
|
||||
<font-awesome icon="chevron-left" class="fa-fw" />
|
||||
</a>
|
||||
|
||||
<!-- Quick Help -->
|
||||
<a
|
||||
class="text-xl text-center text-blue-500 px-2 py-1 absolute -right-2 -top-1"
|
||||
@click="toastQRCodeHelp()"
|
||||
>
|
||||
<font-awesome icon="circle-question" class="fa-fw" />
|
||||
</a>
|
||||
|
||||
Share Contact Info
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!givenName"
|
||||
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4"
|
||||
>
|
||||
<p class="mb-2">
|
||||
<b>Note:</b> your identity currently does <b>not</b> include a name.
|
||||
</p>
|
||||
<button
|
||||
class="inline-block text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
||||
@click="openUserNameDialog"
|
||||
>
|
||||
Set Your Name
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<UserNameDialog ref="userNameDialog" />
|
||||
|
||||
<div
|
||||
v-if="activeDid && activeDid.startsWith(ETHR_DID_PREFIX)"
|
||||
class="block w-full max-w-[calc((100vh-env(safe-area-inset-top)-env(safe-area-inset-bottom))*0.4)] mx-auto mt-4"
|
||||
@click="onCopyUrlToClipboard()"
|
||||
>
|
||||
<!--
|
||||
Play with display options: https://qr-code-styling.com/
|
||||
See docs: https://www.npmjs.com/package/qr-code-generator-vue3
|
||||
-->
|
||||
<QRCodeVue3
|
||||
:value="qrValue"
|
||||
:width="606"
|
||||
:height="606"
|
||||
:corners-square-options="{ type: 'square' }"
|
||||
:dots-options="{ type: 'square', color: '#000' }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="activeDid" class="text-center mt-4">
|
||||
<!-- Not an ETHR DID so force them to paste it. (Passkey Peer DIDs are too big.) -->
|
||||
<span class="text-blue-500" @click="onCopyDidToClipboard()">
|
||||
Click here to copy your DID to your clipboard.
|
||||
</span>
|
||||
<span>
|
||||
Then give it to them so they can paste it in their list of People.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center mt-4">
|
||||
You have no identitifiers yet, so
|
||||
<router-link
|
||||
:to="{ name: 'start' }"
|
||||
class="bg-blue-500 text-white px-1.5 py-1 rounded-md"
|
||||
>
|
||||
create your identifier.
|
||||
</router-link>
|
||||
<br />
|
||||
If you don't do that first, these contacts won't see your activity.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative w-full max-w-[calc((100vh-env(safe-area-inset-top)-env(safe-area-inset-bottom))*0.4)] mx-auto border border-dashed border-white mt-8 aspect-square"
|
||||
>
|
||||
<p
|
||||
class="absolute top-0 left-0 right-0 bg-black bg-opacity-50 text-white text-sm text-center py-2 z-10"
|
||||
>
|
||||
Position QR code in the frame
|
||||
</p>
|
||||
|
||||
<p
|
||||
v-if="error"
|
||||
class="absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 text-sm text-center py-2 z-20 text-rose-400"
|
||||
>
|
||||
{{ error }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -33,18 +113,29 @@ import { NotificationIface } from "../constants/app";
|
||||
import { db } from "../db/index";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
import { getContactJwtFromJwtUrl } from "../libs/crypto";
|
||||
import { decodeEndorserJwt } from "../libs/crypto/vc";
|
||||
import { decodeEndorserJwt, ETHR_DID_PREFIX } from "../libs/crypto/vc";
|
||||
import { retrieveSettingsForActiveAccount } from "../db/index";
|
||||
import { setVisibilityUtil } from "../libs/endorserServer";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
import QRCodeVue3 from "qr-code-generator-vue3";
|
||||
import UserNameDialog from "../components/UserNameDialog.vue";
|
||||
import { generateEndorserJwtUrlForAccount } from "../libs/endorserServer";
|
||||
import { retrieveAccountMetadata } from "../libs/util";
|
||||
|
||||
interface QRScanResult {
|
||||
rawValue?: string;
|
||||
barcode?: string;
|
||||
}
|
||||
|
||||
interface IUserNameDialog {
|
||||
open: (callback: (name: string) => void) => void;
|
||||
}
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
QuickNav,
|
||||
QRCodeVue3,
|
||||
UserNameDialog,
|
||||
},
|
||||
})
|
||||
export default class ContactQRScan extends Vue {
|
||||
@@ -55,11 +146,14 @@ export default class ContactQRScan extends Vue {
|
||||
error: string | null = null;
|
||||
activeDid = "";
|
||||
apiServer = "";
|
||||
givenName = "";
|
||||
qrValue = "";
|
||||
ETHR_DID_PREFIX = ETHR_DID_PREFIX;
|
||||
|
||||
// Add new properties to track scanning state
|
||||
private lastScannedValue: string = "";
|
||||
private lastScanTime: number = 0;
|
||||
private readonly SCAN_DEBOUNCE_MS = 2000; // Prevent duplicate scans within 2 seconds
|
||||
private readonly SCAN_DEBOUNCE_MS = 5000; // Increased from 2000 to 5000ms to better handle mobile scanning
|
||||
|
||||
// Add cleanup tracking
|
||||
private isCleaningUp = false;
|
||||
@@ -70,6 +164,21 @@ export default class ContactQRScan extends Vue {
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
this.activeDid = settings.activeDid || "";
|
||||
this.apiServer = settings.apiServer || "";
|
||||
this.givenName = settings.firstName || "";
|
||||
|
||||
const account = await retrieveAccountMetadata(this.activeDid);
|
||||
if (account) {
|
||||
const name =
|
||||
(settings.firstName || "") +
|
||||
(settings.lastName ? ` ${settings.lastName}` : "");
|
||||
this.qrValue = await generateEndorserJwtUrlForAccount(
|
||||
account,
|
||||
!!settings.isRegistered,
|
||||
name,
|
||||
settings.profileImageUrl || "",
|
||||
false,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error initializing component:", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
@@ -270,14 +379,12 @@ export default class ContactQRScan extends Vue {
|
||||
notes: contactInfo.notes || "",
|
||||
};
|
||||
|
||||
// Add contact and stop scanning
|
||||
// Add contact but keep scanning
|
||||
logger.info("Adding new contact to database:", {
|
||||
did: contact.did,
|
||||
name: contact.name,
|
||||
});
|
||||
await this.addNewContact(contact);
|
||||
await this.stopScanning();
|
||||
this.$router.back(); // Return to previous view after successful scan
|
||||
} catch (error) {
|
||||
logger.error("Error processing contact QR code:", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
@@ -420,6 +527,56 @@ export default class ContactQRScan extends Vue {
|
||||
await this.cleanupScanner();
|
||||
this.$router.back();
|
||||
}
|
||||
|
||||
toastQRCodeHelp() {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "QR Code Help",
|
||||
text: "Click the QR code to copy your contact info to your clipboard.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
}
|
||||
|
||||
onCopyUrlToClipboard() {
|
||||
useClipboard()
|
||||
.copy(this.qrValue)
|
||||
.then(() => {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: "Copied",
|
||||
text: "Contact URL was copied to clipboard.",
|
||||
},
|
||||
2000,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
onCopyDidToClipboard() {
|
||||
useClipboard()
|
||||
.copy(this.activeDid)
|
||||
.then(() => {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Copied",
|
||||
text: "Your DID was copied to the clipboard. Have them paste it in the box on their 'People' screen to add you.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
openUserNameDialog() {
|
||||
(this.$refs.userNameDialog as IUserNameDialog).open((name: string) => {
|
||||
this.givenName = name;
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
<div
|
||||
v-if="activeDid && activeDid.startsWith(ETHR_DID_PREFIX)"
|
||||
class="block w-[90vw] max-w-[40vh] mx-auto my-4"
|
||||
class="block w-[90vw] max-w-[calc((100vh-env(safe-area-inset-top)-env(safe-area-inset-bottom))*0.4)] mx-auto my-4"
|
||||
@click="onCopyUrlToClipboard()"
|
||||
>
|
||||
<!--
|
||||
@@ -75,13 +75,13 @@
|
||||
create your identifier.
|
||||
</router-link>
|
||||
<br />
|
||||
If you don't that first, these contacts won't see your activity.
|
||||
If you don't do that first, these contacts won't see your activity.
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-6">
|
||||
<div
|
||||
v-if="isScanning && !isNativePlatform"
|
||||
class="relative aspect-square overflow-hidden bg-slate-800 w-[90vw] max-w-[40vh] mx-auto"
|
||||
v-if="isScanning"
|
||||
class="relative aspect-square overflow-hidden bg-slate-800 w-[90vw] max-w-[calc((100vh-env(safe-area-inset-top)-env(safe-area-inset-bottom))*0.4)] mx-auto"
|
||||
>
|
||||
<!-- Status Message -->
|
||||
<div
|
||||
@@ -142,7 +142,7 @@
|
||||
</div>
|
||||
|
||||
<qrcode-stream
|
||||
v-if="useQRReader && !isNativePlatform"
|
||||
v-if="useQRReader"
|
||||
:camera="preferredCamera"
|
||||
@decode="onDecode"
|
||||
@init="onInit"
|
||||
@@ -167,17 +167,9 @@
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex items-center justify-center aspect-square overflow-hidden bg-slate-800 w-[90vw] max-w-[40vh] mx-auto"
|
||||
class="flex items-center justify-center aspect-square overflow-hidden bg-slate-800 w-[90vw] max-w-[calc((100vh-env(safe-area-inset-top)-env(safe-area-inset-bottom))*0.4)] mx-auto"
|
||||
>
|
||||
<button
|
||||
v-if="isNativePlatform"
|
||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white text-lg px-3 py-2 rounded-lg"
|
||||
@click="$router.push({ name: 'contact-qr-scan-full' })"
|
||||
>
|
||||
Scan QR Code
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white text-lg px-3 py-2 rounded-lg"
|
||||
@click="startScanning"
|
||||
>
|
||||
@@ -193,7 +185,6 @@ import { AxiosError } from "axios";
|
||||
import QRCodeVue3 from "qr-code-generator-vue3";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
import { QrcodeStream } from "vue-qrcode-reader";
|
||||
|
||||
import QuickNav from "../components/QuickNav.vue";
|
||||
@@ -244,7 +235,6 @@ export default class ContactQRScanShow extends Vue {
|
||||
qrValue = "";
|
||||
isScanning = false;
|
||||
error: string | null = null;
|
||||
isNativePlatform = Capacitor.isNativePlatform();
|
||||
|
||||
// QR Scanner properties
|
||||
isInitializing = true;
|
||||
@@ -534,13 +524,12 @@ export default class ContactQRScanShow extends Vue {
|
||||
notes: contactInfo.notes || "",
|
||||
};
|
||||
|
||||
// Add contact and stop scanning
|
||||
// Add contact but keep scanning
|
||||
logger.info("Adding new contact to database:", {
|
||||
did: contact.did,
|
||||
name: contact.name,
|
||||
});
|
||||
await this.addNewContact(contact);
|
||||
await this.stopScanning();
|
||||
} catch (error) {
|
||||
logger.error("Error processing contact QR code:", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
@@ -692,6 +681,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "QR Code Help",
|
||||
text: "Click the QR code to copy your contact info to your clipboard.",
|
||||
},
|
||||
5000,
|
||||
@@ -726,11 +716,9 @@ export default class ContactQRScanShow extends Vue {
|
||||
this.isMounted = true;
|
||||
document.addEventListener("pause", this.handleAppPause);
|
||||
document.addEventListener("resume", this.handleAppResume);
|
||||
// Start scanning automatically when view is loaded, but only on web platform
|
||||
if (!this.isNativePlatform) {
|
||||
// Start scanning automatically when view is loaded
|
||||
this.startScanning();
|
||||
}
|
||||
}
|
||||
|
||||
beforeDestroy() {
|
||||
this.isMounted = false;
|
||||
@@ -856,11 +844,6 @@ export default class ContactQRScanShow extends Vue {
|
||||
|
||||
async onInit(promise: Promise<void>): Promise<void> {
|
||||
logger.log("[QRScanner] onInit called");
|
||||
if (this.isNativePlatform) {
|
||||
logger.log("Skipping QR scanner initialization on native platform");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await promise;
|
||||
this.isInitializing = false;
|
||||
@@ -889,7 +872,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
|
||||
onDetect(result: unknown): void {
|
||||
this.isScanning = true;
|
||||
this.cameraState = "detecting";
|
||||
this.cameraState = "active";
|
||||
try {
|
||||
let rawValue: string | undefined;
|
||||
if (
|
||||
@@ -899,7 +882,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
) {
|
||||
rawValue = result[0].rawValue;
|
||||
} else if (result && typeof result === "object" && "rawValue" in result) {
|
||||
rawValue = result.rawValue;
|
||||
rawValue = (result as { rawValue: string }).rawValue;
|
||||
}
|
||||
if (rawValue) {
|
||||
this.isInitializing = false;
|
||||
@@ -909,7 +892,6 @@ export default class ContactQRScanShow extends Vue {
|
||||
} catch (error) {
|
||||
this.handleError(error);
|
||||
} finally {
|
||||
this.isScanning = false;
|
||||
this.cameraState = "active";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,12 +69,12 @@
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<router-link
|
||||
:to="{ name: 'contact-qr' }"
|
||||
<button
|
||||
class="flex items-center bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 mr-1 rounded-md"
|
||||
@click="handleQRCodeClick"
|
||||
>
|
||||
<font-awesome icon="qrcode" class="fa-fw text-2xl" />
|
||||
</router-link>
|
||||
</button>
|
||||
|
||||
<textarea
|
||||
v-model="contactInput"
|
||||
@@ -353,6 +353,7 @@ import * as R from "ramda";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
|
||||
import QuickNav from "../components/QuickNav.vue";
|
||||
import EntityIcon from "../components/EntityIcon.vue";
|
||||
@@ -1438,5 +1439,13 @@ export default class ContactsView extends Vue {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private handleQRCodeClick() {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
this.$router.push({ name: "contact-qr-scan-full" });
|
||||
} else {
|
||||
this.$router.push({ name: "contact-qr" });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user