A minimalistic file manager with Fula decentralized storage backup support. Built with Flutter for cross-platform compatibility.
- Local File Browser: Browse and manage local files and folders
- Fula Cloud Storage: Sync files to decentralized Fula network
- Client-Side Encryption: AES-256-GCM encryption before upload
- Authentication: Sign in with Google or Apple
- File Viewers: Built-in viewers for images, videos, and text files
- Search: Search local files by name
- Trash Management: Safely delete and restore files
- Dark/Light Theme: Automatic theme switching
lib/
├── app/
│ ├── app.dart # Root widget
│ ├── router.dart # GoRouter navigation
│ └── theme/ # Theme configuration
├── core/
│ ├── models/ # Data models (LocalFile, FulaObject, SyncState)
│ └── services/ # Core services
│ ├── auth_service.dart # Google/Apple authentication
│ ├── encryption_service.dart # AES-256-GCM encryption
│ ├── file_service.dart # Local file operations
│ ├── fula_api_service.dart # Fula S3-compatible API
│ ├── local_storage_service.dart # Hive local storage
│ ├── secure_storage_service.dart # Secure key storage
│ └── sync_service.dart # File synchronization
├── features/
│ ├── browser/ # Local file browser
│ ├── fula/ # Fula cloud browser
│ ├── home/ # Home screen with categories
│ ├── search/ # File search
│ ├── settings/ # App settings
│ ├── shared/ # Shared files
│ ├── sync/ # Sync providers
│ ├── trash/ # Trash management
│ └── viewer/ # File viewers (image, video, text)
├── shared/
│ └── widgets/ # Reusable widgets
└── main.dart # App entry point
- Flutter SDK 3.2.0 or higher
- Dart SDK 3.2.0 or higher
- Android Studio / Xcode for mobile development
- Clone the repository:
git clone https://github.com/user/FxFiles.git
cd FxFiles- Install dependencies:
flutter pub get- Run the app:
flutter run- Open the app and go to Settings
- Under Fula Configuration, enter:
- API Gateway URL: Your Fula gateway endpoint (e.g.,
https://gateway.fula.network) - JWT Token: Your authentication token
- IPFS Server (optional): Custom IPFS server URL
- API Gateway URL: Your Fula gateway endpoint (e.g.,
Sign in with Google or Apple to enable:
- Encrypted file uploads
- Per-user encryption keys derived from authentication
- Cross-device sync
- Algorithm: AES-256-GCM for symmetric encryption
- Key Derivation: PBKDF2 with SHA-256 for password-based keys
- Per-User Keys: Encryption keys derived from user authentication
- Client-Side: All encryption happens locally before upload
Local File → Encrypt (AES-256-GCM) → Upload to Fula → IPFS Storage
↑
User's Encryption Key
| Package | Purpose |
|---|---|
flutter_riverpod |
State management |
go_router |
Navigation |
minio_new |
S3-compatible API client |
cryptography |
Encryption primitives |
hive_flutter |
Local storage |
flutter_secure_storage |
Secure key storage |
google_sign_in |
Google authentication |
sign_in_with_apple |
Apple authentication |
workmanager |
Background sync tasks |
connectivity_plus |
Network monitoring |
- Browse to local files
- Select files to upload
- Tap the upload button
- Files are encrypted and uploaded automatically
- Open Fula Browser from home screen
- Browse buckets and files
- Tap a file to download and decrypt
await SyncService.instance.syncFolder(
localPath: '/storage/emulated/0/Documents',
remoteBucket: 'my-bucket',
remotePrefix: 'documents',
direction: SyncDirection.bidirectional,
);// Queue file upload
await SyncService.instance.queueUpload(
localPath: '/path/to/file.txt',
remoteBucket: 'my-bucket',
remoteKey: 'file.txt',
encrypt: true,
);
// Queue file download
await SyncService.instance.queueDownload(
remoteBucket: 'my-bucket',
remoteKey: 'file.txt',
localPath: '/path/to/file.txt',
decrypt: true,
);
// Retry failed syncs
await SyncService.instance.retryFailed();// List objects in bucket
final objects = await FulaApiService.instance.listObjects(
'my-bucket',
prefix: 'folder/',
);
// Upload with encryption
await FulaApiService.instance.encryptAndUpload(
'my-bucket',
'file.txt',
fileBytes,
encryptionKey,
);
// Download and decrypt
final bytes = await FulaApiService.instance.downloadAndDecrypt(
'my-bucket',
'file.txt',
encryptionKey,
);For files larger than 5MB, multipart upload is used automatically with progress tracking:
// Upload large file with progress (>5MB uses multipart automatically)
await FulaApiService.instance.uploadLargeFile(
'my-bucket',
'large-video.mp4',
fileBytes,
onProgress: (progress) {
print('Upload: ${progress.percentage.toStringAsFixed(1)}%');
},
);
// Encrypted large file upload
await FulaApiService.instance.encryptAndUploadLargeFile(
'my-bucket',
'large-video.mp4',
fileBytes,
encryptionKey,
originalFilename: 'vacation.mp4',
onProgress: (progress) {
print('Encrypted upload: ${progress.percentage.toStringAsFixed(1)}%');
},
);Background sync runs automatically when the app is closed:
// Initialize background sync (called in main.dart)
await BackgroundSyncService.instance.initialize();
// Schedule periodic sync (every 15 minutes on WiFi)
await BackgroundSyncService.instance.schedulePeriodicSync(
frequency: Duration(minutes: 15),
requiresWifi: true,
);
// Schedule one-time upload
await BackgroundSyncService.instance.scheduleUpload(
localPath: '/path/to/file.txt',
bucket: 'my-bucket',
key: 'file.txt',
encrypt: true,
useMultipart: true,
);
// Schedule one-time download
await BackgroundSyncService.instance.scheduleDownload(
bucket: 'my-bucket',
key: 'file.txt',
localPath: '/path/to/file.txt',
decrypt: true,
);
// Retry failed operations
await BackgroundSyncService.instance.scheduleRetryFailed();
// Cancel all background tasks
await BackgroundSyncService.instance.cancelAll();Share encrypted files with others without exposing your master key:
// Get your public key to share with others
final myPublicKey = await AuthService.instance.getPublicKeyString();
// Create a share for another user
final token = await SharingService.instance.createShare(
pathScope: '/photos/vacation/',
bucket: 'my-bucket',
recipientPublicKey: recipientPublicKeyBytes,
dek: folderEncryptionKey,
permissions: SharePermissions.readOnly,
expiryDays: 30,
label: 'Vacation photos',
);
// Generate shareable link
final link = SharingService.instance.generateShareLink(token);
// Result: fula://share/eyJpZCI6Ii4uLiJ9...
// Accept a share from link
final accepted = await SharingService.instance.acceptShareFromString(encodedToken);
print('Access to: ${accepted.pathScope}');
print('Can write: ${accepted.canWrite}');
// Revoke a share
await SharingService.instance.revokeShare(shareId);Sharing Model (from Fula API):
- Path-Scoped: Share only specific folders
- Time-Limited: Access expires automatically
- Permission-Based: Read-only, read-write, or full access
- Revocable: Cancel access at any time
- Zero Knowledge: Server can't read shared content
How it works:
- Owner creates share token with recipient's public key
- DEK is re-encrypted for recipient using HPKE (X25519 + AES-256-GCM)
- Token sent via any channel (link, QR code, message)
- Recipient decrypts with their private key
- Owner's master key is never exposed
- Fork the repository
- Create a feature branch
- Commit changes
- Push to the branch
- Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Fula Network for decentralized storage
- Flutter team for the amazing framework
- All open-source contributors