diff --git a/src/constants/notifications.ts b/src/constants/notifications.ts index 9ffeb31f..8eb1c06f 100644 --- a/src/constants/notifications.ts +++ b/src/constants/notifications.ts @@ -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", diff --git a/src/services/QRNavigationService.ts b/src/services/QRNavigationService.ts new file mode 100644 index 00000000..33716c39 --- /dev/null +++ b/src/services/QRNavigationService.ts @@ -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; + } { + 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; + } { + 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(); + } +} diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index 3d5576e7..a6533201 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -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 { + 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 { + 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"}`, + ); } } } diff --git a/src/views/HelpView.vue b/src/views/HelpView.vue index 11505ba3..9b05b7aa 100644 --- a/src/views/HelpView.vue +++ b/src/views/HelpView.vue @@ -565,22 +565,22 @@

What app version is this?

{{ package.version }} ({{ commitHash }})

-
+

Do I have the latest version?

-

+

Check the App Store.

-

+

Check the Play Store.

- Sorry, your platform of '{{ Capacitor.getPlatform() }}' is not recognized. + Sorry, your platform is not recognized.

@@ -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); } /** diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue index 84db4f02..b2f2b7f4 100644 --- a/src/views/ProjectsView.vue +++ b/src/views/ProjectsView.vue @@ -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); } /**