cupertino_date_selector 0.0.5 copy "cupertino_date_selector: ^0.0.5" to clipboard
cupertino_date_selector: ^0.0.5 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';

import 'recipes_screen.dart';

/// Shared demo theme: custom sheet + wheel [CupertinoDateSelectorThemeData.backgroundColor].
const CupertinoDateSelectorThemeData kExampleCupertinoPickerTheme =
    CupertinoDateSelectorThemeData(
      backgroundColor: Color(0xFFE8EEF5),
      itemTextStyle: TextStyle(
        fontSize: 20,
        fontWeight: FontWeight.w500,
        color: Color(0xFF181818),
      ),
    );

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

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

  static const Color _seed = Color(0xFF4F46E5);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'cupertino_date_selector example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: _seed,
          brightness: Brightness.light,
        ),
        useMaterial3: true,
        appBarTheme: const AppBarTheme(
          centerTitle: false,
          scrolledUnderElevation: 0,
        ),
        cardTheme: CardThemeData(
          elevation: 0,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
          clipBehavior: Clip.antiAlias,
        ),
      ),
      home: const DemoHomePage(),
    );
  }
}

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

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

class _DemoHomePageState extends State<DemoHomePage> {
  String _lastResult = 'Tap an item below to run a demo.';

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

  static const EdgeInsets _pagePadding = EdgeInsets.fromLTRB(20, 0, 20, 32);

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final cs = theme.colorScheme;

