Browse Source

Added: native clipboard service

- Install @capacitor/clipboard@^6.0.0 for native clipboard support
- Create platform-agnostic ClipboardService with iOS/Android native API
pull/149/head
Jose Olarte III 4 weeks ago
parent
commit
c18a6b334f
  1. 1
      android/app/capacitor.build.gradle
  2. 4
      android/app/src/main/assets/capacitor.plugins.json
  3. 3
      android/capacitor.settings.gradle
  4. 1
      ios/App/Podfile
  5. 8
      ios/App/Podfile.lock
  6. 9
      package-lock.json
  7. 1
      package.json
  8. 185
      src/services/ClipboardService.ts

1
android/app/capacitor.build.gradle

@ -13,6 +13,7 @@ dependencies {
implementation project(':capacitor-mlkit-barcode-scanning')
implementation project(':capacitor-app')
implementation project(':capacitor-camera')
implementation project(':capacitor-clipboard')
implementation project(':capacitor-filesystem')
implementation project(':capacitor-share')
implementation project(':capawesome-capacitor-file-picker')

4
android/app/src/main/assets/capacitor.plugins.json

@ -15,6 +15,10 @@
"pkg": "@capacitor/camera",
"classpath": "com.capacitorjs.plugins.camera.CameraPlugin"
},
{
"pkg": "@capacitor/clipboard",
"classpath": "com.capacitorjs.plugins.clipboard.ClipboardPlugin"
},
{
"pkg": "@capacitor/filesystem",
"classpath": "com.capacitorjs.plugins.filesystem.FilesystemPlugin"

3
android/capacitor.settings.gradle

@ -14,6 +14,9 @@ project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/
include ':capacitor-camera'
project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android')
include ':capacitor-clipboard'
project(':capacitor-clipboard').projectDir = new File('../node_modules/@capacitor/clipboard/android')
include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android')

1
ios/App/Podfile

@ -15,6 +15,7 @@ def capacitor_pods
pod 'CapacitorMlkitBarcodeScanning', :path => '../../node_modules/@capacitor-mlkit/barcode-scanning'
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera'
pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard'
pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem'
pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share'
pod 'CapawesomeCapacitorFilePicker', :path => '../../node_modules/@capawesome/capacitor-file-picker'

8
ios/App/Podfile.lock

@ -5,6 +5,8 @@ PODS:
- Capacitor
- CapacitorCamera (6.1.2):
- Capacitor
- CapacitorClipboard (6.0.2):
- Capacitor
- CapacitorCommunitySqlite (6.0.2):
- Capacitor
- SQLCipher
@ -88,6 +90,7 @@ DEPENDENCIES:
- "Capacitor (from `../../node_modules/@capacitor/ios`)"
- "CapacitorApp (from `../../node_modules/@capacitor/app`)"
- "CapacitorCamera (from `../../node_modules/@capacitor/camera`)"
- "CapacitorClipboard (from `../../node_modules/@capacitor/clipboard`)"
- "CapacitorCommunitySqlite (from `../../node_modules/@capacitor-community/sqlite`)"
- "CapacitorCordova (from `../../node_modules/@capacitor/ios`)"
- "CapacitorFilesystem (from `../../node_modules/@capacitor/filesystem`)"
@ -119,6 +122,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/@capacitor/app"
CapacitorCamera:
:path: "../../node_modules/@capacitor/camera"
CapacitorClipboard:
:path: "../../node_modules/@capacitor/clipboard"
CapacitorCommunitySqlite:
:path: "../../node_modules/@capacitor-community/sqlite"
CapacitorCordova:
@ -136,6 +141,7 @@ SPEC CHECKSUMS:
Capacitor: c95400d761e376be9da6be5a05f226c0e865cebf
CapacitorApp: e1e6b7d05e444d593ca16fd6d76f2b7c48b5aea7
CapacitorCamera: 9bc7b005d0e6f1d5f525b8137045b60cffffce79
CapacitorClipboard: 4443c3cdb7c77b1533dfe3ff0f9f7756aa8579df
CapacitorCommunitySqlite: 0299d20f4b00c2e6aa485a1d8932656753937b9b
CapacitorCordova: 8d93e14982f440181be7304aa9559ca631d77fff
CapacitorFilesystem: 59270a63c60836248812671aa3b15df673fbaf74
@ -157,6 +163,6 @@ SPEC CHECKSUMS:
SQLCipher: 31878d8ebd27e5c96db0b7cb695c96e9f8ad77da
ZIPFoundation: b8c29ea7ae353b309bc810586181fd073cb3312c
PODFILE CHECKSUM: f987510f7383b04a1b09ea8472bdadcd88b6c924
PODFILE CHECKSUM: 60f54b19c5a7a07343ab5ba9e5db49019fd86aa0
COCOAPODS: 1.16.2

9
package-lock.json

@ -15,6 +15,7 @@
"@capacitor/app": "^6.0.0",
"@capacitor/camera": "^6.0.0",
"@capacitor/cli": "^6.2.0",
"@capacitor/clipboard": "^6.0.2",
"@capacitor/core": "^6.2.0",
"@capacitor/filesystem": "^6.0.0",
"@capacitor/ios": "^6.2.0",
@ -2157,6 +2158,14 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@capacitor/clipboard": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/clipboard/-/clipboard-6.0.2.tgz",
"integrity": "sha512-jQ6UeFra5NP58THNZNb7HtzOZU7cHsjgrbQGVuMTgsK1uTILZpNeh+pfqHbKggba6KaNh5DAsJvEVQGpIR1VBA==",
"peerDependencies": {
"@capacitor/core": "^6.0.0"
}
},
"node_modules/@capacitor/core": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.1.tgz",

