flutter_bottom_sheet_pickers 0.1.4
flutter_bottom_sheet_pickers: ^0.1.4 copied to clipboard
Customizable Flutter bottom sheet pickers for single, multiple, searchable, lazy loaded, cascade, date, date range, and year-month selection.
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_bottom_sheet_pickers/flutter_bottom_sheet_pickers.dart';
void main() {
runApp(const PickerExampleApp());
}
class PickerExampleApp extends StatefulWidget {
const PickerExampleApp({super.key});
@override
State<PickerExampleApp> createState() => _PickerExampleAppState();
}
class _PickerExampleAppState extends State<PickerExampleApp> {
Locale _locale = const Locale("en");
@override
void initState() {
super.initState();
_applyPickerLocalizations(_locale);
}
@override
void dispose() {
BottomSheetPickers.clearLocalizations();
super.dispose();
}
void _applyPickerLocalizations(Locale locale) {
BottomSheetPickers.setLocalizations(
localizations: BottomPickerLocalizations.byLocale(locale));
}
void _toggleLocale() {
final nextLocale =
_locale.languageCode == "en" ? const Locale("zh") : const Locale("en");
_applyPickerLocalizations(nextLocale);
setState(() => _locale = nextLocale);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Bottom Pickers",
debugShowCheckedModeBanner: false,
locale: _locale,
supportedLocales: const [
Locale("en"),
Locale("zh"),
],
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1677FF)),
useMaterial3: true,
),
home: PickerExampleHome(
locale: _locale,
onToggleLocale: _toggleLocale,
),
);
}
}
class PickerExampleHome extends StatefulWidget {
final Locale locale;
final VoidCallback onToggleLocale;
const PickerExampleHome({
super.key,
required this.locale,
required this.onToggleLocale,
});
@override
State<PickerExampleHome> createState() => _PickerExampleHomeState();
}
class _PickerExampleHomeState extends State<PickerExampleHome> {
String _result = "No selection";
static const BottomPickerTheme _theme = BottomPickerTheme(
primaryColor: Color(0xFF1677FF),
buttonBorderRadius: BorderRadius.all(Radius.circular(16)),
);
final List<CascadeOption> _locations = const [
CascadeOption(
id: "province_a",
label: "Province A",
children: [
CascadeOption(
id: "city_a",
label: "City A",
children: [
CascadeOption(id: "town_a", label: "Town A"),
CascadeOption(id: "town_b", label: "Town B"),
],
),
CascadeOption(id: "city_b", label: "City B"),
],
),
CascadeOption(
id: "province_b",
label: "Province B",
children: [
CascadeOption(id: "city_c", label: "City C"),
],
),
];
final List<String> _stores = const [
"PVS Store 001",
"PVS Store 002",
"FS Store 001",
"FS Store 002",
"KIOSK Store 001",
"KIOSK Store 002",
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Flutter Bottom Pickers"),
actions: [
TextButton(
onPressed: widget.onToggleLocale,
child: Text(widget.locale.languageCode == "en" ? "δΈζ" : "EN"),
),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
Text(_result, style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 24),
FilledButton(
onPressed: _showSinglePicker,
child: const Text("Show single picker"),
),
const SizedBox(height: 12),
FilledButton(
onPressed: _showMultiplePicker,
child: const Text("Show multiple picker"),
),
const SizedBox(height: 12),
FilledButton(
onPressed: _showCascadePicker,
child: const Text("Show cascade picker"),
),
const SizedBox(height: 12),
FilledButton(
onPressed: _showMultipleCascadePicker,
child: const Text("Show multiple cascade picker"),
),
const SizedBox(height: 12),
FilledButton(
onPressed: _showLazyPicker,
child: const Text("Show lazy picker"),
),
const SizedBox(height: 12),
FilledButton(
onPressed: _showLocalFilterPicker,
child: const Text("Show local filter picker"),
),
const SizedBox(height: 12),
FilledButton(
onPressed: _showRemoteFilterPicker,
child: const Text("Show remote filter picker"),
),
const SizedBox(height: 12),
FilledButton(
onPressed: _showCalendarPicker,
child: const Text("Show calendar picker"),
),
const SizedBox(height: 12),
FilledButton(
onPressed: _showCalendarRangePicker,
child: const Text("Show calendar range picker"),
),
const SizedBox(height: 12),
FilledButton(
onPressed: _showYearMonthPicker,
child: const Text("Show year month picker"),
),
],
),
);
}
Future<void> _showSinglePicker() async {
final selected = await BottomSheetPickers.single<String>(
context,
title: "Choose a fruit",
themeData: _theme,
).options(["Apple", "Orange", "Banana"], initialValue: "Apple").show();
_updateResult(selected);
}
Future<void> _showMultiplePicker() async {
final selected = await BottomSheetPickers.multiple<String>(
context,
title: "Choose tags",
themeData: _theme,
).options(["New", "Popular", "Recommended", "Archived"],
initialValue: ["New"]).show();
_updateResult(selected);
}
Future<void> _showCascadePicker() async {
final selected = await BottomSheetPickers.cascade(
context,
title: "Choose location",
themeData: _theme,
)
.options(_locations)
.initialValue(CascadeSelection.byIds("province_a", "city_a", "town_a"))
.cascadeAllItemSupported()
.show();
if (selected is CascadeSelection) {
_updateResult(selected.path.map((item) => item.label).join(" / "));
}
}
Future<void> _showMultipleCascadePicker() async {
final selected = await BottomSheetPickers.cascade(
context,
title: "Choose locations",
themeData: _theme,
).options(_locations).multiple().initialValues(
[CascadeSelection.byIds("province_a", "city_a", "town_a")]).show();
_updateResult(selected
?.map((item) => item.path.map((option) => option.label).join(" / "))
.join(", "));
}
Future<void> _showLazyPicker() async {
final selected = await BottomSheetPickers.single<String>(
context,
title: "Choose remote item",
themeData: _theme,
).lazyLoad(
lazyRequestFuture: (params) async {
await Future<void>.delayed(const Duration(milliseconds: 300));
final int pageIndex = params["page_index"] as int? ?? 1;
return List<String>.generate(
10, (index) => "Item ${(pageIndex - 1) * 10 + index + 1}");
},
).show();
_updateResult(selected);
}
Future<void> _showLocalFilterPicker() async {
final selected = await BottomSheetPickers.multiple<String>(
context,
title: "Choose stores",
themeData: _theme,
)
.options(_stores)
.searchSupported(placeholder: "Search store")
.withFilterSupported(
PickerFilter<String, String>.local(
options: const [
PickerFilterOption(value: null, label: "All"),
PickerFilterOption(value: "PVS", label: "PVS"),
PickerFilterOption(value: "FS", label: "FS"),
PickerFilterOption(value: "KIOSK", label: "KIOSK"),
],
predicate: (option, filter) =>
filter == null || option.startsWith(filter),
),
)
.checkbox()
.show();
_updateResult(selected);
}
Future<void> _showRemoteFilterPicker() async {
final selected = await BottomSheetPickers.single<String>(
context,
title: "Choose remote store",
themeData: _theme,
)
.searchSupported(placeholder: "Search store")
.withFilterSupported(
PickerFilter<String, String>.remote(
options: const [
PickerFilterOption(value: null, label: "All"),
PickerFilterOption(value: "PVS", label: "PVS"),
PickerFilterOption(value: "FS", label: "FS"),
PickerFilterOption(value: "KIOSK", label: "KIOSK"),
],
parameterBuilder: (filter) => {
if (filter != null) "store_type": filter,
},
),
)
.lazyLoad(
lazyRequestFuture: (params) async {
await Future<void>.delayed(const Duration(milliseconds: 300));
final int pageIndex = params["page_index"] as int? ?? 1;
final int pageSize = params["page_size"] as int? ?? 50;
final String? keyword = params["keyword"] as String?;
final String? filter = params["store_type"] as String?;
final data = _stores.where((store) {
final matchesFilter = filter == null || store.startsWith(filter);
final matchesKeyword = keyword == null ||
keyword.isEmpty ||
store.toLowerCase().contains(keyword.toLowerCase());
return matchesFilter && matchesKeyword;
}).toList();
final start = (pageIndex - 1) * pageSize;
if (start >= data.length) {
return <String>[];
}
final end =
start + pageSize > data.length ? data.length : start + pageSize;
return data.sublist(start, end);
},
).show();
_updateResult(selected);
}
Future<void> _showCalendarPicker() async {
final selected = await BottomSheetPickers.calendar(
context,
title: "Choose date",
calendarType: CalendarType.buddhist,
initialDate: DateTime.now(),
firstDate: DateTime(2020, 1, 1),
lastDate: DateTime(2030, 12, 31),
themeData: _theme,
).show();
_updateResult(selected);
}
Future<void> _showCalendarRangePicker() async {
final selected = await BottomSheetPickers.dateRange(
context,
title: "Choose date range",
calendarType: CalendarType.islamic,
initialDateRange: DateTimeRange(
start: DateTime.now(),
end: DateTime.now().add(const Duration(days: 3)),
),
firstDate: DateTime(2020, 1, 1),
lastDate: DateTime(2030, 12, 31),
themeData: _theme,
).show();
_updateResult(selected);
}
Future<void> _showYearMonthPicker() async {
final selected = await BottomSheetPickers.yearMonth(
context,
title: "Choose year month",
initialYearMonthRange: YearMonthRange(
start: YearMonth.fromDateTime(DateTime.now()),
end: YearMonth.fromDateTime(
DateTime.now().add(const Duration(days: 90))),
),
firstYearMonth: const YearMonth(2020, 1),
lastYearMonth: const YearMonth(2030, 12),
isRange: true,
themeData: _theme,
).show();
_updateResult(selected);
}
void _updateResult(Object? value) {
if (!mounted) {
return;
}
setState(() => _result = value?.toString() ?? "Cancelled");
}
}