cupertino_date_selector 0.0.1 copy "cupertino_date_selector: ^0.0.1" to clipboard
cupertino_date_selector: ^0.0.1 copied to clipboard

Highly customizable Cupertino modal selector for items, dates, times, and durations.

example/lib/main.dart

import 'package:cupertino_date_selector/cupertino_date_selector.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const ExampleApp());
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'cupertino_date_selector example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const DemoHomePage(),
    );
  }
}

class DemoHomePage extends StatefulWidget {
  const DemoHomePage({super.key});

  @override
  State<DemoHomePage> createState() => _DemoHomePageState();
}

class _DemoHomePageState extends State<DemoHomePage> {
  String _lastResult = 'Tap a button to open a selector.';

  void _setResult(String text) {
    setState(() => _lastResult = text);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('cupertino_date_selector')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Text(_lastResult),
            ),
          ),
          const SizedBox(height: 8),
          _demoButton('Items', () async {
            await CupertinoDateSelector.items(
              items: const [Text('One'), Text('Two'), Text('Three')],
              selectedItemIndex: 0,
              theme: const CupertinoDateSelectorThemeData(
                defaultButtonText: 'Done',
              ),
              onSubmit: (index) => _setResult('Items → index: $index'),
            ).show(context);
          }),
          _demoButton('Date', () async {
            final now = DateTime.now();
            await CupertinoDateSelector.date(
              initialDateTime: now,
              minDateTime: now.subtract(const Duration(days: 365)),
              maxDateTime: now.add(const Duration(days: 365)),
              onSubmit: (d) => _setResult('Date → $d'),
            ).show(context);
          }),
          _demoButton('Time', () async {
            await CupertinoDateSelector.time(
              initialDateTime: DateTime.now(),
              minuteInterval: 5,
              use24hFormat: true,
              onSubmit: (d) => _setResult('Time → $d'),
            ).show(context);
          }),
          _demoButton('Date and Time', () async {
            final now = DateTime.now();
            await CupertinoDateSelector.dateTime(
              initialDateTime: now,
              minDateTime: now.subtract(const Duration(days: 7)),
              maxDateTime: now.add(const Duration(days: 30)),
              minuteInterval: 15,
              use24hFormat: false,
              onSubmit: (d) => _setResult('DateTime → $d'),
            ).show(context);
          }),
          _demoButton('Year', () async {
            final now = DateTime.now();
            await CupertinoDateSelector.year(
              initialDateTime: now,
              minDateTime: DateTime(now.year - 10),
              maxDateTime: DateTime(now.year + 10),
              onSubmit: (d) => _setResult('Year → $d'),
            ).show(context);
          }),
          _demoButton('Month & year', () async {
            final now = DateTime.now();
            await CupertinoDateSelector.monthYear(
              initialDateTime: now,
              minDateTime: DateTime(now.year - 2),
              maxDateTime: DateTime(now.year + 2, 12),
              onSubmit: (d) => _setResult('MonthYear → $d'),
            ).show(context);
          }),
          _demoButton(
            'Month & year (design example)',
            () => _showDesignMonthYearSelector(context),
          ),
          _demoButton('Month & year widget screen', () {
            Navigator.of(context).push(
              MaterialPageRoute<void>(
                builder: (_) => const MonthYearWidgetScreen(),
              ),
            );
          }),
          _demoButton('Items widget screen', () {
            Navigator.of(context).push(
              MaterialPageRoute<void>(
                builder: (_) => const ItemsWidgetScreen(),
              ),
            );
          }),
          _demoButton('Date widget screen', () {
            Navigator.of(context).push(
              MaterialPageRoute<void>(builder: (_) => const DateWidgetScreen()),
            );
          }),
          _demoButton('Time widget screen', () {
            Navigator.of(context).push(
              MaterialPageRoute<void>(builder: (_) => const TimeWidgetScreen()),
            );
          }),
          _demoButton('DateTime widget screen', () {
            Navigator.of(context).push(
              MaterialPageRoute<void>(
                builder: (_) => const DateTimeWidgetScreen(),
              ),
            );
          }),
          _demoButton('Year widget screen', () {
            Navigator.of(context).push(
              MaterialPageRoute<void>(builder: (_) => const YearWidgetScreen()),
            );
          }),
          _demoButton('Month widget screen', () {
            Navigator.of(context).push(
              MaterialPageRoute<void>(
                builder: (_) => const MonthWidgetScreen(),
              ),
            );
          }),
          _demoButton('Duration widget screen', () {
            Navigator.of(context).push(
              MaterialPageRoute<void>(
                builder: (_) => const DurationWidgetScreen(),
              ),
            );
          }),
          _demoButton('Month', () async {
            await CupertinoDateSelector.month(
              initialDateTime: DateTime.now(),
              onSubmit: (d) => _setResult('Month → ${d.month}'),
            ).show(context);
          }),
          _demoButton('Duration', () async {
            await CupertinoDateSelector.duration(
              initialDuration: const Duration(hours: 1, minutes: 30),
              onSubmit: (d) => _setResult('Duration → $d'),
            ).show(context);
          }),
        ],
      ),
    );
  }

  Widget _demoButton(String label, VoidCallback onPressed) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: SizedBox(
        width: double.infinity,
        child: FilledButton(onPressed: onPressed, child: Text(label)),
      ),
    );
  }

  Future<void> _showDesignMonthYearSelector(BuildContext context) async {
    final List<String> months = <String>[
      'January',
      'February',
      'March',
      'April',
      'May',
      'June',
      'July',
      'August',
      'September',
      'October',
      'November',
      'December',
    ];

    const int minYear = 2024;
    const int maxYear = 2028;
    final List<int> years = List<int>.generate(
      maxYear - minYear + 1,
      (index) => minYear + index,
    );

    int selectedMonth = 3;
    int selectedYear = 2026;
    final monthController = FixedExtentScrollController(
      initialItem: selectedMonth - 1,
    );
    final yearController = FixedExtentScrollController(
      initialItem: selectedYear - minYear,
    );

    final selected = await showModalBottomSheet<DateTime>(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.transparent,
      builder: (ctx) {
        return StatefulBuilder(
          builder: (context, setSheetState) {
            Future<void> moveBy(int delta) async {
              final int currentMonthIndex = selectedMonth - 1;
              final int currentYearIndex = selectedYear - minYear;
              final int totalIndex =
                  (currentYearIndex * 12) + currentMonthIndex + delta;
              final int clamped = totalIndex.clamp(0, (years.length * 12) - 1);
              final int nextYearIndex = clamped ~/ 12;
              final int nextMonthIndex = clamped % 12;

              setSheetState(() {
                selectedMonth = nextMonthIndex + 1;
                selectedYear = years[nextYearIndex];
              });

              await monthController.animateToItem(
                nextMonthIndex,
                duration: const Duration(milliseconds: 180),
                curve: Curves.easeOut,
              );
              await yearController.animateToItem(
                nextYearIndex,
                duration: const Duration(milliseconds: 180),
                curve: Curves.easeOut,
              );
            }

            return Container(
              height: 390,
              decoration: const BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
              ),
              child: Padding(
                padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Align(
                      alignment: Alignment.center,
                      child: Container(
                        width: 139,
                        height: 5,
                        margin: const EdgeInsets.only(bottom: 14),
                        decoration: BoxDecoration(
                          color: const Color(0xFFEAEBED),
                          borderRadius: BorderRadius.circular(100),
                        ),
                      ),
                    ),
                    const Text(
                      'Choose Month & Year',
                      style: TextStyle(
                        fontSize: 16,
                        height: 19 / 16,
                        fontWeight: FontWeight.w500,
                        color: Color(0xFF181818),
                      ),
                    ),
                    const SizedBox(height: 14),
                    Container(
                      height: 37,
                      padding: const EdgeInsets.symmetric(horizontal: 10),
                      decoration: BoxDecoration(
                        color: const Color(0xFFDFDFDF),
                        borderRadius: BorderRadius.circular(8),
                      ),
                      child: Row(
                        children: [
                          IconButton(
                            onPressed: () => moveBy(-1),
                            padding: EdgeInsets.zero,
                            constraints: const BoxConstraints.tightFor(
                              width: 24,
                              height: 24,
                            ),
                            icon: const Icon(
                              Icons.arrow_back,
                              color: Color(0xFF292D32),
                              size: 24,
                            ),
                          ),
                          const Spacer(),
                          Text(
                            '${months[selectedMonth - 1]} $selectedYear',
                            style: const TextStyle(
                              fontSize: 16,
                              height: 19 / 16,
                              fontWeight: FontWeight.w500,
                              color: Color(0xFF181818),
                            ),
                          ),
                          const Spacer(),
                          IconButton(
                            onPressed: () => moveBy(1),
                            padding: EdgeInsets.zero,
                            constraints: const BoxConstraints.tightFor(
                              width: 24,
                              height: 24,
                            ),
                            icon: const Icon(
                              Icons.arrow_forward,
                              color: Color(0xFF292D32),
                              size: 24,
                            ),
                          ),
                        ],
                      ),
                    ),
                    const SizedBox(height: 8),
                    Expanded(
                      child: Stack(
                        alignment: Alignment.center,
                        children: [
                          IgnorePointer(
                            child: Container(
                              height: 37,
                              margin: const EdgeInsets.symmetric(horizontal: 3),
                              decoration: BoxDecoration(
                                color: const Color(0xFFDFDFDF),
                                borderRadius: BorderRadius.circular(8),
                              ),
                            ),
                          ),
                          Row(
                            children: [
                              Expanded(
                                child: CupertinoPicker(
                                  itemExtent: 32,
                                  scrollController: monthController,
                                  onSelectedItemChanged: (index) {
                                    setSheetState(() {
                                      selectedMonth = index + 1;
                                    });
                                  },
                                  children: months
                                      .map(
                                        (month) => Center(
                                          child: Text(
                                            month,
                                            style: const TextStyle(
                                              fontSize: 16,
                                              fontWeight: FontWeight.w500,
                                            ),
                                          ),
                                        ),
                                      )
                                      .toList(),
                                ),
                              ),
                              Expanded(
                                child: CupertinoPicker(
                                  itemExtent: 32,
                                  scrollController: yearController,
                                  onSelectedItemChanged: (index) {
                                    setSheetState(() {
                                      selectedYear = years[index];
                                    });
                                  },
                                  children: years
                                      .map(
                                        (year) => Center(
                                          child: Text(
                                            year.toString(),
                                            style: const TextStyle(
                                              fontSize: 16,
                                              fontWeight: FontWeight.w500,
                                            ),
                                          ),
                                        ),
                                      )
                                      .toList(),
                                ),
                              ),
                            ],
                          ),
                        ],
                      ),
                    ),
                    SizedBox(
                      width: double.infinity,
                      child: FilledButton(
                        style: FilledButton.styleFrom(
                          backgroundColor: const Color(0xFF181818),
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(35),
                          ),
                          padding: const EdgeInsets.symmetric(vertical: 14),
                        ),
                        onPressed: () {
                          Navigator.of(
                            ctx,
                          ).pop(DateTime(selectedYear, selectedMonth));
                        },
                        child: const Text(
                          'Confirm',
                          style: TextStyle(
                            fontSize: 16,
                            height: 22 / 16,
                            fontWeight: FontWeight.w500,
                            color: Colors.white,
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            );
          },
        );
      },
    );

    if (selected != null) {
      _setResult('Design MonthYear → $selected');
    }
  }
}

class MonthYearWidgetScreen extends StatefulWidget {
  const MonthYearWidgetScreen({super.key});

  @override
  State<MonthYearWidgetScreen> createState() => _MonthYearWidgetScreenState();
}

class _MonthYearWidgetScreenState extends State<MonthYearWidgetScreen> {
  DateTime selectedDate = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Month & year widget')),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Selected: ${selectedDate.month}/${selectedDate.year}',
            style: Theme.of(context).textTheme.titleMedium,
          ),
          const SizedBox(height: 12),
          CupertinoDateSelectorWidget.monthYear(
            initialDateTime: selectedDate,
            minDateTime: DateTime(DateTime.now().year - 2, 1),
            maxDateTime: DateTime(DateTime.now().year + 100, 12),
            onChanged: (DateTime d) {
              setState(() => selectedDate = d);
            },
          ),
        ],
      ),
    );
  }
}

