mirrored_repositories/FlutterHabitsApp/lib/presentation/sleep_onboarding/components
import 'dart:developer';

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:pavlok_test/presentation/sleep_onboarding/components/clock/slider_painter.dart';
import 'package:pavlok_test/utils/assets.dart';
import 'dart:ui' as ui;
import 'dart:io' as Io;
import 'dart:convert';
import 'base_painter.dart';
import 'utils.dart';

enum CircularSliderMode { singleHandler, doubleHandler }

enum SlidingState { none, endIsBiggerThanStart, endIsSmallerThanStart }

typedef SelectionChanged<T> = void Function(T a, T b, T c);

class CircularSliderPaint extends StatefulWidget {
  final CircularSliderMode mode;
  final int init;
  final int end;
  final int divisions;
  final int primarySectors;
  final int secondarySectors;
  final SelectionChanged<int> onSelectionChange;
  final SelectionChanged<int> onSelectionEnd;
  final Color baseColor;
  final Color selectionColor;
  final Color handlerColor;
  final double handlerOutterRadius;
  final Widget? child;
  final bool showRoundedCapInSelection;
  final bool showHandlerOutter;
  final double sliderStrokeWidth;
  final bool shouldCountLaps;
  final ui.Image nightImage;
  final ui.Image dayImage;

  CircularSliderPaint({
    required this.mode,
    required this.divisions,
    required this.init,
    required this.end,
    required this.nightImage,
    this.child,
    required this.primarySectors,
    required this.secondarySectors,
    required this.onSelectionChange,
    required this.onSelectionEnd,
    required this.baseColor,
    required this.dayImage,
    required this.selectionColor,
    required this.handlerColor,
    required this.handlerOutterRadius,
    required this.showRoundedCapInSelection,
    required this.showHandlerOutter,
    required this.sliderStrokeWidth,
    required this.shouldCountLaps,
  });

  @override
  _CircularSliderState createState() => _CircularSliderState();
}

class _CircularSliderState extends State<CircularSliderPaint> {
  bool _isInitHandlerSelected = false;
  bool _isEndHandlerSelected = false;

  SliderPainter? _painter;

  /// start angle in radians where we need to locate the init handler
  double? _startAngle;

  /// end angle in radians where we need to locate the end handler
  double? _endAngle;

  /// the absolute angle in radians representing the selection
  double? _sweepAngle;

  /// in case we have a double slider and we want to move the whole selection by clicking in the slider
  /// this will capture the position in the selection relative to the initial handler
  /// that way we will be able to keep the selection constant when moving
  int? _differenceFromInitPoint;

  /// will store the number of full laps (2pi radians) as part of the selection
  int _laps = 0;

  /// will be used to calculate in the next movement if we need to increase or decrease _laps
  SlidingState _slidingState = SlidingState.none;

  bool get isDoubleHandler => widget.mode == CircularSliderMode.doubleHandler;

  bool get isSingleHandler => widget.mode == CircularSliderMode.singleHandler;

  bool get isBothHandlersSelected =>
      _isEndHandlerSelected && _isInitHandlerSelected;

  bool get isNoHandlersSelected =>
      !_isEndHandlerSelected && !_isInitHandlerSelected;

  @override
  void initState() {
    super.initState();
    _calculatePaintData();
    //  _uploadImage();
  }

  /* Future<void> _uploadImage() async{
 Future<Unit8List> myBackground =  decodeImageFromList( Io.File(' AppAssets.bedTime') .readAsBytes());
      }*/

