diff --git a/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock index af452334..16c60848 100644 Binary files a/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/android/.gradle/file-system.probe b/android/.gradle/file-system.probe index d8388669..283959b9 100644 Binary files a/android/.gradle/file-system.probe and b/android/.gradle/file-system.probe differ diff --git a/android/app/src/main/assets/public/index.html b/android/app/src/main/assets/public/index.html index 971247aa..2f16cb7e 100644 --- a/android/app/src/main/assets/public/index.html +++ b/android/app/src/main/assets/public/index.html @@ -6,7 +6,7 @@ <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="/favicon.ico"> <title>TimeSafari</title> - <script type="module" crossorigin src="/assets/index-DtktPhxR.js"></script> + <script type="module" crossorigin src="/assets/index-BX6tAjMT.js"></script> </head> <body> <noscript> diff --git a/src/components/DataExportSection.vue b/src/components/DataExportSection.vue index 0c14b570..a05d9a63 100644 --- a/src/components/DataExportSection.vue +++ b/src/components/DataExportSection.vue @@ -42,17 +42,17 @@ > If no download happened yet, click again here to download now. </a> - <div class="mt-4" v-if="showPlatformInstructions"> + <div class="mt-4" v-if="platformCapabilities.needsFileHandlingInstructions"> <p> After the download, you can save the file in your preferred storage location. </p> <ul> - <li v-if="platformService.isCapacitor() && isIOS" class="list-disc list-outside ml-4"> + <li v-if="platformCapabilities.isIOS" class="list-disc list-outside ml-4"> On iOS: Choose "More..." and select a place in iCloud, or go "Back" and save to another location. </li> - <li v-if="platformService.isCapacitor() && !isIOS" class="list-disc list-outside ml-4"> + <li v-if="platformCapabilities.isMobile && !platformCapabilities.isIOS" class="list-disc list-outside ml-4"> On Android: Choose "Open" and then share <font-awesome icon="share-nodes" class="fa-fw" /> to your prefered place. @@ -68,7 +68,7 @@ import { NotificationIface } from "../constants/app"; import { db } from "../db/index"; import { logger } from "../utils/logger"; import { PlatformServiceFactory } from "../services/PlatformServiceFactory"; -import { PlatformService } from "../services/PlatformService"; +import { PlatformService, PlatformCapabilities } from "../services/PlatformService"; /** * @vue-component @@ -103,17 +103,10 @@ export default class DataExportSection extends Vue { private platformService: PlatformService = PlatformServiceFactory.getInstance(); /** - * Whether the current platform is iOS + * Platform capabilities for the current platform */ - private get isIOS(): boolean { - return /iPad|iPhone|iPod/.test(navigator.userAgent); - } - - /** - * Whether to show platform-specific instructions - */ - private get showPlatformInstructions(): boolean { - return this.platformService.isCapacitor(); + private get platformCapabilities(): PlatformCapabilities { + return this.platformService.getCapabilities(); } /** @@ -121,7 +114,7 @@ export default class DataExportSection extends Vue { * Revokes object URL when component is unmounted (web platform only) */ beforeUnmount() { - if (this.downloadUrl && this.platformService.isWeb()) { + if (this.downloadUrl && this.platformCapabilities.hasFileDownload) { URL.revokeObjectURL(this.downloadUrl); } } @@ -139,7 +132,7 @@ export default class DataExportSection extends Vue { const blob = await db.export({ prettyJson: true }); const fileName = `${db.name}-backup.json`; - if (this.platformService.isWeb()) { + if (this.platformCapabilities.hasFileDownload) { // Web platform: Use download link this.downloadUrl = URL.createObjectURL(blob); const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement; @@ -147,13 +140,10 @@ export default class DataExportSection extends Vue { downloadAnchor.download = fileName; downloadAnchor.click(); setTimeout(() => URL.revokeObjectURL(this.downloadUrl), 1000); - } else if (this.platformService.isCapacitor()) { - // Capacitor platform: Write to app directory + } else if (this.platformCapabilities.hasFileSystem) { + // Native platform: Write to app directory const content = await blob.text(); await this.platformService.writeFile(fileName, content); - } else { - // Other platforms: Use platform service - await this.platformService.writeFile(fileName, await blob.text()); } this.$notify( @@ -161,7 +151,7 @@ export default class DataExportSection extends Vue { group: "alert", type: "success", title: "Export Successful", - text: this.platformService.isWeb() + text: this.platformCapabilities.hasFileDownload ? "See your downloads directory for the backup. It is in the Dexie format." : "The backup has been saved to your device.", }, @@ -187,7 +177,7 @@ export default class DataExportSection extends Vue { */ public computedStartDownloadLinkClassNames() { return { - hidden: this.downloadUrl && this.platformService.isWeb(), + hidden: this.downloadUrl && this.platformCapabilities.hasFileDownload, }; } @@ -197,7 +187,7 @@ export default class DataExportSection extends Vue { */ public computedDownloadLinkClassNames() { return { - hidden: !this.downloadUrl || !this.platformService.isWeb(), + hidden: !this.downloadUrl || !this.platformCapabilities.hasFileDownload, }; } } diff --git a/src/services/PlatformService.ts b/src/services/PlatformService.ts index 821261ee..5a2c9209 100644 --- a/src/services/PlatformService.ts +++ b/src/services/PlatformService.ts @@ -9,13 +9,38 @@ export interface ImageResult { fileName: string; } +/** + * Platform capabilities interface defining what features are available + * on the current platform implementation + */ +export interface PlatformCapabilities { + /** Whether the platform supports native file system access */ + hasFileSystem: boolean; + /** Whether the platform supports native camera access */ + hasCamera: boolean; + /** Whether the platform is a mobile device */ + isMobile: boolean; + /** Whether the platform is iOS specifically */ + isIOS: boolean; + /** Whether the platform supports native file download */ + hasFileDownload: boolean; + /** Whether the platform requires special file handling instructions */ + needsFileHandlingInstructions: boolean; +} + /** * Platform-agnostic interface for handling platform-specific operations. * Provides a common API for file system operations, camera interactions, - * platform detection, and deep linking across different platforms - * (web, mobile, desktop). + * and platform detection across different platforms (web, mobile, desktop). */ export interface PlatformService { + // Platform capabilities + /** + * Gets the current platform's capabilities + * @returns Object describing what features are available on this platform + */ + getCapabilities(): PlatformCapabilities; + // File system operations /** * Reads the contents of a file at the specified path. @@ -59,32 +84,6 @@ export interface PlatformService { */ pickImage(): Promise<ImageResult>; - // Platform specific features - /** - * Checks if the current platform is Capacitor (mobile). - * @returns true if running on Capacitor - */ - isCapacitor(): boolean; - - /** - * Checks if the current platform is Electron (desktop). - * @returns true if running on Electron - */ - isElectron(): boolean; - - /** - * Checks if the current platform is PyWebView. - * @returns true if running on PyWebView - */ - isPyWebView(): boolean; - - /** - * Checks if the current platform is web browser. - * @returns true if running in a web browser - */ - isWeb(): boolean; - - // Deep linking /** * Handles deep link URLs for the application. * @param url - The deep link URL to handle diff --git a/src/services/platforms/CapacitorPlatformService.ts b/src/services/platforms/CapacitorPlatformService.ts index b6ba1198..b5fcd81e 100644 --- a/src/services/platforms/CapacitorPlatformService.ts +++ b/src/services/platforms/CapacitorPlatformService.ts @@ -1,4 +1,4 @@ -import { ImageResult, PlatformService } from "../PlatformService"; +import { ImageResult, PlatformService, PlatformCapabilities } from "../PlatformService"; import { Filesystem, Directory } from "@capacitor/filesystem"; import { Camera, CameraResultType, CameraSource } from "@capacitor/camera"; import { logger } from "../../utils/logger"; @@ -11,6 +11,21 @@ import { logger } from "../../utils/logger"; * - Platform-specific features */ export class CapacitorPlatformService implements PlatformService { + /** + * Gets the capabilities of the Capacitor platform + * @returns Platform capabilities object + */ + getCapabilities(): PlatformCapabilities { + return { + hasFileSystem: true, + hasCamera: true, + isMobile: true, + isIOS: /iPad|iPhone|iPod/.test(navigator.userAgent), + hasFileDownload: false, + needsFileHandlingInstructions: true + }; + } + /** * Reads a file from the app's data directory. * @param path - Relative path to the file in the app's data directory @@ -146,38 +161,6 @@ export class CapacitorPlatformService implements PlatformService { return new Blob(byteArrays, { type: "image/jpeg" }); } - /** - * Checks if running on Capacitor platform. - * @returns true, as this is the Capacitor implementation - */ - isCapacitor(): boolean { - return true; - } - - /** - * Checks if running on Electron platform. - * @returns false, as this is not Electron - */ - isElectron(): boolean { - return false; - } - - /** - * Checks if running on PyWebView platform. - * @returns false, as this is not PyWebView - */ - isPyWebView(): boolean { - return false; - } - - /** - * Checks if running on web platform. - * @returns false, as this is not web - */ - isWeb(): boolean { - return false; - } - /** * Handles deep link URLs for the application. * Note: Capacitor handles deep links automatically. diff --git a/src/services/platforms/ElectronPlatformService.ts b/src/services/platforms/ElectronPlatformService.ts index 0a99cd43..86725881 100644 --- a/src/services/platforms/ElectronPlatformService.ts +++ b/src/services/platforms/ElectronPlatformService.ts @@ -1,4 +1,4 @@ -import { ImageResult, PlatformService } from "../PlatformService"; +import { ImageResult, PlatformService, PlatformCapabilities } from "../PlatformService"; import { logger } from "../../utils/logger"; /** @@ -14,6 +14,21 @@ import { logger } from "../../utils/logger"; * - System-level features */ export class ElectronPlatformService implements PlatformService { + /** + * Gets the capabilities of the Electron platform + * @returns Platform capabilities object + */ + getCapabilities(): PlatformCapabilities { + return { + hasFileSystem: false, // Not implemented yet + hasCamera: false, // Not implemented yet + isMobile: false, + isIOS: false, + hasFileDownload: false, // Not implemented yet + needsFileHandlingInstructions: false + }; + } + /** * Reads a file from the filesystem. * @param _path - Path to the file to read @@ -79,38 +94,6 @@ export class ElectronPlatformService implements PlatformService { throw new Error("Not implemented"); } - /** - * Checks if running on Capacitor platform. - * @returns false, as this is not Capacitor - */ - isCapacitor(): boolean { - return false; - } - - /** - * Checks if running on Electron platform. - * @returns true, as this is the Electron implementation - */ - isElectron(): boolean { - return true; - } - - /** - * Checks if running on PyWebView platform. - * @returns false, as this is not PyWebView - */ - isPyWebView(): boolean { - return false; - } - - /** - * Checks if running on web platform. - * @returns false, as this is not web - */ - isWeb(): boolean { - return false; - } - /** * Should handle deep link URLs for the desktop application. * @param _url - The deep link URL to handle diff --git a/src/services/platforms/PyWebViewPlatformService.ts b/src/services/platforms/PyWebViewPlatformService.ts index 0e118297..7cd5fd53 100644 --- a/src/services/platforms/PyWebViewPlatformService.ts +++ b/src/services/platforms/PyWebViewPlatformService.ts @@ -1,4 +1,4 @@ -import { ImageResult, PlatformService } from "../PlatformService"; +import { ImageResult, PlatformService, PlatformCapabilities } from "../PlatformService"; import { logger } from "../../utils/logger"; /** @@ -15,6 +15,21 @@ import { logger } from "../../utils/logger"; * - Python-JavaScript bridge functionality */ export class PyWebViewPlatformService implements PlatformService { + /** + * Gets the capabilities of the PyWebView platform + * @returns Platform capabilities object + */ + getCapabilities(): PlatformCapabilities { + return { + hasFileSystem: false, // Not implemented yet + hasCamera: false, // Not implemented yet + isMobile: false, + isIOS: false, + hasFileDownload: false, // Not implemented yet + needsFileHandlingInstructions: false + }; + } + /** * Reads a file using the Python backend. * @param _path - Path to the file to read @@ -80,38 +95,6 @@ export class PyWebViewPlatformService implements PlatformService { throw new Error("Not implemented"); } - /** - * Checks if running on Capacitor platform. - * @returns false, as this is not Capacitor - */ - isCapacitor(): boolean { - return false; - } - - /** - * Checks if running on Electron platform. - * @returns false, as this is not Electron - */ - isElectron(): boolean { - return false; - } - - /** - * Checks if running on PyWebView platform. - * @returns true, as this is the PyWebView implementation - */ - isPyWebView(): boolean { - return true; - } - - /** - * Checks if running on web platform. - * @returns false, as this is not web - */ - isWeb(): boolean { - return false; - } - /** * Should handle deep link URLs through the Python backend. * @param _url - The deep link URL to handle diff --git a/src/services/platforms/WebPlatformService.ts b/src/services/platforms/WebPlatformService.ts index 9a65278a..bcfdfec1 100644 --- a/src/services/platforms/WebPlatformService.ts +++ b/src/services/platforms/WebPlatformService.ts @@ -1,4 +1,4 @@ -import { ImageResult, PlatformService } from "../PlatformService"; +import { ImageResult, PlatformService, PlatformCapabilities } from "../PlatformService"; import { logger } from "../../utils/logger"; /** @@ -15,6 +15,21 @@ import { logger } from "../../utils/logger"; * due to browser security restrictions. These methods throw appropriate errors. */ export class WebPlatformService implements PlatformService { + /** + * Gets the capabilities of the web platform + * @returns Platform capabilities object + */ + getCapabilities(): PlatformCapabilities { + return { + hasFileSystem: false, + hasCamera: true, // Through file input with capture + isMobile: /iPhone|iPad|iPod|Android/i.test(navigator.userAgent), + isIOS: /iPad|iPhone|iPod/.test(navigator.userAgent), + hasFileDownload: true, + needsFileHandlingInstructions: false + }; + } + /** * Not supported in web platform. * @param _path - Unused path parameter