Repository: flutter
Issue: RangeSlider should have an option to include a current value indicator
Labels: c: new feature,framework,f: material design,c: proposal,P3,team-design,triaged-design

I am working on using the RangeSlider to navigate a user selected range of WMS layers which can be animated to a map based on the current value which is between the range.  I have created a sample while trying to get this working that just includes the slider and hard coded date values.  In doing so I add a Positioned > GestureDetector which holds a draggable icon and the current date.  This almost works, however the current indicator i put on there starts positioned just to the right of the beginning (i can move it over by adding Left: -10) but the most troubling issue i am facing is that the indicator can be dragged past the timeline approximately the amount of the "padding" between the slider and the edge of the screen.  I can move the end range in to see that this amount that it goes past the allowable range is variable and decreased slightly as the end limit narrows the range.    

I am attaching an animated gif at the bottom that show this happening.  Notice it starts indented to the right, then slides way past the end, when i slide the limit in the ratio that it goes past gets less and less as i move the range limit in.

I have tried a lot of things to get this to work properly and i have a ton of debug print statements to try to figure out why this is happening and how to fix it.  I believe the problem may be due to the width of the RangeSlider not actually being used, but rather the entire screen.  I can't get it to tell me the width of the RangeSlider.  

Here is the code :

```
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'WMS Time Slider Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'WMS Time Slider Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[TimeSlider()],
        ),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

class TimeSlider extends StatefulWidget {
  const TimeSlider({Key? key}) : super(key: key);

  @override
  _TimeSliderState createState() => _TimeSliderState();
}

class _TimeSliderState extends State<TimeSlider> {
  static const int MIN_DATE = 3848; // July 15, 1980
  static const int MAX_DATE = 14974; // December 31, 2010
  final GlobalKey<ScaffoldState> _sliderKey = GlobalKey();
  int _startValue = MIN_DATE;
  int _endValue = MAX_DATE;
  int _currentValue = MIN_DATE;
  double _sliderWidth = 0;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
        builder: (BuildContext ctx, BoxConstraints constraints) {
      _sliderWidth = constraints.maxWidth;
      print("slider width: " + _sliderWidth.toString());
      print("Position: " + _getPosition().toString());
      return Stack(clipBehavior: Clip.none, children: [
        RangeSlider(
          key: _sliderKey,
          values: RangeValues(_startValue.toDouble(), _endValue.toDouble()),
          min: MIN_DATE.toDouble(),
          max: MAX_DATE.toDouble(),
          divisions: ((MAX_DATE - MIN_DATE) / 365)
              .round()
              .toInt(), // one division per day
          onChanged: (RangeValues values) {
            setState(() {
              _startValue = values.start.toInt();
              _endValue = values.end.toInt();

              if (_currentValue < _startValue) {
                _currentValue = _startValue;
              } else if (_currentValue > _endValue) {
                _currentValue = _endValue;
              }
            });
          },
          onChangeEnd: (RangeValues values) {
            setState(() {
              // _currentValue = values.start.toInt();
            });
            print(formatDate(_currentValue));
            // Update the WMS layer animation with the current value
            // ...
          },
        ),
        Positioned(
          top: 0,
          left: _getPosition(),
          child: GestureDetector(
            onHorizontalDragUpdate: (details) {
              print("dragging");
              setState(() {
                final range = MAX_DATE - MIN_DATE;
                print("got range");
                final dx = details.delta.dx;
                final position = _getPosition() + dx;
                print("got position");
                final DateTime startDate = DateTime(1970, 1, 1);
                // Calculate the number of days between the current date and the start date

                _currentValue = _getDate(position).difference(startDate).inDays;
                // ((position / MediaQuery.of(context).size.width) * range)
                //         .round() +
                //     MIN_DATE;
                print("start: " + formatDate(_startValue));
                print("Current: " + formatDate(_currentValue));
                print("End: " + formatDate(_endValue));

                // Prevent _currentValue from going outside the range
                if (_currentValue < _startValue) {
                  print("fixed less");
                  _currentValue = _startValue;
                  print("Current: " + formatDate(_currentValue));
                } else if (_currentValue > _endValue) {
                  _currentValue = _endValue;
                }
              });
            },
            child: Column(
              children: [
                Icon(Icons.circle, color: Colors.deepPurple),
                Text(
                  formatDate(_currentValue),
                  style: TextStyle(color: Colors.purple),
                ),
              ],
            ),
          ),
        ),
      ]);
    });
  }

  double _getPosition() {
    final date = DateTime.fromMillisecondsSinceEpoch(
        _currentValue * 24 * 60 * 60 * 1000);
    final min =
        DateTime.fromMillisecondsSinceEpoch(MIN_DATE * 24 * 60 * 60 * 1000);
    final max =
        DateTime.fromMillisecondsSinceEpoch(MAX_DATE * 24 * 60 * 60 * 1000)
            .subtract(Duration(days: 32));
    final double ratio =
        date.difference(min).inDays / max.difference(min).inDays;
    print("ratio: " + ratio.toString());
    // return ratio * MediaQuery.of(context).size.width;
    print("MAX_DATE: " + MAX_DATE.toString());
    print("MIN_DATE: " + MIN_DATE.toString());
    print("_currentValue: " + _currentValue.toString());

    final range = MAX_DATE - MIN_DATE;
    final value = _currentValue - MIN_DATE;
    final sliderWidth = (MediaQuery.of(context).size.width);
    print("range: " + range.toString());
    print("Width buffered: " +
        (MediaQuery.of(context).size.width - 64).toString());
    print("Width: " + MediaQuery.of(context).size.width.toString());
    double position = (value / range) * (sliderWidth);

    return position;
  }

  DateTime _getDate(double position) {
    final min =
        DateTime.fromMillisecondsSinceEpoch(MIN_DATE * 24 * 60 * 60 * 1000);
    final max =
        DateTime.fromMillisecondsSinceEpoch(MAX_DATE * 24 * 60 * 60 * 1000);
    final double ratio = position / (MediaQuery.of(context).size.width);
    final int days = (ratio * max.difference(min).inDays).round();
    final date = min.add(Duration(days: days));

    // Make sure the date doesn't go past the max date
    if (date.isAfter(max)) {
      return max;
    } else {
      return date;
    }
  }

  String formatDate(int daysSinceEpoch) {
    final dateTime = DateTime.fromMillisecondsSinceEpoch(
        daysSinceEpoch * 24 * 60 * 60 * 1000);
    return '${dateTime.year}-${_padZero(dateTime.month)}-${_padZero(dateTime.day)}';
  }

  String _padZero(int value) {
    return value.toString().padLeft(2, '0');
  }
}

```
![RangeSlider Sample](https://github.com/flutter/flutter/assets/1868821/bc50764f-818e-4221-ae7b-a384fda00ab5)