file_picker_slider 0.1.0
file_picker_slider: ^0.1.0 copied to clipboard
Cross-platform Flutter file picker with standard input-style UI, optional previews, and mobile camera/image support.
example/lib/main.dart
import 'package:file_picker_slider/file_picker_slider.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ThemeMode _themeMode = ThemeMode.system;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'File Picker Slider',
themeMode: _themeMode,
theme: _buildTheme(Brightness.light),
darkTheme: _buildTheme(Brightness.dark),
home: _ExampleHome(
themeMode: _themeMode,
onToggleTheme: _toggleThemeMode,
),
);
}
ThemeData _buildTheme(Brightness brightness) {
final isDark = brightness == Brightness.dark;
final seed = isDark ? const Color(0xFF56C4D6) : const Color(0xFF0D9488);
final scheme = ColorScheme.fromSeed(
seedColor: seed,
brightness: brightness,
);
return ThemeData(
colorScheme: scheme,
useMaterial3: true,
scaffoldBackgroundColor:
isDark ? const Color(0xFF0B1720) : const Color(0xFFF4F7F5),
appBarTheme: AppBarTheme(
backgroundColor: Colors.transparent,
foregroundColor: scheme.onSurface,
elevation: 0,
),
chipTheme: ChipThemeData(
backgroundColor: scheme.surface,
side: BorderSide(color: scheme.outlineVariant),
),
extensions: <ThemeExtension<dynamic>>[
FilePickerSliderThemeData(
backgroundColor:
isDark ? const Color(0xFF10202A) : const Color(0xFFFFFFFF),
borderColor:
isDark ? const Color(0xFF1F3A45) : const Color(0xFFD1E6E3),
accentColor:
isDark ? const Color(0xFF56C4D6) : const Color(0xFF0D9488),
accentForegroundColor: Colors.white,
overlayColor: Colors.black.withOpacity(0.42),
fileChipColor:
isDark ? const Color(0xFF17303A) : const Color(0xFFE7F5F3),
fileChipForegroundColor:
isDark ? const Color(0xFFE5FBFF) : const Color(0xFF154B46),
indicatorColor:
isDark ? const Color(0xFF9AE8F4) : const Color(0xFF0F766E),
inactiveIndicatorColor:
isDark ? const Color(0xFF2A505B) : const Color(0xFFB7D7D3),
backgroundGradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isDark
? const <Color>[Color(0xFF10202A), Color(0xFF163241)]
: const <Color>[Color(0xFFFFFFFF), Color(0xFFE8F7F3)],
),
),
],
);
}
void _toggleThemeMode() {
setState(() {
_themeMode =
_themeMode == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark;
});
}
}
class _ExampleHome extends StatefulWidget {
const _ExampleHome({
required this.themeMode,
required this.onToggleTheme,
});
final ThemeMode themeMode;
final VoidCallback onToggleTheme;
@override
State<_ExampleHome> createState() => _ExampleHomeState();
}
class _ExampleHomeState extends State<_ExampleHome> {
List<AppFile> _selectedFiles = <AppFile>[];
List<AppFile> _customFiles = <AppFile>[];
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
final colors = theme.colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('File Picker Slider'),
actions: <Widget>[
IconButton(
onPressed: widget.onToggleTheme,
icon: Icon(
widget.themeMode == ThemeMode.dark
? Icons.light_mode_rounded
: Icons.dark_mode_rounded,
),
tooltip: 'Toggle theme',
),
],
),
body: SafeArea(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 840),
child: ListView(
padding: const EdgeInsets.all(20),
children: <Widget>[
Text(
'HTML-style file input',
style: textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w700,
color: colors.onSurface,
),
),
const SizedBox(height: 8),
Text(
'This default layout now behaves like a normal file input on web and mobile, while still giving you optional previews and theme control.',
style: textTheme.bodyLarge?.copyWith(
color: colors.onSurfaceVariant,
height: 1.45,
),
),
const SizedBox(height: 20),
FilePickerWidget(
pickButtonLabel: 'Select files',
placeholderWidget: Container(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
),
decoration: BoxDecoration(
color: colors.primaryContainer,
borderRadius: BorderRadius.circular(8),
),
child: Text(
'Tap to select files',
style: textTheme.bodyMedium?.copyWith(
color: colors.onPrimaryContainer,
),
),
),
title: 'Product gallery',
subtitle:
'Choose one or more files. On mobile you also get a camera shortcut when single image mode is used.',
allowMultiple: true,
height: 320,
onFilePicked: (files) {
setState(() {
_selectedFiles = files;
});
},
),
const SizedBox(height: 16),
Text(
_selectedFiles.isEmpty
? 'No files selected yet.'
: '${_selectedFiles.length} file(s) selected',
style: textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: colors.onSurface,
),
),
const SizedBox(height: 24),
Text(
'Custom widget trigger',
style: textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w700,
color: colors.onSurface,
),
),
const SizedBox(height: 8),
Text(
'Use customBuilder when you want your own widget but still want the same picker logic underneath.',
style: textTheme.bodyLarge?.copyWith(
color: colors.onSurfaceVariant,
height: 1.45,
),
),
const SizedBox(height: 16),
FilePickerWidget(
showPreview: false,
onFilePicked: (files) {
setState(() {
_customFiles = files;
});
},
customBuilder: (context, state) {
final label = state.hasFiles
? state.activeFile!.displayName
: 'Upload agreement';
final localTheme = Theme.of(context);
return DecoratedBox(
decoration: BoxDecoration(
color: localTheme.colorScheme.surface,
borderRadius: BorderRadius.circular(18),
border: Border.all(
color: localTheme.colorScheme.outlineVariant,
),
),
child: ListTile(
leading: const Icon(Icons.file_upload_outlined),
title: Text(label),
subtitle: Text(
state.hasFiles
? 'Tap to replace the selected file'
: 'Tap to open the picker',
),
trailing: Wrap(
spacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: <Widget>[
if (state.canOpenFullscreen)
IconButton(
onPressed: () => state.openFullscreen!(),
tooltip: 'View fullscreen',
icon: const Icon(Icons.fullscreen_rounded),
),
FilledButton(
onPressed: state.isBusy
? null
: () => state.openPicker(),
child: const Text('Browse'),
),
],
),
onTap: state.isBusy ? null : () => state.openPicker(),
),
);
},
),
if (_customFiles.isNotEmpty) ...<Widget>[
const SizedBox(height: 12),
Text(
'Custom picker selection: ${_customFiles.first.displayName}',
style: textTheme.bodyLarge?.copyWith(
color: colors.onSurface,
),
),
],
],
),
),
),
),
);
}
}