Fix iOS contact copy function #154
Open
jose
wants to merge 8 commits from ios-contact-copy
into master
11 changed files with 241 additions and 37 deletions
@ -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.debug("[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.debug("[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…
Reference in new issue