class ItemsWidgetScreen extends StatefulWidget {
  const ItemsWidgetScreen({super.key});

  @override
  State<ItemsWidgetScreen> createState() => _ItemsWidgetScreenState();
}

class _ItemsWidgetScreenState extends State<ItemsWidgetScreen> {
  int selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Items widget')),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Selected index: $selectedIndex',
            style: Theme.of(context).textTheme.titleMedium,
          ),
          const SizedBox(height: 12),
          CupertinoDateSelectorWidget.items(
            items: const [Text('One'), Text('Two'), Text('Three')],
            selectedItemIndex: selectedIndex,
            onChanged: (int index) {
              setState(() => selectedIndex = index);
            },
          ),
        ],
      ),
    );
  }
}

class DateWidgetScreen extends StatefulWidget {
  const DateWidgetScreen({super.key});

  @override
  State<DateWidgetScreen> createState() => _DateWidgetScreenState();
}

class _DateWidgetScreenState extends State<DateWidgetScreen> {
  DateTime selectedDate = DateTime.now();

  @override
  Widget build(BuildContext context) {
    final now = DateTime.now();
    return Scaffold(
      appBar: AppBar(title: const Text('Date widget')),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Selected: ${selectedDate.toLocal()}',
            style: Theme.of(context).textTheme.titleMedium,
          ),
          const SizedBox(height: 12),
          CupertinoDateSelectorWidget.date(
            initialDateTime: selectedDate,
            minDateTime: now.subtract(const Duration(days: 365)),
            maxDateTime: now.add(const Duration(days: 365)),
            onChanged: (DateTime d) {
              setState(() => selectedDate = d);
            },
          ),
        ],
      ),
    );
  }
}