  // we need to update this widget both with gesture detector but
  // also when the parent widget rebuilds itself
  @override
  void didUpdateWidget(CircularSliderPaint oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.init != widget.init || oldWidget.end != widget.end) {
      _calculatePaintData();
    }
  }

  @override
  Widget build(BuildContext context) {
    return RawGestureDetector(
      gestures: <Type, GestureRecognizerFactory>{
        CustomPanGestureRecognizer:
            GestureRecognizerFactoryWithHandlers<CustomPanGestureRecognizer>(
          () => CustomPanGestureRecognizer(
            onPanDown: _onPanDown,
            onPanUpdate: _onPanUpdate,
            onPanEnd: _onPanEnd,
          ),
          (CustomPanGestureRecognizer instance) {},
        ),
      },
      child: CustomPaint(
        painter: BasePainter(
          nightImage: widget.nightImage,
          baseColor: widget.baseColor,
          selectionColor: widget.selectionColor,
          primarySectors: widget.primarySectors,
          secondarySectors: widget.secondarySectors,
          sliderStrokeWidth: widget.sliderStrokeWidth,
        ),
        foregroundPainter: _painter,
        child: Padding(
          padding: const EdgeInsets.all(12.0),
          child: widget.child,
        ),
      ),
    );
  }

  double _currentAngle = 0.0;

  void _calculatePaintData() {
    var initPercent = isDoubleHandler
        ? valueToPercentage(widget.init, widget.divisions)
        : 0.0;

    var endPercent = valueToPercentage(widget.end, widget.divisions);
    var sweep = getSweepAngle(initPercent, endPercent);

    var previousStartAngle = _startAngle;

    var previousEndAngle = _endAngle;

    if (previousStartAngle == null) {
      _startAngle = isDoubleHandler ? percentageToRadians(initPercent) : 0.0;
    }

    _currentAngle = isDoubleHandler ? percentageToRadians(initPercent) : 0.0;
    _endAngle = percentageToRadians(endPercent);
    _sweepAngle = percentageToRadians(sweep.abs());

    // update full laps if need be
    if (widget.shouldCountLaps) {
      var newSlidingState = _calculateSlidingState(_startAngle!, _endAngle!);
      if (isSingleHandler) {
        _laps = _calculateLapsForsSingleHandler(
            _endAngle!, previousEndAngle!, _slidingState, _laps);
        _slidingState = newSlidingState;
      } else {
        // is double handler
        if (newSlidingState != _slidingState) {
          _laps = _calculateLapsForDoubleHandler(
              _startAngle!,
              _endAngle!,
              previousStartAngle!,
              previousEndAngle!,
              _slidingState,
              newSlidingState,
              _laps);
          _slidingState = newSlidingState;
        }
      }
    }

    _painter = SliderPainter(
      dayImage: widget.dayImage,
      nightImage: widget.nightImage,
      mode: widget.mode,
      startAngle: _startAngle!,
      currentAngle: _currentAngle,
      endAngle: _endAngle!,
      sweepAngle: _sweepAngle!,
      selectionColor: widget.selectionColor,
      handlerColor: widget.handlerColor,
      handlerOutterRadius: widget.handlerOutterRadius,
      showRoundedCapInSelection: widget.showRoundedCapInSelection,
      showHandlerOutter: widget.showHandlerOutter,
      sliderStrokeWidth: widget.sliderStrokeWidth,
    );
  }

  int _calculateLapsForsSingleHandler(
      double end, double prevEnd, SlidingState slidingState, int laps) {
    if (slidingState != SlidingState.none) {
      if (radiansWasModuloed(end, prevEnd)) {
        var lapIncrement = end < prevEnd ? 1 : -1;
        var newLaps = laps + lapIncrement;
        return newLaps < 0 ? 0 : newLaps;
      }
    }
    return laps;
  }

  int _calculateLapsForDoubleHandler(
      double start,
      double end,
      double prevStart,
      double prevEnd,
      SlidingState slidingState,
      SlidingState newSlidingState,
      int laps) {
    if (slidingState != SlidingState.none) {
      if (!radiansWasModuloed(start, prevStart) &&
          !radiansWasModuloed(end, prevEnd)) {
        var lapIncrement =
            newSlidingState == SlidingState.endIsBiggerThanStart ? 1 : -1;
        var newLaps = laps + lapIncrement;
        return newLaps < 0 ? 0 : newLaps;
      }
    }
    return laps;
  }

  SlidingState _calculateSlidingState(double start, double end) {
    return end > start
        ? SlidingState.endIsBiggerThanStart
        : SlidingState.endIsSmallerThanStart;
  }

  void _onPanUpdate(Offset details) {
    if (!_isInitHandlerSelected && !_isEndHandlerSelected) {
      return;
    }
    if (_painter!.center == null) {
      return;
    }
    _handlePan(details, false);
  }

  void _onPanEnd(Offset details) {
    _handlePan(details, true);

    _isInitHandlerSelected = false;
    _isEndHandlerSelected = false;
  }

  void _handlePan(Offset details, bool isPanEnd) {
    RenderBox renderBox = context.findRenderObject()! as RenderBox;
    var position = renderBox.globalToLocal(details);

    var angle = coordinatesToRadians(_painter!.center!, position);
    var percentage = radiansToPercentage(angle);
    var newValue = percentageToValue(percentage, widget.divisions);

    if (isBothHandlersSelected) {
      var newValueInit =
          (newValue - _differenceFromInitPoint!) % widget.divisions;
      if (newValueInit != widget.init) {
        var newValueEnd =
            (widget.end + (newValueInit - widget.init)) % widget.divisions;
        widget.onSelectionChange(newValueInit, newValueEnd, _laps);
        if (isPanEnd) {
          widget.onSelectionEnd(newValueInit, newValueEnd, _laps);
        }
      }
      return;
    }

    // isDoubleHandler but one handler was selected
    if (_isInitHandlerSelected) {
      widget.onSelectionChange(newValue, widget.end, _laps);
      if (isPanEnd) {
        widget.onSelectionEnd(newValue, widget.end, _laps);
      }
    } else {
      widget.onSelectionChange(widget.init, newValue, _laps);
      if (isPanEnd) {
        widget.onSelectionEnd(widget.init, newValue, _laps);
      }
    }
  }

  bool _onPanDown(Offset details) {
    if (_painter == null) {
      return false;
    }
    RenderBox renderBox = context.findRenderObject()! as RenderBox;

    var position = renderBox.globalToLocal(details);

    if (position == null) {
      return false;
    }

    if (isSingleHandler) {
      if (isPointAlongCircle(position, _painter!.center!, _painter!.radius!)) {
        _isEndHandlerSelected = true;
        _onPanUpdate(details);
      }
    } else {
      _isInitHandlerSelected = isPointInsideCircle(
          position, _painter!.initHandler!, widget.handlerOutterRadius);

      if (!_isInitHandlerSelected) {
        _isEndHandlerSelected = isPointInsideCircle(
            position, _painter!.endHandler!, widget.handlerOutterRadius);
      }

      if (isNoHandlersSelected) {
        // we check if the user pressed in the selection in a double handler slider
        // that means the user wants to move the selection as a whole
        if (isPointAlongCircle(
            position, _painter!.center!, _painter!.radius!)) {
          var angle = coordinatesToRadians(_painter!.center!, position);
          if (isAngleInsideRadiansSelection(
              angle, _startAngle!, _sweepAngle!)) {
            _isEndHandlerSelected = true;
            _isInitHandlerSelected = true;
            var positionPercentage = radiansToPercentage(angle);

            // no need to account for negative values, that will be sorted out in the onPanUpdate
            _differenceFromInitPoint =
                percentageToValue(positionPercentage, widget.divisions) -
                    widget.init;
          }
        }
      }
    }
    return _isInitHandlerSelected || _isEndHandlerSelected;
  }
}

class CustomPanGestureRecognizer extends OneSequenceGestureRecognizer {
  final Function onPanDown;
  final Function onPanUpdate;
  final Function onPanEnd;

  CustomPanGestureRecognizer({
    required this.onPanDown,
    required this.onPanUpdate,
    required this.onPanEnd,
  });

  @override
  void addPointer(PointerEvent event) {
    if (onPanDown(event.position)) {
      startTrackingPointer(event.pointer);
      resolve(GestureDisposition.accepted);
    } else {
      stopTrackingPointer(event.pointer);
    }
  }

  @override
  void handleEvent(PointerEvent event) {
    if (event is PointerMoveEvent) {
      onPanUpdate(event.position);
    }
    if (event is PointerUpEvent) {
      onPanEnd(event.position);
      stopTrackingPointer(event.pointer);
    }
  }

  @override
  String get debugDescription => 'customPan';

  @override
  void didStopTrackingLastPointer(int pointer) {}
}
