cgustav / lite_rolling_switch

Full customable rolling switch widget for flutter apps based on Pedro Massango's 'crazy-switch' widget https://github.com/pedromassango/crazy-switch
MIT License
55 stars 52 forks source link

abandoned package? #20

Closed clragon closed 2 years ago

clragon commented 2 years ago

It seems that the author of the package is no longer available to work on it. If someone is still looking to use this package, I have written an updated null-safe version of the switch.

It supports arbitrary width and height as well as full customization of the icons and text (Widget, not IconData or String) I have removed all other onTap callbacks because they can instead be done with a GestureDetector from the outside if really needed. I have also removed the swiping because it did not check which direction was swiped and cannot be used like material switch swiping. The switch will also no longer call onChanged in initState which was unecessary and caused assertions to trigger.

import 'package:flutter/material.dart';
import 'dart:ui';
import 'dart:math';

class RollingSwitch extends StatefulWidget {
  /// Rolling switch button.
  /// * [value] determines whether this switch is on or off.
  /// * [onChanged] is called when the user toggles the switch on or off.
  const RollingSwitch({
    super.key,
    required this.value,
    required this.onChanged,
    this.width = 100,
    this.height = 40,
    this.textOff,
    this.textOn,
    this.colorOn = Colors.green,
    this.colorOff = Colors.red,
    this.iconOff,
    this.iconOn,
    this.animationDuration = const Duration(milliseconds: 300),
  });

  /// Value of the switch.
  final bool value;

  /// Called when the switch value changes.
  final ValueChanged<bool> onChanged;

  /// The width of the button.
  final double width;

  /// The height of the button.
  final double height;

  /// Text displayed on the switch when in the "off" state.
  final Widget? textOff;

  /// Text displayed on the switch when in the "on" state.
  final Widget? textOn;

  /// Icon of the switch when in the "off" state.
  final Widget? iconOff;

  /// Icon of the switch when in the "on" state.
  final Widget? iconOn;

  /// Color of the switch when in the "on" state.
  final Color colorOff;

  /// Color of the switch when in the "off" state.
  final Color colorOn;

  /// The duration in which the button transitions states.
  final Duration animationDuration;

  @override
  State<RollingSwitch> createState() => _RollingSwitchState();
}

class _RollingSwitchState extends State<RollingSwitch>
    with SingleTickerProviderStateMixin {
  late AnimationController controller = AnimationController(
    vsync: this,
    duration: widget.animationDuration,
    value: widget.value ? 1 : 0,
  );
  late Animation<double> animation =
      CurvedAnimation(parent: controller, curve: Curves.easeInOut);
  double get animationValue => controller.value;

  @override
  void didUpdateWidget(covariant RollingSwitch oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.value != widget.value) {
      if (widget.value) {
        controller.forward();
      } else {
        controller.reverse();
      }
    }
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => AnimatedBuilder(
        animation: animation,
        builder: (context, child) {
          const Color contrastColor = Colors.white;
          Color transitionColor =
              Color.lerp(widget.colorOff, widget.colorOn, animationValue)!;
          return IconTheme(
            data: Theme.of(context).iconTheme.copyWith(
                  color: transitionColor,
                ),
            child: DefaultTextStyle(
              style: Theme.of(context).textTheme.bodyText2!.copyWith(
                    color: contrastColor,
                    fontWeight: FontWeight.bold,
                  ),
              child: GestureDetector(
                onTap: () => widget.onChanged(!widget.value),
                child: Container(
                  clipBehavior: Clip.antiAlias,
                  padding: const EdgeInsets.all(4),
                  width: widget.width,
                  height: widget.height,
                  decoration: ShapeDecoration(
                    color: transitionColor,
                    shape: const StadiumBorder(),
                  ),
                  child: LayoutBuilder(
                    builder: (context, constraints) {
                      return Stack(
                        alignment: Alignment.center,
                        fit: StackFit.passthrough,
                        children: [
                          Positioned(
                            right: 0,
                            child: Transform.translate(
                              offset: Offset(10 * animationValue, 0),
                              child: Opacity(
                                opacity: 1 - animationValue,
                                child: Container(
                                  padding:
                                      const EdgeInsets.symmetric(horizontal: 4),
                                  child: widget.textOff ?? const Text('Off'),
                                ),
                              ),
                            ),
                          ),
                          Positioned(
                            left: 0,
                            child: Transform.translate(
                              offset: Offset(10 * (1 - animationValue), 0),
                              child: Opacity(
                                opacity: animationValue,
                                child: Container(
                                  padding:
                                      const EdgeInsets.symmetric(horizontal: 4),
                                  child: widget.textOn ?? const Text('On'),
                                ),
                              ),
                            ),
                          ),
                          Row(
                            children: [
                              Transform.translate(
                                offset: Offset(
                                  (constraints.maxWidth - widget.height) *
                                      animationValue,
                                  0,
                                ),
                                child: Transform.rotate(
                                  angle: lerpDouble(0, 2 * pi, animationValue)!,
                                  child: Container(
                                    height: widget.height,
                                    width: widget.height,
                                    decoration: const BoxDecoration(
                                      shape: BoxShape.circle,
                                      color: contrastColor,
                                    ),
                                    child: Stack(
                                      alignment: Alignment.center,
                                      fit: StackFit.passthrough,
                                      children: [
                                        Opacity(
                                          opacity: 1 - animationValue,
                                          child: widget.iconOff ??
                                              const Icon(Icons.flag),
                                        ),
                                        Opacity(
                                          opacity: animationValue,
                                          child: widget.iconOn ??
                                              const Icon(Icons.check),
                                        ),
                                      ],
                                    ),
                                  ),
                                ),
                              ),
                            ],
                          ),
                        ],
                      );
                    },
                  ),
                ),
              ),
            ),
          );
        },
      );
}
cgustav commented 2 years ago

My friend, this repo came back to life! I added some new features and hot fixes from community contributions. I'll release a new tag in a few hours, thanks for your attention and contribution!