diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..6ea5cb24 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dart.lineLength": 120 +} \ No newline at end of file diff --git a/lib/component/lenra_toggle.dart b/lib/component/lenra_toggle.dart new file mode 100644 index 00000000..cff837c6 --- /dev/null +++ b/lib/component/lenra_toggle.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:lenra_components/lenra_components.dart'; +import 'package:lenra_components/theme/lenra_color_theme_data.dart'; + +//TODO: move this value in Theme +const double THUMB_RADIUS_RATIO = 3; +const double TRACK_WIDTH_RATIO = 5; +const double TRACK_HEIGHT_RATIO = 3; +const double THUMB_PADDING_RATIO = 0.25; + +class LenraToggle extends StatefulWidget { + final bool value; + final Function() onPressed; + final Color activeColor = LenraColorThemeData.LENRA_FUN_GREEN_BASE; + final Color inactiveColor = LenraColorThemeData.GREY_NATURE; + final Color disabledColor = LenraColorThemeData.GREY_LIGHT; + final String? label; + final Color labelColor = LenraColorThemeData.BLACK_MOON; + final Color disabledLabelColor = LenraColorThemeData.GREY_NATURE; + final bool disabled; + + const LenraToggle({ + required this.value, + required this.onPressed, + this.label, + this.disabled = false, + Key? key, + }) : super(key: key); + + @override + _LenraToggleState createState() => _LenraToggleState(); +} + +class _LenraToggleState extends State with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late CurvedAnimation animation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 150)); + animation = CurvedAnimation( + parent: _animationController, + curve: Curves.easeIn, + reverseCurve: Curves.easeOut, + ); + } + + @override + void didUpdateWidget(LenraToggle oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.value != widget.value) { + if (widget.value) { + _animationController.reverse(); + } else { + _animationController.forward(); + } + } + } + + @override + Widget build(BuildContext context) { + return widget.disabled ? _buildWidget() : _buildInteractiveWidget(); + } + + Widget _buildInteractiveWidget() { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: widget.onPressed, + child: _buildWidget(), + ), + ); + } + + Widget _buildWidget() { + var lenraSwitch = _LenraSwitch(animation: animation, toggle: widget); + if (widget.label == null) return lenraSwitch; + return LenraRow( + children: [ + _buildLabel(), + lenraSwitch, + ], + ); + } + + Widget _buildLabel() { + return Text( + widget.label!, + style: TextStyle( + color: widget.disabled ? widget.disabledLabelColor : widget.labelColor, + fontWeight: FontWeight.w400, + fontSize: 15.0, + ), + ); + } +} + +class _LenraSwitch extends AnimatedWidget { + final LenraToggle toggle; + final Animation animation; + + const _LenraSwitch({ + Key? key, + required this.animation, + required this.toggle, + }) : super(key: key, listenable: animation); + + @override + Widget build(BuildContext context) { + final LenraThemeData finalLenraThemeData = LenraTheme.of(context); + + final int thumbPadding = (finalLenraThemeData.baseSize * THUMB_PADDING_RATIO).toInt(); + final double thumbRadius = (finalLenraThemeData.baseSize * THUMB_RADIUS_RATIO) - thumbPadding * 2; + final double trackWidth = finalLenraThemeData.baseSize * TRACK_WIDTH_RATIO; + final double trackHeight = finalLenraThemeData.baseSize * TRACK_HEIGHT_RATIO; + final ColorTween colorTween = ColorTween(begin: toggle.inactiveColor, end: toggle.activeColor); + final AlignmentTween alignTween = AlignmentTween(begin: Alignment.centerLeft, end: Alignment.centerRight); + + return Container( + width: trackWidth, + height: trackHeight, + padding: EdgeInsets.only(left: thumbPadding.toDouble(), right: thumbPadding.toDouble()), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(trackHeight / 2), + color: toggle.disabled ? toggle.disabledColor : colorTween.evaluate(this.animation)), + child: Align( + alignment: alignTween.evaluate(animation), + child: Container( + width: thumbRadius, + height: thumbRadius, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: LenraColorThemeData.LENRA_WHITE, + boxShadow: [ + BoxShadow( + offset: Offset(0, 2.0), + blurRadius: 4.0, + color: Colors.black.withOpacity(0.16), + spreadRadius: 0, + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/theme/lenra_color_theme_data.dart b/lib/theme/lenra_color_theme_data.dart index 9d841a71..32586641 100644 --- a/lib/theme/lenra_color_theme_data.dart +++ b/lib/theme/lenra_color_theme_data.dart @@ -13,6 +13,12 @@ class LenraColorThemeData { static const LENRA_CUSTOM_YELLOW = Color(0xFFF6C28B); static const LENRA_CUSTOM_GREEN = Color(0xFF57C0B3); + static const GREY_SUPER_LIGHT = Color(0xFFF0F2F5); + static const GREY_LIGHT = Color(0xFFDCE0E7); + static const GREY_NATURE = Color(0xFFA9B2C4); + static const GREY_DARK_GREY = Color(0xFFF7A8598); + static const BLACK_MOON = Color(0xFF1E232C); + static const LENRA_FUN_RED_PULSE = Color(0xFFE92236); static const LENRA_FUN_RED_BASE = Color(0xFFF27A86); static const LENRA_FUN_RED_FADE = Color(0xFFFACACF); @@ -71,39 +77,25 @@ class LenraColorThemeData { Color? tertiaryForegroundDisabledColor, }) { this.primaryBackgroundColor = primaryBackgroundColor ?? LENRA_BLUE; - this.primaryBackgroundHoverColor = - primaryBackgroundHoverColor ?? LENRA_BLUE_HOVER; - this.primaryBackgroundDisabledColor = - primaryBackgroundDisabledColor ?? LENRA_BLUE_UNAVAILABLE; + this.primaryBackgroundHoverColor = primaryBackgroundHoverColor ?? LENRA_BLUE_HOVER; + this.primaryBackgroundDisabledColor = primaryBackgroundDisabledColor ?? LENRA_BLUE_UNAVAILABLE; this.primaryForegroundColor = primaryForegroundColor ?? LENRA_WHITE; - this.primaryForegroundHoverColor = - primaryForegroundHoverColor ?? LENRA_WHITE; - this.primaryForegroundDisabledColor = - primaryForegroundDisabledColor ?? LENRA_WHITE; + this.primaryForegroundHoverColor = primaryForegroundHoverColor ?? LENRA_WHITE; + this.primaryForegroundDisabledColor = primaryForegroundDisabledColor ?? LENRA_WHITE; - this.secondaryBackgroundColor = - secondaryBackgroundColor ?? Colors.transparent; - this.secondaryBackgroundHoverColor = - secondaryBackgroundHoverColor ?? LENRA_BLUE_UNAVAILABLE; - this.secondaryBackgroundDisabledColor = - secondaryBackgroundDisabledColor ?? Colors.transparent; + this.secondaryBackgroundColor = secondaryBackgroundColor ?? Colors.transparent; + this.secondaryBackgroundHoverColor = secondaryBackgroundHoverColor ?? LENRA_BLUE_UNAVAILABLE; + this.secondaryBackgroundDisabledColor = secondaryBackgroundDisabledColor ?? Colors.transparent; this.secondaryForegroundColor = secondaryForegroundColor ?? LENRA_BLUE; - this.secondaryForegroundHoverColor = - secondaryForegroundHoverColor ?? LENRA_BLUE; - this.secondaryForegroundDisabledColor = - secondaryForegroundDisabledColor ?? LENRA_BLUE_UNAVAILABLE; + this.secondaryForegroundHoverColor = secondaryForegroundHoverColor ?? LENRA_BLUE; + this.secondaryForegroundDisabledColor = secondaryForegroundDisabledColor ?? LENRA_BLUE_UNAVAILABLE; - this.tertiaryBackgroundColor = - tertiaryBackgroundColor ?? Colors.transparent; - this.tertiaryBackgroundHoverColor = - tertiaryBackgroundHoverColor ?? LENRA_BLUE_UNAVAILABLE; - this.tertiaryBackgroundDisabledColor = - tertiaryBackgroundDisabledColor ?? Colors.transparent; + this.tertiaryBackgroundColor = tertiaryBackgroundColor ?? Colors.transparent; + this.tertiaryBackgroundHoverColor = tertiaryBackgroundHoverColor ?? LENRA_BLUE_UNAVAILABLE; + this.tertiaryBackgroundDisabledColor = tertiaryBackgroundDisabledColor ?? Colors.transparent; this.tertiaryForegroundColor = tertiaryForegroundColor ?? LENRA_BLUE; - this.tertiaryForegroundHoverColor = - tertiaryForegroundHoverColor ?? LENRA_BLUE; - this.tertiaryForegroundDisabledColor = - tertiaryForegroundDisabledColor ?? LENRA_BLUE_UNAVAILABLE; + this.tertiaryForegroundHoverColor = tertiaryForegroundHoverColor ?? LENRA_BLUE; + this.tertiaryForegroundDisabledColor = tertiaryForegroundDisabledColor ?? LENRA_BLUE_UNAVAILABLE; } copyWith({ @@ -127,42 +119,24 @@ class LenraColorThemeData { Color? tertiaryForegroundDisabledColor, }) { return LenraColorThemeData( - primaryBackgroundColor: - primaryBackgroundColor ?? this.primaryBackgroundColor, - primaryBackgroundHoverColor: - primaryBackgroundHoverColor ?? this.primaryBackgroundHoverColor, - primaryBackgroundDisabledColor: - primaryBackgroundDisabledColor ?? this.primaryBackgroundDisabledColor, - primaryForegroundColor: - primaryForegroundColor ?? this.primaryForegroundColor, - primaryForegroundHoverColor: - primaryForegroundHoverColor ?? this.primaryForegroundColor, - primaryForegroundDisabledColor: - primaryForegroundDisabledColor ?? this.primaryForegroundDisabledColor, - secondaryBackgroundColor: - secondaryBackgroundColor ?? this.secondaryBackgroundColor, - secondaryBackgroundHoverColor: - secondaryBackgroundHoverColor ?? this.secondaryBackgroundHoverColor, - secondaryBackgroundDisabledColor: secondaryBackgroundDisabledColor ?? - this.secondaryBackgroundDisabledColor, - secondaryForegroundColor: - secondaryForegroundColor ?? this.secondaryForegroundColor, - secondaryForegroundHoverColor: - secondaryForegroundHoverColor ?? this.secondaryForegroundHoverColor, - secondaryForegroundDisabledColor: secondaryForegroundDisabledColor ?? - this.secondaryForegroundDisabledColor, - tertiaryBackgroundColor: - tertiaryBackgroundColor ?? this.tertiaryBackgroundColor, - tertiaryBackgroundHoverColor: - tertiaryBackgroundHoverColor ?? this.tertiaryBackgroundHoverColor, - tertiaryBackgroundDisabledColor: tertiaryBackgroundDisabledColor ?? - this.tertiaryBackgroundDisabledColor, - tertiaryForegroundColor: - tertiaryForegroundColor ?? this.tertiaryForegroundColor, - tertiaryForegroundHoverColor: - tertiaryForegroundHoverColor ?? this.tertiaryForegroundHoverColor, - tertiaryForegroundDisabledColor: tertiaryForegroundDisabledColor ?? - this.tertiaryForegroundDisabledColor, + primaryBackgroundColor: primaryBackgroundColor ?? this.primaryBackgroundColor, + primaryBackgroundHoverColor: primaryBackgroundHoverColor ?? this.primaryBackgroundHoverColor, + primaryBackgroundDisabledColor: primaryBackgroundDisabledColor ?? this.primaryBackgroundDisabledColor, + primaryForegroundColor: primaryForegroundColor ?? this.primaryForegroundColor, + primaryForegroundHoverColor: primaryForegroundHoverColor ?? this.primaryForegroundColor, + primaryForegroundDisabledColor: primaryForegroundDisabledColor ?? this.primaryForegroundDisabledColor, + secondaryBackgroundColor: secondaryBackgroundColor ?? this.secondaryBackgroundColor, + secondaryBackgroundHoverColor: secondaryBackgroundHoverColor ?? this.secondaryBackgroundHoverColor, + secondaryBackgroundDisabledColor: secondaryBackgroundDisabledColor ?? this.secondaryBackgroundDisabledColor, + secondaryForegroundColor: secondaryForegroundColor ?? this.secondaryForegroundColor, + secondaryForegroundHoverColor: secondaryForegroundHoverColor ?? this.secondaryForegroundHoverColor, + secondaryForegroundDisabledColor: secondaryForegroundDisabledColor ?? this.secondaryForegroundDisabledColor, + tertiaryBackgroundColor: tertiaryBackgroundColor ?? this.tertiaryBackgroundColor, + tertiaryBackgroundHoverColor: tertiaryBackgroundHoverColor ?? this.tertiaryBackgroundHoverColor, + tertiaryBackgroundDisabledColor: tertiaryBackgroundDisabledColor ?? this.tertiaryBackgroundDisabledColor, + tertiaryForegroundColor: tertiaryForegroundColor ?? this.tertiaryForegroundColor, + tertiaryForegroundHoverColor: tertiaryForegroundHoverColor ?? this.tertiaryForegroundHoverColor, + tertiaryForegroundDisabledColor: tertiaryForegroundDisabledColor ?? this.tertiaryForegroundDisabledColor, ); } } diff --git a/test/component/lenra_toggle_test.dart b/test/component/lenra_toggle_test.dart new file mode 100644 index 00000000..14c87538 --- /dev/null +++ b/test/component/lenra_toggle_test.dart @@ -0,0 +1,67 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lenra_components/lenra_components.dart'; +import '../../lib/component/lenra_toggle.dart'; + +import '../utils/lenra_page_test_help.dart'; + +void main() { + test('LenraToggle test', () { + LenraToggle component = LenraToggle(value: true, onPressed: () {}); + expect(component is LenraToggle, true); + }); + + testWidgets('LenraToggle without label', (WidgetTester tester) async { + await tester.pumpWidget(createComponentTestWidgets( + LenraToggle( + value: true, + onPressed: () {}, + ), + )); + + expect((tester.widget(find.byType(LenraToggle))) is LenraToggle, true); + expect((find.byType(Text)), findsNothing); + }); + testWidgets('LenraToggle with label', (WidgetTester tester) async { + await tester.pumpWidget(createComponentTestWidgets( + LenraToggle( + value: true, + onPressed: () {}, + label: "test", + ), + )); + + expect((tester.widget(find.byType(LenraRow)) as LenraRow).children.first is Text, true); + }); + testWidgets('LenraToggle size', (WidgetTester tester) async { + await tester.pumpWidget(createComponentTestWidgets( + LenraToggle( + value: true, + onPressed: () {}, + ), + )); + + expect((tester.getSize(find.byType(LenraToggle)).width), equals(40)); + expect((tester.getSize(find.byType(LenraToggle)).height), equals(24)); + }); + + testWidgets('LenraToggle tap should change the state', (WidgetTester tester) async { + bool value = false; + await tester.pumpWidget(createComponentTestWidgets( + LenraToggle( + value: value, + onPressed: () { + value = !value; + }, + ), + )); + expect(value, false); + + await tester.tap(find.byType(LenraToggle)); + await tester.pumpAndSettle(); + + expect(value, true); + }); +}