    return Scaffold(
      backgroundColor: const Color(0xFFF1F5F9),
      body: CustomScrollView(
        physics: const BouncingScrollPhysics(
          parent: AlwaysScrollableScrollPhysics(),
        ),
        slivers: [
          SliverAppBar.large(
            pinned: true,
            backgroundColor: cs.surface,
            surfaceTintColor: cs.surfaceTint,
            title: const Text('Date selector'),
            flexibleSpace: FlexibleSpaceBar(
              background: DecoratedBox(
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    begin: Alignment.topLeft,
                    end: Alignment.bottomRight,
                    colors: [
                      cs.primaryContainer.withValues(alpha: 0.35),
                      cs.surface,
                    ],
                  ),
                ),
              ),
            ),
          ),
          SliverPadding(
            padding: _pagePadding,
            sliver: SliverList(
              delegate: SliverChildListDelegate([
                Text(
                  'Cupertino-style wheels for Flutter — modals, inline pages, and custom chrome.',
                  style: theme.textTheme.bodyMedium?.copyWith(
                    color: cs.onSurfaceVariant,
                    height: 1.45,
                  ),
                ),
                const SizedBox(height: 20),
                _ResultPanel(result: _lastResult, colorScheme: cs),
                const SizedBox(height: 28),
                _FeaturedRecipesCard(
                  onOpen: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        builder: (_) => const RecipesScreen(),
                      ),
                    );
                  },
                  colorScheme: cs,
                ),
                const SizedBox(height: 12),
                _SectionTitle(
                  icon: Icons.layers_outlined,
                  label: 'Modal bottom sheets',
                  colorScheme: cs,
                ),
                _DemoTile(
                  icon: Icons.list_alt_rounded,
                  title: 'Custom items',
                  subtitle: 'Indexed list of widgets',
                  colorScheme: cs,
                  onTap: () async {
                    await CupertinoDateSelector.items(
                      items: const [Text('One'), Text('Two'), Text('Three')],
                      selectedItemIndex: 0,
                      theme: kExampleCupertinoPickerTheme.copyWith(
                        defaultButtonText: 'Done',
                      ),
                      onSubmit: (index) => _setResult('Items → index: $index'),
                    ).show(context);
                  },
                ),
                _DemoTile(
                  icon: Icons.calendar_today_rounded,
                  title: 'Date',
                  subtitle: 'Day / month / year',
                  colorScheme: cs,
                  onTap: () async {
                    final now = DateTime.now();
                    await CupertinoDateSelector.date(
                      initialDateTime: now,
                      minDateTime: now.subtract(const Duration(days: 365)),
                      maxDateTime: now.add(const Duration(days: 365)),
                      theme: kExampleCupertinoPickerTheme,
                      onSubmit: (d) => _setResult('Date → $d'),
                    ).show(context);
                  },
                ),
                _DemoTile(
                  icon: Icons.schedule_rounded,
                  title: 'Time',
                  subtitle: '5-minute steps, 24h',
                  colorScheme: cs,
                  onTap: () async {
                    await CupertinoDateSelector.time(
                      initialDateTime: DateTime.now(),
                      minuteInterval: 5,
                      use24hFormat: true,
                      theme: kExampleCupertinoPickerTheme,
                      onSubmit: (d) => _setResult('Time → $d'),
                    ).show(context);
                  },
                ),
                _DemoTile(
                  icon: Icons.event_rounded,
                  title: 'Date and time',
                  subtitle: 'Combined picker',
                  colorScheme: cs,
                  onTap: () 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,
                      theme: kExampleCupertinoPickerTheme,
                      onSubmit: (d) => _setResult('DateTime → $d'),
                    ).show(context);
                  },
                ),
                _DemoTile(
                  icon: Icons.date_range_rounded,
                  title: 'Year',
                  colorScheme: cs,
                  onTap: () async {
                    final now = DateTime.now();
                    await CupertinoDateSelector.year(
                      initialDateTime: now,
                      minDateTime: DateTime(now.year - 10),
                      maxDateTime: DateTime(now.year + 10),
                      theme: kExampleCupertinoPickerTheme,
                      onSubmit: (d) => _setResult('Year → $d'),
                    ).show(context);
                  },
                ),
                _DemoTile(
                  icon: Icons.calendar_month_rounded,
                  title: 'Month & year',
                  colorScheme: cs,
                  onTap: () async {
                    final now = DateTime.now();
                    await CupertinoDateSelector.monthYear(
                      initialDateTime: now,
                      minDateTime: DateTime(now.year - 2),
                      maxDateTime: DateTime(now.year + 2, 12),
                      theme: kExampleCupertinoPickerTheme,
                      onSubmit: (d) => _setResult('MonthYear → $d'),
                    ).show(context);
                  },
                ),
                _DemoTile(
                  icon: Icons.view_week_rounded,
                  title: 'Month only',
                  colorScheme: cs,
                  onTap: () async {
                    await CupertinoDateSelector.month(
                      initialDateTime: DateTime.now(),
                      theme: kExampleCupertinoPickerTheme,
                      onSubmit: (d) => _setResult('Month → ${d.month}'),
                    ).show(context);
                  },
                ),
                _DemoTile(
                  icon: Icons.timer_outlined,
                  title: 'Duration',
                  subtitle: 'Timer picker',
                  colorScheme: cs,
                  onTap: () async {
                    await CupertinoDateSelector.duration(
                      initialDuration: const Duration(hours: 1, minutes: 30),
                      theme: kExampleCupertinoPickerTheme,
                      onSubmit: (d) => _setResult('Duration → $d'),
                    ).show(context);
                  },
                ),
                _SectionTitle(
                  icon: Icons.palette_outlined,
                  label: 'Design & layout',
                  colorScheme: cs,
                ),
                _DemoTile(
                  icon: Icons.brush_rounded,
                  title: 'Month & year (custom sheet)',
                  subtitle: 'Hand-built bottom sheet UI',
                  colorScheme: cs,
                  onTap: () => _showDesignMonthYearSelector(context),
                ),
                _SectionTitle(
                  icon: Icons.phone_android_rounded,
                  label: 'Full-screen inline demos',
                  colorScheme: cs,
                ),
                _DemoTile(
                  icon: Icons.open_in_full_rounded,
                  title: 'Month & year widget',
                  subtitle: 'Navigate',
                  colorScheme: cs,
                  trailing: true,
                  onTap: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        builder: (_) => const MonthYearWidgetScreen(),
                      ),
                    );
                  },
                ),
                _DemoTile(
                  icon: Icons.open_in_full_rounded,
                  title: 'Items widget',
                  colorScheme: cs,
                  trailing: true,
                  onTap: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        builder: (_) => const ItemsWidgetScreen(),
                      ),
                    );
                  },
                ),
                _DemoTile(
                  icon: Icons.open_in_full_rounded,
                  title: 'Date widget',
                  colorScheme: cs,
                  trailing: true,
                  onTap: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        builder: (_) => const DateWidgetScreen(),
                      ),
                    );
                  },
                ),
                _DemoTile(
                  icon: Icons.open_in_full_rounded,
                  title: 'Time widget',
                  colorScheme: cs,
                  trailing: true,
                  onTap: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        builder: (_) => const TimeWidgetScreen(),
                      ),
                    );
                  },
                ),
                _DemoTile(
                  icon: Icons.open_in_full_rounded,
                  title: 'DateTime widget',
                  colorScheme: cs,
                  trailing: true,
                  onTap: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        builder: (_) => const DateTimeWidgetScreen(),
                      ),
                    );
                  },
                ),
                _DemoTile(
                  icon: Icons.open_in_full_rounded,
                  title: 'Year widget',
                  colorScheme: cs,
                  trailing: true,
                  onTap: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        builder: (_) => const YearWidgetScreen(),
                      ),
                    );
                  },
                ),
                _DemoTile(
                  icon: Icons.open_in_full_rounded,
                  title: 'Month widget',
                  colorScheme: cs,
                  trailing: true,
                  onTap: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        builder: (_) => const MonthWidgetScreen(),
                      ),
                    );
                  },
                ),
                _DemoTile(
                  icon: Icons.open_in_full_rounded,
                  title: 'Duration widget',
                  colorScheme: cs,
                  trailing: true,
                  onTap: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        builder: (_) => const DurationWidgetScreen(),
                      ),
                    );
                  },
                ),
              ]),
            ),
          ),
        ],
      ),
    );
  }

  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 _ResultPanel extends StatelessWidget {
  const _ResultPanel({required this.result, required this.colorScheme});

  final String result;
  final ColorScheme colorScheme;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Material(
      color: colorScheme.surfaceContainerHighest,
      borderRadius: BorderRadius.circular(20),
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(
                  Icons.touch_app_rounded,
                  size: 22,
                  color: colorScheme.primary,
                ),
                const SizedBox(width: 10),
                Text(
                  'Last result',
                  style: theme.textTheme.labelLarge?.copyWith(
                    fontWeight: FontWeight.w700,
                    letterSpacing: 0.2,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            Text(
              result,
              style: theme.textTheme.bodyLarge?.copyWith(
                height: 1.45,
                color: colorScheme.onSurface,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _FeaturedRecipesCard extends StatelessWidget {
  const _FeaturedRecipesCard({
    required this.onOpen,
    required this.colorScheme,
  });

  final VoidCallback onOpen;
  final ColorScheme colorScheme;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Material(
      borderRadius: BorderRadius.circular(20),
      color: colorScheme.primary,
      child: InkWell(
        onTap: onOpen,
        borderRadius: BorderRadius.circular(20),
        child: Padding(
          padding: const EdgeInsets.all(20),
          child: Row(
            children: [
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: colorScheme.onPrimary.withValues(alpha: 0.15),
                  borderRadius: BorderRadius.circular(14),
                ),
                child: Icon(
                  Icons.auto_awesome_rounded,
                  color: colorScheme.onPrimary,
                  size: 28,
                ),
              ),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Recipes',
                      style: theme.textTheme.titleMedium?.copyWith(
                        color: colorScheme.onPrimary,
                        fontWeight: FontWeight.w700,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      'Picker-only body, modifiers & controller',
                      style: theme.textTheme.bodySmall?.copyWith(
                        color: colorScheme.onPrimary.withValues(alpha: 0.88),
                      ),
                    ),
                  ],
                ),
              ),
              Icon(
                Icons.arrow_forward_ios_rounded,
                size: 14,
                color: colorScheme.onPrimary.withValues(alpha: 0.85),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _SectionTitle extends StatelessWidget {
  const _SectionTitle({
    required this.icon,
    required this.label,
    required this.colorScheme,
  });

  final IconData icon;
  final String label;
  final ColorScheme colorScheme;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Padding(
      padding: const EdgeInsets.only(top: 8, bottom: 10),
      child: Row(
        children: [
          Icon(icon, size: 18, color: colorScheme.primary),
          const SizedBox(width: 8),
          Expanded(
            child: Text(
              label,
              style: theme.textTheme.labelLarge?.copyWith(
                fontWeight: FontWeight.w700,
                letterSpacing: 0.2,
                color: colorScheme.onSurfaceVariant,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _DemoTile extends StatelessWidget {
  const _DemoTile({
    required this.icon,
    required this.title,
    required this.colorScheme,
    required this.onTap,
    this.subtitle,
    this.trailing = false,
  });

  final IconData icon;
  final String title;
  final String? subtitle;
  final ColorScheme colorScheme;
  final VoidCallback onTap;
  final bool trailing;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Material(
        color: theme.colorScheme.surface,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(14),
          side: BorderSide(
            color: colorScheme.outlineVariant.withValues(alpha: 0.55),
          ),
        ),
        child: InkWell(
          onTap: onTap,
          borderRadius: BorderRadius.circular(14),
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
            child: Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: colorScheme.primaryContainer.withValues(alpha: 0.55),
                    borderRadius: BorderRadius.circular(10),
                  ),
                  child: Icon(
                    icon,
                    size: 22,
                    color: colorScheme.primary,
                  ),
                ),
                const SizedBox(width: 14),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        title,
                        style: theme.textTheme.titleSmall?.copyWith(
                          fontWeight: FontWeight.w600,
                        ),
                      ),
                      if (subtitle != null) ...[
                        const SizedBox(height: 2),
                        Text(
                          subtitle!,
                          style: theme.textTheme.bodySmall?.copyWith(
                            color: colorScheme.onSurfaceVariant,
                          ),
                        ),
                      ],
                    ],
                  ),
                ),
                if (trailing)
                  Icon(
                    Icons.chevron_right_rounded,
                    color: colorScheme.outline,
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

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: [
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
            child: Text(
              'Selected: ${selectedDate.month}/${selectedDate.year}',
              style: Theme.of(context).textTheme.titleMedium,
            ),
          ),

          Expanded(
            child: CupertinoDateSelectorWidget.monthYear(
              initialDateTime: selectedDate,

              minDateTime: DateTime(DateTime.now().year - 2, 1),
              maxDateTime: DateTime(DateTime.now().year + 100, 12),
              theme: kExampleCupertinoPickerTheme,
              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(
            enableHapticFeedback: true,
            theme: kExampleCupertinoPickerTheme,
            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
160
points
45
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

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

Repository (GitHub)
View/report issues

Topics

#cupertino #picker #date-picker #time-picker #modal

License

MIT (license)

Dependencies

flutter, intl

More

Packages that depend on cupertino_date_selector