time_ring_picker 0.0.2 copy "time_ring_picker: ^0.0.2" to clipboard
time_ring_picker: ^0.0.2 copied to clipboard

A highly customizable circular clock-style time range picker for selecting start/end times (e.g. sleep schedules) with overnight range support.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:time_ring_picker/time_ring_picker.dart';

void main() => runApp(const ExampleApp());

/// Demo app for `time_ring_picker` — shows a default-styled picker and a
/// fully customized one side by side.
class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'time_ring_picker example',
      theme: ThemeData.dark(useMaterial3: true),
      home: const ExampleHomePage(),
    );
  }
}

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

  @override
  State<ExampleHomePage> createState() => _ExampleHomePageState();
}

class _ExampleHomePageState extends State<ExampleHomePage> {
  TimeOfDay _start = const TimeOfDay(hour: 21, minute: 0);
  TimeOfDay _end = const TimeOfDay(hour: 6, minute: 0);
  Duration _duration = const Duration(hours: 9);

  // Which half the "Day & Night style" ring below is zoomed into.
  // start=21:00 (PM) and end=06:00 (AM) each only show up -- and can
  // only be dragged -- while their own half is selected here.
  DayHalf _dayNightHalf = DayHalf.pm;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('time_ring_picker')),
      body: SafeArea(
        child: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            Text(
              'Default style',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),
            Center(
              child: TimeRingPicker(
                initialStartTime: _start,
                initialEndTime: _end,
                onTimeChanged: (start, end, duration) {
                  setState(() {
                    _start = start;
                    _end = end;
                    _duration = duration;
                  });
                },
              ),
            ),
            const SizedBox(height: 16),
            Text(
              'Selected: ${_start.format(context)} - ${_end.format(context)} '
              '(${_duration.inHours}h ${_duration.inMinutes % 60}m)',
              textAlign: TextAlign.center,
              style: Theme.of(context).textTheme.bodyLarge,
            ),
            const SizedBox(height: 32),
            Text(
              'Fully customized style, 24h format',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),
            Center(
              child: TimeRingPicker(
                initialStartTime: const TimeOfDay(hour: 22, minute: 30),
                initialEndTime: const TimeOfDay(hour: 5, minute: 45),
                timeFormat: TimeFormat.h24,
                minuteInterval: 15,
                style: const TimeRingPickerStyle(
                  size: 220,
                  borderWidth: 10,
                  backgroundColor: Color(0xFF1B1D28),
                  // Gradient on the selected arc only -- the unselected
                  // track stays a plain flat backgroundColor, so which
                  // part is actually selected reads clearly at a
                  // glance instead of the whole ring being one
                  // indistinguishable bright gradient.
                  gradientColors: [Color(0xFFFF7E5F), Color(0xFFFEB47B)],
                  handleColor: Colors.white,
                  handleSize: 20,
                  handleBorderColor: Color(0xFFFF7E5F),
                  durationTextStyle: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  ),
                  startEndTimeTextStyle: TextStyle(
                    fontSize: 13,
                    color: Colors.white70,
                  ),
                  showTicks: false,
                  numberPosition: NumberPosition.inside,
                  startHandleIcon: Icons.bedtime,
                  endHandleIcon: Icons.wb_sunny,
                  handleIconColor: Color(0xFFFF7E5F),
                  arcGlowBlurRadius: 10,
                  arcGlowSpread: 1,
                  arcGlowOpacity: 0.55,
                ),
                onTimeChanged: (start, end, duration) {},
              ),
            ),
            const SizedBox(height: 32),
            Text(
              'Day & Night style',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 12),
            _AmPmHeaderTabs(
              isAm: _dayNightHalf == DayHalf.am,
              onChanged: (isAm) => setState(
                () => _dayNightHalf = isAm ? DayHalf.am : DayHalf.pm,
              ),
            ),
            const SizedBox(height: 16),
            Center(
              child: TimeRingPicker(
                initialStartTime: const TimeOfDay(hour: 21, minute: 0),
                initialEndTime: const TimeOfDay(hour: 6, minute: 0),
                visibleHalf: _dayNightHalf,
                style: const TimeRingPickerStyle(
                  backgroundColor: Color(0xFF141A2E),
                  // gradientColors[0] paints at the *start* handle
                  // (moon/night), [1]/[2] toward the *end* handle
                  // (sun/day) -- gold fading through orange to a deep
                  // red-orange, gradient on the selected arc only (the
                  // unselected track stays a plain flat backgroundColor).
                  gradientColors: [
                    Color(0xFFFFCB4D),
                    Color(0xFFFF8B3D),
                    Color(0xFFE85B3A),
                  ],
                  arcGlowBlurRadius: 12,
                  arcGlowSpread: 2,
                  arcGlowOpacity: 0.5,
                  handleColor: Colors.white,
                  handleBorderColor: Color(0xFFFF8B3D),
                  numberStyle: TextStyle(
                    fontSize: 13,
                    fontWeight: FontWeight.w500,
                    color: Colors.white70,
                  ),
                  // The moon/sun handle icons and the gradient sweep
                  // already show which end is night vs day, so a plain
                  // classic clock face (no repeated-digit caption, no
                  // extra half-tint) reads cleanest here.
                  showNumberPeriodCaption: false,
                  showHalfDayShading: false,
                  showTicks: false,
                  startHandleIcon: Icons.nightlight_round,
                  endHandleIcon: Icons.wb_sunny_rounded,
                  handleIconColor: Color(0xFF1B2A4A),
                  durationTextStyle: TextStyle(
                    fontSize: 32,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  ),
                  numberPosition: NumberPosition.inside,
                  startEndTimeTextStyle: TextStyle(
                    fontSize: 14,
                    color: Colors.white70,
                  ),
                ),
                onTimeChanged: (start, end, duration) {},
              ),
            ),
          ],
        ),
      ),
    );
  }
}

/// An `AM` / `PM` segmented header, styled to match the "Day & Night
/// style" ring below it, that drives which half that ring's
/// [TimeRingPicker.visibleHalf] is zoomed into.
class _AmPmHeaderTabs extends StatelessWidget {
  const _AmPmHeaderTabs({required this.isAm, required this.onChanged});

  final bool isAm;
  final ValueChanged<bool> onChanged;

  static const _activeColor = Color(0xFFFFC371);
  static const _inactiveColor = Colors.white38;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 220,
      padding: const EdgeInsets.symmetric(vertical: 12),
      decoration: BoxDecoration(
        color: const Color(0xFF1B2233),
        borderRadius: BorderRadius.circular(14),
      ),
      child: Row(
        children: [
          Expanded(child: _tab('AM', isAm, () => onChanged(true))),
          Container(width: 1, height: 20, color: Colors.white24),
          Expanded(child: _tab('PM', !isAm, () => onChanged(false))),
        ],
      ),
    );
  }

  Widget _tab(String label, bool selected, VoidCallback onTap) {
    return InkWell(
      onTap: onTap,
      child: Text(
        label,
        textAlign: TextAlign.center,
        style: TextStyle(
          color: selected ? _activeColor : _inactiveColor,
          fontWeight: selected ? FontWeight.bold : FontWeight.w500,
        ),
      ),
    );
  }
}
1
likes
160
points
113
downloads
screenshot

Documentation

API reference

Publisher

verified publisherdashstack.tech

Weekly Downloads

A highly customizable circular clock-style time range picker for selecting start/end times (e.g. sleep schedules) with overnight range support.

Repository (GitHub)
View/report issues

Topics

#picker #clock #time #slider #ui

License

MIT (license)

Dependencies

flutter

More

Packages that depend on time_ring_picker