image_picker2 1.0.1
image_picker2: ^1.0.1 copied to clipboard
A modern, zero-permission Flutter plugin to pick images and videos with OS-level selection limits and native compression.
example/lib/main.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker2/image_picker2.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<String> _mediaPaths = [];
PickerMediaType _selectedType = PickerMediaType.image;
bool _isLimitReached = false;
final TextEditingController _limitController = TextEditingController(
text: '3',
);
final TextEditingController _maxWidthController = TextEditingController(
text: '1000',
);
final TextEditingController _qualityController = TextEditingController(
text: '80',
);
Future<void> _pickMedia() async {
final limit = int.tryParse(_limitController.text) ?? 1;
final maxWidth = int.tryParse(_maxWidthController.text);
final quality = int.tryParse(_qualityController.text);
final List<String>? result = await ImagePicker2.pickMedia(
limit: limit,
type: _selectedType,
maxWidth: maxWidth,
quality: quality,
);
if (!mounted) return;
if (result != null) {
setState(() {
_mediaPaths = result;
_isLimitReached = _mediaPaths.length == limit;
});
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.deepPurple,
brightness: Brightness.light,
),
home: Scaffold(
appBar: AppBar(
title: const Text('ImagePicker2 Pro'),
centerTitle: true,
elevation: 4,
shadowColor: Colors.black26,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Row(
children: [
const Icon(Icons.settings, color: Colors.deepPurple),
const SizedBox(width: 12),
const Expanded(
child: Text(
'Settings',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
if (_isLimitReached)
const Chip(
label: Text('LIMIT REACHED'),
backgroundColor: Colors.redAccent,
labelStyle: TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
],
),
const Divider(height: 30),
Row(
children: [
const Expanded(
child: Text(
'Media Type:',
style: TextStyle(fontWeight: FontWeight.w600),
),
),
DropdownButton<PickerMediaType>(
value: _selectedType,
onChanged: (PickerMediaType? newValue) {
if (newValue != null)
setState(() => _selectedType = newValue);
},
items: PickerMediaType.values.map((type) {
return DropdownMenuItem(
value: type,
child: Text(type.name.toUpperCase()),
);
}).toList(),
),
],
),
const SizedBox(height: 12),
Row(
children: [
const Expanded(
child: Text(
'Selection Limit:',
style: TextStyle(fontWeight: FontWeight.w600),
),
),
SizedBox(
width: 60,
child: TextField(
controller: _limitController,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
decoration: const InputDecoration(isDense: true),
),
),
],
),
if (_selectedType == PickerMediaType.image) ...[
const SizedBox(height: 12),
Row(
children: [
const Expanded(
child: Text(
'Max Width (px):',
style: TextStyle(fontWeight: FontWeight.w600),
),
),
SizedBox(
width: 80,
child: TextField(
controller: _maxWidthController,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
decoration: const InputDecoration(
isDense: true,
),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
const Expanded(
child: Text(
'Compression Quality:',
style: TextStyle(fontWeight: FontWeight.w600),
),
),
SizedBox(
width: 60,
child: TextField(
controller: _qualityController,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
decoration: const InputDecoration(
isDense: true,
),
),
),
],
),
],
],
),
),
),
const SizedBox(height: 20),
ElevatedButton.icon(
onPressed: _pickMedia,
icon: const Icon(Icons.add_photo_alternate),
label: Text(
'Open Pro Picker (${_selectedType.name.toUpperCase()})',
),
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 60),
backgroundColor: Colors.deepPurple,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 6,
),
),
const SizedBox(height: 20),
Expanded(
child: _mediaPaths.isEmpty
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.photo_library_outlined,
size: 64,
color: Colors.grey,
),
SizedBox(height: 16),
Text(
'No media selected yet',
style: TextStyle(
color: Colors.grey,
fontSize: 16,
),
),
],
),
)
: GridView.builder(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 0.8,
),
itemCount: _mediaPaths.length,
itemBuilder: (context, index) {
final path = _mediaPaths[index];
final isVideo =
path.endsWith('.mp4') ||
path.endsWith('.mov') ||
path.endsWith('.m4v');
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: Column(
children: [
Expanded(
child: ClipRRect(
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12),
),
child: isVideo
? Container(
width: double.infinity,
color: Colors.black87,
child: const Center(
child: Icon(
Icons.movie,
color: Colors.white,
size: 32,
),
),
)
: Image.file(
File(path),
width: double.infinity,
fit: BoxFit.cover,
),
),
),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
vertical: 4,
),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(12),
),
),
child: Text(
'${(File(path).lengthSync() / 1024).toStringAsFixed(0)} KB',
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
},
),
),
],
),
),
),
);
}
}