class TimeWidgetScreen extends StatefulWidget {
  const TimeWidgetScreen({super.key});

  @override
  State<TimeWidgetScreen> createState() => _TimeWidgetScreenState();
}

class _TimeWidgetScreenState extends State<TimeWidgetScreen> {
  DateTime selectedTime = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Time widget')),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Selected: ${selectedTime.hour}:${selectedTime.minute.toString().padLeft(2, '0')}',
            style: Theme.of(context).textTheme.titleMedium,
          ),
          const SizedBox(height: 12),
          CupertinoDateSelectorWidget.time(
            initialDateTime: selectedTime,
            minuteInterval: 5,
            use24hFormat: true,
            onChanged: (DateTime d) {
              setState(() => selectedTime = d);
            },
            onSubmit: (DateTime d) {
              setState(() => selectedTime = d);
            },
            headerBuilder: (context, close) => Container(
              height: 20,
              width: double.infinity,
              decoration: BoxDecoration(color: Colors.red),
              child: Text('Header'),
            ),
          ),
        ],
      ),
    );
  }
}

class DateTimeWidgetScreen extends StatefulWidget {
  const DateTimeWidgetScreen({super.key});

  @override
  State<DateTimeWidgetScreen> createState() => _DateTimeWidgetScreenState();
}

