performance-optimizations-testing #159
@@ -846,6 +846,12 @@ export const NOTIFY_CONTACTS_ADDED = {
|
||||
message: "They were added.",
|
||||
};
|
||||
|
||||
// Used in: ContactsView.vue (addContact method - export data prompt after contact addition)
|
||||
export const NOTIFY_EXPORT_DATA_PROMPT = {
|
||||
title: "Export Your Data",
|
||||
message: "Would you like to export your contact data as a backup?",
|
||||
};
|
||||
|
||||
// Used in: ContactsView.vue (showCopySelectionsInfo method - info about copying contacts)
|
||||
export const NOTIFY_CONTACT_INFO_COPY = {
|
||||
title: "Info",
|
||||
|
||||
99
src/services/QRNavigationService.ts
Normal file
99
src/services/QRNavigationService.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { PlatformServiceFactory } from "./PlatformServiceFactory";
|
||||
import { PlatformService } from "./PlatformService";
|
||||
import { logger } from "@/utils/logger";
|
||||
|
||||
/**
|
||||
* QR Navigation Service
|
||||
*
|
||||
* Handles platform-specific routing logic for QR scanning operations.
|
||||
* Removes coupling between views and routing logic by centralizing
|
||||
* navigation decisions based on platform capabilities.
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
*/
|
||||
export class QRNavigationService {
|
||||
private static instance: QRNavigationService | null = null;
|
||||
private platformService: PlatformService;
|
||||
|
||||
private constructor() {
|
||||
this.platformService = PlatformServiceFactory.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance of QRNavigationService
|
||||
*/
|
||||
public static getInstance(): QRNavigationService {
|
||||
if (!QRNavigationService.instance) {
|
||||
QRNavigationService.instance = new QRNavigationService();
|
||||
}
|
||||
return QRNavigationService.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate QR scanner route based on platform
|
||||
*
|
||||
* @returns Object with route name and parameters for QR scanning
|
||||
*/
|
||||
public getQRScannerRoute(): {
|
||||
name: string;
|
||||
params?: Record<string, string | number>;
|
||||
} {
|
||||
const isCapacitor = this.platformService.isCapacitor();
|
||||
|
||||
logger.debug("QR Navigation - Platform detection:", {
|
||||
isCapacitor,
|
||||
platform: this.platformService.getCapabilities(),
|
||||
});
|
||||
|
||||
if (isCapacitor) {
|
||||
// Use native scanner on mobile platforms
|
||||
return { name: "contact-qr-scan-full" };
|
||||
} else {
|
||||
// Use web scanner on other platforms
|
||||
return { name: "contact-qr" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate QR display route based on platform
|
||||
*
|
||||
* @returns Object with route name and parameters for QR display
|
||||
*/
|
||||
public getQRDisplayRoute(): {
|
||||
name: string;
|
||||
params?: Record<string, string | number>;
|
||||
} {
|
||||
const isCapacitor = this.platformService.isCapacitor();
|
||||
|
||||
logger.debug("QR Navigation - Display route detection:", {
|
||||
isCapacitor,
|
||||
platform: this.platformService.getCapabilities(),
|
||||
});
|
||||
|
||||
if (isCapacitor) {
|
||||
// Use dedicated display view on mobile
|
||||
return { name: "contact-qr-scan-show" };
|
||||
} else {
|
||||
// Use combined view on web
|
||||
return { name: "contact-qr" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if native QR scanning is available on current platform
|
||||
*
|
||||
* @returns true if native scanning is available, false otherwise
|
||||
*/
|
||||
public isNativeScanningAvailable(): boolean {
|
||||
return this.platformService.isCapacitor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get platform capabilities for QR operations
|
||||
*
|
||||
* @returns Platform capabilities object
|
||||
*/
|
||||
public getPlatformCapabilities() {
|
||||
return this.platformService.getCapabilities();
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,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";
|
||||
// Capacitor import removed - using PlatformService instead
|
||||
|
||||
import QuickNav from "../components/QuickNav.vue";
|
||||
import EntityIcon from "../components/EntityIcon.vue";
|
||||
@@ -161,13 +161,17 @@ import { GiveSummaryRecord } from "@/interfaces/records";
|
||||
import { UserInfo } from "@/interfaces/common";
|
||||
import { VerifiableCredential } from "@/interfaces/claims-result";
|
||||
import * as libsUtil from "../libs/util";
|
||||
import { generateSaveAndActivateIdentity } from "../libs/util";
|
||||
import {
|
||||
generateSaveAndActivateIdentity,
|
||||
contactsToExportJson,
|
||||
} from "../libs/util";
|
||||
import { logger } from "../utils/logger";
|
||||
// No longer needed - using PlatformServiceMixin methods
|
||||
// import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import { APP_SERVER } from "@/constants/app";
|
||||
import { QRNavigationService } from "@/services/QRNavigationService";
|
||||
import {
|
||||
NOTIFY_CONTACT_NO_INFO,
|
||||
NOTIFY_CONTACTS_ADD_ERROR,
|
||||
@@ -193,6 +197,7 @@ import {
|
||||
NOTIFY_REGISTRATION_ERROR_FALLBACK,
|
||||
NOTIFY_REGISTRATION_ERROR_GENERIC,
|
||||
NOTIFY_VISIBILITY_ERROR_FALLBACK,
|
||||
NOTIFY_EXPORT_DATA_PROMPT,
|
||||
getRegisterPersonSuccessMessage,
|
||||
getVisibilitySuccessMessage,
|
||||
getGivesRetrievalErrorMessage,
|
||||
@@ -780,6 +785,9 @@ export default class ContactsView extends Vue {
|
||||
|
||||
// Show success notification
|
||||
this.notify.success(addedMessage);
|
||||
|
||||
// Show export data prompt after successful contact addition
|
||||
await this.showExportDataPrompt();
|
||||
} catch (err) {
|
||||
this.handleContactAddError(err);
|
||||
}
|
||||
@@ -1243,19 +1251,75 @@ export default class ContactsView extends Vue {
|
||||
|
||||
/**
|
||||
* Handle QR code button click - route to appropriate scanner
|
||||
* Uses native scanner on mobile platforms, web scanner otherwise
|
||||
* Uses QRNavigationService to determine scanner type and route
|
||||
*/
|
||||
|
||||
public handleQRCodeClick() {
|
||||
this.$logAndConsole(
|
||||
"[ContactsView] handleQRCodeClick method called",
|
||||
false,
|
||||
);
|
||||
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
this.$router.push({ name: "contact-qr-scan-full" });
|
||||
} else {
|
||||
this.$router.push({ name: "contact-qr" });
|
||||
const qrNavigationService = QRNavigationService.getInstance();
|
||||
const route = qrNavigationService.getQRScannerRoute();
|
||||
|
||||
this.$router.push(route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show export data prompt after adding a contact
|
||||
* Prompts user to export their contact data as a backup
|
||||
*/
|
||||
private async showExportDataPrompt(): Promise<void> {
|
||||
setTimeout(() => {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: NOTIFY_EXPORT_DATA_PROMPT.title,
|
||||
text: NOTIFY_EXPORT_DATA_PROMPT.message,
|
||||
onYes: async () => {
|
||||
await this.exportContactData();
|
||||
},
|
||||
yesText: "Export Data",
|
||||
onNo: async () => {
|
||||
// User chose not to export - no action needed
|
||||
},
|
||||
noText: "Not Now",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}, 1000); // Small delay to ensure success notification is shown first
|
||||
}
|
||||
|
||||
/**
|
||||
* Export contact data to JSON file
|
||||
* Uses platform service to handle platform-specific export logic
|
||||
*/
|
||||
private async exportContactData(): Promise<void> {
|
||||
try {
|
||||
// Fetch all contacts from database
|
||||
const allContacts = await this.$contacts();
|
||||
|
||||
// Convert contacts to export format
|
||||
const exportData = contactsToExportJson(allContacts);
|
||||
const jsonStr = JSON.stringify(exportData, null, 2);
|
||||
|
||||
// Generate filename with current date
|
||||
const today = new Date();
|
||||
const dateString = today.toISOString().split("T")[0]; // YYYY-MM-DD format
|
||||
const fileName = `timesafari-backup-contacts-${dateString}.json`;
|
||||
|
||||
// Use platform service to handle export
|
||||
await this.platformService.writeAndShareFile(fileName, jsonStr);
|
||||
|
||||
this.notify.success(
|
||||
"Contact export completed successfully. Check your downloads or share dialog.",
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("Export Error:", error);
|
||||
this.notify.error(
|
||||
`There was an error exporting the data: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,22 +565,22 @@
|
||||
<h2 class="text-xl font-semibold">What app version is this?</h2>
|
||||
<p>{{ package.version }} ({{ commitHash }})</p>
|
||||
|
||||
<div v-if="Capacitor.isNativePlatform()">
|
||||
<div v-if="isCapacitor">
|
||||
<h2 class="text-xl font-semibold">
|
||||
Do I have the latest version?
|
||||
</h2>
|
||||
<p v-if="Capacitor.getPlatform() === 'ios'">
|
||||
<p v-if="capabilities.isIOS">
|
||||
<a href="https://apps.apple.com/us/app/time-safari/id6742664907" target="_blank" class="text-blue-500">
|
||||
Check the App Store.
|
||||
</a>
|
||||
</p>
|
||||
<p v-else-if="Capacitor.getPlatform() === 'android'">
|
||||
<p v-else-if="!capabilities.isIOS && capabilities.isMobile">
|
||||
<a href="https://play.google.com/store/apps/details?id=app.timesafari.app" target="_blank" class="text-blue-500">
|
||||
Check the Play Store.
|
||||
</a>
|
||||
</p>
|
||||
<p v-else>
|
||||
Sorry, your platform of '{{ Capacitor.getPlatform() }}' is not recognized.
|
||||
Sorry, your platform is not recognized.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -592,12 +592,13 @@
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { Router } from "vue-router";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
// Capacitor import removed - using QRNavigationService instead
|
||||
|
||||
import * as Package from "../../package.json";
|
||||
import QuickNav from "../components/QuickNav.vue";
|
||||
import { APP_SERVER } from "../constants/app";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { QRNavigationService } from "@/services/QRNavigationService";
|
||||
|
||||
/**
|
||||
* HelpView.vue - Comprehensive Help System Component
|
||||
@@ -643,7 +644,7 @@ export default class HelpView extends Vue {
|
||||
showVerifiable = false;
|
||||
|
||||
APP_SERVER = APP_SERVER;
|
||||
Capacitor = Capacitor;
|
||||
// Capacitor reference removed - using QRNavigationService instead
|
||||
|
||||
// Ideally, we put no functionality in here, especially in the setup,
|
||||
// because we never want this page to have a chance of throwing an error.
|
||||
@@ -711,11 +712,10 @@ export default class HelpView extends Vue {
|
||||
* @private
|
||||
*/
|
||||
private handleQRCodeClick(): void {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
this.$router.push({ name: "contact-qr-scan-full" });
|
||||
} else {
|
||||
this.$router.push({ name: "contact-qr" });
|
||||
}
|
||||
const qrNavigationService = QRNavigationService.getInstance();
|
||||
const route = qrNavigationService.getQRScannerRoute();
|
||||
|
||||
this.$router.push(route);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
import { AxiosRequestConfig } from "axios";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { Router } from "vue-router";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
// Capacitor import removed - using QRNavigationService instead
|
||||
|
||||
import { NotificationIface } from "../constants/app";
|
||||
import EntityIcon from "../components/EntityIcon.vue";
|
||||
@@ -281,6 +281,7 @@ import { OnboardPage, iconForUnitCode } from "../libs/util";
|
||||
import { logger } from "../utils/logger";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import { QRNavigationService } from "@/services/QRNavigationService";
|
||||
import {
|
||||
NOTIFY_NO_ACCOUNT_ERROR,
|
||||
NOTIFY_PROJECT_LOAD_ERROR,
|
||||
@@ -755,11 +756,10 @@ export default class ProjectsView extends Vue {
|
||||
* - Web-based QR interface for browser environments
|
||||
*/
|
||||
private handleQRCodeClick() {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
this.$router.push({ name: "contact-qr-scan-full" });
|
||||
} else {
|
||||
this.$router.push({ name: "contact-qr" });
|
||||
}
|
||||
const qrNavigationService = QRNavigationService.getInstance();
|
||||
const route = qrNavigationService.getQRScannerRoute();
|
||||
|
||||
this.$router.push(route);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user