universal_file_picker 0.1.0
universal_file_picker: ^0.1.0 copied to clipboard
A cross-platform Flutter package for picking, previewing, and uploading files with a single, consistent API across Android, iOS, Web, macOS, Windows, and Linux.
import 'package:flutter/material.dart';
import 'package:universal_file_picker/universal_file_picker.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Universal File Picker Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<UFile> _pickedFiles = [];
final List<UploadResult> _uploadResults = [];
bool _isUploading = false;
double _uploadProgress = 0.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Universal File Picker Demo'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildPickSection(),
const SizedBox(height: 24),
if (_pickedFiles.isNotEmpty) ...[
_buildPreviewSection(),
const SizedBox(height: 24),
_buildUploadSection(),
const SizedBox(height: 24),
_buildInspectorSection(),
],
],
),
),
);
}
Widget _buildPickSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Pick Files',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton.icon(
onPressed: _pickSingleFile,
icon: const Icon(Icons.file_open),
label: const Text('Pick Single File'),
),
ElevatedButton.icon(
onPressed: _pickMultipleFiles,
icon: const Icon(Icons.folder_open),
label: const Text('Pick Multiple Files'),
),
ElevatedButton.icon(
onPressed: _pickImages,
icon: const Icon(Icons.image),
label: const Text('Pick Images'),
),
ElevatedButton.icon(
onPressed: _pickDocuments,
icon: const Icon(Icons.description),
label: const Text('Pick Documents'),
),
ElevatedButton.icon(
onPressed: _pickWithCamera,
icon: const Icon(Icons.camera_alt),
label: const Text('Camera'),
),
],
),
if (_pickedFiles.isNotEmpty) ...[
const SizedBox(height: 16),
Text(
'Picked Files (${_pickedFiles.length}):',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
SizedBox(
height: 80,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _pickedFiles.length,
itemBuilder: (context, index) {
final file = _pickedFiles[index];
return Container(
width: 120,
margin: const EdgeInsets.only(right: 8),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(8),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
_getFileIcon(file.mimeType),
size: 24,
color: Colors.blue[600],
),
const SizedBox(height: 4),
Text(
file.name,
style: const TextStyle(fontSize: 10),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
);
},
),
),
],
],
),
),
);
}
Widget _buildPreviewSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'File Preview',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
if (_pickedFiles.isNotEmpty)
UFilePreview(
file: _pickedFiles.first,
height: 300,
showFileInfo: true,
),
],
),
),
);
}
Widget _buildUploadSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Upload Files',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
if (_isUploading) ...[
LinearProgressIndicator(value: _uploadProgress),
const SizedBox(height: 8),
Text(
'Uploading... ${(_uploadProgress * 100).toStringAsFixed(1)}%'),
const SizedBox(height: 16),
],
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _isUploading ? null : _uploadFiles,
icon: const Icon(Icons.upload),
label: const Text('Upload All Files'),
),
),
const SizedBox(width: 8),
ElevatedButton.icon(
onPressed: _isUploading ? _cancelUpload : null,
icon: const Icon(Icons.cancel),
label: const Text('Cancel'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[50],
foregroundColor: Colors.red[700],
),
),
],
),
if (_uploadResults.isNotEmpty) ...[
const SizedBox(height: 16),
Text(
'Upload Results:',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
..._uploadResults.map((result) => Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: Icon(
result.success ? Icons.check_circle : Icons.error,
color: result.success ? Colors.green : Colors.red,
),
title: Text(
result.success ? 'Success' : 'Failed',
style: TextStyle(
fontWeight: FontWeight.bold,
color: result.success
? Colors.green[700]
: Colors.red[700],
),
),
subtitle: Text(
result.error ??
'Uploaded ${result.bytesUploaded} bytes',
),
),
)),
],
],
),
),
);
}
Widget _buildInspectorSection() {
return UFileInspector(
files: _pickedFiles,
uploadResults: _uploadResults,
);
}
Future<void> _pickSingleFile() async {
try {
final file = await UniversalFilePicker().pickFile();
if (file != null) {
setState(() {
_pickedFiles.clear();
_pickedFiles.add(file);
});
}
} catch (e) {
_showError('Failed to pick file: $e');
}
}
Future<void> _pickMultipleFiles() async {
try {
final files = await UniversalFilePicker().pickFiles(
options: const PickOptions(allowMultiple: true),
);
setState(() {
_pickedFiles.clear();
_pickedFiles.addAll(files);
});
} catch (e) {
_showError('Failed to pick files: $e');
}
}
Future<void> _pickImages() async {
try {
final files = await UniversalFilePicker().pickImages(
allowMultiple: true,
allowCamera: true,
);
setState(() {
_pickedFiles.clear();
_pickedFiles.addAll(files);
});
} catch (e) {
_showError('Failed to pick images: $e');
}
}
Future<void> _pickDocuments() async {
try {
final files = await UniversalFilePicker().pickDocuments(
allowMultiple: true,
extensions: ['pdf', 'doc', 'docx', 'txt', 'json'],
);
setState(() {
_pickedFiles.clear();
_pickedFiles.addAll(files);
});
} catch (e) {
_showError('Failed to pick documents: $e');
}
}
Future<void> _pickWithCamera() async {
try {
final files = await UniversalFilePicker().pickImages(
allowMultiple: false,
allowCamera: true,
);
setState(() {
_pickedFiles.clear();
_pickedFiles.addAll(files);
});
} catch (e) {
_showError('Failed to capture image: $e');
}
}
Future<void> _uploadFiles() async {
if (_pickedFiles.isEmpty) return;
setState(() {
_isUploading = true;
_uploadProgress = 0.0;
_uploadResults.clear();
});
try {
final uploader = UFileUploader();
final results = await uploader.uploadBatchWithProgress(
files: _pickedFiles,
options: const UploadOptions(
url: 'https://httpbin.org/post', // Test endpoint
fieldName: 'file',
),
onProgress: (current, total, progress) {
setState(() {
_uploadProgress = progress;
});
},
);
setState(() {
_uploadResults.addAll(results);
_isUploading = false;
_uploadProgress = 1.0;
});
} catch (e) {
setState(() {
_uploadResults.add(
UploadResult.failure(
error: e.toString(),
bytesUploaded: 0,
),
);
_isUploading = false;
});
}
}
void _cancelUpload() {
// In a real implementation, you'd cancel the upload here
setState(() {
_isUploading = false;
});
}
IconData _getFileIcon(String mimeType) {
if (mimeType.startsWith('image/')) {
return Icons.image;
} else if (mimeType == 'application/pdf') {
return Icons.picture_as_pdf;
} else if (mimeType.startsWith('text/') || mimeType == 'application/json') {
return Icons.description;
} else if (mimeType.startsWith('video/')) {
return Icons.video_file;
} else if (mimeType.startsWith('audio/')) {
return Icons.audio_file;
} else {
return Icons.insert_drive_file;
}
}
void _showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
),
);
}
}