class _DateTimeWidgetScreenState extends State<DateTimeWidgetScreen> {
  DateTime selectedDateTime = DateTime.now();

  @override
  Widget build(BuildContext context) {
    final now = DateTime.now();
    return Scaffold(
      appBar: AppBar(title: const Text('DateTime widget')),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Selected: $selectedDateTime',
            style: Theme.of(context).textTheme.titleMedium,
          ),
          const SizedBox(height: 12),
          CupertinoDateSelectorWidget.dateTime(
            initialDateTime: selectedDateTime,
            minDateTime: now.subtract(const Duration(days: 7)),
            maxDateTime: now.add(const Duration(days: 30)),
            minuteInterval: 15,
            onChanged: (DateTime d) {
              setState(() => selectedDateTime = d);
            },
          ),
        ],
      ),
    );
  }
}

class YearWidgetScreen extends StatefulWidget {
  const YearWidgetScreen({super.key});

  @override
  State<YearWidgetScreen> createState() => _YearWidgetScreenState();
}

class _YearWidgetScreenState extends State<YearWidgetScreen> {
  DateTime selectedDate = DateTime.now();

  @override
  Widget build(BuildContext context) {
    final now = DateTime.now();
    return Scaffold(
      appBar: AppBar(title: const Text('Year widget')),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Selected year: ${selectedDate.year}',
            style: Theme.of(context).textTheme.titleMedium,
          ),
          const SizedBox(height: 12),
          CupertinoDateSelectorWidget.year(
            initialDateTime: selectedDate,
            minDateTime: DateTime(now.year - 10),
            maxDateTime: DateTime(now.year + 10),
            onChanged: (DateTime d) {
              setState(() => selectedDate = d);
            },
          ),
        ],
      ),
    );
  }
}

class MonthWidgetScreen extends StatefulWidget {
  const MonthWidgetScreen({super.key});

  @override
  State<MonthWidgetScreen> createState() => _MonthWidgetScreenState();
}

class _MonthWidgetScreenState extends State<MonthWidgetScreen> {
  DateTime selectedDate = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Month widget')),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Selected month: ${selectedDate.month}',
            style: Theme.of(context).textTheme.titleMedium,
          ),
          const SizedBox(height: 12),
          CupertinoDateSelectorWidget.month(
            initialDateTime: selectedDate,
            onChanged: (DateTime d) {
              setState(() => selectedDate = d);
            },
          ),
        ],
      ),
    );
  }
}

class DurationWidgetScreen extends StatefulWidget {
  const DurationWidgetScreen({super.key});

  @override
  State<DurationWidgetScreen> createState() => _DurationWidgetScreenState();
}

class _DurationWidgetScreenState extends State<DurationWidgetScreen> {
  Duration selectedDuration = const Duration(hours: 1, minutes: 30);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Duration widget')),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Selected duration: $selectedDuration',
            style: Theme.of(context).textTheme.titleMedium,
          ),
          const SizedBox(height: 12),
          CupertinoDateSelectorWidget.duration(
            initialDuration: selectedDuration,
            onChanged: (Duration d) {
              setState(() => selectedDuration = d);
            },
          ),
        ],
      ),
    );
  }
}
1
likes
0
points
45
downloads

Publisher

unverified uploader

Weekly Downloads

Highly customizable Cupertino modal selector for items, dates, times, and durations.

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on cupertino_date_selector