diff --git a/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock
index af452334b0..16c60848e7 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 d838866929..283959b920 100644
Binary files a/android/.gradle/file-system.probe and b/android/.gradle/file-system.probe differ
diff --git a/src/components/DataExportSection.vue b/src/components/DataExportSection.vue
index 0c14b570d6..a05d9a63ad 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.
-
+
After the download, you can save the file in your preferred storage
location.
- -
+
-
On iOS: Choose "More..." and select a place in iCloud, or go "Back"
and save to another location.
- -
+
-
On Android: Choose "Open" and then share
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 821261eed0..5a2c920948 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;
- // 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 b6ba119808..b5fcd81e91 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 0a99cd4331..86725881dc 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 0e118297a0..7cd5fd53b9 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 9a65278aaf..bcfdfec156 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