@ -6,6 +6,7 @@ import {
CameraDirection ,
} from "@capacitor/camera" ;
import { Share } from "@capacitor/share" ;
import { FilePicker } from "@capawesome/capacitor-file-picker" ;
import {
SQLiteConnection ,
SQLiteDBConnection ,
@ -271,22 +272,24 @@ export class CapacitorPlatformService implements PlatformService {
) ;
if ( this . getCapabilities ( ) . isIOS ) {
// iOS uses different permission model
// iOS uses different permission model - Documents directory is accessible
logger . log ( "iOS platform - Documents directory is accessible by default" ) ;
return ;
}
// Try to access a test directory to check permissions
// For Android, try to access external storage to check permissions
try {
await Filesystem . stat ( {
path : "/storage/emulated/0/Download" ,
directory : Directory.Documents ,
// Try to list files in external storage to check permissions
await Filesystem . readdir ( {
path : "." ,
directory : Directory.External ,
} ) ;
logger . log (
"S torage permissions already granted" ,
"External s torage permissions already granted" ,
JSON . stringify ( { timestamp : new Date ( ) . toISOString ( ) } , null , 2 ) ,
) ;
return ;
} catch ( error : unknown ) {
} catch ( error ) {
const err = error as Error ;
const errorLogData = {
error : {
@ -297,19 +300,11 @@ export class CapacitorPlatformService implements PlatformService {
timestamp : new Date ( ) . toISOString ( ) ,
} ;
// "File does not exist" is expected and not a permission error
if ( err . message === "File does not exist" ) {
logger . log (
"Directory does not exist (expected), proceeding with write" ,
JSON . stringify ( errorLogData , null , 2 ) ,
) ;
return ;
}
// Check for actual permission errors
if (
err . message . includes ( "permission" ) ||
err . message . includes ( "access" )
err . message . includes ( "access" ) ||
err . message . includes ( "denied" )
) {
logger . log (
"Permission check failed, requesting permissions" ,
@ -319,45 +314,33 @@ export class CapacitorPlatformService implements PlatformService {
// The Filesystem plugin will automatically request permissions when needed
// We just need to try the operation again
try {
await Filesystem . stat ( {
path : "/storage/emulated/0/Download " ,
directory : Directory.Documents ,
await Filesystem . readdir ( {
path : ". " ,
directory : Directory.External ,
} ) ;
logger . log (
"S torage permissions granted after request" ,
"External s torage permissions granted after request" ,
JSON . stringify ( { timestamp : new Date ( ) . toISOString ( ) } , null , 2 ) ,
) ;
return ;
} catch ( retryError : unknown ) {
const retryErr = retryError as Error ;
throw new Error (
` Failed to obtain storage permissions: ${ retryErr . message } ` ,
` Failed to obtain external storage permissions: ${ retryErr . message } ` ,
) ;
}
}
// For any other error, log it but don't treat as permission error
logger . log (
"Unexpected error during permission check" ,
"Unexpected error during permission check, proceeding anyway " ,
JSON . stringify ( errorLogData , null , 2 ) ,
) ;
return ;
}
} catch ( error : unknown ) {
const err = error as Error ;
const errorLogData = {
error : {
message : err.message ,
name : err.name ,
stack : err.stack ,
} ,
timestamp : new Date ( ) . toISOString ( ) ,
} ;
logger . error (
"Error checking/requesting permissions" ,
JSON . stringify ( errorLogData , null , 2 ) ,
) ;
throw new Error ( ` Failed to obtain storage permissions: ${ err . message } ` ) ;
} catch ( error ) {
logger . error ( "Error in checkStoragePermissions:" , error ) ;
throw error ;
}
}
@ -382,8 +365,8 @@ export class CapacitorPlatformService implements PlatformService {
* Enhanced file save and share functionality with location selection .
*
* Provides multiple options for saving files :
* 1 . Save to app - privat e storage and share ( curren t behavior )
* 2 . Save to device Downloads folder ( Android ) or Documents ( iOS )
* 1 . Save to user - accessibl e storage ( Downloads / Documents ) and share ( defaul t behavior )
* 2 . Save to app - private storage and share ( for sensitive data )
* 3 . Allow user to choose save location via file picker
* 4 . Direct share without saving locally
*
@ -398,7 +381,10 @@ export class CapacitorPlatformService implements PlatformService {
options : {
allowLocationSelection? : boolean ;
saveToDownloads? : boolean ;
saveToPrivateStorage? : boolean ;
mimeType? : string ;
showShareDialog? : boolean ;
showLocationSelectionDialog? : boolean ;
} = { }
) : Promise < { saved : boolean ; uri? : string ; shared : boolean ; error? : string } > {
const timestamp = new Date ( ) . toISOString ( ) ;
@ -420,15 +406,11 @@ export class CapacitorPlatformService implements PlatformService {
// Determine save strategy based on options and platform
if ( options . allowLocationSelection ) {
// Use file picker to let user choose location
fileUri = await this . saveFileWithPicker ( fileName , content , options . mimeType ) ;
saved = true ;
} else if ( options . saveToDownloads ) {
// Save directly to Downloads folder
fileUri = await this . saveToDownloads ( fileName , content ) ;
// Use enhanced location selection with multiple save options
fileUri = await this . saveWithLocationOptions ( fileName , content , options . mimeType , options . showLocationSelectionDialog ) ;
saved = true ;
} else {
// Fallback to app-private storage (current behavior )
} else if ( options . saveToPrivateStorage ) {
// Save to app-private storage (for sensitive data)
const result = await Filesystem . writeFile ( {
path : fileName ,
data : content ,
@ -438,6 +420,10 @@ export class CapacitorPlatformService implements PlatformService {
} ) ;
fileUri = result . uri ;
saved = true ;
} else {
// Default: Save to user-accessible location (Downloads/Documents)
fileUri = await this . saveToDownloads ( fileName , content ) ;
saved = true ;
}
logger . log ( "[CapacitorPlatformService] File write successful:" , {
@ -446,20 +432,53 @@ export class CapacitorPlatformService implements PlatformService {
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
// Share the file
// Share the file (unless explicitly disabled)
let shared = false ;
try {
await Share . share ( {
title : "TimeSafari Backup" ,
text : "Here is your backup file." ,
url : fileUri ,
dialogTitle : "Share your backup file" ,
} ) ;
if ( options . showShareDialog !== false && ! options . allowLocationSelection ) {
try {
logger . log ( "[CapacitorPlatformService] Starting share operation:" , {
uri : fileUri ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
// Share the file with improved timeout handling
const shareResult = await this . handleShareDialog ( {
title : "TimeSafari Backup" ,
text : "Here is your backup file." ,
url : fileUri ,
dialogTitle : "Share your backup file" ,
} ) ;
shared = true ;
logger . log ( "[CapacitorPlatformService] Share dialog completed successfully:" , {
activityType : shareResult?.activityType ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
} catch ( shareError ) {
const shareErr = shareError as Error ;
logger . warn ( "[CapacitorPlatformService] Share operation completed (may have been cancelled or timed out):" , {
error : shareErr.message ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
// Check if it's a user cancellation, timeout, or other expected completion
if ( shareErr . message . includes ( "cancel" ) ||
shareErr . message . includes ( "dismiss" ) ||
shareErr . message . includes ( "timeout" ) ||
shareErr . message . includes ( "Share dialog timeout" ) ) {
logger . log ( "[CapacitorPlatformService] Share dialog completed (cancelled/timeout) - this is expected behavior" ) ;
// Don't treat cancellation/timeout as an error, file is still saved
// The dialog should close automatically on timeout
} else {
// Log other share errors but don't fail the operation
logger . error ( "[CapacitorPlatformService] Unexpected share error:" , shareErr ) ;
}
}
} else if ( options . allowLocationSelection ) {
// Location selection already handled the sharing, mark as shared
shared = true ;
logger . log ( "[CapacitorPlatformService] File shared successfully" ) ;
} catch ( shareError ) {
logger . warn ( "[CapacitorPlatformService] Share failed, but file was saved:" , shareError ) ;
// Don't throw error if sharing fails, file is still saved
logger . log ( "[CapacitorPlatformService] Location selection handled sharing" ) ;
}
return { saved , uri : fileUri , shared } ;
@ -479,7 +498,59 @@ export class CapacitorPlatformService implements PlatformService {
}
/ * *
* Saves a file using the file picker to let user choose location .
* Handles share dialog with improved timeout and dismissal handling .
* @param shareOptions - Options for the share dialog
* @param timeoutMs - Timeout in milliseconds ( default : 15 seconds )
* @returns Promise that resolves when share operation completes
* /
private async handleShareDialog ( shareOptions : any , timeoutMs : number = 15000 ) : Promise < any > {
try {
logger . log ( "[CapacitorPlatformService] Starting share dialog with timeout:" , {
timeoutMs ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
const sharePromise = Share . share ( shareOptions ) ;
// Add timeout to prevent hanging
const timeoutPromise = new Promise < never > ( ( _ , reject ) = > {
setTimeout ( ( ) = > {
logger . warn ( "[CapacitorPlatformService] Share dialog timeout reached - forcing completion" ) ;
reject ( new Error ( "Share dialog timeout - forcing completion" ) ) ;
} , timeoutMs ) ;
} ) ;
// Race between share completion and timeout
const result = await Promise . race ( [ sharePromise , timeoutPromise ] ) ;
logger . log ( "[CapacitorPlatformService] Share dialog completed successfully:" , {
activityType : result?.activityType ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
return result ;
} catch ( error ) {
const err = error as Error ;
// Check if it's an expected completion (timeout, cancellation, etc.)
if ( err . message . includes ( "timeout" ) ||
err . message . includes ( "cancel" ) ||
err . message . includes ( "dismiss" ) ) {
logger . log ( "[CapacitorPlatformService] Share dialog completed (expected):" , {
reason : err.message ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
// Return a success result even for timeout/cancellation
return { activityType : "timeout_or_cancellation" } ;
} else {
// Re-throw unexpected errors
throw error ;
}
}
}
/ * *
* Saves a file using the native share dialog to let user choose save location .
* @param fileName - Name of the file to save
* @param content - File content
* @param mimeType - MIME type of the file
@ -491,29 +562,135 @@ export class CapacitorPlatformService implements PlatformService {
mimeType : string = "application/json"
) : Promise < string > {
try {
// For now, fallback to regular save since file picker save API is complex
// Save to app-private storage and let user share to choose location
const result = await Filesystem . writeFile ( {
path : fileName ,
logger . log ( "[CapacitorPlatformService] Using native share dialog for save location selection:" , {
fileName ,
mimeType ,
platform : this.getCapabilities ( ) . isIOS ? "iOS" : "Android" ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
// First, save the file to a temporary location
const tempFileName = ` temp_ ${ Date . now ( ) } _ ${ fileName } ` ;
const tempResult = await Filesystem . writeFile ( {
path : tempFileName ,
data : content ,
directory : Directory.Data ,
encoding : Encoding.UTF8 ,
} ) ;
logger . log ( "[CapacitorPlatformService] File saved to temp location:" , {
tempUri : tempResult.uri ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
// Use the native share dialog which includes "Save to Files" options
// This allows users to choose where to save the file using the system's file manager
await this . handleShareDialog ( {
title : "Save TimeSafari File" ,
text : ` Save ${ fileName } to your preferred location ` ,
url : tempResult.uri ,
dialogTitle : "Choose where to save your file" ,
} ) ;
logger . log ( "[CapacitorPlatformService] Native share dialog completed for location selection" ) ;
// Return the temp URI - the user can save it wherever they want via the share dialog
// The share dialog will provide options like "Save to Files", "Copy to Downloads", etc.
return tempResult . uri ;
} catch ( error ) {
logger . error ( "[CapacitorPlatformService] Native share dialog for location selection failed:" , error ) ;
throw new Error ( ` Failed to open location selection dialog: ${ error } ` ) ;
}
}
/ * *
* Provides multiple save location options for the user to choose from .
* @param fileName - Name of the file to save
* @param content - File content
* @param mimeType - MIME type of the file
* @param showDialog - Whether to show the location selection dialog
* @returns Promise resolving to the saved file URI
* /
private async saveWithLocationOptions (
fileName : string ,
content : string ,
mimeType : string = "application/json" ,
showDialog : boolean = true
) : Promise < string > {
try {
logger . log ( "[CapacitorPlatformService] Providing save location options:" , {
fileName ,
mimeType ,
showDialog ,
platform : this.getCapabilities ( ) . isIOS ? "iOS" : "Android" ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
// Save to multiple locations and let user choose via share dialog
const locations = [ ] ;
// Save to Documents (iOS) or Downloads (Android)
const primaryLocation = await this . saveToDownloads ( fileName , content ) ;
locations . push ( primaryLocation ) ;
// Save to app data directory as backup
const backupFileName = ` backup_ ${ Date . now ( ) } _ ${ fileName } ` ;
const backupResult = await Filesystem . writeFile ( {
path : backupFileName ,
data : content ,
directory : Directory.Data ,
encoding : Encoding.UTF8 ,
} ) ;
locations . push ( backupResult . uri ) ;
logger . log ( "[CapacitorPlatformService] File saved to app storage for picker fallback:" , {
uri : result.uri ,
logger . log ( "[CapacitorPlatformService] File saved to multiple locations:" , {
primaryLocation ,
backupLocation : backupResult.uri ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
return result . uri ;
// Use share dialog to let user choose which location to use (if enabled)
if ( showDialog ) {
try {
await this . handleShareDialog ( {
title : "TimeSafari File Saved" ,
text : ` Your file has been saved to multiple locations. Choose where to access it. ` ,
url : primaryLocation ,
dialogTitle : "Access your saved file" ,
} ) ;
logger . log ( "[CapacitorPlatformService] Share dialog completed for location selection" ) ;
} catch ( shareError ) {
const shareErr = shareError as Error ;
logger . warn ( "[CapacitorPlatformService] Share dialog failed, but file was saved:" , {
error : shareErr.message ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
// Check if it's a user cancellation or timeout
if ( shareErr . message . includes ( "cancel" ) ||
shareErr . message . includes ( "dismiss" ) ||
shareErr . message . includes ( "timeout" ) ) {
logger . log ( "[CapacitorPlatformService] User cancelled location selection share dialog or timeout occurred" ) ;
} else {
logger . error ( "[CapacitorPlatformService] Location selection share error:" , shareErr ) ;
}
}
} else {
logger . log ( "[CapacitorPlatformService] Location selection dialog disabled, file saved silently" ) ;
}
// Return the primary location (user can access others via share dialog)
return primaryLocation ;
} catch ( error ) {
logger . error ( "[CapacitorPlatformService] File picker save failed:" , error ) ;
throw new Error ( ` Failed to save file with picker: ${ error } ` ) ;
logger . error ( "[CapacitorPlatformService] Save with location options failed:" , error ) ;
throw new Error ( ` Failed to save with location options : ${ error } ` ) ;
}
}
/ * *
* Saves a file directly to the Downloads folder ( Android ) or Documents ( iOS ) .
* These locations are user - accessible through file managers and the app .
* @param fileName - Name of the file to save
* @param content - File content
* @returns Promise resolving to the saved file URI
@ -521,22 +698,43 @@ export class CapacitorPlatformService implements PlatformService {
private async saveToDownloads ( fileName : string , content : string ) : Promise < string > {
try {
if ( this . getCapabilities ( ) . isIOS ) {
// iOS: Save to Documents directory
// iOS: Save to Documents directory (user accessible)
const result = await Filesystem . writeFile ( {
path : fileName ,
data : content ,
directory : Directory.Documents ,
encoding : Encoding.UTF8 ,
} ) ;
logger . log ( "[CapacitorPlatformService] File saved to iOS Documents:" , {
uri : result.uri ,
fileName ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
return result . uri ;
} else {
// Android: Save to Downloads directory
// Android: Save to app's external storage (accessible via file managers)
// Due to Android 11+ restrictions, we can't directly write to public Downloads
// Users can access files through file managers or use share dialog to save to Downloads
const downloadsPath = ` TimeSafari/ ${ fileName } ` ;
const result = await Filesystem . writeFile ( {
path : fileName ,
path : downloadsPath ,
data : content ,
directory : Directory.External ,
directory : Directory.External , // App's external storage (accessible via file managers)
encoding : Encoding.UTF8 ,
recursive : true ,
} ) ;
logger . log ( "[CapacitorPlatformService] File saved to Android external storage:" , {
uri : result.uri ,
fileName ,
downloadsPath ,
note : "File is accessible via file managers. Use share dialog to save to Downloads." ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
return result . uri ;
}
} catch ( error ) {
@ -573,6 +771,175 @@ export class CapacitorPlatformService implements PlatformService {
) ;
}
/ * *
* Lists user - accessible files saved by the app .
* Returns files from Downloads ( Android ) or Documents ( iOS ) directories .
* @returns Promise resolving to array of file information
* /
async listUserAccessibleFiles ( ) : Promise < Array < { name : string , uri : string , size ? : number } > > {
try {
if ( this . getCapabilities ( ) . isIOS ) {
// iOS: List files in Documents directory
const result = await Filesystem . readdir ( {
path : "." ,
directory : Directory.Documents ,
} ) ;
return result . files . map ( ( file ) = > ( {
name : typeof file === "string" ? file : file.name ,
uri : ` file:// ${ file . uri || file } ` ,
size : typeof file === "string" ? undefined : file . size ,
} ) ) ;
} else {
// Android: List files in app's external storage (TimeSafari subdirectory)
try {
const result = await Filesystem . readdir ( {
path : "TimeSafari" ,
directory : Directory.External ,
} ) ;
return result . files . map ( ( file ) = > ( {
name : typeof file === "string" ? file : file.name ,
uri : ` file:// ${ file . uri || file } ` ,
size : typeof file === "string" ? undefined : file . size ,
} ) ) ;
} catch ( downloadsError ) {
logger . warn ( "[CapacitorPlatformService] Could not read external storage directory:" , downloadsError ) ;
return [ ] ;
}
}
} catch ( error ) {
logger . error ( "[CapacitorPlatformService] Failed to list user accessible files:" , error ) ;
return [ ] ;
}
}
/ * *
* Tests the file sharing functionality by creating and sharing a test file .
* @returns Promise resolving to a test result message
* /
async testFileSharing ( ) : Promise < string > {
try {
const testContent = {
message : "This is a test file for TimeSafari file sharing" ,
timestamp : new Date ( ) . toISOString ( ) ,
platform : this.getCapabilities ( ) . isIOS ? "iOS" : "Android" ,
test : true
} ;
const fileName = ` timesafari-test- ${ Date . now ( ) } .json ` ;
const content = JSON . stringify ( testContent , null , 2 ) ;
const result = await this . writeAndShareFile ( fileName , content , {
mimeType : "application/json"
} ) ;
if ( result . saved ) {
return ` ✅ File saved successfully! URI: ${ result . uri } . Shared: ${ result . shared ? 'Yes' : 'No' } ` ;
} else {
return ` ❌ File save failed: ${ result . error } ` ;
}
} catch ( error ) {
const err = error as Error ;
return ` ❌ Test failed: ${ err . message } ` ;
}
}
/ * *
* Tests saving a file without showing the share dialog .
* @returns Promise resolving to a test result message
* /
async testFileSaveOnly ( ) : Promise < string > {
try {
const testContent = {
message : "This is a test file saved without sharing" ,
timestamp : new Date ( ) . toISOString ( ) ,
platform : this.getCapabilities ( ) . isIOS ? "iOS" : "Android" ,
test : true ,
saveOnly : true
} ;
const fileName = ` timesafari-save-only- ${ Date . now ( ) } .json ` ;
const content = JSON . stringify ( testContent , null , 2 ) ;
const result = await this . writeAndShareFile ( fileName , content , {
mimeType : "application/json" ,
showShareDialog : false
} ) ;
if ( result . saved ) {
return ` ✅ File saved successfully without sharing! URI: ${ result . uri } ` ;
} else {
return ` ❌ File save failed: ${ result . error } ` ;
}
} catch ( error ) {
const err = error as Error ;
return ` ❌ Test failed: ${ err . message } ` ;
}
}
/ * *
* Tests the location selection functionality using the file picker .
* @returns Promise resolving to a test result message
* /
async testLocationSelection ( ) : Promise < string > {
try {
const testContent = {
message : "This is a test file for location selection" ,
timestamp : new Date ( ) . toISOString ( ) ,
platform : this.getCapabilities ( ) . isIOS ? "iOS" : "Android" ,
test : true ,
locationSelection : true
} ;
const fileName = ` timesafari-location-test- ${ Date . now ( ) } .json ` ;
const content = JSON . stringify ( testContent , null , 2 ) ;
// Use the FilePicker to let user choose where to save the file
const result = await this . saveFileWithLocationPicker ( fileName , content , "application/json" ) ;
return ` ✅ Location selection test successful! File saved using directory picker. URI: ${ result } ` ;
} catch ( error ) {
const err = error as Error ;
return ` ❌ Location selection test failed: ${ err . message } ` ;
}
}
/ * *
* Tests location selection without showing the dialog ( restores original behavior ) .
* @returns Promise resolving to a test result message
* /
async testLocationSelectionSilent ( ) : Promise < string > {
try {
const testContent = {
message : "This is a test file for silent location selection" ,
timestamp : new Date ( ) . toISOString ( ) ,
platform : this.getCapabilities ( ) . isIOS ? "iOS" : "Android" ,
test : true ,
locationSelection : true ,
silent : true
} ;
const fileName = ` timesafari-silent-location-test- ${ Date . now ( ) } .json ` ;
const content = JSON . stringify ( testContent , null , 2 ) ;
const result = await this . writeAndShareFile ( fileName , content , {
allowLocationSelection : true ,
showLocationSelectionDialog : false ,
mimeType : "application/json"
} ) ;
if ( result . saved ) {
return ` ✅ Silent location selection test successful! File saved to multiple locations without showing dialog. URI: ${ result . uri } ` ;
} else {
return ` ❌ Silent location selection test failed: ${ result . error } ` ;
}
} catch ( error ) {
const err = error as Error ;
return ` ❌ Silent location selection test failed: ${ err . message } ` ;
}
}
/ * *
* Opens the device camera to take a picture .
* Configures camera for high quality images with editing enabled .
@ -752,4 +1119,104 @@ export class CapacitorPlatformService implements PlatformService {
throw new Error ( ` Failed to save file: ${ err . message } ` ) ;
}
}
/ * *
* Saves a file using the FilePicker to let user choose the save location .
* This provides true location selection rather than using a share dialog .
* @param fileName - Name of the file to save
* @param content - File content
* @param mimeType - MIME type of the file
* @returns Promise resolving to the saved file URI
* /
private async saveFileWithLocationPicker (
fileName : string ,
content : string ,
mimeType : string = "application/json"
) : Promise < string > {
try {
logger . log ( "[CapacitorPlatformService] Using FilePicker pickDirectory for location selection:" , {
fileName ,
mimeType ,
platform : this.getCapabilities ( ) . isIOS ? "iOS" : "Android" ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
// Use FilePicker to let user choose a directory to save the file
const directoryResult = await FilePicker . pickDirectory ( ) ;
logger . log ( "[CapacitorPlatformService] User selected directory:" , {
selectedPath : directoryResult.path ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
// Save the file to the selected directory
const fullPath = ` ${ directoryResult . path } / ${ fileName } ` ;
// Try to save to the user-selected directory
try {
const result = await Filesystem . writeFile ( {
path : fullPath ,
data : content ,
directory : Directory.ExternalStorage , // Use external storage for user-selected location
encoding : Encoding.UTF8 ,
recursive : true ,
} ) ;
logger . log ( "[CapacitorPlatformService] File saved to selected directory:" , {
finalUri : result.uri ,
fullPath ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
return result . uri ;
} catch ( writeError ) {
logger . warn ( "[CapacitorPlatformService] Failed to write to selected directory, trying alternative:" , writeError ) ;
// Fallback: Save to Downloads with the selected filename
const downloadsResult = await this . saveToDownloads ( fileName , content ) ;
logger . log ( "[CapacitorPlatformService] File saved to Downloads as fallback:" , {
finalUri : downloadsResult ,
originalPath : fullPath ,
timestamp : new Date ( ) . toISOString ( ) ,
} ) ;
return downloadsResult ;
}
} catch ( error ) {
logger . error ( "[CapacitorPlatformService] FilePicker directory selection failed:" , error ) ;
// If directory picker fails, fallback to Downloads
try {
logger . log ( "[CapacitorPlatformService] Falling back to Downloads directory" ) ;
const fallbackResult = await this . saveToDownloads ( fileName , content ) ;
return fallbackResult ;
} catch ( fallbackError ) {
throw new Error ( ` Failed to select directory with FilePicker and fallback failed: ${ error } ` ) ;
}
}
}
/ * *
* Tests listing user - accessible files saved by the app .
* @returns Promise resolving to a test result message
* /
async testListUserFiles ( ) : Promise < string > {
try {
const files = await this . listUserAccessibleFiles ( ) ;
if ( files . length === 0 ) {
return ` 📁 No user-accessible files found. Try saving some files first using the other test buttons. ` ;
}
const fileList = files . map ( file = >
` - ${ file . name } ( ${ file . size ? ` ${ file . size } bytes ` : 'size unknown' } ) `
) . join ( '\n' ) ;
return ` 📁 Found ${ files . length } user-accessible file(s): \ n ${ fileList } ` ;
} catch ( error ) {
const err = error as Error ;
return ` ❌ Failed to list user files: ${ err . message } ` ;
}
}
}