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",
|
"appName": "TimeSafari",
|
||||||
"webDir": "dist",
|
"webDir": "dist",
|
||||||
"bundledWebRuntime": false,
|
"bundledWebRuntime": false,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 48;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -123,8 +123,9 @@
|
|||||||
504EC2FC1FED79650016851F /* Project object */ = {
|
504EC2FC1FED79650016851F /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastSwiftUpdateCheck = 920;
|
LastSwiftUpdateCheck = 920;
|
||||||
LastUpgradeCheck = 920;
|
LastUpgradeCheck = 1630;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
504EC3031FED79650016851F = {
|
504EC3031FED79650016851F = {
|
||||||
CreatedOnToolsVersion = 9.2;
|
CreatedOnToolsVersion = 9.2;
|
||||||
@@ -268,6 +269,7 @@
|
|||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
CLANG_WARN_COMMA = YES;
|
CLANG_WARN_COMMA = YES;
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
@@ -275,8 +277,10 @@
|
|||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
CLANG_WARN_NON_LITERAL_NULL_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_LITERAL_CONVERSION = YES;
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
@@ -286,8 +290,10 @@
|
|||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
DEVELOPMENT_TEAM = 7XVXYPEQYJ;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
@@ -325,6 +331,7 @@
|
|||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
CLANG_WARN_COMMA = YES;
|
CLANG_WARN_COMMA = YES;
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
@@ -332,8 +339,10 @@
|
|||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
CLANG_WARN_NON_LITERAL_NULL_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_LITERAL_CONVERSION = YES;
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
@@ -343,8 +352,10 @@
|
|||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
DEVELOPMENT_TEAM = 7XVXYPEQYJ;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
@@ -356,7 +367,8 @@
|
|||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@@ -368,18 +380,22 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 18;
|
CURRENT_PROJECT_VERSION = 18;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
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;
|
MARKETING_VERSION = 0.4.7;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
PRODUCT_BUNDLE_IDENTIFIER = app.trentlarson.timesafari;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
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;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -390,17 +406,21 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 18;
|
CURRENT_PROJECT_VERSION = 18;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
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;
|
MARKETING_VERSION = 0.4.7;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
PRODUCT_BUNDLE_IDENTIFIER = app.trentlarson.timesafari;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
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;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,24 +1,104 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<section id="Content" class="relativew-[100vw] h-[100vh]">
|
<section id="Content" class="relative w-[100vw] h-[100vh]">
|
||||||
<div
|
<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">
|
<div class="mb-4">
|
||||||
Point your camera at a TimeSafari contact QR code to scan it
|
<h1 class="text-xl text-center font-semibold relative">
|
||||||
automatically.
|
<!-- Back -->
|
||||||
</p>
|
<a
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="handleBack"
|
||||||
|
>
|
||||||
|
<font-awesome icon="chevron-left" class="fa-fw" />
|
||||||
|
</a>
|
||||||
|
|
||||||
<p v-if="error" class="text-center text-rose-300 mb-3">{{ error }}</p>
|
<!-- 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>
|
||||||
|
|
||||||
<div class="flex justify-center items-center">
|
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
|
<button
|
||||||
class="text-center text-slate-600 leading-none bg-white p-2 rounded-full drop-shadow-lg"
|
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="handleBack"
|
@click="openUserNameDialog"
|
||||||
>
|
>
|
||||||
<font-awesome icon="xmark" class="size-6"></font-awesome>
|
Set Your Name
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@@ -33,18 +113,29 @@ import { NotificationIface } from "../constants/app";
|
|||||||
import { db } from "../db/index";
|
import { db } from "../db/index";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import { getContactJwtFromJwtUrl } from "../libs/crypto";
|
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 { retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
import { setVisibilityUtil } from "../libs/endorserServer";
|
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 {
|
interface QRScanResult {
|
||||||
rawValue?: string;
|
rawValue?: string;
|
||||||
barcode?: string;
|
barcode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IUserNameDialog {
|
||||||
|
open: (callback: (name: string) => void) => void;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
QuickNav,
|
QuickNav,
|
||||||
|
QRCodeVue3,
|
||||||
|
UserNameDialog,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class ContactQRScan extends Vue {
|
export default class ContactQRScan extends Vue {
|
||||||
@@ -55,11 +146,14 @@ export default class ContactQRScan extends Vue {
|
|||||||
error: string | null = null;
|
error: string | null = null;
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
|
givenName = "";
|
||||||
|
qrValue = "";
|
||||||
|
ETHR_DID_PREFIX = ETHR_DID_PREFIX;
|
||||||
|
|
||||||
// Add new properties to track scanning state
|
// Add new properties to track scanning state
|
||||||
private lastScannedValue: string = "";
|
private lastScannedValue: string = "";
|
||||||
private lastScanTime: number = 0;
|
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
|
// Add cleanup tracking
|
||||||
private isCleaningUp = false;
|
private isCleaningUp = false;
|
||||||
@@ -70,6 +164,21 @@ export default class ContactQRScan extends Vue {
|
|||||||
const settings = await retrieveSettingsForActiveAccount();
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
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) {
|
} catch (error) {
|
||||||
logger.error("Error initializing component:", {
|
logger.error("Error initializing component:", {
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
@@ -270,14 +379,12 @@ export default class ContactQRScan extends Vue {
|
|||||||
notes: contactInfo.notes || "",
|
notes: contactInfo.notes || "",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add contact and stop scanning
|
// Add contact but keep scanning
|
||||||
logger.info("Adding new contact to database:", {
|
logger.info("Adding new contact to database:", {
|
||||||
did: contact.did,
|
did: contact.did,
|
||||||
name: contact.name,
|
name: contact.name,
|
||||||
});
|
});
|
||||||
await this.addNewContact(contact);
|
await this.addNewContact(contact);
|
||||||
await this.stopScanning();
|
|
||||||
this.$router.back(); // Return to previous view after successful scan
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error processing contact QR code:", {
|
logger.error("Error processing contact QR code:", {
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
@@ -420,6 +527,56 @@ export default class ContactQRScan extends Vue {
|
|||||||
await this.cleanupScanner();
|
await this.cleanupScanner();
|
||||||
this.$router.back();
|
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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="activeDid && activeDid.startsWith(ETHR_DID_PREFIX)"
|
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()"
|
@click="onCopyUrlToClipboard()"
|
||||||
>
|
>
|
||||||
<!--
|
<!--
|
||||||
@@ -75,13 +75,13 @@
|
|||||||
create your identifier.
|
create your identifier.
|
||||||
</router-link>
|
</router-link>
|
||||||
<br />
|
<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>
|
||||||
|
|
||||||
<div class="text-center mt-6">
|
<div class="text-center mt-6">
|
||||||
<div
|
<div
|
||||||
v-if="isScanning && !isNativePlatform"
|
v-if="isScanning"
|
||||||
class="relative aspect-square overflow-hidden bg-slate-800 w-[90vw] max-w-[40vh] mx-auto"
|
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 -->
|
<!-- Status Message -->
|
||||||
<div
|
<div
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<qrcode-stream
|
<qrcode-stream
|
||||||
v-if="useQRReader && !isNativePlatform"
|
v-if="useQRReader"
|
||||||
:camera="preferredCamera"
|
:camera="preferredCamera"
|
||||||
@decode="onDecode"
|
@decode="onDecode"
|
||||||
@init="onInit"
|
@init="onInit"
|
||||||
@@ -167,17 +167,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
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
|
<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"
|
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"
|
@click="startScanning"
|
||||||
>
|
>
|
||||||
@@ -193,7 +185,6 @@ import { AxiosError } from "axios";
|
|||||||
import QRCodeVue3 from "qr-code-generator-vue3";
|
import QRCodeVue3 from "qr-code-generator-vue3";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
import { Capacitor } from "@capacitor/core";
|
|
||||||
import { QrcodeStream } from "vue-qrcode-reader";
|
import { QrcodeStream } from "vue-qrcode-reader";
|
||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
@@ -244,7 +235,6 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
qrValue = "";
|
qrValue = "";
|
||||||
isScanning = false;
|
isScanning = false;
|
||||||
error: string | null = null;
|
error: string | null = null;
|
||||||
isNativePlatform = Capacitor.isNativePlatform();
|
|
||||||
|
|
||||||
// QR Scanner properties
|
// QR Scanner properties
|
||||||
isInitializing = true;
|
isInitializing = true;
|
||||||
@@ -534,13 +524,12 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
notes: contactInfo.notes || "",
|
notes: contactInfo.notes || "",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add contact and stop scanning
|
// Add contact but keep scanning
|
||||||
logger.info("Adding new contact to database:", {
|
logger.info("Adding new contact to database:", {
|
||||||
did: contact.did,
|
did: contact.did,
|
||||||
name: contact.name,
|
name: contact.name,
|
||||||
});
|
});
|
||||||
await this.addNewContact(contact);
|
await this.addNewContact(contact);
|
||||||
await this.stopScanning();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error processing contact QR code:", {
|
logger.error("Error processing contact QR code:", {
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
@@ -692,6 +681,7 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "info",
|
type: "info",
|
||||||
|
title: "QR Code Help",
|
||||||
text: "Click the QR code to copy your contact info to your clipboard.",
|
text: "Click the QR code to copy your contact info to your clipboard.",
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
@@ -726,10 +716,8 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
this.isMounted = true;
|
this.isMounted = true;
|
||||||
document.addEventListener("pause", this.handleAppPause);
|
document.addEventListener("pause", this.handleAppPause);
|
||||||
document.addEventListener("resume", this.handleAppResume);
|
document.addEventListener("resume", this.handleAppResume);
|
||||||
// Start scanning automatically when view is loaded, but only on web platform
|
// Start scanning automatically when view is loaded
|
||||||
if (!this.isNativePlatform) {
|
this.startScanning();
|
||||||
this.startScanning();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@@ -856,11 +844,6 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
|
|
||||||
async onInit(promise: Promise<void>): Promise<void> {
|
async onInit(promise: Promise<void>): Promise<void> {
|
||||||
logger.log("[QRScanner] onInit called");
|
logger.log("[QRScanner] onInit called");
|
||||||
if (this.isNativePlatform) {
|
|
||||||
logger.log("Skipping QR scanner initialization on native platform");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await promise;
|
await promise;
|
||||||
this.isInitializing = false;
|
this.isInitializing = false;
|
||||||
@@ -889,7 +872,7 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
|
|
||||||
onDetect(result: unknown): void {
|
onDetect(result: unknown): void {
|
||||||
this.isScanning = true;
|
this.isScanning = true;
|
||||||
this.cameraState = "detecting";
|
this.cameraState = "active";
|
||||||
try {
|
try {
|
||||||
let rawValue: string | undefined;
|
let rawValue: string | undefined;
|
||||||
if (
|
if (
|
||||||
@@ -899,7 +882,7 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
) {
|
) {
|
||||||
rawValue = result[0].rawValue;
|
rawValue = result[0].rawValue;
|
||||||
} else if (result && typeof result === "object" && "rawValue" in result) {
|
} else if (result && typeof result === "object" && "rawValue" in result) {
|
||||||
rawValue = result.rawValue;
|
rawValue = (result as { rawValue: string }).rawValue;
|
||||||
}
|
}
|
||||||
if (rawValue) {
|
if (rawValue) {
|
||||||
this.isInitializing = false;
|
this.isInitializing = false;
|
||||||
@@ -909,7 +892,6 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
} finally {
|
} finally {
|
||||||
this.isScanning = false;
|
|
||||||
this.cameraState = "active";
|
this.cameraState = "active";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,12 +69,12 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<router-link
|
<button
|
||||||
:to="{ name: 'contact-qr' }"
|
|
||||||
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"
|
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" />
|
<font-awesome icon="qrcode" class="fa-fw text-2xl" />
|
||||||
</router-link>
|
</button>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
v-model="contactInput"
|
v-model="contactInput"
|
||||||
@@ -353,6 +353,7 @@ import * as R from "ramda";
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
import { Capacitor } from "@capacitor/core";
|
||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import EntityIcon from "../components/EntityIcon.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>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user