1
package.json

@ -113,6 +113,7 @@
"@capacitor/app": "^6.0.0",
"@capacitor/camera": "^6.0.0",
"@capacitor/cli": "^6.2.0",
"@capacitor/clipboard": "^6.0.2",
"@capacitor/core": "^6.2.0",
"@capacitor/filesystem": "^6.0.0",
"@capacitor/ios": "^6.2.0",

185
src/services/ClipboardService.ts

@ -0,0 +1,185 @@
import { Capacitor } from "@capacitor/core";
import { Clipboard } from "@capacitor/clipboard";
import { useClipboard } from "@vueuse/core";
import { logger } from "@/utils/logger";
/**
* Platform-agnostic clipboard service that handles both web and native platforms
* Provides reliable clipboard functionality across all platforms including iOS
*/
export class ClipboardService {
private static instance: ClipboardService | null = null;
/**
* Get singleton instance of ClipboardService
*/
public static getInstance(): ClipboardService {
if (!ClipboardService.instance) {
ClipboardService.instance = new ClipboardService();
}
return ClipboardService.instance;
}
/**
* Copy text to clipboard with platform-specific handling
*
* @param text - The text to copy to clipboard
* @returns Promise that resolves when copy is complete
* @throws Error if copy operation fails
*/
public async copyToClipboard(text: string): Promise<void> {
const platform = Capacitor.getPlatform();
const isNative = Capacitor.isNativePlatform();
logger.log("[ClipboardService] Copying to clipboard:", {
text: text.substring(0, 50) + (text.length > 50 ? "..." : ""),
platform,
isNative,
timestamp: new Date().toISOString(),
});
try {
if (isNative && (platform === "ios" || platform === "android")) {
// Use native Capacitor clipboard for mobile platforms
await this.copyNative(text);
} else {
// Use web clipboard API for web/desktop platforms
await this.copyWeb(text);
}
logger.log("[ClipboardService] Copy successful", {
platform,
timestamp: new Date().toISOString(),
});
} catch (error) {
logger.error("[ClipboardService] Copy failed:", {
error: error instanceof Error ? error.message : String(error),
platform,
timestamp: new Date().toISOString(),
});
throw error;
}
}
/**
* Copy text using native Capacitor clipboard API
*
* @param text - The text to copy
* @returns Promise that resolves when copy is complete
*/
private async copyNative(text: string): Promise<void> {
try {
await Clipboard.write({
string: text,
});
} catch (error) {
logger.error("[ClipboardService] Native copy failed:", {
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString(),
});
throw new Error(
`Native clipboard copy failed: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
/**
* Copy text using web clipboard API with fallback
*
* @param text - The text to copy
* @returns Promise that resolves when copy is complete
*/
private async copyWeb(text: string): Promise<void> {
try {
// Try VueUse clipboard first (handles some edge cases)
const { copy } = useClipboard();
await copy(text);
} catch (error) {
logger.warn(
"[ClipboardService] VueUse clipboard failed, trying native API:",
{
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString(),
},
);
// Fallback to native navigator.clipboard
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
} else {
throw new Error("Clipboard API not supported in this browser");
}
}
}
/**
* Read text from clipboard (platform-specific)
*
* @returns Promise that resolves to the clipboard text
* @throws Error if read operation fails
*/
public async readFromClipboard(): Promise<string> {
const platform = Capacitor.getPlatform();
const isNative = Capacitor.isNativePlatform();
try {
if (isNative && (platform === "ios" || platform === "android")) {
// Use native Capacitor clipboard for mobile platforms
const result = await Clipboard.read();
return result.value || "";
} else {
// Use web clipboard API for web/desktop platforms
if (navigator.clipboard && navigator.clipboard.readText) {
return await navigator.clipboard.readText();
} else {
throw new Error("Clipboard read API not supported in this browser");
}
}
} catch (error) {
logger.error("[ClipboardService] Read from clipboard failed:", {
error: error instanceof Error ? error.message : String(error),
platform,
timestamp: new Date().toISOString(),
});
throw error;
}
}
/**
* Check if clipboard is supported on current platform
*
* @returns boolean indicating if clipboard is supported
*/
public isSupported(): boolean {
const platform = Capacitor.getPlatform();
const isNative = Capacitor.isNativePlatform();
if (isNative && (platform === "ios" || platform === "android")) {
return true; // Capacitor clipboard should work on native platforms
}
// Check web clipboard support
return !!(navigator.clipboard && navigator.clipboard.writeText);
}
}
/**
* Convenience function to copy text to clipboard
* Uses the singleton ClipboardService instance
*
* @param text - The text to copy to clipboard
* @returns Promise that resolves when copy is complete
*/
export async function copyToClipboard(text: string): Promise<void> {
return ClipboardService.getInstance().copyToClipboard(text);
}
/**
* Convenience function to read text from clipboard
* Uses the singleton ClipboardService instance
*
* @returns Promise that resolves to the clipboard text
*/
export async function readFromClipboard(): Promise<string> {
return ClipboardService.getInstance().readFromClipboard();
}
Loading…
Cancel
Save