From 58919add00fb4f7e7d0636d040598f5f72018dd8 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Sat, 8 Jul 2023 13:19:24 +0200 Subject: [PATCH 01/36] adds first tests --- CHANGELOG.md | 4 + example/pubspec.lock | 2 +- pubspec.lock | 2 +- pubspec.yaml | 2 +- test/animated_toggle_switch_test.dart | 1 - test/directionality_test.dart | 61 ++++++++ test/helper.dart | 211 ++++++++++++++++++++++++++ test/icon_builder_test.dart | 50 ++++++ 8 files changed, 329 insertions(+), 4 deletions(-) delete mode 100644 test/animated_toggle_switch_test.dart create mode 100644 test/directionality_test.dart create mode 100644 test/helper.dart create mode 100644 test/icon_builder_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index fc95ce0..c1e073a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.1 +- updates dart sdk dependency to `>=3.0.0` +- adds tests for `AnimatedToggleSwitch` + ## 0.7.0 (2023-06-19) - adds possibility that no valid value is selected (by setting `allowUnlistedValues` to `true`) diff --git a/example/pubspec.lock b/example/pubspec.lock index da07f6e..1de7606 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -168,5 +168,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=3.0.0-0 <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=1.17.0" diff --git a/pubspec.lock b/pubspec.lock index e19afa9..7a62cd9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -161,5 +161,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=3.0.0-0 <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=1.17.0" diff --git a/pubspec.yaml b/pubspec.yaml index 65f189d..720aa66 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ repository: https://github.com/SplashByte/animated_toggle_switch issue_tracker: https://github.com/SplashByte/animated_toggle_switch/issues environment: - sdk: '>=2.17.0 <4.0.0' + sdk: '>=3.0.0 <4.0.0' flutter: ">=1.17.0" dependencies: diff --git a/test/animated_toggle_switch_test.dart b/test/animated_toggle_switch_test.dart deleted file mode 100644 index ab73b3a..0000000 --- a/test/animated_toggle_switch_test.dart +++ /dev/null @@ -1 +0,0 @@ -void main() {} diff --git a/test/directionality_test.dart b/test/directionality_test.dart new file mode 100644 index 0000000..b1402ed --- /dev/null +++ b/test/directionality_test.dart @@ -0,0 +1,61 @@ +import 'dart:ui'; + +import 'package:flutter_test/flutter_test.dart'; + +import 'helper.dart'; + +void _verifyTextDirection( + WidgetTester tester, List values, TextDirection textDirection) { + final firstPos = tester.getCenter(find.byKey(iconKey(values.first))); + final lastPos = tester.getCenter(find.byKey(iconKey(values.last))); + switch (textDirection) { + case TextDirection.ltr: + expect((firstPos - lastPos).dx < 0, true, + reason: 'First icon is to the left of the second'); + case TextDirection.rtl: + expect((firstPos - lastPos).dx > 0, true, + reason: 'First icon is to the right of the second'); + } +} + +void main() { + defaultTestAllSwitches('Switch respects TextDirection', + (tester, buildSwitch, values) async { + final current = values[1]; + await tester.pumpWidget(TestWrapper( + textDirection: TextDirection.ltr, + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + ), + )); + _verifyTextDirection(tester, values, TextDirection.ltr); + + await tester.pumpWidget(TestWrapper( + textDirection: TextDirection.rtl, + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + ), + )); + _verifyTextDirection(tester, values, TextDirection.rtl); + + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + textDirection: TextDirection.ltr, + ), + )); + _verifyTextDirection(tester, values, TextDirection.ltr); + + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + textDirection: TextDirection.rtl, + ), + )); + _verifyTextDirection(tester, values, TextDirection.rtl); + }); +} diff --git a/test/helper.dart b/test/helper.dart new file mode 100644 index 0000000..b7793c6 --- /dev/null +++ b/test/helper.dart @@ -0,0 +1,211 @@ +import 'dart:async'; + +import 'package:animated_toggle_switch/animated_toggle_switch.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +const defaultValues = [0, 1, 2, 3]; + +class TestWrapper extends StatelessWidget { + final Widget child; + final TextDirection textDirection; + + const TestWrapper({ + Key? key, + required this.child, + this.textDirection = TextDirection.ltr, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Directionality(textDirection: textDirection, child: child); + } +} + +Widget iconBuilder(T value, bool foreground) => + Text(key: iconKey(value, foreground: foreground), '$value'); + +Key iconKey(T value, {bool foreground = false}) => + ValueKey((foreground, value)); + +typedef TestIconBuilder = Widget Function(T value, bool foreground); + +typedef SwitchBuilder = AnimatedToggleSwitch Function({ + required T current, + required List values, + TestIconBuilder? iconBuilder, + TextDirection? textDirection, +}); + +typedef SimpleSwitchBuilder = AnimatedToggleSwitch Function({ + required T current, + TestIconBuilder? iconBuilder, + TextDirection? textDirection, +}); + +/// Tests all AnimatedToggleSwitch constructors +void defaultTestAllSwitches( + String description, + FutureOr Function(WidgetTester tester, + SimpleSwitchBuilder buildSwitch, List values) + test, + {bool testDual = false}) { + testAllSwitches( + description, + (tester, buildSwitch) => test( + tester, + ({ + required int current, + TestIconBuilder? iconBuilder, + TextDirection? textDirection, + }) => + buildSwitch( + current: current, + values: defaultValues, + iconBuilder: iconBuilder, + textDirection: textDirection, + ), + defaultValues, + )); + if (testDual) { + testWidgets( + '$description (AnimatedToggleSwitch.dual)', + (tester) async => await test( + tester, + ({ + required int current, + TestIconBuilder? iconBuilder, + TextDirection? textDirection, + }) => + AnimatedToggleSwitch.dual( + current: current, + first: defaultValues[0], + second: defaultValues[1], + iconBuilder: + iconBuilder == null ? null : (value) => iconBuilder(value, true), + textDirection: textDirection, + ), + defaultValues.sublist(0, 2), + ), + ); + } +} + +/// Tests all AnimatedToggleSwitch constructors except [AnimatedToggleSwitch.dual] +void testAllSwitches( + String description, + FutureOr Function(WidgetTester tester, SwitchBuilder buildSwitch) + test) { + testWidgets( + '$description (AnimatedToggleSwitch.rolling)', + (tester) async => await test( + tester, + ({ + required T current, + required List values, + TestIconBuilder? iconBuilder, + TextDirection? textDirection, + }) => + AnimatedToggleSwitch.rolling( + current: current, + values: values, + iconBuilder: iconBuilder == null + ? null + : (value, size, foreground) => + iconBuilder(value, foreground), + textDirection: textDirection, + ))); + testWidgets( + '$description (AnimatedToggleSwitch.size)', + (tester) async => await test( + tester, + ({ + required T current, + required List values, + TestIconBuilder? iconBuilder, + TextDirection? textDirection, + }) => + AnimatedToggleSwitch.size( + current: current, + values: values, + iconBuilder: iconBuilder == null + ? null + : (value, size) => iconBuilder(value, false), + textDirection: textDirection, + ))); + testWidgets( + '$description (AnimatedToggleSwitch.rollingByHeight)', + (tester) async => await test( + tester, + ({ + required T current, + required List values, + TestIconBuilder? iconBuilder, + TextDirection? textDirection, + }) => + AnimatedToggleSwitch.rollingByHeight( + current: current, + values: values, + iconBuilder: iconBuilder == null + ? null + : (value, size, foreground) => + iconBuilder(value, foreground), + textDirection: textDirection, + ))); + testWidgets( + '$description (AnimatedToggleSwitch.sizeByHeight)', + (tester) async => await test( + tester, + ({ + required T current, + required List values, + TestIconBuilder? iconBuilder, + TextDirection? textDirection, + }) => + AnimatedToggleSwitch.sizeByHeight( + current: current, + values: values, + iconBuilder: iconBuilder == null + ? null + : (value, size) => iconBuilder(value, false), + textDirection: textDirection, + ))); + testWidgets( + '$description (AnimatedToggleSwitch.custom)', + (tester) async => await test( + tester, + ({ + required T current, + required List values, + TestIconBuilder? iconBuilder, + TextDirection? textDirection, + }) => + AnimatedToggleSwitch.custom( + current: current, + values: values, + animatedIconBuilder: iconBuilder == null + ? null + : (context, local, global) => + iconBuilder(local.value, false), + textDirection: textDirection, + ))); + testWidgets( + '$description (AnimatedToggleSwitch.customByHeight)', + (tester) async => await test( + tester, + ({ + required T current, + required List values, + TestIconBuilder? iconBuilder, + TextDirection? textDirection, + }) => + AnimatedToggleSwitch.customByHeight( + current: current, + values: values, + animatedIconBuilder: iconBuilder == null + ? null + : (context, local, global) => + iconBuilder(local.value, false), + textDirection: textDirection, + ))); +} diff --git a/test/icon_builder_test.dart b/test/icon_builder_test.dart new file mode 100644 index 0000000..8f418ab --- /dev/null +++ b/test/icon_builder_test.dart @@ -0,0 +1,50 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'helper.dart'; + +void _checkValidSwitchIconBuilderState(T current, List values) { + for (var value in values) { + final iconFinder = find.byKey(iconKey(value)); + final iconForegroundFinder = find.byKey(iconKey(value, foreground: true)); + expect(iconFinder, findsOneWidget); + expect(iconForegroundFinder, + current == value ? anyOf(findsNothing, findsOneWidget) : findsNothing); + } +} + +void main() { + defaultTestAllSwitches( + 'Switch builds only one foreground icon & all background icons once', + (tester, buildSwitch, values) async { + final current = values[1]; + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + ), + )); + _checkValidSwitchIconBuilderState(current, values); + }); + defaultTestAllSwitches( + 'AnimatedToggleSwitch changes its state when current changes', + (tester, buildSwitch, values) async { + final current = values.first; + final next = values.last; + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + ), + )); + _checkValidSwitchIconBuilderState(current, values); + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: next, + iconBuilder: iconBuilder, + ), + )); + _checkValidSwitchIconBuilderState(current, values); + await tester.pump(const Duration(seconds: 1)); + _checkValidSwitchIconBuilderState(next, values); + }); +} From a87855b9abeed5a557a6da3dcc68266c78dff95a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Sat, 8 Jul 2023 13:37:27 +0200 Subject: [PATCH 02/36] Create flutter.yml --- .github/workflows/flutter.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/flutter.yml diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml new file mode 100644 index 0000000..0399d01 --- /dev/null +++ b/.github/workflows/flutter.yml @@ -0,0 +1,28 @@ +name: Flutter Tests + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: subosito/flutter-action@v2 + + - name: Install dependencies + run: flutter pub get + + - name: Verify formatting + run: dart format --output=none --set-exit-if-changed . + + - name: Analyze project source + run: flutter analyze + + - name: Run tests + run: flutter test From 4a9eb11ae3341fba77056a5851b265a4270ac625 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Sat, 8 Jul 2023 20:49:42 +0200 Subject: [PATCH 03/36] adds loading & gesture tests --- example/lib/main.dart | 1 - example/test/widget_test.dart | 10 +- .../custom_animated_toggle_switch.dart | 4 +- pubspec.lock | 264 ++++++++++++++++++ pubspec.yaml | 1 + test/gesture_test.dart | 87 ++++++ test/helper.dart | 80 ++++++ test/icon_builder_test.dart | 20 +- test/loading_test.dart | 78 ++++++ test/mocks.dart | 14 + 10 files changed, 531 insertions(+), 28 deletions(-) create mode 100644 test/gesture_test.dart create mode 100644 test/loading_test.dart create mode 100644 test/mocks.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index b9aa23a..21ef62c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -8,7 +8,6 @@ void main() { } class MyApp extends StatelessWidget { - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 3672b62..49fe357 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -1,16 +1,8 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - import 'package:example/main.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. + testWidgets('App starts successfully', (WidgetTester tester) async { await tester.pumpWidget(MyApp()); }); } diff --git a/lib/src/widgets/custom_animated_toggle_switch.dart b/lib/src/widgets/custom_animated_toggle_switch.dart index bb76cd5..34adae6 100644 --- a/lib/src/widgets/custom_animated_toggle_switch.dart +++ b/lib/src/widgets/custom_animated_toggle_switch.dart @@ -320,7 +320,7 @@ class _CustomAnimatedToggleSwitchState var result = widget.onChanged?.call(value); if (result is Future && widget.loading == null) { _loading(true); - result.onError((e, s) => null).then((value) => _loading(false)); + result.whenComplete(() => _loading(false)); } } @@ -329,7 +329,7 @@ class _CustomAnimatedToggleSwitchState var result = widget.onTap?.call(); if (result is Future && widget.loading == null) { _loading(true); - result.onError((e, s) => null).then((value) => _loading(false)); + result.whenComplete(() => _loading(false)); } } diff --git a/pubspec.lock b/pubspec.lock index 7a62cd9..98112e1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + url: "https://pub.dev" + source: hosted + version: "61.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + url: "https://pub.dev" + source: hosted + version: "5.13.0" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" async: dependency: transitive description: @@ -41,6 +65,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.1" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + url: "https://pub.dev" + source: hosted + version: "1.6.3" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" fake_async: dependency: transitive description: @@ -49,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -59,6 +115,46 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" js: dependency: transitive description: @@ -67,6 +163,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: @@ -91,6 +195,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + mocktail: + dependency: "direct dev" + description: + name: mocktail + sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53" + url: "https://pub.dev" + source: hosted + version: "0.3.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" path: dependency: transitive description: @@ -99,11 +235,75 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" source_span: dependency: transitive description: @@ -144,6 +344,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + test: + dependency: transitive + description: + name: test + sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + url: "https://pub.dev" + source: hosted + version: "1.24.1" test_api: dependency: transitive description: @@ -152,6 +360,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" + test_core: + dependency: transitive + description: + name: test_core + sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" vector_math: dependency: transitive description: @@ -160,6 +384,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b8c67f5fa3897b122cf60fe9ff314f7b0ef71eab25c5f8b771480bc338f48823 + url: "https://pub.dev" + source: hosted + version: "11.7.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: dart: ">=3.0.0 <4.0.0" flutter: ">=1.17.0" diff --git a/pubspec.yaml b/pubspec.yaml index 720aa66..4ff1ff7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + mocktail: ^0.3.0 flutter: diff --git a/test/gesture_test.dart b/test/gesture_test.dart new file mode 100644 index 0000000..3d9209f --- /dev/null +++ b/test/gesture_test.dart @@ -0,0 +1,87 @@ +import 'package:animated_toggle_switch/animated_toggle_switch.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import 'helper.dart'; +import 'mocks.dart'; + +void main() { + defaultTestAllSwitches('Switch handles taps correctly', + (tester, buildSwitch, values) async { + final current = values.first; + final next = values.last; + final tapFunction = MockFunction(); + final changedFunction = MockOnChangedFunction(); + + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + onTap: tapFunction, + onChanged: changedFunction, + ), + )); + verifyNever(() => tapFunction.call()); + final currentFinder = find.byKey(iconKey(current)); + final nextFinder = find.byKey(iconKey(next)); + + await tester.tap(currentFinder, warnIfMissed: false); + verify(() => tapFunction()).called(1); + + await tester.tap(nextFinder, warnIfMissed: false); + verify(() => changedFunction(next)).called(1); + }); + + testWidgets('Tap on AnimatedToggleSwitch.dual triggers onChanged by default', + (tester) async { + final values = defaultValues.sublist(0, 2); + final current = values.first; + final next = values.last; + final changedFunction = MockOnChangedFunction(); + + await tester.pumpWidget(TestWrapper( + child: AnimatedToggleSwitch.dual( + current: current, + first: values.first, + second: values.last, + iconBuilder: (value) => iconBuilder(value, true), + onChanged: changedFunction, + ), + )); + verifyNever(() => changedFunction(any())); + final currentFinder = find.byKey(iconKey(current, foreground: true)); + + await tester.tap(currentFinder, warnIfMissed: false); + verify(() => changedFunction(next)).called(1); + }); + + defaultTestAllSwitches('Switch handles swipes correctly', + (tester, buildSwitch, values) async { + final current = values.first; + final next = values.last; + final tapFunction = MockFunction(); + final changedFunction = MockOnChangedFunction(); + + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + onTap: tapFunction, + onChanged: changedFunction, + ), + )); + verifyNever(() => tapFunction.call()); + verifyNever(() => changedFunction(any())); + final currentFinder = find.byKey(iconKey(current)); + final nextFinder = find.byKey(iconKey(next)); + + await tester.drag(currentFinder, tester.getCenter(nextFinder), + warnIfMissed: false); + verify(() => changedFunction(next)).called(1); + + await tester.drag(currentFinder, tester.getCenter(nextFinder), + warnIfMissed: false); + verify(() => changedFunction(next)).called(1); + verifyNever(() => changedFunction(current)); + }); +} diff --git a/test/helper.dart b/test/helper.dart index b7793c6..8f4b9b5 100644 --- a/test/helper.dart +++ b/test/helper.dart @@ -22,12 +22,28 @@ class TestWrapper extends StatelessWidget { } } +void checkValidSwitchIconBuilderState(T current, List values) { + for (var value in values) { + final iconFinder = find.byKey(iconKey(value)); + final iconForegroundFinder = find.byKey(iconKey(value, foreground: true)); + expect(iconFinder, findsOneWidget); + expect(iconForegroundFinder, + current == value ? anyOf(findsNothing, findsOneWidget) : findsNothing); + } +} + Widget iconBuilder(T value, bool foreground) => Text(key: iconKey(value, foreground: foreground), '$value'); Key iconKey(T value, {bool foreground = false}) => ValueKey((foreground, value)); +final loadingIconKey = GlobalKey(); + +Widget _loadingIconBuilder( + BuildContext context, GlobalToggleProperties global) => + SizedBox(key: loadingIconKey); + typedef TestIconBuilder = Widget Function(T value, bool foreground); typedef SwitchBuilder = AnimatedToggleSwitch Function({ @@ -35,12 +51,18 @@ typedef SwitchBuilder = AnimatedToggleSwitch Function({ required List values, TestIconBuilder? iconBuilder, TextDirection? textDirection, + Function(T)? onChanged, + Function()? onTap, + bool? loading, }); typedef SimpleSwitchBuilder = AnimatedToggleSwitch Function({ required T current, TestIconBuilder? iconBuilder, TextDirection? textDirection, + Function(T)? onChanged, + Function()? onTap, + bool? loading, }); /// Tests all AnimatedToggleSwitch constructors @@ -58,12 +80,18 @@ void defaultTestAllSwitches( required int current, TestIconBuilder? iconBuilder, TextDirection? textDirection, + Function(int)? onChanged, + Function()? onTap, + bool? loading, }) => buildSwitch( current: current, values: defaultValues, iconBuilder: iconBuilder, textDirection: textDirection, + onChanged: onChanged, + onTap: onTap, + loading: loading, ), defaultValues, )); @@ -76,6 +104,10 @@ void defaultTestAllSwitches( required int current, TestIconBuilder? iconBuilder, TextDirection? textDirection, + Function(int)? onChanged, + Function()? onTap, + LoadingIconBuilder? loadingIconBuilder, + bool? loading, }) => AnimatedToggleSwitch.dual( current: current, @@ -84,6 +116,7 @@ void defaultTestAllSwitches( iconBuilder: iconBuilder == null ? null : (value) => iconBuilder(value, true), textDirection: textDirection, + loading: loading, ), defaultValues.sublist(0, 2), ), @@ -105,6 +138,10 @@ void testAllSwitches( required List values, TestIconBuilder? iconBuilder, TextDirection? textDirection, + Function(T)? onChanged, + Function()? onTap, + LoadingIconBuilder? loadingIconBuilder, + bool? loading, }) => AnimatedToggleSwitch.rolling( current: current, @@ -114,6 +151,10 @@ void testAllSwitches( : (value, size, foreground) => iconBuilder(value, foreground), textDirection: textDirection, + onTap: onTap, + onChanged: onChanged, + loadingIconBuilder: _loadingIconBuilder, + loading: loading, ))); testWidgets( '$description (AnimatedToggleSwitch.size)', @@ -124,6 +165,10 @@ void testAllSwitches( required List values, TestIconBuilder? iconBuilder, TextDirection? textDirection, + Function(T)? onChanged, + Function()? onTap, + LoadingIconBuilder? loadingIconBuilder, + bool? loading, }) => AnimatedToggleSwitch.size( current: current, @@ -132,6 +177,10 @@ void testAllSwitches( ? null : (value, size) => iconBuilder(value, false), textDirection: textDirection, + onChanged: onChanged, + onTap: onTap, + loadingIconBuilder: _loadingIconBuilder, + loading: loading, ))); testWidgets( '$description (AnimatedToggleSwitch.rollingByHeight)', @@ -142,6 +191,10 @@ void testAllSwitches( required List values, TestIconBuilder? iconBuilder, TextDirection? textDirection, + Function(T)? onChanged, + Function()? onTap, + LoadingIconBuilder? loadingIconBuilder, + bool? loading, }) => AnimatedToggleSwitch.rollingByHeight( current: current, @@ -151,6 +204,10 @@ void testAllSwitches( : (value, size, foreground) => iconBuilder(value, foreground), textDirection: textDirection, + onChanged: onChanged, + onTap: onTap, + loadingIconBuilder: _loadingIconBuilder, + loading: loading, ))); testWidgets( '$description (AnimatedToggleSwitch.sizeByHeight)', @@ -161,6 +218,10 @@ void testAllSwitches( required List values, TestIconBuilder? iconBuilder, TextDirection? textDirection, + Function(T)? onChanged, + Function()? onTap, + LoadingIconBuilder? loadingIconBuilder, + bool? loading, }) => AnimatedToggleSwitch.sizeByHeight( current: current, @@ -169,6 +230,10 @@ void testAllSwitches( ? null : (value, size) => iconBuilder(value, false), textDirection: textDirection, + onChanged: onChanged, + onTap: onTap, + loadingIconBuilder: _loadingIconBuilder, + loading: loading, ))); testWidgets( '$description (AnimatedToggleSwitch.custom)', @@ -179,6 +244,10 @@ void testAllSwitches( required List values, TestIconBuilder? iconBuilder, TextDirection? textDirection, + Function(T)? onChanged, + Function()? onTap, + LoadingIconBuilder? loadingIconBuilder, + bool? loading, }) => AnimatedToggleSwitch.custom( current: current, @@ -188,6 +257,10 @@ void testAllSwitches( : (context, local, global) => iconBuilder(local.value, false), textDirection: textDirection, + onChanged: onChanged, + onTap: onTap, + loadingIconBuilder: _loadingIconBuilder, + loading: loading, ))); testWidgets( '$description (AnimatedToggleSwitch.customByHeight)', @@ -198,6 +271,9 @@ void testAllSwitches( required List values, TestIconBuilder? iconBuilder, TextDirection? textDirection, + Function(T)? onChanged, + Function()? onTap, + bool? loading, }) => AnimatedToggleSwitch.customByHeight( current: current, @@ -207,5 +283,9 @@ void testAllSwitches( : (context, local, global) => iconBuilder(local.value, false), textDirection: textDirection, + onChanged: onChanged, + onTap: onTap, + loadingIconBuilder: _loadingIconBuilder, + loading: loading, ))); } diff --git a/test/icon_builder_test.dart b/test/icon_builder_test.dart index 8f418ab..98a419a 100644 --- a/test/icon_builder_test.dart +++ b/test/icon_builder_test.dart @@ -1,17 +1,5 @@ -import 'package:flutter_test/flutter_test.dart'; - import 'helper.dart'; -void _checkValidSwitchIconBuilderState(T current, List values) { - for (var value in values) { - final iconFinder = find.byKey(iconKey(value)); - final iconForegroundFinder = find.byKey(iconKey(value, foreground: true)); - expect(iconFinder, findsOneWidget); - expect(iconForegroundFinder, - current == value ? anyOf(findsNothing, findsOneWidget) : findsNothing); - } -} - void main() { defaultTestAllSwitches( 'Switch builds only one foreground icon & all background icons once', @@ -23,7 +11,7 @@ void main() { iconBuilder: iconBuilder, ), )); - _checkValidSwitchIconBuilderState(current, values); + checkValidSwitchIconBuilderState(current, values); }); defaultTestAllSwitches( 'AnimatedToggleSwitch changes its state when current changes', @@ -36,15 +24,15 @@ void main() { iconBuilder: iconBuilder, ), )); - _checkValidSwitchIconBuilderState(current, values); + checkValidSwitchIconBuilderState(current, values); await tester.pumpWidget(TestWrapper( child: buildSwitch( current: next, iconBuilder: iconBuilder, ), )); - _checkValidSwitchIconBuilderState(current, values); + checkValidSwitchIconBuilderState(current, values); await tester.pump(const Duration(seconds: 1)); - _checkValidSwitchIconBuilderState(next, values); + checkValidSwitchIconBuilderState(next, values); }); } diff --git a/test/loading_test.dart b/test/loading_test.dart new file mode 100644 index 0000000..1e49231 --- /dev/null +++ b/test/loading_test.dart @@ -0,0 +1,78 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'helper.dart'; + +void main() { + defaultTestAllSwitches( + 'Switch starts loading by returning Future in onTap or onChanged', + (tester, buildSwitch, values) async { + final current = values.first; + final next = values.last; + const loadingDuration = Duration(seconds: 3); + + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + onTap: () => Future.delayed(loadingDuration), + onChanged: (_) => Future.delayed(loadingDuration), + ), + )); + final currentFinder = find.byKey(iconKey(current)); + final nextFinder = find.byKey(iconKey(next)); + final loadingFinder = find.byKey(loadingIconKey); + + Future testLoading() async { + expect(loadingFinder, findsNothing); + await tester.pump(Duration.zero); + await tester.pump(loadingDuration ~/ 2); + expect(loadingFinder, findsOneWidget); + await tester.pump(loadingDuration ~/ 2); + await tester.pump(const Duration(milliseconds: 500)); + expect(loadingFinder, findsNothing); + } + + // tests onChanged + await tester.tap(nextFinder, warnIfMissed: false); + await testLoading(); + + // tests onTap + await tester.tap(currentFinder, warnIfMissed: false); + await testLoading(); + }); + + defaultTestAllSwitches('Switch starts loading by setting loading parameter', + (tester, buildSwitch, values) async { + final current = values.first; + + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + loading: false, + ), + )); + final loadingFinder = find.byKey(loadingIconKey); + + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + loading: true, + ), + )); + expect(loadingFinder, findsNothing); + await tester.pump(const Duration(milliseconds: 500)); + expect(loadingFinder, findsOneWidget); + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + loading: false, + ), + )); + expect(loadingFinder, findsOneWidget); + await tester.pump(const Duration(milliseconds: 500)); + expect(loadingFinder, findsNothing); + }); +} diff --git a/test/mocks.dart b/test/mocks.dart new file mode 100644 index 0000000..20794ac --- /dev/null +++ b/test/mocks.dart @@ -0,0 +1,14 @@ +import 'package:mocktail/mocktail.dart'; + +abstract class TestFunction { + void call(); +} + +abstract class TestOnChangedFunction { + void call(T value); +} + +class MockFunction extends Mock implements TestFunction {} + +class MockOnChangedFunction extends Mock + implements TestOnChangedFunction {} From 5084af865652fb30d5507d438cdf352a9e60b48b Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Sun, 9 Jul 2023 13:04:15 +0200 Subject: [PATCH 04/36] adds & optimizes some tests --- CHANGELOG.md | 4 +- README.md | 2 +- example/pubspec.lock | 2 +- .../custom_animated_toggle_switch.dart | 3 +- pubspec.yaml | 4 +- test/directionality_test.dart | 2 +- test/gesture_test.dart | 22 ++++++--- test/helper.dart | 28 ++++++----- test/keys.dart | 19 +++++++ test/loading_test.dart | 49 ++++++++++++++++++- 10 files changed, 106 insertions(+), 29 deletions(-) create mode 100644 test/keys.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index c1e073a..d05bb1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.7.1 -- updates dart sdk dependency to `>=3.0.0` -- adds tests for `AnimatedToggleSwitch` +- adds tests for all `AnimatedToggleSwitch` constructors +- fixes initial loading ## 0.7.0 (2023-06-19) diff --git a/README.md b/README.md index 2a6ed13..5d2c68f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ### If you like this package, please leave a like there on [pub.dev](https://pub.dev/packages/animated_toggle_switch) and star on [GitHub](https://github.com/SplashByte/animated_toggle_switch). -Simple and animated switch with multiple choices and smooth loading animation. It's an easy way if you don't want to use something like a `DropDownMenuButton`. +Fully customizable, draggable and animated switch with multiple choices and smooth loading animation. It has prebuilt constructors for rolling and size animations. `LTR` and `RTL` are both supported. [Switches without an (initial) selection](#simple-rolling-animation-with-nullable-selection) are also possible. Most builder arguments of `AnimatedToggleSwitch` have a standard and a custom version. This ensures that you can get started easily and still customize a lot if necessary. diff --git a/example/pubspec.lock b/example/pubspec.lock index 1de7606..da07f6e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -168,5 +168,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.0.0-0 <4.0.0" flutter: ">=1.17.0" diff --git a/lib/src/widgets/custom_animated_toggle_switch.dart b/lib/src/widgets/custom_animated_toggle_switch.dart index 34adae6..9baf1c1 100644 --- a/lib/src/widgets/custom_animated_toggle_switch.dart +++ b/lib/src/widgets/custom_animated_toggle_switch.dart @@ -244,7 +244,8 @@ class _CustomAnimatedToggleSwitchState final current = widget.current; final isValueSelected = widget.values.contains(current); _animationInfo = _AnimationInfo( - isValueSelected ? widget.values.indexOf(current).toDouble() : 0.0); + isValueSelected ? widget.values.indexOf(current).toDouble() : 0.0) + .setLoading(widget.loading ?? false); _controller = AnimationController(vsync: this, duration: widget.animationDuration) ..addStatusListener((status) { diff --git a/pubspec.yaml b/pubspec.yaml index 4ff1ff7..ac22a66 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,11 @@ name: animated_toggle_switch -description: Simple and animated switch with multiple choices and smooth loading animation. It's a good alternative if you don't want to use something like a dropdown menu. +description: Fully customizable, draggable and animated switch with multiple choices and smooth loading animation. It has prebuilt constructors for rolling and size animations. version: 0.7.0 repository: https://github.com/SplashByte/animated_toggle_switch issue_tracker: https://github.com/SplashByte/animated_toggle_switch/issues environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=2.17.0 <4.0.0' flutter: ">=1.17.0" dependencies: diff --git a/test/directionality_test.dart b/test/directionality_test.dart index b1402ed..40da100 100644 --- a/test/directionality_test.dart +++ b/test/directionality_test.dart @@ -57,5 +57,5 @@ void main() { ), )); _verifyTextDirection(tester, values, TextDirection.rtl); - }); + }, testDual: false); } diff --git a/test/gesture_test.dart b/test/gesture_test.dart index 3d9209f..2d01e18 100644 --- a/test/gesture_test.dart +++ b/test/gesture_test.dart @@ -30,7 +30,10 @@ void main() { await tester.tap(nextFinder, warnIfMissed: false); verify(() => changedFunction(next)).called(1); - }); + verify(() => tapFunction()).called(1); + + verifyNoMoreInteractions(changedFunction); + }, testDual: false); testWidgets('Tap on AnimatedToggleSwitch.dual triggers onChanged by default', (tester) async { @@ -48,14 +51,16 @@ void main() { onChanged: changedFunction, ), )); - verifyNever(() => changedFunction(any())); final currentFinder = find.byKey(iconKey(current, foreground: true)); + verifyNoMoreInteractions(changedFunction); + await tester.tap(currentFinder, warnIfMissed: false); verify(() => changedFunction(next)).called(1); + verifyNoMoreInteractions(changedFunction); }); - defaultTestAllSwitches('Switch handles swipes correctly', + defaultTestAllSwitches('Switch handles drags correctly', (tester, buildSwitch, values) async { final current = values.first; final next = values.last; @@ -70,18 +75,19 @@ void main() { onChanged: changedFunction, ), )); - verifyNever(() => tapFunction.call()); - verifyNever(() => changedFunction(any())); final currentFinder = find.byKey(iconKey(current)); final nextFinder = find.byKey(iconKey(next)); + verifyNoMoreInteractions(changedFunction); + await tester.drag(currentFinder, tester.getCenter(nextFinder), warnIfMissed: false); verify(() => changedFunction(next)).called(1); - await tester.drag(currentFinder, tester.getCenter(nextFinder), + await tester.drag(nextFinder, tester.getCenter(currentFinder), warnIfMissed: false); - verify(() => changedFunction(next)).called(1); - verifyNever(() => changedFunction(current)); + + verifyNoMoreInteractions(changedFunction); + verifyNoMoreInteractions(tapFunction); }); } diff --git a/test/helper.dart b/test/helper.dart index 8f4b9b5..762f8de 100644 --- a/test/helper.dart +++ b/test/helper.dart @@ -4,6 +4,8 @@ import 'package:animated_toggle_switch/animated_toggle_switch.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'keys.dart'; + const defaultValues = [0, 1, 2, 3]; class TestWrapper extends StatelessWidget { @@ -36,7 +38,7 @@ Widget iconBuilder(T value, bool foreground) => Text(key: iconKey(value, foreground: foreground), '$value'); Key iconKey(T value, {bool foreground = false}) => - ValueKey((foreground, value)); + IconKey(value, foreground: foreground); final loadingIconKey = GlobalKey(); @@ -71,7 +73,7 @@ void defaultTestAllSwitches( FutureOr Function(WidgetTester tester, SimpleSwitchBuilder buildSwitch, List values) test, - {bool testDual = false}) { + {bool testDual = true}) { testAllSwitches( description, (tester, buildSwitch) => test( @@ -96,6 +98,7 @@ void defaultTestAllSwitches( defaultValues, )); if (testDual) { + final values = defaultValues.sublist(0, 2); testWidgets( '$description (AnimatedToggleSwitch.dual)', (tester) async => await test( @@ -106,19 +109,25 @@ void defaultTestAllSwitches( TextDirection? textDirection, Function(int)? onChanged, Function()? onTap, - LoadingIconBuilder? loadingIconBuilder, bool? loading, }) => - AnimatedToggleSwitch.dual( + AnimatedToggleSwitch.dual( current: current, - first: defaultValues[0], - second: defaultValues[1], + first: values[0], + second: values[1], iconBuilder: iconBuilder == null ? null : (value) => iconBuilder(value, true), + textBuilder: iconBuilder == null + ? null + : (value) => + iconBuilder(values[(values.indexOf(value) + 1) % 2], false), textDirection: textDirection, + onTap: onTap, + onChanged: onChanged, + loadingIconBuilder: _loadingIconBuilder, loading: loading, ), - defaultValues.sublist(0, 2), + values, ), ); } @@ -140,7 +149,6 @@ void testAllSwitches( TextDirection? textDirection, Function(T)? onChanged, Function()? onTap, - LoadingIconBuilder? loadingIconBuilder, bool? loading, }) => AnimatedToggleSwitch.rolling( @@ -167,7 +175,6 @@ void testAllSwitches( TextDirection? textDirection, Function(T)? onChanged, Function()? onTap, - LoadingIconBuilder? loadingIconBuilder, bool? loading, }) => AnimatedToggleSwitch.size( @@ -193,7 +200,6 @@ void testAllSwitches( TextDirection? textDirection, Function(T)? onChanged, Function()? onTap, - LoadingIconBuilder? loadingIconBuilder, bool? loading, }) => AnimatedToggleSwitch.rollingByHeight( @@ -220,7 +226,6 @@ void testAllSwitches( TextDirection? textDirection, Function(T)? onChanged, Function()? onTap, - LoadingIconBuilder? loadingIconBuilder, bool? loading, }) => AnimatedToggleSwitch.sizeByHeight( @@ -246,7 +251,6 @@ void testAllSwitches( TextDirection? textDirection, Function(T)? onChanged, Function()? onTap, - LoadingIconBuilder? loadingIconBuilder, bool? loading, }) => AnimatedToggleSwitch.custom( diff --git a/test/keys.dart b/test/keys.dart new file mode 100644 index 0000000..d8890db --- /dev/null +++ b/test/keys.dart @@ -0,0 +1,19 @@ +import 'package:flutter/cupertino.dart'; + +//TODO: Replace with record after Dart SDK upgrade +class IconKey extends LocalKey { + final bool foreground; + final T value; + + IconKey(this.value, {this.foreground = false}); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is IconKey && + foreground == other.foreground && + value == other.value; + + @override + int get hashCode => foreground.hashCode ^ value.hashCode; +} \ No newline at end of file diff --git a/test/loading_test.dart b/test/loading_test.dart index 1e49231..de53f2a 100644 --- a/test/loading_test.dart +++ b/test/loading_test.dart @@ -74,5 +74,52 @@ void main() { expect(loadingFinder, findsOneWidget); await tester.pump(const Duration(milliseconds: 500)); expect(loadingFinder, findsNothing); - }); + }, testDual: false); + + defaultTestAllSwitches('Switch supports initial loading', + (tester, buildSwitch, values) async { + final current = values.first; + + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + loading: true, + ), + )); + final currentFinder = find.byKey(iconKey(current, foreground: true)); + final loadingFinder = find.byKey(loadingIconKey); + + expect(loadingFinder, findsOneWidget); + expect(currentFinder, findsNothing); + }); + + defaultTestAllSwitches('Switch disables loading by setting loading to false', + (tester, buildSwitch, values) async { + final current = values.first; + final next = values.last; + const loadingDuration = Duration(seconds: 3); + + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + onTap: () => Future.delayed(loadingDuration), + onChanged: (_) => Future.delayed(loadingDuration), + loading: false, + ), + )); + final nextFinder = find.byKey(iconKey(next)); + final loadingFinder = find.byKey(loadingIconKey); + + expect(loadingFinder, findsNothing); + + await tester.tap(nextFinder, warnIfMissed: false); + await tester.pump(Duration.zero); + await tester.pump(const Duration(milliseconds: 500)); + + expect(loadingFinder, findsNothing); + + await tester.pump(loadingDuration); + }); } From c86ce39b994826e01cb5c0b2a4e795255bfc3d90 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Sun, 9 Jul 2023 13:05:05 +0200 Subject: [PATCH 05/36] minor fix --- test/directionality_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/directionality_test.dart b/test/directionality_test.dart index 40da100..bd12d0f 100644 --- a/test/directionality_test.dart +++ b/test/directionality_test.dart @@ -12,9 +12,11 @@ void _verifyTextDirection( case TextDirection.ltr: expect((firstPos - lastPos).dx < 0, true, reason: 'First icon is to the left of the second'); + break; case TextDirection.rtl: expect((firstPos - lastPos).dx > 0, true, reason: 'First icon is to the right of the second'); + break; } } From 2d58747ccd5b9dfa4524202200dd4d38dba39ce7 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Mon, 10 Jul 2023 10:22:18 +0200 Subject: [PATCH 06/36] adds test for values & fixes initial loading --- README.md | 2 +- lib/src/properties.dart | 7 ++-- lib/src/widgets/animated_toggle_switch.dart | 4 +-- .../custom_animated_toggle_switch.dart | 5 +++ test/helper.dart | 17 ++++++++++ test/keys.dart | 2 +- test/values_test.dart | 33 +++++++++++++++++++ 7 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 test/values_test.dart diff --git a/README.md b/README.md index 5d2c68f..6d64778 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ### If you like this package, please leave a like there on [pub.dev](https://pub.dev/packages/animated_toggle_switch) and star on [GitHub](https://github.com/SplashByte/animated_toggle_switch). -Fully customizable, draggable and animated switch with multiple choices and smooth loading animation. It has prebuilt constructors for rolling and size animations. +Fully customizable, draggable and animated switch with multiple choices and smooth loading animation. It has prebuilt constructors for rolling and size animations, but it also allows you to create your own switches with `CustomAnimatedToggleSwitch`. `LTR` and `RTL` are both supported. [Switches without an (initial) selection](#simple-rolling-animation-with-nullable-selection) are also possible. Most builder arguments of `AnimatedToggleSwitch` have a standard and a custom version. This ensures that you can get started easily and still customize a lot if necessary. diff --git a/lib/src/properties.dart b/lib/src/properties.dart index 01c4f14..b0af074 100644 --- a/lib/src/properties.dart +++ b/lib/src/properties.dart @@ -88,10 +88,11 @@ class LocalToggleProperties { } class AnimatedToggleProperties extends LocalToggleProperties { - /// A value between 0 and 1. + /// A value between [0] and [1]. /// - /// 0 indicates that [value] is not selected. - /// 1 indicates that [value] is selected. + /// [0] indicates that [value] is not selected. + /// + /// [1] indicates that [value] is selected. final double animationValue; AnimatedToggleProperties.fromLocal({ diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart index 44adf35..fc21c90 100644 --- a/lib/src/widgets/animated_toggle_switch.dart +++ b/lib/src/widgets/animated_toggle_switch.dart @@ -1114,12 +1114,12 @@ class _MyLoading extends StatelessWidget { final color = Theme.of(context).iconTheme.color; return Padding( padding: const EdgeInsets.all(8.0), - child: Theme.of(context).platform.apple + child: Theme.of(context).platform.isApple ? CupertinoActivityIndicator(color: color) : CircularProgressIndicator(color: color)); } } extension _XTargetPlatform on TargetPlatform { - bool get apple => this == TargetPlatform.iOS || this == TargetPlatform.macOS; + bool get isApple => this == TargetPlatform.iOS || this == TargetPlatform.macOS; } diff --git a/lib/src/widgets/custom_animated_toggle_switch.dart b/lib/src/widgets/custom_animated_toggle_switch.dart index 9baf1c1..aef6ab4 100644 --- a/lib/src/widgets/custom_animated_toggle_switch.dart +++ b/lib/src/widgets/custom_animated_toggle_switch.dart @@ -243,6 +243,7 @@ class _CustomAnimatedToggleSwitchState final current = widget.current; final isValueSelected = widget.values.contains(current); + _checkForUnlistedValue(); _animationInfo = _AnimationInfo( isValueSelected ? widget.values.indexOf(current).toDouble() : 0.0) .setLoading(widget.loading ?? false); @@ -779,8 +780,12 @@ class _WidgetPart extends StatelessWidget { /// A class for holding the current state of [_CustomAnimatedToggleSwitchState]. class _AnimationInfo { + /// The start position of the current animation. final double start; + + /// The end position of the current animation. final double end; + final ToggleMode toggleMode; final bool loading; diff --git a/test/helper.dart b/test/helper.dart index 762f8de..1fcae30 100644 --- a/test/helper.dart +++ b/test/helper.dart @@ -56,6 +56,7 @@ typedef SwitchBuilder = AnimatedToggleSwitch Function({ Function(T)? onChanged, Function()? onTap, bool? loading, + bool allowUnlistedValues, }); typedef SimpleSwitchBuilder = AnimatedToggleSwitch Function({ @@ -65,6 +66,7 @@ typedef SimpleSwitchBuilder = AnimatedToggleSwitch Function({ Function(T)? onChanged, Function()? onTap, bool? loading, + bool allowUnlistedValues, }); /// Tests all AnimatedToggleSwitch constructors @@ -85,6 +87,7 @@ void defaultTestAllSwitches( Function(int)? onChanged, Function()? onTap, bool? loading, + bool allowUnlistedValues = false, }) => buildSwitch( current: current, @@ -94,6 +97,7 @@ void defaultTestAllSwitches( onChanged: onChanged, onTap: onTap, loading: loading, + allowUnlistedValues: allowUnlistedValues, ), defaultValues, )); @@ -110,6 +114,7 @@ void defaultTestAllSwitches( Function(int)? onChanged, Function()? onTap, bool? loading, + bool allowUnlistedValues = false, }) => AnimatedToggleSwitch.dual( current: current, @@ -150,6 +155,7 @@ void testAllSwitches( Function(T)? onChanged, Function()? onTap, bool? loading, + bool allowUnlistedValues = false, }) => AnimatedToggleSwitch.rolling( current: current, @@ -163,6 +169,7 @@ void testAllSwitches( onChanged: onChanged, loadingIconBuilder: _loadingIconBuilder, loading: loading, + allowUnlistedValues: allowUnlistedValues, ))); testWidgets( '$description (AnimatedToggleSwitch.size)', @@ -176,6 +183,7 @@ void testAllSwitches( Function(T)? onChanged, Function()? onTap, bool? loading, + bool allowUnlistedValues = false, }) => AnimatedToggleSwitch.size( current: current, @@ -188,6 +196,7 @@ void testAllSwitches( onTap: onTap, loadingIconBuilder: _loadingIconBuilder, loading: loading, + allowUnlistedValues: allowUnlistedValues, ))); testWidgets( '$description (AnimatedToggleSwitch.rollingByHeight)', @@ -201,6 +210,7 @@ void testAllSwitches( Function(T)? onChanged, Function()? onTap, bool? loading, + bool allowUnlistedValues = false, }) => AnimatedToggleSwitch.rollingByHeight( current: current, @@ -214,6 +224,7 @@ void testAllSwitches( onTap: onTap, loadingIconBuilder: _loadingIconBuilder, loading: loading, + allowUnlistedValues: allowUnlistedValues, ))); testWidgets( '$description (AnimatedToggleSwitch.sizeByHeight)', @@ -227,6 +238,7 @@ void testAllSwitches( Function(T)? onChanged, Function()? onTap, bool? loading, + bool allowUnlistedValues = false, }) => AnimatedToggleSwitch.sizeByHeight( current: current, @@ -239,6 +251,7 @@ void testAllSwitches( onTap: onTap, loadingIconBuilder: _loadingIconBuilder, loading: loading, + allowUnlistedValues: allowUnlistedValues, ))); testWidgets( '$description (AnimatedToggleSwitch.custom)', @@ -252,6 +265,7 @@ void testAllSwitches( Function(T)? onChanged, Function()? onTap, bool? loading, + bool allowUnlistedValues = false, }) => AnimatedToggleSwitch.custom( current: current, @@ -265,6 +279,7 @@ void testAllSwitches( onTap: onTap, loadingIconBuilder: _loadingIconBuilder, loading: loading, + allowUnlistedValues: allowUnlistedValues, ))); testWidgets( '$description (AnimatedToggleSwitch.customByHeight)', @@ -278,6 +293,7 @@ void testAllSwitches( Function(T)? onChanged, Function()? onTap, bool? loading, + bool allowUnlistedValues = false, }) => AnimatedToggleSwitch.customByHeight( current: current, @@ -291,5 +307,6 @@ void testAllSwitches( onTap: onTap, loadingIconBuilder: _loadingIconBuilder, loading: loading, + allowUnlistedValues: allowUnlistedValues, ))); } diff --git a/test/keys.dart b/test/keys.dart index d8890db..c7ef54b 100644 --- a/test/keys.dart +++ b/test/keys.dart @@ -1,4 +1,4 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; //TODO: Replace with record after Dart SDK upgrade class IconKey extends LocalKey { diff --git a/test/values_test.dart b/test/values_test.dart new file mode 100644 index 0000000..5991cb6 --- /dev/null +++ b/test/values_test.dart @@ -0,0 +1,33 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'helper.dart'; + +void main() { + defaultTestAllSwitches('Switch does not allow unlisted values', + (tester, buildSwitch, values) async { + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: 100, + iconBuilder: iconBuilder, + ), + )); + + expect(tester.takeException(), isA()); + }); + + defaultTestAllSwitches( + 'Switch respects allowUnlistedValues', + (tester, buildSwitch, values) async { + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: 100, + iconBuilder: iconBuilder, + allowUnlistedValues: true, + ), + )); + + expect(tester.takeException(), null); + }, + testDual: false, + ); +} From c5277d46ded0c928896f28d209056917bb98636b Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Mon, 10 Jul 2023 12:23:59 +0200 Subject: [PATCH 07/36] adds examples for separator --- CHANGELOG.md | 2 + example/lib/main.dart | 50 ++++++++++++++++ lib/src/properties.dart | 12 ++++ lib/src/widgets/animated_toggle_switch.dart | 36 ++++++++++- .../custom_animated_toggle_switch.dart | 59 ++++++++++++++----- 5 files changed, 142 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d05bb1e..3466c26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## 0.7.1 - adds tests for all `AnimatedToggleSwitch` constructors +- adds `separatorBuilder` and `customSeparatorBuilder` to `AnimatedToggleSwitch` +- adds `separatorBuilder` to `CustomAnimatedToggleSwitch` - fixes initial loading ## 0.7.0 (2023-06-19) diff --git a/example/lib/main.dart b/example/lib/main.dart index 21ef62c..f9b81c8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -151,6 +151,23 @@ class _MyHomePageState extends State { ], ), SizedBox(height: 16.0), + AnimatedToggleSwitch.rolling( + current: value, + allowUnlistedValues: true, + values: const [0, 1, 2, 3], + onChanged: (i) => setState(() => value = i), + iconBuilder: rollingIconBuilder, + separatorBuilder: (context, index) => const VerticalDivider(), + borderWidth: 4.5, + indicatorColor: Colors.white, + borderRadius: BorderRadius.circular(10.0), + innerColor: Colors.amber, + height: 55, + dif: 20.0, + borderColor: Colors.transparent, + loading: loading, + ), + SizedBox(height: 16.0), AnimatedToggleSwitch.rolling( current: nullableValue, allowUnlistedValues: true, @@ -262,6 +279,39 @@ class _MyHomePageState extends State { return Future.delayed(Duration(seconds: 3)); }, ), + SizedBox(height: 16.0), + AnimatedToggleSwitch.size( + current: min(value, 2), + borderRadius: BorderRadius.circular(10.0), + indicatorBorderRadius: BorderRadius.zero, + innerColor: Color(0xFF919191), + values: const [0, 1, 2], + iconOpacity: 1.0, + indicatorSize: const Size.fromWidth(100), + iconAnimationType: AnimationType.onHover, + indicatorAnimationType: AnimationType.onHover, + dif: 2.0, + customSeparatorBuilder: (context, local, global) { + final opacity = + ((global.position - local.position).abs() - 0.5).clamp(0.0, 1.0); + return VerticalDivider( + indent: 10.0, + endIndent: 10.0, + color: Colors.white38.withOpacity(opacity)); + }, + customIconBuilder: (context, local, global) { + final text = const ['not', 'just', 'icons'][local.index]; + return Center( + child: Text(text, + style: TextStyle( + color: Color.lerp(Colors.black, Colors.white, + local.animationValue)))); + }, + borderWidth: 0.0, + borderColor: Colors.transparent, + colorBuilder: (i) => Color(0xFFEC3345), + onChanged: (i) => setState(() => value = i), + ), Padding( padding: const EdgeInsets.all(8.0), child: Text( diff --git a/lib/src/properties.dart b/lib/src/properties.dart index b0af074..77237a3 100644 --- a/lib/src/properties.dart +++ b/lib/src/properties.dart @@ -59,6 +59,8 @@ class DetailedGlobalToggleProperties extends GlobalToggleProperties { /// The size of the switch exclusive the outer wrapper final Size switchSize; + Size get difSize => Size(dif, switchSize.height); + const DetailedGlobalToggleProperties({ required this.dif, required this.indicatorSize, @@ -164,3 +166,13 @@ class SizeProperties extends AnimatedToggleProperties { required super.animationValue, }); } + +class DifProperties { + final int index; + + double get position => index + 0.5; + + const DifProperties({ + required this.index, + }); +} diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart index fc21c90..d5c6e63 100644 --- a/lib/src/widgets/animated_toggle_switch.dart +++ b/lib/src/widgets/animated_toggle_switch.dart @@ -227,6 +227,20 @@ class AnimatedToggleSwitch extends StatelessWidget { /// Curve of the appearing animation. final Curve indicatorAppearingCurve; + /// Builder for divider or other separators between the icons. Consider using [customSeparatorBuilder] for maximum customizability. + /// + /// The available width is specified by [dif]. + /// + /// This builder is supported by [IconArrangement.row] only. + final IndexedWidgetBuilder? separatorBuilder; + + /// Builder for divider or other separators between the icons. Consider using [separatorBuilder] for a simpler builder function. + /// + /// The available width is specified by [dif]. + /// + /// This builder is supported by [IconArrangement.row] only. + final CustomSeparatorBuilder? customSeparatorBuilder; + /// Constructor of AnimatedToggleSwitch with all possible settings. /// /// Consider using [CustomAnimatedToggleSwitch] for maximum customizability. @@ -278,6 +292,8 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAppearingDuration = _defaultIndicatorAppearingAnimationDuration, this.indicatorAppearingCurve = _defaultIndicatorAppearingAnimationCurve, + this.separatorBuilder, + this.customSeparatorBuilder, }) : this._iconArrangement = IconArrangement.row, super(key: key); @@ -335,6 +351,8 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAppearingDuration = _defaultIndicatorAppearingAnimationDuration, this.indicatorAppearingCurve = _defaultIndicatorAppearingAnimationCurve, + this.separatorBuilder, + this.customSeparatorBuilder, }) : animatedIconBuilder = _iconSizeBuilder( iconBuilder, customIconBuilder, iconSize, selectedIconSize), this._iconArrangement = IconArrangement.row, @@ -395,6 +413,8 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAppearingDuration = _defaultIndicatorAppearingAnimationDuration, this.indicatorAppearingCurve = _defaultIndicatorAppearingAnimationCurve, + this.separatorBuilder, + this.customSeparatorBuilder, }) : this.indicatorSize = indicatorSize * (height - 2 * borderWidth), this.dif = dif * (height - 2 * borderWidth), animatedIconBuilder = _iconSizeBuilder( @@ -484,6 +504,8 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAppearingDuration = _defaultIndicatorAppearingAnimationDuration, this.indicatorAppearingCurve = _defaultIndicatorAppearingAnimationCurve, + this.separatorBuilder, + this.customSeparatorBuilder, }) : this.dif = dif * (height - 2 * borderWidth), this.indicatorSize = indicatorSize * (height - 2 * borderWidth), this._iconArrangement = IconArrangement.row, @@ -547,6 +569,8 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAppearingDuration = _defaultIndicatorAppearingAnimationDuration, this.indicatorAppearingCurve = _defaultIndicatorAppearingAnimationCurve, + this.separatorBuilder, + this.customSeparatorBuilder, }) : this.iconAnimationCurve = Curves.linear, this.dif = dif * (height - 2 * borderWidth), this.iconAnimationDuration = Duration.zero, @@ -624,6 +648,8 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAppearingDuration = _defaultIndicatorAppearingAnimationDuration, this.indicatorAppearingCurve = _defaultIndicatorAppearingAnimationCurve, + this.separatorBuilder, + this.customSeparatorBuilder, }) : this.iconAnimationCurve = Curves.linear, this.iconAnimationDuration = Duration.zero, this.selectedIconOpacity = iconOpacity, @@ -804,6 +830,8 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAppearingDuration = _defaultIndicatorAppearingAnimationDuration, this.indicatorAppearingCurve = _defaultIndicatorAppearingAnimationCurve, + this.separatorBuilder = null, + this.customSeparatorBuilder = null, super(key: key); static Function() _dualOnTap( @@ -906,6 +934,11 @@ class AnimatedToggleSwitch extends StatelessWidget { indicatorAppearingBuilder: indicatorAppearingBuilder, indicatorAppearingDuration: indicatorAppearingDuration, indicatorAppearingCurve: indicatorAppearingCurve, + separatorBuilder: customSeparatorBuilder ?? + (separatorBuilder == null + ? null + : (context, local, global) => + separatorBuilder!(context, local.index)), backgroundIndicatorBuilder: foregroundIndicatorIconBuilder != null ? null : (context, properties) => _indicatorBuilder(context, properties, @@ -1121,5 +1154,6 @@ class _MyLoading extends StatelessWidget { } extension _XTargetPlatform on TargetPlatform { - bool get isApple => this == TargetPlatform.iOS || this == TargetPlatform.macOS; + bool get isApple => + this == TargetPlatform.iOS || this == TargetPlatform.macOS; } diff --git a/lib/src/widgets/custom_animated_toggle_switch.dart b/lib/src/widgets/custom_animated_toggle_switch.dart index aef6ab4..cb90b2d 100644 --- a/lib/src/widgets/custom_animated_toggle_switch.dart +++ b/lib/src/widgets/custom_animated_toggle_switch.dart @@ -10,7 +10,11 @@ typedef CustomIndicatorBuilder = Widget Function( /// Custom builder for the wrapper of the switch. typedef CustomWrapperBuilder = Widget Function( - BuildContext context, GlobalToggleProperties local, Widget child); + BuildContext context, GlobalToggleProperties global, Widget child); + +/// Custom builder for the dif section between the icons. +typedef CustomSeparatorBuilder = Widget Function(BuildContext context, + DifProperties local, DetailedGlobalToggleProperties global); /// Custom builder for the appearing animation of the indicator. /// @@ -108,6 +112,13 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { /// Space between the "indicator rooms" of the adjacent icons. final double dif; + /// Builder for divider or other separators between the icons. + /// + /// The available width is specified by [dif]. + /// + /// This builder is supported by [IconArrangement.row] only. + final CustomSeparatorBuilder? separatorBuilder; + /// Callback for tapping anywhere on the widget. final Function()? onTap; @@ -184,6 +195,7 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { this.indicatorSize = const Size(48.0, double.infinity), this.onChanged, this.dif = 0.0, + this.separatorBuilder, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, this.wrapperBuilder, @@ -211,6 +223,8 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { this.allowUnlistedValues = false, }) : assert(foregroundIndicatorBuilder != null || backgroundIndicatorBuilder != null), + assert(separatorBuilder == null || + (dif > 0 && iconArrangement == IconArrangement.row)), super(key: key); @override @@ -601,7 +615,9 @@ class _CustomAnimatedToggleSwitchState position: DecorationPosition.background, decoration: const BoxDecoration(), child: Stack( - clipBehavior: Clip.none, children: stack)), + clipBehavior: Clip.none, + children: stack, + )), ), ), ), @@ -629,9 +645,10 @@ class _CustomAnimatedToggleSwitchState properties.indicatorSize.width, height: properties.indicatorSize.height, child: widget.iconBuilder( - context, - LocalToggleProperties(value: widget.values[i], index: i), - properties), + context, + LocalToggleProperties(value: widget.values[i], index: i), + properties, + ), ); }).toList(); } @@ -639,21 +656,31 @@ class _CustomAnimatedToggleSwitchState /// The builder of the icons for [IconArrangement.row]. List _buildBackgroundRow( BuildContext context, DetailedGlobalToggleProperties properties) { + final length = properties.values.length; return [ Row( textDirection: _textDirectionOf(context), mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: List.generate( - widget.values.length, - (i) => SizedBox( - width: properties.indicatorSize.width, - height: properties.indicatorSize.height, - child: widget.iconBuilder( - context, - LocalToggleProperties(value: widget.values[i], index: i), - properties), - ), - ), + children: [ + for (int i = 0; i < length; i++) ...[ + SizedBox( + width: properties.indicatorSize.width, + height: properties.indicatorSize.height, + child: widget.iconBuilder( + context, + LocalToggleProperties(value: widget.values[i], index: i), + properties, + )), + if (i < length - 1 && widget.separatorBuilder != null) + SizedBox( + width: properties.dif, + child: Center( + child: widget.separatorBuilder!( + context, DifProperties(index: i), properties), + ), + ), + ] + ], ), ]; } From 40cec11cfe7f87974ce8abfa5f8698d837b9970c Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Thu, 13 Jul 2023 18:01:52 +0200 Subject: [PATCH 08/36] minor fixes --- example/lib/main.dart | 7 ++++--- lib/src/properties.dart | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index f9b81c8..d717d8a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -11,7 +11,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', + title: 'AnimatedToggleSwitch Demo', theme: ThemeData(), home: MyHomePage(title: 'Example'), ); @@ -293,14 +293,15 @@ class _MyHomePageState extends State { dif: 2.0, customSeparatorBuilder: (context, local, global) { final opacity = - ((global.position - local.position).abs() - 0.5).clamp(0.0, 1.0); + ((global.position - local.position).abs() - 0.5) + .clamp(0.0, 1.0); return VerticalDivider( indent: 10.0, endIndent: 10.0, color: Colors.white38.withOpacity(opacity)); }, customIconBuilder: (context, local, global) { - final text = const ['not', 'just', 'icons'][local.index]; + final text = const ['not', 'only', 'icons'][local.index]; return Center( child: Text(text, style: TextStyle( diff --git a/lib/src/properties.dart b/lib/src/properties.dart index 77237a3..247a008 100644 --- a/lib/src/properties.dart +++ b/lib/src/properties.dart @@ -168,8 +168,12 @@ class SizeProperties extends AnimatedToggleProperties { } class DifProperties { + /// Index of the separator. + /// + /// The separator is located between the items at [index] and [index+1]. final int index; + /// The position of the separator relative to the indices of the values. double get position => index + 0.5; const DifProperties({ From 496fe3c9c411e409830c6db3c3279d42143b9fc6 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Thu, 13 Jul 2023 18:25:20 +0200 Subject: [PATCH 09/36] adds example --- example/lib/main.dart | 53 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index d717d8a..4e86e71 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:animated_toggle_switch/animated_toggle_switch.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; void main() { @@ -70,10 +71,7 @@ class _MyHomePageState extends State { offset: Offset(0, 1.5), ), ], - onChanged: (b) { - setState(() => positive = b); - return Future.delayed(Duration(seconds: 2)); - }, + onChanged: (b) => setState(() => positive = b), colorBuilder: (b) => b ? Colors.red : Colors.green, iconBuilder: (value) => value ? Icon(Icons.coronavirus_rounded) @@ -82,6 +80,53 @@ class _MyHomePageState extends State { ? Center(child: Text('Oh no...')) : Center(child: Text('Nice :)')), ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'AnimatedToggleSwitch.dual with loading animation:', + textAlign: TextAlign.center, + ), + ), + DefaultTextStyle.merge( + style: const TextStyle(color: Colors.white), + child: IconTheme.merge( + data: IconThemeData(color: Colors.white), + child: AnimatedToggleSwitch.dual( + current: positive, + first: false, + second: true, + dif: 45.0, + borderColor: Colors.transparent, + borderWidth: 10.0, + height: 50, + loadingIconBuilder: (context, global) => + const CupertinoActivityIndicator(color: Colors.white), + borderRadius: BorderRadius.circular(4.0), + boxShadow: const [ + BoxShadow( + color: Colors.purple, + spreadRadius: 1, + blurRadius: 1, + offset: Offset(0, 0.5), + ), + ], + onChanged: (b) { + setState(() => positive = b); + return Future.delayed(Duration(seconds: 2)); + }, + innerColor: Colors.black, + colorBuilder: (b) => b ? Colors.purple : Colors.green, + iconBuilder: (value) => value + ? Icon(Icons.coronavirus_rounded) + : Icon(Icons.tag_faces_rounded), + textBuilder: (value) => value + ? Center( + child: Text('Oh no...', + style: const TextStyle(color: Colors.white))) + : Center(child: Text('Nice :)')), + ), + ), + ), Padding( padding: const EdgeInsets.all(8.0), child: Text( From ff0f07e241569048576e7ad5d8c2f6868813fd55 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Thu, 20 Jul 2023 09:36:00 +0200 Subject: [PATCH 10/36] adds colors & colorAnimationType --- .github/workflows/flutter.yml | 5 + CHANGELOG.md | 8 +- README.md | 32 +- example/lib/main.dart | 196 ++++++++-- example/pubspec.lock | 4 +- example/pubspec.yaml | 2 +- lib/src/widgets/animated_toggle_switch.dart | 382 +++++++++++++------- pubspec.yaml | 2 +- 8 files changed, 452 insertions(+), 179 deletions(-) diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 0399d01..5f7055e 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -26,3 +26,8 @@ jobs: - name: Run tests run: flutter test + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 3466c26..d0d9817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ -## 0.7.1 +## 0.8.0 - adds tests for all `AnimatedToggleSwitch` constructors -- adds `separatorBuilder` and `customSeparatorBuilder` to `AnimatedToggleSwitch` +- adds `separatorBuilder`, `customSeparatorBuilder`, `colors` and `colorAnimationType` to `AnimatedToggleSwitch` - adds `separatorBuilder` to `CustomAnimatedToggleSwitch` - fixes initial loading +- BREAKING: moves `innerColor`, `innerGradient`, `borderColor` and `indicatorColor` to `colors` +- BREAKING: merges `borderColorBuilder` with `colorBuilder` +- BREAKING: `indicatorAnimationType` handles `SwitchColors.indicatorColor` and `SwitchColors.indicatorBorderRadius` now +- BREAKING: renames `foregroundBoxShadow` to `indicatorBoxShadow` ## 0.7.0 (2023-06-19) diff --git a/README.md b/README.md index 6d64778..6423ac1 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ AnimatedToggleSwitch.rolling( values: [0, 1, 2, 3], onChanged: (i) => setState(() => value = i), iconBuilder: iconBuilder, + colors: SwitchColors(...), // optional color settings ... // many more parameters available ) ``` @@ -125,16 +126,19 @@ CustomAnimatedToggleSwitch( ```dart AnimatedToggleSwitch.size( current: value, - values: [0, 1, 2, 3], + values: const [0, 1, 2, 3], iconOpacity: 0.2, - indicatorSize: Size.fromWidth(100), + indicatorSize: const Size.fromWidth(100), + iconAnimationType: AnimationType.onHover, + colorAnimationType: AnimationType.onHover, iconBuilder: (value, size) { IconData data = Icons.access_time_rounded; if (value.isEven) data = Icons.cancel; return Icon(data, size: min(size.width, size.height)); }, - borderColor: value.isEven ? Colors.blue : Colors.red, - colorBuilder: (i) => i.isEven ? Colors.amber : Colors.red, + colors: const SwitchColors(borderColor: Colors.transparent), + borderWidth: 0.0, + colorBuilder: (i) => SwitchColors(indicatorColor: i.isEven == true ? Colors.amber : Colors.red), onChanged: (i) => setState(() => value = i), ) ``` @@ -143,10 +147,10 @@ AnimatedToggleSwitch.size( ```dart AnimatedToggleSwitch.size( - value: value, - values: [0, 1, 2, 3], + current: value, + values: const [0, 1, 2, 3], iconOpacity: 1.0, - indicatorSize: Size.fromWidth(25), + indicatorSize: const Size.fromWidth(25), foregroundIndicatorIconBuilder: (context, global) { double pos = global.position; double transitionValue = pos - pos.floorToDouble(); @@ -159,15 +163,15 @@ AnimatedToggleSwitch.size( Opacity( opacity: transitionValue, child: iconBuilder(pos.ceil(), global.indicatorSize)) - ]), - ); + ])); }, - selectedIconSize: Size.square(20), - iconSize: Size.square(20), + selectedIconSize: const Size.square(20), + iconSize: const Size.square(20), iconBuilder: iconBuilder, - colorBuilder: (i) => i.isEven ? Colors.green : Colors.tealAccent, + colors: const SwitchColors(borderColor: Colors.red), + colorBuilder: (i) => SwitchColors(indicatorColor: i.isEven == true ? Colors.green : Colors.tealAccent), onChanged: (i) => setState(() => value = i), borderRadius: BorderRadius.circular(8.0), - borderColor: Colors.red, -), + indicatorBorderRadius: BorderRadius.zero, +) ``` diff --git a/example/lib/main.dart b/example/lib/main.dart index 4e86e71..be5ea55 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -37,6 +37,7 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { ThemeData theme = Theme.of(context); + const green = Color(0xFF45CC0D); return Scaffold( appBar: AppBar( @@ -60,7 +61,7 @@ class _MyHomePageState extends State { first: false, second: true, dif: 50.0, - borderColor: Colors.transparent, + colors: const SwitchColors(borderColor: Colors.transparent), borderWidth: 5.0, height: 55, boxShadow: const [ @@ -72,7 +73,8 @@ class _MyHomePageState extends State { ), ], onChanged: (b) => setState(() => positive = b), - colorBuilder: (b) => b ? Colors.red : Colors.green, + colorBuilder: (b) => + SwitchColors(indicatorColor: b ? Colors.red : Colors.green), iconBuilder: (value) => value ? Icon(Icons.coronavirus_rounded) : Icon(Icons.tag_faces_rounded), @@ -80,6 +82,41 @@ class _MyHomePageState extends State { ? Center(child: Text('Oh no...')) : Center(child: Text('Nice :)')), ), + SizedBox(height: 16.0), + AnimatedToggleSwitch.dual( + current: positive, + first: false, + second: true, + dif: 50.0, + colors: const SwitchColors(borderColor: Colors.transparent), + borderWidth: 5.0, + height: 55, + boxShadow: const [ + BoxShadow( + color: Colors.black26, + spreadRadius: 1, + blurRadius: 2, + offset: Offset(0, 1.5), + ), + ], + onChanged: (b) => setState(() => positive = b), + colorBuilder: (b) => SwitchColors.withBorder( + backgroundColor: b ? Colors.white : Colors.black, + indicatorColor: b ? Colors.blue : Colors.red, + borderRadius: const BorderRadius.horizontal( + left: Radius.circular(4.0), right: Radius.circular(50.0)), + indicatorBorderRadius: BorderRadius.circular(b ? 50.0 : 4.0), + ), + iconBuilder: (value) => Icon( + value + ? Icons.access_time_rounded + : Icons.power_settings_new_rounded, + size: 32.0, + ), + textBuilder: (value) => value + ? const Center(child: Text('On', style: TextStyle(color: Colors.black))) + : const Center(child: Text('Off', style: TextStyle(color: Colors.white))), + ), Padding( padding: const EdgeInsets.all(8.0), child: Text( @@ -96,7 +133,10 @@ class _MyHomePageState extends State { first: false, second: true, dif: 45.0, - borderColor: Colors.transparent, + colors: const SwitchColors( + borderColor: Colors.transparent, + backgroundColor: Colors.black, + ), borderWidth: 10.0, height: 50, loadingIconBuilder: (context, global) => @@ -114,8 +154,8 @@ class _MyHomePageState extends State { setState(() => positive = b); return Future.delayed(Duration(seconds: 2)); }, - innerColor: Colors.black, - colorBuilder: (b) => b ? Colors.purple : Colors.green, + colorBuilder: (b) => SwitchColors( + indicatorColor: b ? Colors.purple : Colors.green), iconBuilder: (value) => value ? Icon(Icons.coronavirus_rounded) : Icon(Icons.tag_faces_rounded), @@ -127,6 +167,46 @@ class _MyHomePageState extends State { ), ), ), + SizedBox(height: 16.0), + DefaultTextStyle.merge( + style: const TextStyle( + color: Colors.white, + fontSize: 18.0, + fontWeight: FontWeight.bold), + child: IconTheme.merge( + data: IconThemeData(color: Colors.white), + child: AnimatedToggleSwitch.dual( + current: positive, + first: false, + second: true, + dif: 45.0, + colors: SwitchColors( + borderColor: Colors.transparent, + indicatorColor: Colors.white, + backgroundColor: Colors.black, + ), + colorBuilder: (value) => SwitchColors( + backgroundColor: value ? green : Colors.red[800]), + borderWidth: 6.0, + height: 60.0, + loadingIconBuilder: (context, global) => + CupertinoActivityIndicator( + color: Color.lerp( + Colors.red[800], green, global.position)), + onChanged: (b) { + setState(() => positive = b); + return Future.delayed(Duration(seconds: 2)); + }, + iconBuilder: (value) => value + ? Icon(Icons.power_outlined, color: green, size: 32.0) + : Icon(Icons.power_settings_new_rounded, + color: Colors.red[800], size: 32.0), + textBuilder: (value) => value + ? Center(child: Text('Active')) + : Center(child: Text('Inactive')), + ), + ), + ), Padding( padding: const EdgeInsets.all(8.0), child: Text( @@ -177,8 +257,8 @@ class _MyHomePageState extends State { .then((_) => setState(() => loading = false)); }, iconBuilder: rollingIconBuilder, - borderColor: Colors.transparent, - foregroundBoxShadow: const [ + colors: SwitchColors(borderColor: Colors.transparent), + indicatorBoxShadow: const [ BoxShadow( color: Colors.black26, spreadRadius: 1, @@ -196,6 +276,35 @@ class _MyHomePageState extends State { ], ), SizedBox(height: 16.0), + IconTheme.merge( + data: IconThemeData(color: Colors.white), + child: AnimatedToggleSwitch.rolling( + current: value, + values: const [0, 1, 2, 3], + onChanged: (i) => setState(() => value = i), + colors: SwitchColors( + indicatorColor: Colors.white, + borderColor: Colors.transparent, + ), + iconBuilder: coloredRollingIconBuilder, + borderWidth: 3.0, + colorAnimationType: AnimationType.onHover, + colorBuilder: (value) => SwitchColors.withBorder( + backgroundColor: colorBuilder(value), + borderRadius: BorderRadius.circular(value * 10.0), + indicatorBorderRadius: BorderRadius.circular(value * 10.0), + ), + boxShadow: const [ + BoxShadow( + color: Colors.black26, + spreadRadius: 1, + blurRadius: 2, + offset: Offset(0, 1.5), + ) + ], + ), + ), + SizedBox(height: 16.0), AnimatedToggleSwitch.rolling( current: value, allowUnlistedValues: true, @@ -204,12 +313,14 @@ class _MyHomePageState extends State { iconBuilder: rollingIconBuilder, separatorBuilder: (context, index) => const VerticalDivider(), borderWidth: 4.5, - indicatorColor: Colors.white, + colors: SwitchColors( + indicatorColor: Colors.white, + backgroundColor: Colors.amber, + borderColor: Colors.transparent, + ), borderRadius: BorderRadius.circular(10.0), - innerColor: Colors.amber, height: 55, dif: 20.0, - borderColor: Colors.transparent, loading: loading, ), SizedBox(height: 16.0), @@ -221,13 +332,14 @@ class _MyHomePageState extends State { onChanged: (i) => setState(() => nullableValue = i), iconBuilder: rollingIconBuilder, borderWidth: 4.5, - indicatorColor: Colors.white, - innerGradient: - const LinearGradient(colors: [Colors.red, Colors.blue]), - innerColor: Colors.amber, + colors: SwitchColors( + indicatorColor: Colors.white, + backgroundGradient: + const LinearGradient(colors: [Colors.red, Colors.blue]), + borderColor: Colors.transparent, + ), height: 55, dif: 20.0, - borderColor: Colors.transparent, loading: loading, ), Padding( @@ -306,7 +418,7 @@ class _MyHomePageState extends State { iconOpacity: 0.2, indicatorSize: const Size.fromWidth(100), iconAnimationType: AnimationType.onHover, - indicatorAnimationType: AnimationType.onHover, + colorAnimationType: AnimationType.onHover, iconBuilder: (value, size) { IconData data = Icons.access_time_rounded; if (value.isEven) data = Icons.cancel; @@ -315,10 +427,11 @@ class _MyHomePageState extends State { size: min(size.width, size.height), ); }, + colors: const SwitchColors(borderColor: Colors.transparent), borderWidth: 0.0, - borderColor: Colors.transparent, - colorBuilder: (i) => - i.isEven == true ? Colors.amber : Colors.red, + colorBuilder: (i) => SwitchColors( + indicatorColor: + i.isEven == true ? Colors.amber : Colors.red), onChanged: (i) { setState(() => value = i); return Future.delayed(Duration(seconds: 3)); @@ -329,12 +442,16 @@ class _MyHomePageState extends State { current: min(value, 2), borderRadius: BorderRadius.circular(10.0), indicatorBorderRadius: BorderRadius.zero, - innerColor: Color(0xFF919191), + colors: SwitchColors( + backgroundColor: Color(0xFF919191), + indicatorColor: Color(0xFFEC3345), + borderColor: Colors.transparent, + ), values: const [0, 1, 2], iconOpacity: 1.0, indicatorSize: const Size.fromWidth(100), iconAnimationType: AnimationType.onHover, - indicatorAnimationType: AnimationType.onHover, + colorAnimationType: AnimationType.onHover, dif: 2.0, customSeparatorBuilder: (context, local, global) { final opacity = @@ -354,8 +471,6 @@ class _MyHomePageState extends State { local.animationValue)))); }, borderWidth: 0.0, - borderColor: Colors.transparent, - colorBuilder: (i) => Color(0xFFEC3345), onChanged: (i) => setState(() => value = i), ), Padding( @@ -380,9 +495,10 @@ class _MyHomePageState extends State { ], ); }, - borderColor: value.isEven == true ? Colors.blue : Colors.red, - colorBuilder: (i) => - i.isEven == true ? Colors.amber : Colors.red, + colors: SwitchColors(borderColor: Colors.transparent), + colorBuilder: (i) => SwitchColors( + indicatorColor: + i.isEven == true ? Colors.amber : Colors.red), onChanged: (i) => setState(() => value = i), ), Padding( @@ -416,12 +532,13 @@ class _MyHomePageState extends State { selectedIconSize: const Size.square(20), iconSize: const Size.square(20), iconBuilder: iconBuilder, - colorBuilder: (i) => - i.isEven == true ? Colors.green : Colors.tealAccent, + colors: const SwitchColors(borderColor: Colors.red), + colorBuilder: (i) => SwitchColors( + indicatorColor: + i.isEven == true ? Colors.green : Colors.tealAccent), onChanged: (i) => setState(() => value = i), borderRadius: BorderRadius.circular(8.0), indicatorBorderRadius: BorderRadius.zero, - borderColor: Colors.red, ), Padding( padding: const EdgeInsets.all(8.0), @@ -458,6 +575,27 @@ class _MyHomePageState extends State { ); } + Color colorBuilder(int value) => switch (value) { + 0 => Colors.blueAccent, + 1 => Colors.green, + 2 => Colors.orangeAccent, + _ => Colors.red, + }; + + Widget coloredRollingIconBuilder(int value, Size iconSize, bool foreground) { + final color = foreground ? colorBuilder(value) : null; + return Icon( + switch (value) { + 0 => Icons.access_time_rounded, + 1 => Icons.check_circle_outline_rounded, + 2 => Icons.power_settings_new_rounded, + _ => Icons.lightbulb_outline_rounded, + }, + color: color, + size: iconSize.shortestSide, + ); + } + Widget iconBuilder(int value, Size iconSize) { return rollingIconBuilder(value, iconSize, false); } diff --git a/example/pubspec.lock b/example/pubspec.lock index da07f6e..928d22e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: path: ".." relative: true source: path - version: "0.7.0" + version: "0.8.0" async: dependency: transitive description: @@ -168,5 +168,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=3.0.0-0 <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=1.17.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 52e13b4..9f83a50 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -6,7 +6,7 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=3.0.0 <4.0.0' dependencies: flutter: diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart index d5c6e63..78c5293 100644 --- a/lib/src/widgets/animated_toggle_switch.dart +++ b/lib/src/widgets/animated_toggle_switch.dart @@ -25,14 +25,115 @@ typedef AnimatedIconBuilder = Widget Function( typedef IconBuilder = Widget Function(BuildContext context, LocalToggleProperties local, DetailedGlobalToggleProperties global); -typedef ColorBuilder = Color? Function(T value); +typedef ColorBuilder = SwitchColors? Function(T value); -/// Specifies when an icon should be highlighted. +typedef GradientBuilder = Gradient? Function(T value); + +class SwitchColors { + /// Background color of the indicator. + final Color? indicatorColor; + + /// Background color of the switch. + final Color? backgroundColor; + + /// Gradient of the background. Overwrites [innerColor] if not [null]. + final Gradient? backgroundGradient; + + /// Border color of the switch. + /// + /// For deactivating please set this to [Colors.transparent]. + final Color? borderColor; + + /// [BorderRadius] of the switch. + /// + /// If set, this value overwrites [AnimatedToggleSwitch.borderRadius]. + final BorderRadiusGeometry? borderRadius; + + /// [BorderRadius] of the indicator. + /// + /// If set, this value overwrites [AnimatedToggleSwitch.indicatorBorderRadius]. + final BorderRadiusGeometry? indicatorBorderRadius; + + /// Default constructor for [SwitchColors]. + /// + /// If you want to adapt the [BorderRadius], you should use [SwitchColors.withBorder] instead. + const SwitchColors({ + this.indicatorColor, + this.backgroundColor, + this.backgroundGradient, + this.borderColor, + }) : borderRadius = null, + indicatorBorderRadius = null; + + /// Constructor for adapting the [BorderRadius] of switch and indicator also. + /// + /// This should mainly be used with [AnimatedToggleSwitch.colorBuilder]. + const SwitchColors.withBorder({ + this.indicatorColor, + this.backgroundColor, + this.backgroundGradient, + this.borderColor, + this.borderRadius, + this.indicatorBorderRadius, + }); + + /// Private constructor for setting all possible parameters. + SwitchColors._({ + required this.indicatorColor, + required this.backgroundColor, + required this.backgroundGradient, + required this.borderColor, + required this.borderRadius, + required this.indicatorBorderRadius, + }); + + SwitchColors _merge(SwitchColors? other) => other == null + ? this + : SwitchColors._( + indicatorColor: other.indicatorColor ?? indicatorColor, + backgroundColor: other.backgroundColor ?? backgroundColor, + backgroundGradient: other.backgroundGradient ?? + (other.backgroundColor == null ? null : backgroundGradient), + borderColor: other.borderColor ?? borderColor, + borderRadius: other.borderRadius ?? borderRadius, + indicatorBorderRadius: other.indicatorBorderRadius ?? + indicatorBorderRadius ?? + other.borderRadius ?? + borderRadius, + ); + + static SwitchColors _lerp( + SwitchColors colors1, SwitchColors colors2, double t) => + SwitchColors._( + indicatorColor: + Color.lerp(colors1.indicatorColor, colors2.indicatorColor, t), + backgroundColor: + Color.lerp(colors1.backgroundColor, colors2.backgroundColor, t), + backgroundGradient: Gradient.lerp( + colors1.backgroundGradient ?? colors1.backgroundColor?.toGradient(), + colors2.backgroundGradient ?? colors2.backgroundColor?.toGradient(), + t, + ), + borderColor: Color.lerp(colors1.borderColor, colors2.borderColor, t), + borderRadius: BorderRadiusGeometry.lerp( + colors1.borderRadius, + colors2.borderRadius, + t, + ), + indicatorBorderRadius: BorderRadiusGeometry.lerp( + colors1.indicatorBorderRadius ?? colors1.borderRadius, + colors2.indicatorBorderRadius ?? colors2.borderRadius, + t, + ), + ); +} + +/// Specifies when an value should be animated. enum AnimationType { /// Starts an animation if an item is selected. onSelected, - /// Start an animation if an item is hovered by the indicator. + /// Starts an animation if an item is hovered by the indicator. onHover, } @@ -68,7 +169,14 @@ class AnimatedToggleSwitch extends StatelessWidget { /// The IconBuilder for all icons with the specified size. final AnimatedIconBuilder? animatedIconBuilder; - /// Builder for the color of the indicator depending on the current value. + /// The default colors for this switch. + /// + /// This value can be overwritten by [colorBuilder]. + final SwitchColors colors; + + /// Builder for the colors of the indicator depending on the current value. + /// + /// The return values overwrite the [colors] parameter if not [null]. final ColorBuilder? colorBuilder; /// Duration of the motion animation. @@ -96,21 +204,18 @@ class AnimatedToggleSwitch extends StatelessWidget { /// Width of the border of the switch. For deactivating please set this to 0.0 and set [borderColor] to Colors.transparent. final double borderWidth; - /// [BorderRadius] of the border. If this is null, the standard BorderRadius is taken. + /// [BorderRadius] of the switch. + /// + /// You can alternatively use [SwitchColors.withBorder] and [colorBuilder] for + /// animating this value. final BorderRadiusGeometry? borderRadius; /// [BorderRadius] of the indicator. Defaults to [borderRadius]. + /// + /// You can alternatively use [SwitchColors.withBorder] and [colorBuilder] for + /// animating this value. final BorderRadiusGeometry? indicatorBorderRadius; - /// Standard color of the border of the switch. For deactivating please set this to Colors.transparent and set [borderWidth] to 0.0. - final Color? borderColor; - - /// Color of the background. - final Color? innerColor; - - /// Gradient of the background. Overwrites [innerColor] if not null. - final Gradient? innerGradient; - /// Opacity for the icons. /// /// Please set [iconOpacity] and [selectedIconOpacity] to 1.0 for deactivating the AnimatedOpacity. @@ -121,7 +226,7 @@ class AnimatedToggleSwitch extends StatelessWidget { /// Please set [iconOpacity] and [selectedIconOpacity] to 1.0 for deactivating the AnimatedOpacity. final double selectedIconOpacity; - /// Space between the "indicator rooms" of the adjacent icons. + /// Space between the "indicator spaces" of the adjacent icons. final double dif; /// Total height of the widget. @@ -130,16 +235,18 @@ class AnimatedToggleSwitch extends StatelessWidget { /// If null, the indicator is behind the icons. Otherwise an icon is in the indicator and is built using this function. final CustomIndicatorBuilder? foregroundIndicatorIconBuilder; - /// Standard Indicator Color - final Color? indicatorColor; - - /// A builder for the Color of the Border. Can be used alternatively to [borderColor]. - final ColorBuilder? borderColorBuilder; - - /// Which iconAnimationType for the [animatedIconBuilder] should be taken? + /// The [AnimationType] for the [animatedIconBuilder]. final AnimationType iconAnimationType; - /// Which iconAnimationType for the indicator should be taken? + /// The [AnimationType] for [colorBuilder]. + /// + /// The [AnimationType] for [SwitchColors.indicatorColor] and [SwitchColors.indicatorBorderRadius] + /// is managed separately with [indicatorAnimationType]. + final AnimationType colorAnimationType; + + /// The [AnimationType] for the [SwitchColors.indicatorColor]. + /// + /// For the other colors, please use [colorAnimationType]. final AnimationType indicatorAnimationType; /// Callback for tapping anywhere on the widget. @@ -170,7 +277,7 @@ class AnimatedToggleSwitch extends StatelessWidget { final BoxBorder? indicatorBorder; /// Shadow for the indicator [Container]. - final List foregroundBoxShadow; + final List indicatorBoxShadow; /// Shadow for the [Container] in the background. final List boxShadow; @@ -208,7 +315,7 @@ class AnimatedToggleSwitch extends StatelessWidget { /// Defaults to [animationCurve]. final Curve? loadingAnimationCurve; - /// Indicates if an error should be thrown if [current] is not in [values]. + /// Indicates that no error should be thrown if [current] is not contained in [values]. /// /// If [allowUnlistedValues] is [true] and [values] does not contain [current], /// the indicator disappears with the specified [indicatorAppearingBuilder]. @@ -254,9 +361,7 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorSize = const Size(48.0, double.infinity), this.onChanged, this.borderWidth = 2, - this.borderColor, - this.innerColor, - this.indicatorColor, + this.colors = const SwitchColors(), this.colorBuilder, this.iconAnimationCurve = Curves.easeOutBack, this.iconAnimationDuration, @@ -266,13 +371,13 @@ class AnimatedToggleSwitch extends StatelessWidget { this.foregroundIndicatorIconBuilder, this.selectedIconOpacity = 1.0, this.height = 50.0, - this.borderColorBuilder, this.iconAnimationType = AnimationType.onSelected, - this.indicatorAnimationType = AnimationType.onSelected, + this.colorAnimationType = AnimationType.onSelected, + this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, this.indicatorBorder, - this.foregroundBoxShadow = const [], + this.indicatorBoxShadow = const [], this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, @@ -286,7 +391,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.loading, this.loadingAnimationDuration, this.loadingAnimationCurve, - this.innerGradient, this.allowUnlistedValues = false, this.indicatorAppearingBuilder = _defaultIndicatorAppearingBuilder, this.indicatorAppearingDuration = @@ -311,27 +415,25 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorSize = const Size(48.0, double.infinity), this.onChanged, this.borderWidth = 2, - this.borderColor, - this.innerColor, - this.indicatorColor, + this.colors = const SwitchColors(), this.colorBuilder, iconSize = const Size(23.0, 23.0), selectedIconSize = const Size(34.5, 34.5), this.iconAnimationCurve = Curves.easeOutBack, this.iconAnimationDuration, this.iconOpacity = 0.5, + this.selectedIconOpacity = 1.0, this.borderRadius, this.dif = 0.0, this.foregroundIndicatorIconBuilder, - this.selectedIconOpacity = 1.0, this.height = 50.0, - this.borderColorBuilder, this.iconAnimationType = AnimationType.onSelected, - this.indicatorAnimationType = AnimationType.onSelected, + this.colorAnimationType = AnimationType.onSelected, + this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, this.indicatorBorder, - this.foregroundBoxShadow = const [], + this.indicatorBoxShadow = const [], this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, @@ -345,7 +447,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.loading, this.loadingAnimationDuration, this.loadingAnimationCurve, - this.innerGradient, this.allowUnlistedValues = false, this.indicatorAppearingBuilder = _defaultIndicatorAppearingBuilder, this.indicatorAppearingDuration = @@ -374,9 +475,7 @@ class AnimatedToggleSwitch extends StatelessWidget { SizeIconBuilder? customIconBuilder, this.onChanged, this.borderWidth = 2, - this.borderColor, - this.innerColor, - this.indicatorColor, + this.colors = const SwitchColors(), this.colorBuilder, iconSize = const Size(0.5, 0.5), selectedIconSize = const Size(0.75, 0.75), @@ -387,13 +486,13 @@ class AnimatedToggleSwitch extends StatelessWidget { dif = 0.0, this.foregroundIndicatorIconBuilder, this.selectedIconOpacity = 1.0, - this.borderColorBuilder, this.iconAnimationType = AnimationType.onSelected, - this.indicatorAnimationType = AnimationType.onSelected, + this.colorAnimationType = AnimationType.onSelected, + this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, this.indicatorBorder, - this.foregroundBoxShadow = const [], + this.indicatorBoxShadow = const [], this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, @@ -407,7 +506,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.loading, this.loadingAnimationDuration, this.loadingAnimationCurve, - this.innerGradient, this.allowUnlistedValues = false, this.indicatorAppearingBuilder = _defaultIndicatorAppearingBuilder, this.indicatorAppearingDuration = @@ -467,9 +565,7 @@ class AnimatedToggleSwitch extends StatelessWidget { Size indicatorSize = const Size(1.0, 1.0), this.onChanged, this.borderWidth = 2, - this.borderColor, - this.innerColor, - this.indicatorColor, + this.colors = const SwitchColors(), this.colorBuilder, this.iconAnimationCurve = Curves.easeOutBack, this.iconAnimationDuration, @@ -478,13 +574,13 @@ class AnimatedToggleSwitch extends StatelessWidget { dif = 0.0, this.foregroundIndicatorIconBuilder, this.selectedIconOpacity = 1.0, - this.borderColorBuilder, this.iconAnimationType = AnimationType.onSelected, - this.indicatorAnimationType = AnimationType.onSelected, + this.colorAnimationType = AnimationType.onSelected, + this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, this.indicatorBorder, - this.foregroundBoxShadow = const [], + this.indicatorBoxShadow = const [], this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, @@ -498,7 +594,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.loading, this.loadingAnimationDuration, this.loadingAnimationCurve, - this.innerGradient, this.allowUnlistedValues = false, this.indicatorAppearingBuilder = _defaultIndicatorAppearingBuilder, this.indicatorAppearingDuration = @@ -533,21 +628,19 @@ class AnimatedToggleSwitch extends StatelessWidget { Size indicatorSize = const Size(1.0, 1.0), this.onChanged, this.borderWidth = 2, - this.borderColor, - this.innerColor, - this.indicatorColor, + this.colors = const SwitchColors(), this.colorBuilder, double iconRadius = 0.25, double selectedIconRadius = 0.35, this.iconOpacity = 0.5, this.borderRadius, double dif = 0.0, - this.borderColorBuilder, - this.indicatorAnimationType = AnimationType.onSelected, + this.colorAnimationType = AnimationType.onSelected, + this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, this.indicatorBorder, - this.foregroundBoxShadow = const [], + this.indicatorBoxShadow = const [], this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, @@ -563,7 +656,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.loadingAnimationCurve, ForegroundIndicatorTransitionType transitionType = ForegroundIndicatorTransitionType.rolling, - this.innerGradient, this.allowUnlistedValues = false, this.indicatorAppearingBuilder = _defaultIndicatorAppearingBuilder, this.indicatorAppearingDuration = @@ -611,9 +703,7 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorSize = const Size(46.0, double.infinity), this.onChanged, this.borderWidth = 2, - this.borderColor, - this.innerColor, - this.indicatorColor, + this.colors = const SwitchColors(), this.colorBuilder, double iconRadius = 11.5, double selectedIconRadius = 16.1, @@ -621,12 +711,12 @@ class AnimatedToggleSwitch extends StatelessWidget { this.borderRadius, this.dif = 0.0, this.height = 50.0, - this.borderColorBuilder, - this.indicatorAnimationType = AnimationType.onSelected, + this.colorAnimationType = AnimationType.onSelected, + this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, this.indicatorBorder, - this.foregroundBoxShadow = const [], + this.indicatorBoxShadow = const [], this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, @@ -642,7 +732,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.loadingAnimationCurve, ForegroundIndicatorTransitionType transitionType = ForegroundIndicatorTransitionType.rolling, - this.innerGradient, this.allowUnlistedValues = false, this.indicatorAppearingBuilder = _defaultIndicatorAppearingBuilder, this.indicatorAppearingDuration = @@ -765,9 +854,7 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorSize = const Size(46.0, double.infinity), this.onChanged, this.borderWidth = 2, - this.borderColor, - this.innerColor, - this.indicatorColor, + this.colors = const SwitchColors(), this.colorBuilder, double iconRadius = 16.1, this.borderRadius, @@ -775,12 +862,12 @@ class AnimatedToggleSwitch extends StatelessWidget { this.height = 50.0, this.iconAnimationDuration = const Duration(milliseconds: 500), this.iconAnimationCurve = Curves.easeInOut, - this.borderColorBuilder, + this.colorAnimationType = AnimationType.onHover, this.indicatorAnimationType = AnimationType.onHover, this.fittingMode = FittingMode.preventHorizontalOverlapping, Function()? onTap, this.indicatorBorder, - this.foregroundBoxShadow = const [], + this.indicatorBoxShadow = const [], this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, @@ -799,7 +886,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.loadingAnimationCurve, ForegroundIndicatorTransitionType transitionType = ForegroundIndicatorTransitionType.rolling, - this.innerGradient, }) : assert(clipAnimation || opacityAnimation), this.iconOpacity = 1.0, this.selectedIconOpacity = 1.0, @@ -904,9 +990,16 @@ class AnimatedToggleSwitch extends StatelessWidget { @override Widget build(BuildContext context) { ThemeData theme = Theme.of(context); - BorderRadiusGeometry borderRadius = + BorderRadiusGeometry defaultBorderRadius = this.borderRadius ?? BorderRadius.circular(height / 2); - Color indicatorColor = this.indicatorColor ?? theme.colorScheme.secondary; + final colors = SwitchColors._( + indicatorColor: theme.colorScheme.secondary, + backgroundColor: theme.scaffoldBackgroundColor, + backgroundGradient: null, + borderColor: theme.colorScheme.secondary, + borderRadius: defaultBorderRadius, + indicatorBorderRadius: indicatorBorderRadius ?? defaultBorderRadius, + )._merge(this.colors); return CustomAnimatedToggleSwitch( animationCurve: animationCurve, @@ -941,76 +1034,90 @@ class AnimatedToggleSwitch extends StatelessWidget { separatorBuilder!(context, local.index)), backgroundIndicatorBuilder: foregroundIndicatorIconBuilder != null ? null - : (context, properties) => _indicatorBuilder(context, properties, - indicatorColor, indicatorBorderRadius ?? borderRadius), + : (context, properties) => + _indicatorBuilder(context, properties, colors), foregroundIndicatorBuilder: foregroundIndicatorIconBuilder == null ? null - : (context, properties) => _indicatorBuilder(context, properties, - indicatorColor, indicatorBorderRadius ?? borderRadius), + : (context, properties) => + _indicatorBuilder(context, properties, colors), iconBuilder: (context, local, global) => _animatedOpacityIcon( _animatedSizeIcon(context, local, global), local.value == current), padding: EdgeInsets.all(borderWidth), - wrapperBuilder: (context, properties, child) => - TweenAnimationBuilder( - duration: animationDuration, - tween: ColorTween( - begin: borderColorBuilder?.call(current) ?? - borderColor ?? - theme.colorScheme.secondary, - end: borderColorBuilder?.call(current) ?? - borderColor ?? - theme.colorScheme.secondary, + wrapperBuilder: (context, properties, child) { + //TODO: extract this method in separate widget (performance improvement) + return _animationTypeBuilder( + context, + colorAnimationType, + (t) => colors._merge(colorBuilder?.call(t)), + SwitchColors._lerp, + (colors) => DecoratedBox( + decoration: BoxDecoration( + color: colors.backgroundColor, + gradient: colors.backgroundGradient, + borderRadius: colors.borderRadius, + boxShadow: boxShadow, ), - builder: (c, color, _) => Container( - clipBehavior: Clip.hardEdge, - foregroundDecoration: BoxDecoration( - border: Border.all(color: color!, width: borderWidth), - borderRadius: borderRadius, - ), + child: DecoratedBox( + position: DecorationPosition.foreground, decoration: BoxDecoration( - gradient: innerGradient, - // Redundant check - color: innerGradient != null - ? null - : (innerColor ?? theme.scaffoldBackgroundColor), - borderRadius: borderRadius, - boxShadow: boxShadow, + border: Border.all( + color: colors.borderColor!, + width: borderWidth, + ), + borderRadius: colors.borderRadius, + ), + child: ClipRRect( + borderRadius: colors.borderRadius, + child: child, ), - child: child, ), - )); + ), + properties, + ); + }); } - Widget _indicatorBuilder( - BuildContext context, - DetailedGlobalToggleProperties properties, - Color indicatorColor, - BorderRadiusGeometry borderRadius) { + Widget _animationTypeBuilder( + BuildContext context, + AnimationType animationType, + V Function(T item) valueProvider, + V Function(V value1, V value2, double t) lerp, + Widget Function(V value) builder, + GlobalToggleProperties properties, + ) { double pos = properties.position; - switch (iconAnimationType) { + switch (animationType) { case AnimationType.onSelected: - Color currentColor = colorBuilder?.call(current) ?? indicatorColor; - return TweenAnimationBuilder( - child: foregroundIndicatorIconBuilder?.call(context, properties), + V currentValue = valueProvider(current); + return TweenAnimationBuilder( duration: animationDuration, - tween: ColorTween(begin: currentColor, end: currentColor), - builder: (c, color, child) => _customIndicatorBuilder( - context, color!, borderRadius, child, properties)); + tween: _CustomTween(lerp, begin: currentValue, end: currentValue), + builder: (context, v, _) => builder(v)); case AnimationType.onHover: - return _customIndicatorBuilder( - context, - Color.lerp( - colorBuilder?.call(values[pos.floor()]) ?? indicatorColor, - colorBuilder?.call(values[pos.ceil()]) ?? indicatorColor, - pos - pos.floor()) ?? - indicatorColor, - borderRadius, - foregroundIndicatorIconBuilder?.call(context, properties), - properties, - ); + return builder(lerp(valueProvider(values[pos.floor()]), + valueProvider(values[pos.ceil()]), pos - pos.floor())); } } + Widget _indicatorBuilder(BuildContext context, + DetailedGlobalToggleProperties properties, SwitchColors colors) { + final child = foregroundIndicatorIconBuilder?.call(context, properties); + return _animationTypeBuilder( + context, + indicatorAnimationType, + (item) => colors._merge(colorBuilder?.call(item)), + SwitchColors._lerp, + (colors) => _customIndicatorBuilder( + context, + colors.indicatorColor!, + colors.indicatorBorderRadius!, + child, + properties, + ), + properties, + ); + } + Widget _animatedIcon(BuildContext context, AnimatedToggleProperties local, DetailedGlobalToggleProperties global) { return Opacity( @@ -1049,7 +1156,9 @@ class AnimatedToggleSwitch extends StatelessWidget { return _animatedIcon( context, AnimatedToggleProperties.fromLocal( - animationValue: animationValue, properties: local), + animationValue: animationValue, + properties: local, + ), global, ); } @@ -1077,7 +1186,7 @@ class AnimatedToggleSwitch extends StatelessWidget { color: color, borderRadius: borderRadius, border: indicatorBorder, - boxShadow: foregroundBoxShadow, + boxShadow: indicatorBoxShadow, ), child: Center( child: Stack( @@ -1153,7 +1262,20 @@ class _MyLoading extends StatelessWidget { } } +class _CustomTween extends Tween { + final V Function(V value1, V value2, double t) lerpFunction; + + _CustomTween(this.lerpFunction, {super.begin, super.end}); + + @override + V lerp(double t) => lerpFunction(begin!, end!, t); +} + extension _XTargetPlatform on TargetPlatform { bool get isApple => this == TargetPlatform.iOS || this == TargetPlatform.macOS; } + +extension _XColorToGradient on Color { + Gradient toGradient() => LinearGradient(colors: [this, this]); +} diff --git a/pubspec.yaml b/pubspec.yaml index ac22a66..06da78e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: animated_toggle_switch description: Fully customizable, draggable and animated switch with multiple choices and smooth loading animation. It has prebuilt constructors for rolling and size animations. -version: 0.7.0 +version: 0.8.0 repository: https://github.com/SplashByte/animated_toggle_switch issue_tracker: https://github.com/SplashByte/animated_toggle_switch/issues From fbbc6dedb53903f1075e067a8806487b36b9acba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Thu, 20 Jul 2023 09:39:46 +0200 Subject: [PATCH 11/36] Update flutter.yml --- .github/workflows/flutter.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 5f7055e..7cd51de 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -1,10 +1,6 @@ name: Flutter Tests -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] +on: [push, pull_request] jobs: build: From 9c4fa3882dea7f1da7518b0af7a9b818e9a68c49 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Thu, 20 Jul 2023 09:43:58 +0200 Subject: [PATCH 12/36] dart format --- .github/workflows/flutter.yml | 2 +- example/lib/main.dart | 8 +++- test/gesture_test.dart | 2 +- test/keys.dart | 2 +- test/loading_test.dart | 86 +++++++++++++++++------------------ 5 files changed, 52 insertions(+), 48 deletions(-) diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 5f7055e..572e128 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -1,4 +1,4 @@ -name: Flutter Tests +name: Flutter Analyze & Test on: push: diff --git a/example/lib/main.dart b/example/lib/main.dart index be5ea55..33d7e4a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -114,8 +114,12 @@ class _MyHomePageState extends State { size: 32.0, ), textBuilder: (value) => value - ? const Center(child: Text('On', style: TextStyle(color: Colors.black))) - : const Center(child: Text('Off', style: TextStyle(color: Colors.white))), + ? const Center( + child: + Text('On', style: TextStyle(color: Colors.black))) + : const Center( + child: + Text('Off', style: TextStyle(color: Colors.white))), ), Padding( padding: const EdgeInsets.all(8.0), diff --git a/test/gesture_test.dart b/test/gesture_test.dart index 2d01e18..b1646c3 100644 --- a/test/gesture_test.dart +++ b/test/gesture_test.dart @@ -86,7 +86,7 @@ void main() { await tester.drag(nextFinder, tester.getCenter(currentFinder), warnIfMissed: false); - + verifyNoMoreInteractions(changedFunction); verifyNoMoreInteractions(tapFunction); }); diff --git a/test/keys.dart b/test/keys.dart index c7ef54b..1dbcccf 100644 --- a/test/keys.dart +++ b/test/keys.dart @@ -16,4 +16,4 @@ class IconKey extends LocalKey { @override int get hashCode => foreground.hashCode ^ value.hashCode; -} \ No newline at end of file +} diff --git a/test/loading_test.dart b/test/loading_test.dart index de53f2a..786bd7b 100644 --- a/test/loading_test.dart +++ b/test/loading_test.dart @@ -77,49 +77,49 @@ void main() { }, testDual: false); defaultTestAllSwitches('Switch supports initial loading', - (tester, buildSwitch, values) async { - final current = values.first; - - await tester.pumpWidget(TestWrapper( - child: buildSwitch( - current: current, - iconBuilder: iconBuilder, - loading: true, - ), - )); - final currentFinder = find.byKey(iconKey(current, foreground: true)); - final loadingFinder = find.byKey(loadingIconKey); - - expect(loadingFinder, findsOneWidget); - expect(currentFinder, findsNothing); - }); + (tester, buildSwitch, values) async { + final current = values.first; + + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + loading: true, + ), + )); + final currentFinder = find.byKey(iconKey(current, foreground: true)); + final loadingFinder = find.byKey(loadingIconKey); + + expect(loadingFinder, findsOneWidget); + expect(currentFinder, findsNothing); + }); defaultTestAllSwitches('Switch disables loading by setting loading to false', - (tester, buildSwitch, values) async { - final current = values.first; - final next = values.last; - const loadingDuration = Duration(seconds: 3); - - await tester.pumpWidget(TestWrapper( - child: buildSwitch( - current: current, - iconBuilder: iconBuilder, - onTap: () => Future.delayed(loadingDuration), - onChanged: (_) => Future.delayed(loadingDuration), - loading: false, - ), - )); - final nextFinder = find.byKey(iconKey(next)); - final loadingFinder = find.byKey(loadingIconKey); - - expect(loadingFinder, findsNothing); - - await tester.tap(nextFinder, warnIfMissed: false); - await tester.pump(Duration.zero); - await tester.pump(const Duration(milliseconds: 500)); - - expect(loadingFinder, findsNothing); - - await tester.pump(loadingDuration); - }); + (tester, buildSwitch, values) async { + final current = values.first; + final next = values.last; + const loadingDuration = Duration(seconds: 3); + + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + onTap: () => Future.delayed(loadingDuration), + onChanged: (_) => Future.delayed(loadingDuration), + loading: false, + ), + )); + final nextFinder = find.byKey(iconKey(next)); + final loadingFinder = find.byKey(loadingIconKey); + + expect(loadingFinder, findsNothing); + + await tester.tap(nextFinder, warnIfMissed: false); + await tester.pump(Duration.zero); + await tester.pump(const Duration(milliseconds: 500)); + + expect(loadingFinder, findsNothing); + + await tester.pump(loadingDuration); + }); } From 3b0b8093586cafbba8046f6361b45394b82d3709 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Thu, 20 Jul 2023 20:47:00 +0200 Subject: [PATCH 13/36] documentation improvements --- .github/workflows/flutter.yml | 2 +- lib/src/widgets/animated_toggle_switch.dart | 4 +++- .../custom_animated_toggle_switch.dart | 24 +++++++++---------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 1dc9094..9358013 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -21,7 +21,7 @@ jobs: run: flutter analyze - name: Run tests - run: flutter test + run: flutter test --coverage - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart index 78c5293..23ed53c 100644 --- a/lib/src/widgets/animated_toggle_switch.dart +++ b/lib/src/widgets/animated_toggle_switch.dart @@ -67,7 +67,9 @@ class SwitchColors { /// Constructor for adapting the [BorderRadius] of switch and indicator also. /// - /// This should mainly be used with [AnimatedToggleSwitch.colorBuilder]. + /// This constructor should mainly be used with [AnimatedToggleSwitch.colorBuilder] + /// because the [BorderRadius] can be adapted with [AnimatedToggleSwitch.borderRadius] + /// and [AnimatedToggleSwitch.indicatorBorderRadius] otherwise. const SwitchColors.withBorder({ this.indicatorColor, this.backgroundColor, diff --git a/lib/src/widgets/custom_animated_toggle_switch.dart b/lib/src/widgets/custom_animated_toggle_switch.dart index cb90b2d..20a73b9 100644 --- a/lib/src/widgets/custom_animated_toggle_switch.dart +++ b/lib/src/widgets/custom_animated_toggle_switch.dart @@ -42,11 +42,11 @@ enum IconArrangement { row, /// Indicates that the icons should overlap. - /// Normally you don't need this setting unless you want the icons to overlap. + /// Normally you don't need this setting. /// /// This is used for example with [AnimatedToggleSwitch.dual], /// because the texts partially overlap here. - overlap + overlap, } /// With this widget you can implement your own switches with nice animations. @@ -59,13 +59,13 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { /// [current] has to be in [values] for working correctly if [allowUnlistedValues] is false. final T current; - /// All possible values. + /// All selectable values. final List values; - /// The IconBuilder for all icons with the specified size. + /// The builder for the wrapper around the switch. final CustomWrapperBuilder? wrapperBuilder; - /// The IconBuilder for all icons with the specified size. + /// The builder for all icons. final CustomIconBuilder iconBuilder; /// A builder for an indicator which is in front of the icons. @@ -133,7 +133,7 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { /// The [FittingMode] of the switch. /// - /// Change this only if you don't want the switch to adjust when the constraints are too small. + /// Change this only if you don't want the switch to adjust if the constraints are too small. final FittingMode fittingMode; /// The height of the whole switch including wrapper. @@ -243,10 +243,10 @@ class _CustomAnimatedToggleSwitchState late final AnimationController _appearingController; /// The [Animation] for the movement of the indicator. - late CurvedAnimation _animation; + late final CurvedAnimation _animation; /// The [Animation] for the appearing of the indicator. - late CurvedAnimation _appearingAnimation; + late final CurvedAnimation _appearingAnimation; /// The current state of the movement of the indicator. late _AnimationInfo _animationInfo; @@ -437,13 +437,13 @@ class _CustomAnimatedToggleSwitchState constraints.maxWidth.isFinite || (widget.indicatorSize.width.isFinite && dif.isFinite), - "With unbound width constraints " - "the width of the indicator and the dif " + 'With unbound width constraints ' + 'the width of the indicator and the dif ' "can't be infinite"); assert( widget.indicatorSize.width.isFinite || dif.isFinite, - "The width of the indicator " - "or the dif must be finite."); + 'The width of the indicator ' + 'or the dif must be finite.'); // Recalculates the indicatorSize if its width or height is // infinite. From 2fcae8da66591a9703c95a2a4e61cd09cab7f683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Thu, 20 Jul 2023 22:12:46 +0200 Subject: [PATCH 14/36] Update README.md --- README.md | 53 +++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 6423ac1..a197c58 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,13 @@ [![popularity](https://img.shields.io/pub/popularity/animated_toggle_switch)](https://pub.dev/packages/animated_toggle_switch/score) [![pub points](https://img.shields.io/pub/points/animated_toggle_switch)](https://pub.dev/packages/animated_toggle_switch/score) [![license](https://img.shields.io/github/license/SplashByte/animated_toggle_switch.svg)](https://github.com/SplashByte/animated_toggle_switch/blob/main/LICENSE) +[![codecov](https://codecov.io/gh/splashbyte/animated_toggle_switch/branch/main/graph/badge.svg?token=NY1D6W88H2)](https://codecov.io/gh/splashbyte/animated_toggle_switch) -[![buy me a coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/splashbyte) +[![buy me a coffee](https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20pizza&emoji=🍕&slug=splashbyte&button_colour=FF8838&font_colour=000000&font_family=Poppins&outline_colour=000000&coffee_colour=ffffff')](https://www.buymeacoffee.com/splashbyte) ### If you like this package, please leave a like there on [pub.dev](https://pub.dev/packages/animated_toggle_switch) and star on [GitHub](https://github.com/SplashByte/animated_toggle_switch). -Fully customizable, draggable and animated switch with multiple choices and smooth loading animation. It has prebuilt constructors for rolling and size animations, but it also allows you to create your own switches with `CustomAnimatedToggleSwitch`. +Fully customizable, draggable and animated switch with multiple choices and [smooth loading animation](#simple-rolling-animation-with-loading). It has prebuilt constructors for rolling and size animations, but it also allows you to create your own switches with `CustomAnimatedToggleSwitch`. `LTR` and `RTL` are both supported. [Switches without an (initial) selection](#simple-rolling-animation-with-nullable-selection) are also possible. Most builder arguments of `AnimatedToggleSwitch` have a standard and a custom version. This ensures that you can get started easily and still customize a lot if necessary. @@ -23,39 +24,37 @@ For a slider with a similar look, you can check out [action_slider](https://pub. ### Examples `AnimatedToggleSwitch.dual()` -![animated_toggle_switch_example_dual](https://user-images.githubusercontent.com/43761463/161432631-e6dd3d16-7b64-410b-a9fa-c956d3442598.gif) -`AnimatedToggleSwitch.dual()` with loading animation -![animated_toggle_switch_example_dual_loading](https://user-images.githubusercontent.com/43761463/209120973-1d4d4fdc-3274-4747-9f6e-9523a75830e7.gif) +![animated_toggle_switch_example_dual](https://user-images.githubusercontent.com/43761463/161432631-e6dd3d16-7b64-410b-a9fa-c956d3442598.gif) +![animated_toggle_switch_example_borderradius_builder](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/985492df-82a4-4225-a0ad-500970ad692d) + +Switch inspired by [lite_rolling_switch](https://pub.dev/packages/lite_rolling_switch) (made with `AnimatedToggleSwitch.dual()`) +![animated_toggle_switch_example_lite_rolling_switch_highres](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/357c31b5-710f-47e9-9132-07779371c1a6) + +Switch inspired by [toggle_switch](https://pub.dev/packages/toggle_switch) (made with `AnimatedToggleSwitch.size()`) +![animated_toggle_switch_example_toggle_switch](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/4812efdc-fe9a-4c34-808a-0983de65d2a4) + + `AnimatedToggleSwitch.rolling()` -![animated_toggle_switch_example_1](https://user-images.githubusercontent.com/43761463/161432579-9fe81c57-6463-45c3-a48f-75db666a3a22.gif) -`AnimatedToggleSwitch.rolling()` with no initial selection -![animated_toggle_switch_example_unlisted_value](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/570f39e8-bc5c-4a19-a91a-d186d4bbd8fe) -`AnimatedToggleSwitch.rolling()` with loading animation -![animated_toggle_switch_example_rolling_loading](https://user-images.githubusercontent.com/43761463/209121057-2ff2bfc3-063e-4704-a981-f5cc5f54720a.gif) +![animated_toggle_switch_example_1](https://user-images.githubusercontent.com/43761463/161432579-9fe81c57-6463-45c3-a48f-75db666a3a22.gif) +![animated_toggle_switch_example_2](https://user-images.githubusercontent.com/43761463/161432589-d76f61f6-cb97-42e2-b1fd-8c5203a965fa.gif) +![animated_toggle_switch_example_gradient](https://user-images.githubusercontent.com/43761463/209117203-90a41ddc-db1c-41be-8375-5304317d1352.gif) +![animated_toggle_switch_example_borderradius_builder_2](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/e9a6328e-fc6a-4080-9868-1f0eaf60f6db) -Modified `AnimatedToggleSwitch.rolling()` -![animated_toggle_switch_example_2](https://user-images.githubusercontent.com/43761463/161432589-d76f61f6-cb97-42e2-b1fd-8c5203a965fa.gif) -![animated_toggle_switch_example_3](https://user-images.githubusercontent.com/43761463/161432610-e8e4014a-34f1-4cbc-876b-2ab6ef856484.gif) -![animated_toggle_switch_example_gradient](https://user-images.githubusercontent.com/43761463/209117203-90a41ddc-db1c-41be-8375-5304317d1352.gif) You can build any other switch with `CustomAnimatedToggleSwitch()` ![animated_toggle_switch_example_custom_1](https://user-images.githubusercontent.com/43761463/161433015-c3ec634a-38da-463d-a06e-4ae0b29f77ed.gif) `AnimatedToggleSwitch.size()` -![animated_toggle_switch_example_4](https://user-images.githubusercontent.com/43761463/161432714-435d8369-7e54-432a-8b9b-6b55a0764f4a.gif) -`AnimatedToggleSwitch.size()` with loading animation -![animated_toggle_switch_example_size_loading](https://user-images.githubusercontent.com/43761463/209121115-ed0f634b-0ec4-46b5-b030-21dbdde8cf07.gif) - -Modified `AnimatedToggleSwitch.size()` -![animated_toggle_switch_example_5](https://user-images.githubusercontent.com/43761463/161432720-1d5fa49e-6d20-401a-9a90-a6df88873266.gif) +![animated_toggle_switch_example_4](https://user-images.githubusercontent.com/43761463/161432714-435d8369-7e54-432a-8b9b-6b55a0764f4a.gif) +![animated_toggle_switch_example_size_loading](https://user-images.githubusercontent.com/43761463/209121115-ed0f634b-0ec4-46b5-b030-21dbdde8cf07.gif) +![animated_toggle_switch_example_5](https://user-images.githubusercontent.com/43761463/161432720-1d5fa49e-6d20-401a-9a90-a6df88873266.gif) `AnimatedToggleSwitch.size()` with custom rolling animation - ![animated_toggle_switch_example_6](https://user-images.githubusercontent.com/43761463/161432744-f60b660d-30d9-4d1d-9b87-14b62bc54e39.gif) - +![animated_toggle_switch_example_6](https://user-images.githubusercontent.com/43761463/161432744-f60b660d-30d9-4d1d-9b87-14b62bc54e39.gif) + `AnimatedToggleSwitch.rolling()` with custom `indicatorSize`, `borderRadius` and `indicatorBorderRadius` -![animated_toggle_switch_example_7](https://user-images.githubusercontent.com/43761463/161432823-6cf3c855-2a9a-4f4a-9e5c-2951c4166f49.gif) -![animated_toggle_switch_example_8](https://user-images.githubusercontent.com/43761463/161432826-4b0c3e57-eed7-4567-8e7e-31b8a2ba6bee.gif) +![animated_toggle_switch_example_7](https://user-images.githubusercontent.com/43761463/161432823-6cf3c855-2a9a-4f4a-9e5c-2951c4166f49.gif) ![animated_toggle_switch_example_8](https://user-images.githubusercontent.com/43761463/161432826-4b0c3e57-eed7-4567-8e7e-31b8a2ba6bee.gif) ## Easy Usage @@ -75,6 +74,7 @@ AnimatedToggleSwitch.rolling( ``` ### Simple rolling animation with loading +![animated_toggle_switch_example_rolling_loading](https://user-images.githubusercontent.com/43761463/209121057-2ff2bfc3-063e-4704-a981-f5cc5f54720a.gif) To use the loading animation, you simply have to return a `Future` in `onChanged` or `onTap`. You can alternatively control the loading manually with the `loading` parameter. Hence, to disable the loading animation, `loading: false` must be set. @@ -94,6 +94,7 @@ AnimatedToggleSwitch.rolling( ``` ### Simple rolling animation with nullable selection +![animated_toggle_switch_example_unlisted_value](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/570f39e8-bc5c-4a19-a91a-d186d4bbd8fe) To use this feature, you simply have to set `allowUnlistedValues` to `true`. ```dart @@ -122,7 +123,7 @@ CustomAnimatedToggleSwitch( ``` ### `AnimatedToggleSwitch.size` with some settings - +![animated_toggle_switch_example_4](https://user-images.githubusercontent.com/43761463/161432714-435d8369-7e54-432a-8b9b-6b55a0764f4a.gif) ```dart AnimatedToggleSwitch.size( current: value, @@ -144,7 +145,7 @@ AnimatedToggleSwitch.size( ``` ### Self-made rolling animation (with `foregroundIndicatorIconBuilder`) - +![animated_toggle_switch_example_6](https://user-images.githubusercontent.com/43761463/161432744-f60b660d-30d9-4d1d-9b87-14b62bc54e39.gif) ```dart AnimatedToggleSwitch.size( current: value, From 9dae4deffa0b469b780e985dd693581863a8c2f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Thu, 20 Jul 2023 22:15:21 +0200 Subject: [PATCH 15/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a197c58..4273897 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![license](https://img.shields.io/github/license/SplashByte/animated_toggle_switch.svg)](https://github.com/SplashByte/animated_toggle_switch/blob/main/LICENSE) [![codecov](https://codecov.io/gh/splashbyte/animated_toggle_switch/branch/main/graph/badge.svg?token=NY1D6W88H2)](https://codecov.io/gh/splashbyte/animated_toggle_switch) -[![buy me a coffee](https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20pizza&emoji=🍕&slug=splashbyte&button_colour=FF8838&font_colour=000000&font_family=Poppins&outline_colour=000000&coffee_colour=ffffff')](https://www.buymeacoffee.com/splashbyte) +[![buy me a coffee](https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20pizza&emoji=🍕&slug=splashbyte&button_colour=FF8838&font_colour=ffffff&font_family=Poppins&outline_colour=000000&coffee_colour=ffffff')](https://www.buymeacoffee.com/splashbyte) ### If you like this package, please leave a like there on [pub.dev](https://pub.dev/packages/animated_toggle_switch) and star on [GitHub](https://github.com/SplashByte/animated_toggle_switch). From 8303306e199ebc1d9e9a71078f148460416f7919 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Fri, 21 Jul 2023 09:26:06 +0200 Subject: [PATCH 16/36] introduces active parameter --- .../widgets/custom_animated_toggle_switch.dart | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/src/widgets/custom_animated_toggle_switch.dart b/lib/src/widgets/custom_animated_toggle_switch.dart index 20a73b9..0f62f01 100644 --- a/lib/src/widgets/custom_animated_toggle_switch.dart +++ b/lib/src/widgets/custom_animated_toggle_switch.dart @@ -185,6 +185,9 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { /// the indicator disappears with the specified [indicatorAppearingBuilder]. final bool allowUnlistedValues; + /// Indicates if the switch is active. + final bool active = true; + const CustomAnimatedToggleSwitch({ Key? key, required this.current, @@ -325,6 +328,9 @@ class _CustomAnimatedToggleSwitchState if (oldWidget.animationCurve != widget.animationCurve) { _animation.curve = widget.animationCurve; } + if (oldWidget.active != widget.active && !widget.active) { + _onDragEnd(); + } _checkValuePosition(); if (oldWidget.loading != widget.loading) { @@ -332,7 +338,10 @@ class _CustomAnimatedToggleSwitchState } } + bool get _isActive => widget.active && !_animationInfo.loading; + void _onChanged(T value) { + if (!_isActive) return; var result = widget.onChanged?.call(value); if (result is Future && widget.loading == null) { _loading(true); @@ -341,7 +350,7 @@ class _CustomAnimatedToggleSwitchState } void _onTap() { - if (_animationInfo.loading) return; + if (!_isActive) return; var result = widget.onTap?.call(); if (result is Future && widget.loading == null) { _loading(true); @@ -504,7 +513,7 @@ class _CustomAnimatedToggleSwitchState : position; bool isHoveringIndicator(Offset offset) { - if (_animationInfo.loading || widget._isCurrentUnlisted) + if (!_isActive || widget._isCurrentUnlisted) return false; double dx = textDirection == TextDirection.rtl ? width - offset.dx @@ -705,7 +714,7 @@ class _CustomAnimatedToggleSwitchState /// Starts the dragging of the indicator and starts the animation to /// the current cursor position. void _onDragged(double indexPosition, double pos) { - if (_animationInfo.loading) return; + if (!_isActive) return; _animationInfo = _animationInfo.dragged(indexPosition, pos: pos); _controller.duration = widget.dragStartDuration; _animation.curve = widget.dragStartCurve; From 01c6b94d50c8a2dfa1b9e23389c2a89621e73426 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Fri, 21 Jul 2023 09:29:01 +0200 Subject: [PATCH 17/36] dart format --- lib/src/widgets/custom_animated_toggle_switch.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/widgets/custom_animated_toggle_switch.dart b/lib/src/widgets/custom_animated_toggle_switch.dart index 0f62f01..3d2eb62 100644 --- a/lib/src/widgets/custom_animated_toggle_switch.dart +++ b/lib/src/widgets/custom_animated_toggle_switch.dart @@ -513,8 +513,7 @@ class _CustomAnimatedToggleSwitchState : position; bool isHoveringIndicator(Offset offset) { - if (!_isActive || widget._isCurrentUnlisted) - return false; + if (!_isActive || widget._isCurrentUnlisted) return false; double dx = textDirection == TextDirection.rtl ? width - offset.dx : offset.dx; From 61b276200c81f34144030d8feaa032b9774ae5a9 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Fri, 28 Jul 2023 14:17:32 +0200 Subject: [PATCH 18/36] renames SwitchColors to ToggleStyle --- CHANGELOG.md | 8 +- README.md | 12 +- example/lib/main.dart | 121 ++++++--- lib/src/properties.dart | 20 +- lib/src/widgets/animated_toggle_switch.dart | 273 +++++++++++--------- test/helper.dart | 54 ++++ test/style_test.dart | 19 ++ 7 files changed, 334 insertions(+), 173 deletions(-) create mode 100644 test/style_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index d0d9817..e7be684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ ## 0.8.0 - adds tests for all `AnimatedToggleSwitch` constructors -- adds `separatorBuilder`, `customSeparatorBuilder`, `colors` and `colorAnimationType` to `AnimatedToggleSwitch` +- adds `separatorBuilder`, `customSeparatorBuilder`, `style` and `styleAnimationType` to `AnimatedToggleSwitch` - adds `separatorBuilder` to `CustomAnimatedToggleSwitch` - fixes initial loading -- BREAKING: moves `innerColor`, `innerGradient`, `borderColor` and `indicatorColor` to `colors` -- BREAKING: merges `borderColorBuilder` with `colorBuilder` -- BREAKING: `indicatorAnimationType` handles `SwitchColors.indicatorColor` and `SwitchColors.indicatorBorderRadius` now +- BREAKING: moves `innerColor`, `innerGradient`, `borderColor`, `indicatorColor`, `borderRadius` and `indicatorBorderColor` to `style` +- BREAKING: merges `borderColorBuilder` with `styleBuilder` +- BREAKING: `indicatorAnimationType` handles `ToggleStyle.indicatorColor` and `ToggleStyle.indicatorBorderRadius` now - BREAKING: renames `foregroundBoxShadow` to `indicatorBoxShadow` ## 0.7.0 (2023-06-19) diff --git a/README.md b/README.md index 4273897..babd4a8 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ AnimatedToggleSwitch.rolling( values: [0, 1, 2, 3], onChanged: (i) => setState(() => value = i), iconBuilder: iconBuilder, - colors: SwitchColors(...), // optional color settings + style: ToggleStyle(...), // optional style settings ... // many more parameters available ) ``` @@ -137,7 +137,7 @@ AnimatedToggleSwitch.size( if (value.isEven) data = Icons.cancel; return Icon(data, size: min(size.width, size.height)); }, - colors: const SwitchColors(borderColor: Colors.transparent), + style: const ToggleStyle(borderColor: Colors.transparent), borderWidth: 0.0, colorBuilder: (i) => SwitchColors(indicatorColor: i.isEven == true ? Colors.amber : Colors.red), onChanged: (i) => setState(() => value = i), @@ -169,10 +169,12 @@ AnimatedToggleSwitch.size( selectedIconSize: const Size.square(20), iconSize: const Size.square(20), iconBuilder: iconBuilder, - colors: const SwitchColors(borderColor: Colors.red), + style: ToggleStyle( + borderColor: Colors.red, + borderRadius: BorderRadius.circular(8.0), + indicatorBorderRadius: BorderRadius.zero, + ), colorBuilder: (i) => SwitchColors(indicatorColor: i.isEven == true ? Colors.green : Colors.tealAccent), onChanged: (i) => setState(() => value = i), - borderRadius: BorderRadius.circular(8.0), - indicatorBorderRadius: BorderRadius.zero, ) ``` diff --git a/example/lib/main.dart b/example/lib/main.dart index 33d7e4a..459fcaf 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -61,7 +61,7 @@ class _MyHomePageState extends State { first: false, second: true, dif: 50.0, - colors: const SwitchColors(borderColor: Colors.transparent), + style: const ToggleStyle(borderColor: Colors.transparent), borderWidth: 5.0, height: 55, boxShadow: const [ @@ -73,8 +73,8 @@ class _MyHomePageState extends State { ), ], onChanged: (b) => setState(() => positive = b), - colorBuilder: (b) => - SwitchColors(indicatorColor: b ? Colors.red : Colors.green), + styleBuilder: (b) => + ToggleStyle(indicatorColor: b ? Colors.red : Colors.green), iconBuilder: (value) => value ? Icon(Icons.coronavirus_rounded) : Icon(Icons.tag_faces_rounded), @@ -88,7 +88,7 @@ class _MyHomePageState extends State { first: false, second: true, dif: 50.0, - colors: const SwitchColors(borderColor: Colors.transparent), + style: const ToggleStyle(borderColor: Colors.transparent), borderWidth: 5.0, height: 55, boxShadow: const [ @@ -100,7 +100,7 @@ class _MyHomePageState extends State { ), ], onChanged: (b) => setState(() => positive = b), - colorBuilder: (b) => SwitchColors.withBorder( + styleBuilder: (b) => ToggleStyle( backgroundColor: b ? Colors.white : Colors.black, indicatorColor: b ? Colors.blue : Colors.red, borderRadius: const BorderRadius.horizontal( @@ -112,6 +112,7 @@ class _MyHomePageState extends State { ? Icons.access_time_rounded : Icons.power_settings_new_rounded, size: 32.0, + color: value ? Colors.black : Colors.white, ), textBuilder: (value) => value ? const Center( @@ -137,15 +138,15 @@ class _MyHomePageState extends State { first: false, second: true, dif: 45.0, - colors: const SwitchColors( + style: const ToggleStyle( borderColor: Colors.transparent, backgroundColor: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(4.0)), ), borderWidth: 10.0, height: 50, loadingIconBuilder: (context, global) => const CupertinoActivityIndicator(color: Colors.white), - borderRadius: BorderRadius.circular(4.0), boxShadow: const [ BoxShadow( color: Colors.purple, @@ -158,7 +159,7 @@ class _MyHomePageState extends State { setState(() => positive = b); return Future.delayed(Duration(seconds: 2)); }, - colorBuilder: (b) => SwitchColors( + styleBuilder: (b) => ToggleStyle( indicatorColor: b ? Colors.purple : Colors.green), iconBuilder: (value) => value ? Icon(Icons.coronavirus_rounded) @@ -184,12 +185,14 @@ class _MyHomePageState extends State { first: false, second: true, dif: 45.0, - colors: SwitchColors( + animationCurve: Curves.easeInOut, + animationDuration: const Duration(milliseconds: 600), + style: ToggleStyle( borderColor: Colors.transparent, indicatorColor: Colors.white, backgroundColor: Colors.black, ), - colorBuilder: (value) => SwitchColors( + styleBuilder: (value) => ToggleStyle( backgroundColor: value ? green : Colors.red[800]), borderWidth: 6.0, height: 60.0, @@ -211,6 +214,56 @@ class _MyHomePageState extends State { ), ), ), + SizedBox(height: 16.0), + DefaultTextStyle.merge( + style: const TextStyle( + color: Colors.white, + fontSize: 18.0, + fontWeight: FontWeight.bold), + child: IconTheme.merge( + data: IconThemeData(color: Colors.white), + child: AnimatedToggleSwitch.dual( + current: positive, + first: false, + second: true, + dif: 45.0, + animationDuration: const Duration(milliseconds: 600), + style: ToggleStyle( + borderColor: Colors.transparent, + indicatorColor: Colors.white, + backgroundColor: Colors.black, + ), + customStyleBuilder: (context, local, global) { + if (global.position <= 0.0) + return ToggleStyle(backgroundColor: Colors.red[800]); + return ToggleStyle( + backgroundGradient: LinearGradient( + colors: [green, Colors.red[800]!], + stops: [ + global.position - + (1 - 2 * (global.position - 0.5).abs()) * 0.2, + global.position + + (1 - 2 * (global.position - 0.5).abs()) * 0.2, + ], + )); + }, + borderWidth: 6.0, + height: 60.0, + loadingIconBuilder: (context, global) => + CupertinoActivityIndicator( + color: Color.lerp( + Colors.red[800], green, global.position)), + onChanged: (b) => setState(() => positive = b), + iconBuilder: (value) => value + ? Icon(Icons.power_outlined, color: green, size: 32.0) + : Icon(Icons.power_settings_new_rounded, + color: Colors.red[800], size: 32.0), + textBuilder: (value) => value + ? Center(child: Text('Active')) + : Center(child: Text('Inactive')), + ), + ), + ), Padding( padding: const EdgeInsets.all(8.0), child: Text( @@ -261,7 +314,7 @@ class _MyHomePageState extends State { .then((_) => setState(() => loading = false)); }, iconBuilder: rollingIconBuilder, - colors: SwitchColors(borderColor: Colors.transparent), + style: ToggleStyle(borderColor: Colors.transparent), indicatorBoxShadow: const [ BoxShadow( color: Colors.black26, @@ -286,14 +339,14 @@ class _MyHomePageState extends State { current: value, values: const [0, 1, 2, 3], onChanged: (i) => setState(() => value = i), - colors: SwitchColors( + style: ToggleStyle( indicatorColor: Colors.white, borderColor: Colors.transparent, ), iconBuilder: coloredRollingIconBuilder, borderWidth: 3.0, - colorAnimationType: AnimationType.onHover, - colorBuilder: (value) => SwitchColors.withBorder( + styleAnimationType: AnimationType.onHover, + styleBuilder: (value) => ToggleStyle( backgroundColor: colorBuilder(value), borderRadius: BorderRadius.circular(value * 10.0), indicatorBorderRadius: BorderRadius.circular(value * 10.0), @@ -317,12 +370,12 @@ class _MyHomePageState extends State { iconBuilder: rollingIconBuilder, separatorBuilder: (context, index) => const VerticalDivider(), borderWidth: 4.5, - colors: SwitchColors( + style: ToggleStyle( indicatorColor: Colors.white, backgroundColor: Colors.amber, borderColor: Colors.transparent, + borderRadius: BorderRadius.circular(10.0), ), - borderRadius: BorderRadius.circular(10.0), height: 55, dif: 20.0, loading: loading, @@ -336,7 +389,7 @@ class _MyHomePageState extends State { onChanged: (i) => setState(() => nullableValue = i), iconBuilder: rollingIconBuilder, borderWidth: 4.5, - colors: SwitchColors( + style: ToggleStyle( indicatorColor: Colors.white, backgroundGradient: const LinearGradient(colors: [Colors.red, Colors.blue]), @@ -422,7 +475,7 @@ class _MyHomePageState extends State { iconOpacity: 0.2, indicatorSize: const Size.fromWidth(100), iconAnimationType: AnimationType.onHover, - colorAnimationType: AnimationType.onHover, + styleAnimationType: AnimationType.onHover, iconBuilder: (value, size) { IconData data = Icons.access_time_rounded; if (value.isEven) data = Icons.cancel; @@ -431,9 +484,9 @@ class _MyHomePageState extends State { size: min(size.width, size.height), ); }, - colors: const SwitchColors(borderColor: Colors.transparent), + style: const ToggleStyle(borderColor: Colors.transparent), borderWidth: 0.0, - colorBuilder: (i) => SwitchColors( + styleBuilder: (i) => ToggleStyle( indicatorColor: i.isEven == true ? Colors.amber : Colors.red), onChanged: (i) { @@ -444,18 +497,18 @@ class _MyHomePageState extends State { SizedBox(height: 16.0), AnimatedToggleSwitch.size( current: min(value, 2), - borderRadius: BorderRadius.circular(10.0), - indicatorBorderRadius: BorderRadius.zero, - colors: SwitchColors( + style: ToggleStyle( backgroundColor: Color(0xFF919191), indicatorColor: Color(0xFFEC3345), borderColor: Colors.transparent, + borderRadius: BorderRadius.circular(10.0), + indicatorBorderRadius: BorderRadius.zero, ), values: const [0, 1, 2], iconOpacity: 1.0, indicatorSize: const Size.fromWidth(100), iconAnimationType: AnimationType.onHover, - colorAnimationType: AnimationType.onHover, + styleAnimationType: AnimationType.onHover, dif: 2.0, customSeparatorBuilder: (context, local, global) { final opacity = @@ -499,8 +552,8 @@ class _MyHomePageState extends State { ], ); }, - colors: SwitchColors(borderColor: Colors.transparent), - colorBuilder: (i) => SwitchColors( + style: ToggleStyle(borderColor: Colors.transparent), + styleBuilder: (i) => ToggleStyle( indicatorColor: i.isEven == true ? Colors.amber : Colors.red), onChanged: (i) => setState(() => value = i), @@ -536,13 +589,15 @@ class _MyHomePageState extends State { selectedIconSize: const Size.square(20), iconSize: const Size.square(20), iconBuilder: iconBuilder, - colors: const SwitchColors(borderColor: Colors.red), - colorBuilder: (i) => SwitchColors( + style: const ToggleStyle( + borderColor: Colors.red, + borderRadius: BorderRadius.all(Radius.circular(8.0)), + indicatorBorderRadius: BorderRadius.zero, + ), + styleBuilder: (i) => ToggleStyle( indicatorColor: i.isEven == true ? Colors.green : Colors.tealAccent), onChanged: (i) => setState(() => value = i), - borderRadius: BorderRadius.circular(8.0), - indicatorBorderRadius: BorderRadius.zero, ), Padding( padding: const EdgeInsets.all(8.0), @@ -559,17 +614,15 @@ class _MyHomePageState extends State { iconBuilder: rollingIconBuilder, indicatorSize: const Size.fromWidth(2), ), - SizedBox( - height: 16.0, - ), + SizedBox(height: 16.0), AnimatedToggleSwitch.rollingByHeight( height: 50.0, current: value, values: const [0, 1, 2, 3], onChanged: (i) => setState(() => value = i), iconBuilder: rollingIconBuilder, - borderRadius: BorderRadius.circular(75.0), indicatorSize: const Size.square(1.5), + style: ToggleStyle(borderRadius: BorderRadius.circular(75.0)), ), SizedBox(height: MediaQuery.of(context).padding.bottom + 16.0), ], diff --git a/lib/src/properties.dart b/lib/src/properties.dart index 247a008..8204dc9 100644 --- a/lib/src/properties.dart +++ b/lib/src/properties.dart @@ -89,7 +89,19 @@ class LocalToggleProperties { }); } -class AnimatedToggleProperties extends LocalToggleProperties { +class StyledToggleProperties extends LocalToggleProperties { + //TODO: Add style to this class + StyledToggleProperties._fromLocal({ + required LocalToggleProperties properties, + }) : super(value: properties.value, index: properties.index); + + const StyledToggleProperties({ + required super.value, + required super.index, + }); +} + +class AnimatedToggleProperties extends StyledToggleProperties { /// A value between [0] and [1]. /// /// [0] indicates that [value] is not selected. @@ -97,7 +109,7 @@ class AnimatedToggleProperties extends LocalToggleProperties { /// [1] indicates that [value] is selected. final double animationValue; - AnimatedToggleProperties.fromLocal({ + AnimatedToggleProperties._fromLocal({ required this.animationValue, required LocalToggleProperties properties, }) : super(value: properties.value, index: properties.index); @@ -116,7 +128,7 @@ class AnimatedToggleProperties extends LocalToggleProperties { } } -class RollingProperties extends LocalToggleProperties { +class RollingProperties extends StyledToggleProperties { /// The size the icon should currently have. final Size iconSize; @@ -126,7 +138,7 @@ class RollingProperties extends LocalToggleProperties { /// or in the background. final bool foreground; - RollingProperties.fromLocal({ + RollingProperties._fromLocal({ required Size iconSize, required bool foreground, required LocalToggleProperties properties, diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart index 23ed53c..d715612 100644 --- a/lib/src/widgets/animated_toggle_switch.dart +++ b/lib/src/widgets/animated_toggle_switch.dart @@ -1,14 +1,20 @@ part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; -typedef SizeIconBuilder = Widget Function(BuildContext context, - SizeProperties local, DetailedGlobalToggleProperties global); +typedef SizeIconBuilder = Widget Function( + BuildContext context, + SizeProperties local, + DetailedGlobalToggleProperties global, +); typedef SimpleSizeIconBuilder = Widget Function(T value, Size size); typedef SimpleIconBuilder = Widget Function(T value); -typedef RollingIconBuilder = Widget Function(BuildContext context, - RollingProperties local, DetailedGlobalToggleProperties global); +typedef RollingIconBuilder = Widget Function( + BuildContext context, + RollingProperties local, + DetailedGlobalToggleProperties global, +); typedef SimpleRollingIconBuilder = Widget Function( T value, Size size, bool foreground); @@ -22,14 +28,21 @@ typedef AnimatedIconBuilder = Widget Function( AnimatedToggleProperties local, DetailedGlobalToggleProperties global); -typedef IconBuilder = Widget Function(BuildContext context, - LocalToggleProperties local, DetailedGlobalToggleProperties global); +typedef IconBuilder = Widget Function( + BuildContext context, + StyledToggleProperties local, + DetailedGlobalToggleProperties global, +); -typedef ColorBuilder = SwitchColors? Function(T value); +typedef StyleBuilder = ToggleStyle? Function(T value); -typedef GradientBuilder = Gradient? Function(T value); +typedef CustomStyleBuilder = ToggleStyle? Function( + BuildContext context, + StyledToggleProperties local, + GlobalToggleProperties global, +); -class SwitchColors { +class ToggleStyle { /// Background color of the indicator. final Color? indicatorColor; @@ -54,23 +67,10 @@ class SwitchColors { /// If set, this value overwrites [AnimatedToggleSwitch.indicatorBorderRadius]. final BorderRadiusGeometry? indicatorBorderRadius; - /// Default constructor for [SwitchColors]. + /// Default constructor for [ToggleStyle]. /// /// If you want to adapt the [BorderRadius], you should use [SwitchColors.withBorder] instead. - const SwitchColors({ - this.indicatorColor, - this.backgroundColor, - this.backgroundGradient, - this.borderColor, - }) : borderRadius = null, - indicatorBorderRadius = null; - - /// Constructor for adapting the [BorderRadius] of switch and indicator also. - /// - /// This constructor should mainly be used with [AnimatedToggleSwitch.colorBuilder] - /// because the [BorderRadius] can be adapted with [AnimatedToggleSwitch.borderRadius] - /// and [AnimatedToggleSwitch.indicatorBorderRadius] otherwise. - const SwitchColors.withBorder({ + const ToggleStyle({ this.indicatorColor, this.backgroundColor, this.backgroundGradient, @@ -80,7 +80,7 @@ class SwitchColors { }); /// Private constructor for setting all possible parameters. - SwitchColors._({ + ToggleStyle._({ required this.indicatorColor, required this.backgroundColor, required this.backgroundGradient, @@ -89,9 +89,9 @@ class SwitchColors { required this.indicatorBorderRadius, }); - SwitchColors _merge(SwitchColors? other) => other == null + ToggleStyle _merge(ToggleStyle? other) => other == null ? this - : SwitchColors._( + : ToggleStyle._( indicatorColor: other.indicatorColor ?? indicatorColor, backgroundColor: other.backgroundColor ?? backgroundColor, backgroundGradient: other.backgroundGradient ?? @@ -104,26 +104,25 @@ class SwitchColors { borderRadius, ); - static SwitchColors _lerp( - SwitchColors colors1, SwitchColors colors2, double t) => - SwitchColors._( + static ToggleStyle _lerp(ToggleStyle style1, ToggleStyle colors2, double t) => + ToggleStyle._( indicatorColor: - Color.lerp(colors1.indicatorColor, colors2.indicatorColor, t), + Color.lerp(style1.indicatorColor, colors2.indicatorColor, t), backgroundColor: - Color.lerp(colors1.backgroundColor, colors2.backgroundColor, t), + Color.lerp(style1.backgroundColor, colors2.backgroundColor, t), backgroundGradient: Gradient.lerp( - colors1.backgroundGradient ?? colors1.backgroundColor?.toGradient(), + style1.backgroundGradient ?? style1.backgroundColor?.toGradient(), colors2.backgroundGradient ?? colors2.backgroundColor?.toGradient(), t, ), - borderColor: Color.lerp(colors1.borderColor, colors2.borderColor, t), + borderColor: Color.lerp(style1.borderColor, colors2.borderColor, t), borderRadius: BorderRadiusGeometry.lerp( - colors1.borderRadius, + style1.borderRadius, colors2.borderRadius, t, ), indicatorBorderRadius: BorderRadiusGeometry.lerp( - colors1.indicatorBorderRadius ?? colors1.borderRadius, + style1.indicatorBorderRadius ?? style1.borderRadius, colors2.indicatorBorderRadius ?? colors2.borderRadius, t, ), @@ -171,15 +170,24 @@ class AnimatedToggleSwitch extends StatelessWidget { /// The IconBuilder for all icons with the specified size. final AnimatedIconBuilder? animatedIconBuilder; - /// The default colors for this switch. + /// The default style of this switch. /// - /// This value can be overwritten by [colorBuilder]. - final SwitchColors colors; + /// This value can be overwritten by [styleBuilder]. + final ToggleStyle style; - /// Builder for the colors of the indicator depending on the current value. + /// Builder for the style of the indicator depending on the current value. + /// + /// The returned style values overwrite the values of the [style] parameter if not [null]. + /// + /// For a version of this builder with more parameters, please use [customStyleBuilder]. + final StyleBuilder? styleBuilder; + + /// Custom builder for the style of the indicator. + /// + /// The returned style values overwrite the values of the [style] parameter if not [null]. /// - /// The return values overwrite the [colors] parameter if not [null]. - final ColorBuilder? colorBuilder; + /// For a simpler version of this builder, please use [styleBuilder]. + final CustomStyleBuilder? customStyleBuilder; /// Duration of the motion animation. final Duration animationDuration; @@ -203,29 +211,17 @@ class AnimatedToggleSwitch extends StatelessWidget { /// Callback for selecting a new value. The new [current] should be set here. final Function(T)? onChanged; - /// Width of the border of the switch. For deactivating please set this to 0.0 and set [borderColor] to Colors.transparent. + /// Width of the border of the switch. For deactivating please set this to [0.0]. final double borderWidth; - /// [BorderRadius] of the switch. - /// - /// You can alternatively use [SwitchColors.withBorder] and [colorBuilder] for - /// animating this value. - final BorderRadiusGeometry? borderRadius; - - /// [BorderRadius] of the indicator. Defaults to [borderRadius]. - /// - /// You can alternatively use [SwitchColors.withBorder] and [colorBuilder] for - /// animating this value. - final BorderRadiusGeometry? indicatorBorderRadius; - /// Opacity for the icons. /// - /// Please set [iconOpacity] and [selectedIconOpacity] to 1.0 for deactivating the AnimatedOpacity. + /// Please set [iconOpacity] and [selectedIconOpacity] to [1.0] for deactivating the AnimatedOpacity. final double iconOpacity; /// Opacity for the currently selected icon. /// - /// Please set [iconOpacity] and [selectedIconOpacity] to 1.0 for deactivating the AnimatedOpacity. + /// Please set [iconOpacity] and [selectedIconOpacity] to [1.0] for deactivating the AnimatedOpacity. final double selectedIconOpacity; /// Space between the "indicator spaces" of the adjacent icons. @@ -240,15 +236,15 @@ class AnimatedToggleSwitch extends StatelessWidget { /// The [AnimationType] for the [animatedIconBuilder]. final AnimationType iconAnimationType; - /// The [AnimationType] for [colorBuilder]. + /// The [AnimationType] for [styleBuilder]. /// - /// The [AnimationType] for [SwitchColors.indicatorColor] and [SwitchColors.indicatorBorderRadius] + /// The [AnimationType] for [ToggleStyle.indicatorColor] and [ToggleStyle.indicatorBorderRadius] /// is managed separately with [indicatorAnimationType]. - final AnimationType colorAnimationType; + final AnimationType styleAnimationType; - /// The [AnimationType] for the [SwitchColors.indicatorColor]. + /// The [AnimationType] for the [ToggleStyle.indicatorColor]. /// - /// For the other colors, please use [colorAnimationType]. + /// For the other style parameters, please use [styleAnimationType]. final AnimationType indicatorAnimationType; /// Callback for tapping anywhere on the widget. @@ -288,14 +284,14 @@ class AnimatedToggleSwitch extends StatelessWidget { /// If [false] the user can change the value only by dragging the indicator. final bool iconsTappable; - /// The minimum width of the indicator's hitbox. + /// The minimum size of the indicator's hitbox. /// /// Helpful if the indicator is so small that you can hardly grip it. final double minTouchTargetSize; /// The direction in which the icons are arranged. /// - /// If null, the [TextDirection] is taken from the [BuildContext]. + /// If [null], the [TextDirection] is taken from the [BuildContext]. final TextDirection? textDirection; /// A builder for the loading icon. @@ -363,18 +359,18 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorSize = const Size(48.0, double.infinity), this.onChanged, this.borderWidth = 2, - this.colors = const SwitchColors(), - this.colorBuilder, + this.style = const ToggleStyle(), + this.styleBuilder, + this.customStyleBuilder, this.iconAnimationCurve = Curves.easeOutBack, this.iconAnimationDuration, this.iconOpacity = 0.5, - this.borderRadius, this.dif = 0.0, this.foregroundIndicatorIconBuilder, this.selectedIconOpacity = 1.0, this.height = 50.0, this.iconAnimationType = AnimationType.onSelected, - this.colorAnimationType = AnimationType.onSelected, + this.styleAnimationType = AnimationType.onSelected, this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, @@ -383,7 +379,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, - this.indicatorBorderRadius, this.iconsTappable = true, this.defaultCursor, this.draggingCursor = SystemMouseCursors.grabbing, @@ -401,6 +396,7 @@ class AnimatedToggleSwitch extends StatelessWidget { this.separatorBuilder, this.customSeparatorBuilder, }) : this._iconArrangement = IconArrangement.row, + assert(styleBuilder == null || customStyleBuilder == null), super(key: key); /// Provides an [AnimatedToggleSwitch] with the standard size animation of the icons. @@ -417,20 +413,20 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorSize = const Size(48.0, double.infinity), this.onChanged, this.borderWidth = 2, - this.colors = const SwitchColors(), - this.colorBuilder, + this.style = const ToggleStyle(), + this.styleBuilder, + this.customStyleBuilder, iconSize = const Size(23.0, 23.0), selectedIconSize = const Size(34.5, 34.5), this.iconAnimationCurve = Curves.easeOutBack, this.iconAnimationDuration, this.iconOpacity = 0.5, this.selectedIconOpacity = 1.0, - this.borderRadius, this.dif = 0.0, this.foregroundIndicatorIconBuilder, this.height = 50.0, this.iconAnimationType = AnimationType.onSelected, - this.colorAnimationType = AnimationType.onSelected, + this.styleAnimationType = AnimationType.onSelected, this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, @@ -439,7 +435,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, - this.indicatorBorderRadius, this.iconsTappable = true, this.defaultCursor, this.draggingCursor = SystemMouseCursors.grabbing, @@ -459,6 +454,7 @@ class AnimatedToggleSwitch extends StatelessWidget { }) : animatedIconBuilder = _iconSizeBuilder( iconBuilder, customIconBuilder, iconSize, selectedIconSize), this._iconArrangement = IconArrangement.row, + assert(styleBuilder == null || customStyleBuilder == null), super(key: key); /// All size values ([indicatorWidth], [iconSize], [selectedIconSize]) are relative to the specified height. @@ -477,19 +473,19 @@ class AnimatedToggleSwitch extends StatelessWidget { SizeIconBuilder? customIconBuilder, this.onChanged, this.borderWidth = 2, - this.colors = const SwitchColors(), - this.colorBuilder, + this.style = const ToggleStyle(), + this.styleBuilder, + this.customStyleBuilder, iconSize = const Size(0.5, 0.5), selectedIconSize = const Size(0.75, 0.75), this.iconAnimationCurve = Curves.easeOutBack, this.iconAnimationDuration, this.iconOpacity = 0.5, - this.borderRadius, dif = 0.0, this.foregroundIndicatorIconBuilder, this.selectedIconOpacity = 1.0, this.iconAnimationType = AnimationType.onSelected, - this.colorAnimationType = AnimationType.onSelected, + this.styleAnimationType = AnimationType.onSelected, this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, @@ -498,7 +494,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, - this.indicatorBorderRadius, this.iconsTappable = true, this.defaultCursor, this.draggingCursor = SystemMouseCursors.grabbing, @@ -523,6 +518,7 @@ class AnimatedToggleSwitch extends StatelessWidget { iconSize * (height + 2 * borderWidth), selectedIconSize * (height + 2 * borderWidth)), this._iconArrangement = IconArrangement.row, + assert(styleBuilder == null || customStyleBuilder == null), super(key: key); static AnimatedIconBuilder? _iconSizeBuilder( @@ -567,17 +563,17 @@ class AnimatedToggleSwitch extends StatelessWidget { Size indicatorSize = const Size(1.0, 1.0), this.onChanged, this.borderWidth = 2, - this.colors = const SwitchColors(), - this.colorBuilder, + this.style = const ToggleStyle(), + this.styleBuilder, + this.customStyleBuilder, this.iconAnimationCurve = Curves.easeOutBack, this.iconAnimationDuration, this.iconOpacity = 0.5, - this.borderRadius, dif = 0.0, this.foregroundIndicatorIconBuilder, this.selectedIconOpacity = 1.0, this.iconAnimationType = AnimationType.onSelected, - this.colorAnimationType = AnimationType.onSelected, + this.styleAnimationType = AnimationType.onSelected, this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, @@ -586,7 +582,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, - this.indicatorBorderRadius, this.iconsTappable = true, this.defaultCursor, this.draggingCursor = SystemMouseCursors.grabbing, @@ -606,6 +601,7 @@ class AnimatedToggleSwitch extends StatelessWidget { }) : this.dif = dif * (height - 2 * borderWidth), this.indicatorSize = indicatorSize * (height - 2 * borderWidth), this._iconArrangement = IconArrangement.row, + assert(styleBuilder == null || customStyleBuilder == null), super(key: key); /// Special version of [AnimatedToggleSwitch.customByHeight]. @@ -630,14 +626,14 @@ class AnimatedToggleSwitch extends StatelessWidget { Size indicatorSize = const Size(1.0, 1.0), this.onChanged, this.borderWidth = 2, - this.colors = const SwitchColors(), - this.colorBuilder, + this.style = const ToggleStyle(), + this.styleBuilder, + this.customStyleBuilder, double iconRadius = 0.25, double selectedIconRadius = 0.35, this.iconOpacity = 0.5, - this.borderRadius, double dif = 0.0, - this.colorAnimationType = AnimationType.onSelected, + this.styleAnimationType = AnimationType.onSelected, this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, @@ -646,7 +642,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, - this.indicatorBorderRadius, this.iconsTappable = true, this.defaultCursor, this.draggingCursor = SystemMouseCursors.grabbing, @@ -685,6 +680,7 @@ class AnimatedToggleSwitch extends StatelessWidget { Size.square(iconRadius * 2 * (height - 2 * borderWidth)), Size.square(iconRadius * 2 * (height - 2 * borderWidth))), this._iconArrangement = IconArrangement.row, + assert(styleBuilder == null || customStyleBuilder == null), super(key: key); /// Defining a rolling animation using the [foregroundIndicatorIconBuilder] of [AnimatedToggleSwitch]. @@ -705,15 +701,15 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorSize = const Size(46.0, double.infinity), this.onChanged, this.borderWidth = 2, - this.colors = const SwitchColors(), - this.colorBuilder, + this.style = const ToggleStyle(), + this.styleBuilder, + this.customStyleBuilder, double iconRadius = 11.5, double selectedIconRadius = 16.1, this.iconOpacity = 0.5, - this.borderRadius, this.dif = 0.0, this.height = 50.0, - this.colorAnimationType = AnimationType.onSelected, + this.styleAnimationType = AnimationType.onSelected, this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, @@ -722,7 +718,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, - this.indicatorBorderRadius, this.iconsTappable = true, this.defaultCursor, this.draggingCursor = SystemMouseCursors.grabbing, @@ -758,6 +753,7 @@ class AnimatedToggleSwitch extends StatelessWidget { Size.square(iconRadius * 2), Size.square(iconRadius * 2)), this._iconArrangement = IconArrangement.row, + assert(styleBuilder == null || customStyleBuilder == null), super(key: key); static CustomIndicatorBuilder _rollingForegroundIndicatorIconBuilder( @@ -832,7 +828,7 @@ class AnimatedToggleSwitch extends StatelessWidget { ? null : (t, local, global) => customIconBuilder!( t, - RollingProperties.fromLocal( + RollingProperties._fromLocal( iconSize: iconSize, foreground: false, properties: local), global, ); @@ -856,15 +852,15 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorSize = const Size(46.0, double.infinity), this.onChanged, this.borderWidth = 2, - this.colors = const SwitchColors(), - this.colorBuilder, + this.style = const ToggleStyle(), + this.styleBuilder, + this.customStyleBuilder, double iconRadius = 16.1, - this.borderRadius, this.dif = 40.0, this.height = 50.0, this.iconAnimationDuration = const Duration(milliseconds: 500), this.iconAnimationCurve = Curves.easeInOut, - this.colorAnimationType = AnimationType.onHover, + this.styleAnimationType = AnimationType.onHover, this.indicatorAnimationType = AnimationType.onHover, this.fittingMode = FittingMode.preventHorizontalOverlapping, Function()? onTap, @@ -873,7 +869,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, - this.indicatorBorderRadius, this.defaultCursor = SystemMouseCursors.click, this.draggingCursor = SystemMouseCursors.grabbing, this.dragCursor = SystemMouseCursors.grab, @@ -920,6 +915,7 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAppearingCurve = _defaultIndicatorAppearingAnimationCurve, this.separatorBuilder = null, this.customSeparatorBuilder = null, + assert(styleBuilder == null || customStyleBuilder == null), super(key: key); static Function() _dualOnTap( @@ -987,21 +983,32 @@ class AnimatedToggleSwitch extends StatelessWidget { BuildContext context, dynamic properties) => const _MyLoading(); + ToggleStyle? _styleBuilder(BuildContext context, + StyledToggleProperties local, GlobalToggleProperties global) { + if (customStyleBuilder != null) { + return customStyleBuilder!(context, local, global); + } + if (styleBuilder != null) { + return styleBuilder!(local.value); + } + return null; + } + // END OF CONSTRUCTOR SECTION @override Widget build(BuildContext context) { ThemeData theme = Theme.of(context); BorderRadiusGeometry defaultBorderRadius = - this.borderRadius ?? BorderRadius.circular(height / 2); - final colors = SwitchColors._( + BorderRadius.circular(height / 2); + final colors = ToggleStyle._( indicatorColor: theme.colorScheme.secondary, backgroundColor: theme.scaffoldBackgroundColor, backgroundGradient: null, borderColor: theme.colorScheme.secondary, borderRadius: defaultBorderRadius, - indicatorBorderRadius: indicatorBorderRadius ?? defaultBorderRadius, - )._merge(this.colors); + indicatorBorderRadius: defaultBorderRadius, + )._merge(this.style); return CustomAnimatedToggleSwitch( animationCurve: animationCurve, @@ -1045,13 +1052,13 @@ class AnimatedToggleSwitch extends StatelessWidget { iconBuilder: (context, local, global) => _animatedOpacityIcon( _animatedSizeIcon(context, local, global), local.value == current), padding: EdgeInsets.all(borderWidth), - wrapperBuilder: (context, properties, child) { - //TODO: extract this method in separate widget (performance improvement) - return _animationTypeBuilder( + wrapperBuilder: (context, global, child) { + //TODO: extract this method to separate widget + return _animationTypeBuilder( context, - colorAnimationType, - (t) => colors._merge(colorBuilder?.call(t)), - SwitchColors._lerp, + styleAnimationType, + (local) => colors._merge(_styleBuilder(context, local, global)), + ToggleStyle._lerp, (colors) => DecoratedBox( decoration: BoxDecoration( color: colors.backgroundColor, @@ -1062,10 +1069,12 @@ class AnimatedToggleSwitch extends StatelessWidget { child: DecoratedBox( position: DecorationPosition.foreground, decoration: BoxDecoration( - border: Border.all( - color: colors.borderColor!, - width: borderWidth, - ), + border: borderWidth <= 0.0 + ? null + : Border.all( + color: colors.borderColor!, + width: borderWidth, + ), borderRadius: colors.borderRadius, ), child: ClipRRect( @@ -1074,7 +1083,7 @@ class AnimatedToggleSwitch extends StatelessWidget { ), ), ), - properties, + global, ); }); } @@ -1082,7 +1091,7 @@ class AnimatedToggleSwitch extends StatelessWidget { Widget _animationTypeBuilder( BuildContext context, AnimationType animationType, - V Function(T item) valueProvider, + V Function(StyledToggleProperties local) valueProvider, V Function(V value1, V value2, double t) lerp, Widget Function(V value) builder, GlobalToggleProperties properties, @@ -1090,25 +1099,35 @@ class AnimatedToggleSwitch extends StatelessWidget { double pos = properties.position; switch (animationType) { case AnimationType.onSelected: - V currentValue = valueProvider(current); + V currentValue = valueProvider( + StyledToggleProperties( + value: current, index: values.indexOf(current)), + ); return TweenAnimationBuilder( duration: animationDuration, tween: _CustomTween(lerp, begin: currentValue, end: currentValue), builder: (context, v, _) => builder(v)); case AnimationType.onHover: - return builder(lerp(valueProvider(values[pos.floor()]), - valueProvider(values[pos.ceil()]), pos - pos.floor())); + final index1 = pos.floor(); + final index2 = pos.ceil(); + return builder(lerp( + valueProvider( + StyledToggleProperties(value: values[index1], index: index1)), + valueProvider( + StyledToggleProperties(value: values[index2], index: index2)), + pos - pos.floor(), + )); } } Widget _indicatorBuilder(BuildContext context, - DetailedGlobalToggleProperties properties, SwitchColors colors) { + DetailedGlobalToggleProperties properties, ToggleStyle colors) { final child = foregroundIndicatorIconBuilder?.call(context, properties); - return _animationTypeBuilder( + return _animationTypeBuilder( context, indicatorAnimationType, - (item) => colors._merge(colorBuilder?.call(item)), - SwitchColors._lerp, + (local) => colors._merge(_styleBuilder(context, local, properties)), + ToggleStyle._lerp, (colors) => _customIndicatorBuilder( context, colors.indicatorColor!, @@ -1141,8 +1160,10 @@ class AnimatedToggleSwitch extends StatelessWidget { builder: (c, value, child) { return _animatedIcon( c, - AnimatedToggleProperties.fromLocal( - animationValue: value as double, properties: local), + AnimatedToggleProperties._fromLocal( + animationValue: value as double, + properties: local, + ), global, ); }, @@ -1157,7 +1178,7 @@ class AnimatedToggleSwitch extends StatelessWidget { animationValue = localPosition; return _animatedIcon( context, - AnimatedToggleProperties.fromLocal( + AnimatedToggleProperties._fromLocal( animationValue: animationValue, properties: local, ), diff --git a/test/helper.dart b/test/helper.dart index 1fcae30..6ab6d53 100644 --- a/test/helper.dart +++ b/test/helper.dart @@ -57,6 +57,9 @@ typedef SwitchBuilder = AnimatedToggleSwitch Function({ Function()? onTap, bool? loading, bool allowUnlistedValues, + ToggleStyle? style, + StyleBuilder? styleBuilder, + CustomStyleBuilder? customStyleBuilder, }); typedef SimpleSwitchBuilder = AnimatedToggleSwitch Function({ @@ -67,6 +70,9 @@ typedef SimpleSwitchBuilder = AnimatedToggleSwitch Function({ Function()? onTap, bool? loading, bool allowUnlistedValues, + ToggleStyle? style, + StyleBuilder? styleBuilder, + CustomStyleBuilder? customStyleBuilder, }); /// Tests all AnimatedToggleSwitch constructors @@ -88,6 +94,9 @@ void defaultTestAllSwitches( Function()? onTap, bool? loading, bool allowUnlistedValues = false, + ToggleStyle? style, + StyleBuilder? styleBuilder, + CustomStyleBuilder? customStyleBuilder, }) => buildSwitch( current: current, @@ -98,6 +107,9 @@ void defaultTestAllSwitches( onTap: onTap, loading: loading, allowUnlistedValues: allowUnlistedValues, + style: style, + styleBuilder: styleBuilder, + customStyleBuilder: customStyleBuilder, ), defaultValues, )); @@ -115,6 +127,9 @@ void defaultTestAllSwitches( Function()? onTap, bool? loading, bool allowUnlistedValues = false, + ToggleStyle? style, + StyleBuilder? styleBuilder, + CustomStyleBuilder? customStyleBuilder, }) => AnimatedToggleSwitch.dual( current: current, @@ -131,6 +146,9 @@ void defaultTestAllSwitches( onChanged: onChanged, loadingIconBuilder: _loadingIconBuilder, loading: loading, + style: style ?? const ToggleStyle(), + styleBuilder: styleBuilder, + customStyleBuilder: customStyleBuilder, ), values, ), @@ -156,6 +174,9 @@ void testAllSwitches( Function()? onTap, bool? loading, bool allowUnlistedValues = false, + ToggleStyle? style, + StyleBuilder? styleBuilder, + CustomStyleBuilder? customStyleBuilder, }) => AnimatedToggleSwitch.rolling( current: current, @@ -170,6 +191,9 @@ void testAllSwitches( loadingIconBuilder: _loadingIconBuilder, loading: loading, allowUnlistedValues: allowUnlistedValues, + style: style ?? const ToggleStyle(), + styleBuilder: styleBuilder, + customStyleBuilder: customStyleBuilder, ))); testWidgets( '$description (AnimatedToggleSwitch.size)', @@ -184,6 +208,9 @@ void testAllSwitches( Function()? onTap, bool? loading, bool allowUnlistedValues = false, + ToggleStyle? style, + StyleBuilder? styleBuilder, + CustomStyleBuilder? customStyleBuilder, }) => AnimatedToggleSwitch.size( current: current, @@ -197,6 +224,9 @@ void testAllSwitches( loadingIconBuilder: _loadingIconBuilder, loading: loading, allowUnlistedValues: allowUnlistedValues, + style: style ?? const ToggleStyle(), + styleBuilder: styleBuilder, + customStyleBuilder: customStyleBuilder, ))); testWidgets( '$description (AnimatedToggleSwitch.rollingByHeight)', @@ -211,6 +241,9 @@ void testAllSwitches( Function()? onTap, bool? loading, bool allowUnlistedValues = false, + ToggleStyle? style, + StyleBuilder? styleBuilder, + CustomStyleBuilder? customStyleBuilder, }) => AnimatedToggleSwitch.rollingByHeight( current: current, @@ -225,6 +258,9 @@ void testAllSwitches( loadingIconBuilder: _loadingIconBuilder, loading: loading, allowUnlistedValues: allowUnlistedValues, + style: style ?? const ToggleStyle(), + styleBuilder: styleBuilder, + customStyleBuilder: customStyleBuilder, ))); testWidgets( '$description (AnimatedToggleSwitch.sizeByHeight)', @@ -239,6 +275,9 @@ void testAllSwitches( Function()? onTap, bool? loading, bool allowUnlistedValues = false, + ToggleStyle? style, + StyleBuilder? styleBuilder, + CustomStyleBuilder? customStyleBuilder, }) => AnimatedToggleSwitch.sizeByHeight( current: current, @@ -252,6 +291,9 @@ void testAllSwitches( loadingIconBuilder: _loadingIconBuilder, loading: loading, allowUnlistedValues: allowUnlistedValues, + style: style ?? const ToggleStyle(), + styleBuilder: styleBuilder, + customStyleBuilder: customStyleBuilder, ))); testWidgets( '$description (AnimatedToggleSwitch.custom)', @@ -266,6 +308,9 @@ void testAllSwitches( Function()? onTap, bool? loading, bool allowUnlistedValues = false, + ToggleStyle? style, + StyleBuilder? styleBuilder, + CustomStyleBuilder? customStyleBuilder, }) => AnimatedToggleSwitch.custom( current: current, @@ -280,6 +325,9 @@ void testAllSwitches( loadingIconBuilder: _loadingIconBuilder, loading: loading, allowUnlistedValues: allowUnlistedValues, + style: style ?? const ToggleStyle(), + styleBuilder: styleBuilder, + customStyleBuilder: customStyleBuilder, ))); testWidgets( '$description (AnimatedToggleSwitch.customByHeight)', @@ -294,6 +342,9 @@ void testAllSwitches( Function()? onTap, bool? loading, bool allowUnlistedValues = false, + ToggleStyle? style, + StyleBuilder? styleBuilder, + CustomStyleBuilder? customStyleBuilder, }) => AnimatedToggleSwitch.customByHeight( current: current, @@ -308,5 +359,8 @@ void testAllSwitches( loadingIconBuilder: _loadingIconBuilder, loading: loading, allowUnlistedValues: allowUnlistedValues, + style: style ?? const ToggleStyle(), + styleBuilder: styleBuilder, + customStyleBuilder: customStyleBuilder, ))); } diff --git a/test/style_test.dart b/test/style_test.dart new file mode 100644 index 0000000..0b80dbf --- /dev/null +++ b/test/style_test.dart @@ -0,0 +1,19 @@ +import 'package:animated_toggle_switch/animated_toggle_switch.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'helper.dart'; + +void main() { + defaultTestAllSwitches( + 'Switch throws error if styleBuilder and customStyleBuilder are set both', + (tester, buildSwitch, values) async { + expect( + () => buildSwitch( + current: 100, + styleBuilder: (v) => const ToggleStyle(), + customStyleBuilder: (c, l, g) => const ToggleStyle(), + ), + throwsAssertionError, + ); + }); +} From aaa3b0f4956deea8b53baa51a989f54c4f9d2d6b Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Fri, 28 Jul 2023 14:20:53 +0200 Subject: [PATCH 19/36] fixes analyzing issue --- lib/src/properties.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/properties.dart b/lib/src/properties.dart index 8204dc9..58aab92 100644 --- a/lib/src/properties.dart +++ b/lib/src/properties.dart @@ -91,9 +91,6 @@ class LocalToggleProperties { class StyledToggleProperties extends LocalToggleProperties { //TODO: Add style to this class - StyledToggleProperties._fromLocal({ - required LocalToggleProperties properties, - }) : super(value: properties.value, index: properties.index); const StyledToggleProperties({ required super.value, From 88de27fcb6e7cd8c4c65d2c9358c8178671bc9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 14:38:30 +0200 Subject: [PATCH 20/36] Update README.md --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index babd4a8..dce731e 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,20 @@ AnimatedToggleSwitch.rolling( ) ``` +### Styling +`style`, `styleBuilder` and `customStyleBuilder` can be used to style the switch. +For the general look of the switch, you can use `style`. +For parameters that should change with the selection, you can use `styleBuilder`. +If you need additional parameters, you can use `customStyleBuilder`. +```dart +AnimatedToggleSwitch.rolling( + ... + style: ToggleStyle(backgroundColor: Colors.red), // backgroundColor is set independently of the current selection + styleBuilder: ToggleStyle(indicatorColor: (value) => value.isEven ? Colors.yellow : Colors.green)), // indicatorColor changes and animates its value with the selection + ... +) +``` + ### Simple rolling animation with loading ![animated_toggle_switch_example_rolling_loading](https://user-images.githubusercontent.com/43761463/209121057-2ff2bfc3-063e-4704-a981-f5cc5f54720a.gif) To use the loading animation, you simply have to return a `Future` in `onChanged` or `onTap`. From 11f44a6588e8e52bb73da90479c67b5ba13ad0e6 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Fri, 28 Jul 2023 15:07:14 +0200 Subject: [PATCH 21/36] adds test --- lib/animated_toggle_switch.dart | 2 +- lib/src/properties.dart | 1 + lib/src/utils.dart | 1 + lib/src/widgets/drag_region.dart | 14 +++++++------- test/gesture_test.dart | 29 +++++++++++++++++++++++++++++ test/helper.dart | 17 +++++++++++++++++ 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/lib/animated_toggle_switch.dart b/lib/animated_toggle_switch.dart index a3588ae..4abfee2 100644 --- a/lib/animated_toggle_switch.dart +++ b/lib/animated_toggle_switch.dart @@ -2,7 +2,6 @@ library animated_toggle_switch; import 'dart:math'; -import 'package:animated_toggle_switch/src/widgets/drag_region.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -12,3 +11,4 @@ part 'src/properties.dart'; part 'src/utils.dart'; part 'src/widgets/animated_toggle_switch.dart'; part 'src/widgets/custom_animated_toggle_switch.dart'; +part 'src/widgets/drag_region.dart'; diff --git a/lib/src/properties.dart b/lib/src/properties.dart index 58aab92..facf13b 100644 --- a/lib/src/properties.dart +++ b/lib/src/properties.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; class GlobalToggleProperties { diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 0222602..daaa9a4 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; enum ToggleMode { animating, dragged, none } diff --git a/lib/src/widgets/drag_region.dart b/lib/src/widgets/drag_region.dart index 40336b7..939e93e 100644 --- a/lib/src/widgets/drag_region.dart +++ b/lib/src/widgets/drag_region.dart @@ -1,13 +1,13 @@ -import 'package:flutter/material.dart'; +part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; -class HoverRegion extends StatefulWidget { +class _HoverRegion extends StatefulWidget { final MouseCursor? cursor; final MouseCursor hoverCursor; final Widget child; final bool Function(Offset offset) hoverCheck; final MouseCursor defaultCursor; - const HoverRegion({ + const _HoverRegion({ Key? key, this.cursor, required this.hoverCursor, @@ -19,10 +19,10 @@ class HoverRegion extends StatefulWidget { static bool _defaultHoverCheck(Offset offset) => true; @override - State createState() => _HoverRegionState(); + State<_HoverRegion> createState() => _HoverRegionState(); } -class _HoverRegionState extends State { +class _HoverRegionState extends State<_HoverRegion> { bool _hovering = false; Offset? _position; @@ -74,7 +74,7 @@ class DragRegion extends StatelessWidget { Key? key, this.dragging = false, required this.child, - this.hoverCheck = HoverRegion._defaultHoverCheck, + this.hoverCheck = _HoverRegion._defaultHoverCheck, this.defaultCursor = MouseCursor.defer, this.dragCursor = SystemMouseCursors.grab, this.draggingCursor = SystemMouseCursors.grabbing, @@ -82,7 +82,7 @@ class DragRegion extends StatelessWidget { @override Widget build(BuildContext context) { - return HoverRegion( + return _HoverRegion( cursor: dragging ? draggingCursor : null, hoverCursor: dragCursor, child: child, diff --git a/test/gesture_test.dart b/test/gesture_test.dart index b1646c3..043c7a5 100644 --- a/test/gesture_test.dart +++ b/test/gesture_test.dart @@ -90,4 +90,33 @@ void main() { verifyNoMoreInteractions(changedFunction); verifyNoMoreInteractions(tapFunction); }); + + defaultTestAllSwitches('Switch respects iconsTappable parameter', + (tester, buildSwitch, values) async { + final current = values.first; + final next = values.last; + final tapFunction = MockFunction(); + final changedFunction = MockOnChangedFunction(); + + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + onTap: tapFunction, + onChanged: changedFunction, + iconsTappable: false, + ), + )); + verifyNever(() => tapFunction.call()); + final currentFinder = find.byKey(iconKey(current)); + final nextFinder = find.byKey(iconKey(next)); + + await tester.tap(currentFinder, warnIfMissed: false); + verify(() => tapFunction()).called(1); + + await tester.tap(nextFinder, warnIfMissed: false); + verify(() => tapFunction()).called(1); + + verifyNoMoreInteractions(changedFunction); + }, testDual: false); } diff --git a/test/helper.dart b/test/helper.dart index 6ab6d53..1699ac0 100644 --- a/test/helper.dart +++ b/test/helper.dart @@ -60,6 +60,7 @@ typedef SwitchBuilder = AnimatedToggleSwitch Function({ ToggleStyle? style, StyleBuilder? styleBuilder, CustomStyleBuilder? customStyleBuilder, + bool? iconsTappable, }); typedef SimpleSwitchBuilder = AnimatedToggleSwitch Function({ @@ -73,6 +74,7 @@ typedef SimpleSwitchBuilder = AnimatedToggleSwitch Function({ ToggleStyle? style, StyleBuilder? styleBuilder, CustomStyleBuilder? customStyleBuilder, + bool? iconsTappable, }); /// Tests all AnimatedToggleSwitch constructors @@ -97,6 +99,7 @@ void defaultTestAllSwitches( ToggleStyle? style, StyleBuilder? styleBuilder, CustomStyleBuilder? customStyleBuilder, + bool? iconsTappable, }) => buildSwitch( current: current, @@ -110,6 +113,7 @@ void defaultTestAllSwitches( style: style, styleBuilder: styleBuilder, customStyleBuilder: customStyleBuilder, + iconsTappable: iconsTappable, ), defaultValues, )); @@ -130,6 +134,7 @@ void defaultTestAllSwitches( ToggleStyle? style, StyleBuilder? styleBuilder, CustomStyleBuilder? customStyleBuilder, + bool? iconsTappable, }) => AnimatedToggleSwitch.dual( current: current, @@ -177,6 +182,7 @@ void testAllSwitches( ToggleStyle? style, StyleBuilder? styleBuilder, CustomStyleBuilder? customStyleBuilder, + bool? iconsTappable, }) => AnimatedToggleSwitch.rolling( current: current, @@ -194,6 +200,7 @@ void testAllSwitches( style: style ?? const ToggleStyle(), styleBuilder: styleBuilder, customStyleBuilder: customStyleBuilder, + iconsTappable: iconsTappable ?? true, ))); testWidgets( '$description (AnimatedToggleSwitch.size)', @@ -211,6 +218,7 @@ void testAllSwitches( ToggleStyle? style, StyleBuilder? styleBuilder, CustomStyleBuilder? customStyleBuilder, + bool? iconsTappable, }) => AnimatedToggleSwitch.size( current: current, @@ -227,6 +235,7 @@ void testAllSwitches( style: style ?? const ToggleStyle(), styleBuilder: styleBuilder, customStyleBuilder: customStyleBuilder, + iconsTappable: iconsTappable ?? true, ))); testWidgets( '$description (AnimatedToggleSwitch.rollingByHeight)', @@ -244,6 +253,7 @@ void testAllSwitches( ToggleStyle? style, StyleBuilder? styleBuilder, CustomStyleBuilder? customStyleBuilder, + bool? iconsTappable, }) => AnimatedToggleSwitch.rollingByHeight( current: current, @@ -261,6 +271,7 @@ void testAllSwitches( style: style ?? const ToggleStyle(), styleBuilder: styleBuilder, customStyleBuilder: customStyleBuilder, + iconsTappable: iconsTappable ?? true, ))); testWidgets( '$description (AnimatedToggleSwitch.sizeByHeight)', @@ -278,6 +289,7 @@ void testAllSwitches( ToggleStyle? style, StyleBuilder? styleBuilder, CustomStyleBuilder? customStyleBuilder, + bool? iconsTappable, }) => AnimatedToggleSwitch.sizeByHeight( current: current, @@ -294,6 +306,7 @@ void testAllSwitches( style: style ?? const ToggleStyle(), styleBuilder: styleBuilder, customStyleBuilder: customStyleBuilder, + iconsTappable: iconsTappable ?? true, ))); testWidgets( '$description (AnimatedToggleSwitch.custom)', @@ -311,6 +324,7 @@ void testAllSwitches( ToggleStyle? style, StyleBuilder? styleBuilder, CustomStyleBuilder? customStyleBuilder, + bool? iconsTappable, }) => AnimatedToggleSwitch.custom( current: current, @@ -328,6 +342,7 @@ void testAllSwitches( style: style ?? const ToggleStyle(), styleBuilder: styleBuilder, customStyleBuilder: customStyleBuilder, + iconsTappable: iconsTappable ?? true, ))); testWidgets( '$description (AnimatedToggleSwitch.customByHeight)', @@ -345,6 +360,7 @@ void testAllSwitches( ToggleStyle? style, StyleBuilder? styleBuilder, CustomStyleBuilder? customStyleBuilder, + bool? iconsTappable, }) => AnimatedToggleSwitch.customByHeight( current: current, @@ -362,5 +378,6 @@ void testAllSwitches( style: style ?? const ToggleStyle(), styleBuilder: styleBuilder, customStyleBuilder: customStyleBuilder, + iconsTappable: iconsTappable ?? true, ))); } From e0535507c12dddb415049e814266829158a7cd3e Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Fri, 28 Jul 2023 15:09:24 +0200 Subject: [PATCH 22/36] deletes unnecessary file --- lib/animated_toggle_switch.dart | 1 - lib/src/utils.dart | 4 ---- lib/src/widgets/custom_animated_toggle_switch.dart | 2 ++ 3 files changed, 2 insertions(+), 5 deletions(-) delete mode 100644 lib/src/utils.dart diff --git a/lib/animated_toggle_switch.dart b/lib/animated_toggle_switch.dart index 4abfee2..7012f0a 100644 --- a/lib/animated_toggle_switch.dart +++ b/lib/animated_toggle_switch.dart @@ -8,7 +8,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; part 'src/properties.dart'; -part 'src/utils.dart'; part 'src/widgets/animated_toggle_switch.dart'; part 'src/widgets/custom_animated_toggle_switch.dart'; part 'src/widgets/drag_region.dart'; diff --git a/lib/src/utils.dart b/lib/src/utils.dart deleted file mode 100644 index daaa9a4..0000000 --- a/lib/src/utils.dart +++ /dev/null @@ -1,4 +0,0 @@ -// coverage:ignore-file -part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; - -enum ToggleMode { animating, dragged, none } diff --git a/lib/src/widgets/custom_animated_toggle_switch.dart b/lib/src/widgets/custom_animated_toggle_switch.dart index 3d2eb62..d5aa09b 100644 --- a/lib/src/widgets/custom_animated_toggle_switch.dart +++ b/lib/src/widgets/custom_animated_toggle_switch.dart @@ -24,6 +24,8 @@ typedef CustomSeparatorBuilder = Widget Function(BuildContext context, typedef IndicatorAppearingBuilder = Widget Function( BuildContext context, double value, Widget indicator); +enum ToggleMode { animating, dragged, none } + enum FittingMode { none, preventHorizontalOverlapping } // global parameter default values From f706b9f0e75951f7e91ab046416458ebe9f97d33 Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Fri, 28 Jul 2023 15:14:57 +0200 Subject: [PATCH 23/36] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dce731e..cdaf79c 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ AnimatedToggleSwitch.size( }, style: const ToggleStyle(borderColor: Colors.transparent), borderWidth: 0.0, - colorBuilder: (i) => SwitchColors(indicatorColor: i.isEven == true ? Colors.amber : Colors.red), +styleBuilder: (i) => ToggleStyle(indicatorColor: i.isEven == true ? Colors.amber : Colors.red), onChanged: (i) => setState(() => value = i), ) ``` @@ -188,7 +188,7 @@ AnimatedToggleSwitch.size( borderRadius: BorderRadius.circular(8.0), indicatorBorderRadius: BorderRadius.zero, ), - colorBuilder: (i) => SwitchColors(indicatorColor: i.isEven == true ? Colors.green : Colors.tealAccent), + styleBuilder: (i) => ToggleStyle(indicatorColor: i.isEven == true ? Colors.green : Colors.tealAccent), onChanged: (i) => setState(() => value = i), ) ``` From d21cad541a88b5f23cd5940e0611589121aa319d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 15:39:50 +0200 Subject: [PATCH 24/36] moves parameters to style --- CHANGELOG.md | 12 +- example/lib/main.dart | 108 ++++++++------- lib/src/widgets/animated_toggle_switch.dart | 140 +++++++++----------- 3 files changed, 133 insertions(+), 127 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7be684..89d96b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,17 @@ - adds `separatorBuilder`, `customSeparatorBuilder`, `style` and `styleAnimationType` to `AnimatedToggleSwitch` - adds `separatorBuilder` to `CustomAnimatedToggleSwitch` - fixes initial loading -- BREAKING: moves `innerColor`, `innerGradient`, `borderColor`, `indicatorColor`, `borderRadius` and `indicatorBorderColor` to `style` +- BREAKING: moves many parameters to `style`: + - `innerColor` (renamed to `backgroundColor`) + - `innerGradient` (renamed to `backgroundGradient`) + - `borderColor` + - `indicatorColor` + - `borderRadius` + - `indicatorBorderColor` + - `indicatorBorder` + - `indicatorBorder` + - `indicatorBoxShadow` + - `boxShadow` - BREAKING: merges `borderColorBuilder` with `styleBuilder` - BREAKING: `indicatorAnimationType` handles `ToggleStyle.indicatorColor` and `ToggleStyle.indicatorBorderRadius` now - BREAKING: renames `foregroundBoxShadow` to `indicatorBoxShadow` diff --git a/example/lib/main.dart b/example/lib/main.dart index 459fcaf..381310f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -61,17 +61,19 @@ class _MyHomePageState extends State { first: false, second: true, dif: 50.0, - style: const ToggleStyle(borderColor: Colors.transparent), + style: const ToggleStyle( + borderColor: Colors.transparent, + boxShadow: const [ + BoxShadow( + color: Colors.black26, + spreadRadius: 1, + blurRadius: 2, + offset: Offset(0, 1.5), + ), + ], + ), borderWidth: 5.0, height: 55, - boxShadow: const [ - BoxShadow( - color: Colors.black26, - spreadRadius: 1, - blurRadius: 2, - offset: Offset(0, 1.5), - ), - ], onChanged: (b) => setState(() => positive = b), styleBuilder: (b) => ToggleStyle(indicatorColor: b ? Colors.red : Colors.green), @@ -88,17 +90,19 @@ class _MyHomePageState extends State { first: false, second: true, dif: 50.0, - style: const ToggleStyle(borderColor: Colors.transparent), + style: const ToggleStyle( + borderColor: Colors.transparent, + boxShadow: const [ + BoxShadow( + color: Colors.black26, + spreadRadius: 1, + blurRadius: 2, + offset: Offset(0, 1.5), + ), + ], + ), borderWidth: 5.0, height: 55, - boxShadow: const [ - BoxShadow( - color: Colors.black26, - spreadRadius: 1, - blurRadius: 2, - offset: Offset(0, 1.5), - ), - ], onChanged: (b) => setState(() => positive = b), styleBuilder: (b) => ToggleStyle( backgroundColor: b ? Colors.white : Colors.black, @@ -142,19 +146,19 @@ class _MyHomePageState extends State { borderColor: Colors.transparent, backgroundColor: Colors.black, borderRadius: BorderRadius.all(Radius.circular(4.0)), + boxShadow: const [ + BoxShadow( + color: Colors.purple, + spreadRadius: 1, + blurRadius: 1, + offset: Offset(0, 0.5), + ), + ], ), borderWidth: 10.0, height: 50, loadingIconBuilder: (context, global) => const CupertinoActivityIndicator(color: Colors.white), - boxShadow: const [ - BoxShadow( - color: Colors.purple, - spreadRadius: 1, - blurRadius: 1, - offset: Offset(0, 0.5), - ), - ], onChanged: (b) { setState(() => positive = b); return Future.delayed(Duration(seconds: 2)); @@ -314,23 +318,25 @@ class _MyHomePageState extends State { .then((_) => setState(() => loading = false)); }, iconBuilder: rollingIconBuilder, - style: ToggleStyle(borderColor: Colors.transparent), - indicatorBoxShadow: const [ - BoxShadow( - color: Colors.black26, - spreadRadius: 1, - blurRadius: 2, - offset: Offset(0, 1.5), - ) - ], - boxShadow: const [ - BoxShadow( - color: Colors.black26, - spreadRadius: 1, - blurRadius: 2, - offset: Offset(0, 1.5), - ) - ], + style: ToggleStyle( + borderColor: Colors.transparent, + indicatorBoxShadow: const [ + BoxShadow( + color: Colors.black26, + spreadRadius: 1, + blurRadius: 2, + offset: Offset(0, 1.5), + ) + ], + boxShadow: const [ + BoxShadow( + color: Colors.black26, + spreadRadius: 1, + blurRadius: 2, + offset: Offset(0, 1.5), + ) + ], + ), ), SizedBox(height: 16.0), IconTheme.merge( @@ -342,6 +348,14 @@ class _MyHomePageState extends State { style: ToggleStyle( indicatorColor: Colors.white, borderColor: Colors.transparent, + boxShadow: const [ + BoxShadow( + color: Colors.black26, + spreadRadius: 1, + blurRadius: 2, + offset: Offset(0, 1.5), + ) + ], ), iconBuilder: coloredRollingIconBuilder, borderWidth: 3.0, @@ -351,14 +365,6 @@ class _MyHomePageState extends State { borderRadius: BorderRadius.circular(value * 10.0), indicatorBorderRadius: BorderRadius.circular(value * 10.0), ), - boxShadow: const [ - BoxShadow( - color: Colors.black26, - spreadRadius: 1, - blurRadius: 2, - offset: Offset(0, 1.5), - ) - ], ), ), SizedBox(height: 16.0), diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart index d715612..9dfc403 100644 --- a/lib/src/widgets/animated_toggle_switch.dart +++ b/lib/src/widgets/animated_toggle_switch.dart @@ -58,18 +58,21 @@ class ToggleStyle { final Color? borderColor; /// [BorderRadius] of the switch. - /// - /// If set, this value overwrites [AnimatedToggleSwitch.borderRadius]. final BorderRadiusGeometry? borderRadius; /// [BorderRadius] of the indicator. - /// - /// If set, this value overwrites [AnimatedToggleSwitch.indicatorBorderRadius]. final BorderRadiusGeometry? indicatorBorderRadius; + /// [BorderRadius] of the indicator. + final BoxBorder? indicatorBorder; + + /// Shadow for the indicator [Container]. + final List? indicatorBoxShadow; + + /// Shadow for the [Container] in the background. + final List? boxShadow; + /// Default constructor for [ToggleStyle]. - /// - /// If you want to adapt the [BorderRadius], you should use [SwitchColors.withBorder] instead. const ToggleStyle({ this.indicatorColor, this.backgroundColor, @@ -77,6 +80,9 @@ class ToggleStyle { this.borderColor, this.borderRadius, this.indicatorBorderRadius, + this.indicatorBorder, + this.indicatorBoxShadow, + this.boxShadow, }); /// Private constructor for setting all possible parameters. @@ -87,6 +93,9 @@ class ToggleStyle { required this.borderColor, required this.borderRadius, required this.indicatorBorderRadius, + required this.indicatorBorder, + required this.indicatorBoxShadow, + required this.boxShadow, }); ToggleStyle _merge(ToggleStyle? other) => other == null @@ -102,28 +111,46 @@ class ToggleStyle { indicatorBorderRadius ?? other.borderRadius ?? borderRadius, + indicatorBorder: other.indicatorBorder ?? indicatorBorder, + indicatorBoxShadow: other.indicatorBoxShadow ?? indicatorBoxShadow, + boxShadow: other.boxShadow ?? boxShadow, ); - static ToggleStyle _lerp(ToggleStyle style1, ToggleStyle colors2, double t) => + static ToggleStyle _lerp(ToggleStyle style1, ToggleStyle style2, double t) => ToggleStyle._( indicatorColor: - Color.lerp(style1.indicatorColor, colors2.indicatorColor, t), + Color.lerp(style1.indicatorColor, style2.indicatorColor, t), backgroundColor: - Color.lerp(style1.backgroundColor, colors2.backgroundColor, t), + Color.lerp(style1.backgroundColor, style2.backgroundColor, t), backgroundGradient: Gradient.lerp( style1.backgroundGradient ?? style1.backgroundColor?.toGradient(), - colors2.backgroundGradient ?? colors2.backgroundColor?.toGradient(), + style2.backgroundGradient ?? style2.backgroundColor?.toGradient(), t, ), - borderColor: Color.lerp(style1.borderColor, colors2.borderColor, t), + borderColor: Color.lerp(style1.borderColor, style2.borderColor, t), borderRadius: BorderRadiusGeometry.lerp( style1.borderRadius, - colors2.borderRadius, + style2.borderRadius, t, ), indicatorBorderRadius: BorderRadiusGeometry.lerp( style1.indicatorBorderRadius ?? style1.borderRadius, - colors2.indicatorBorderRadius ?? colors2.borderRadius, + style2.indicatorBorderRadius ?? style2.borderRadius, + t, + ), + indicatorBorder: BoxBorder.lerp( + style1.indicatorBorder, + style2.indicatorBorder, + t, + ), + indicatorBoxShadow: BoxShadow.lerpList( + style1.indicatorBoxShadow, + style2.indicatorBoxShadow, + t, + ), + boxShadow: BoxShadow.lerpList( + style1.boxShadow, + style2.boxShadow, t, ), ); @@ -146,8 +173,7 @@ enum ForegroundIndicatorTransitionType { /// Fades between the different icons. fading, - /// Fades between the different icons and shows a rolling animation - /// additionally. + /// Fades between the different icons and shows a rolling animation additionally. rolling, } @@ -272,14 +298,6 @@ class AnimatedToggleSwitch extends StatelessWidget { /// Change this only if you don't want the switch to adjust when the constraints are too small. final FittingMode fittingMode; - final BoxBorder? indicatorBorder; - - /// Shadow for the indicator [Container]. - final List indicatorBoxShadow; - - /// Shadow for the [Container] in the background. - final List boxShadow; - /// Indicates if [onChanged] is called when an icon is tapped. /// If [false] the user can change the value only by dragging the indicator. final bool iconsTappable; @@ -374,9 +392,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, - this.indicatorBorder, - this.indicatorBoxShadow = const [], - this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, this.iconsTappable = true, @@ -430,9 +445,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, - this.indicatorBorder, - this.indicatorBoxShadow = const [], - this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, this.iconsTappable = true, @@ -489,9 +501,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, - this.indicatorBorder, - this.indicatorBoxShadow = const [], - this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, this.iconsTappable = true, @@ -577,9 +586,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, - this.indicatorBorder, - this.indicatorBoxShadow = const [], - this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, this.iconsTappable = true, @@ -637,9 +643,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, - this.indicatorBorder, - this.indicatorBoxShadow = const [], - this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, this.iconsTappable = true, @@ -713,9 +716,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAnimationType = AnimationType.onHover, this.onTap, this.fittingMode = FittingMode.preventHorizontalOverlapping, - this.indicatorBorder, - this.indicatorBoxShadow = const [], - this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, this.iconsTappable = true, @@ -864,9 +864,6 @@ class AnimatedToggleSwitch extends StatelessWidget { this.indicatorAnimationType = AnimationType.onHover, this.fittingMode = FittingMode.preventHorizontalOverlapping, Function()? onTap, - this.indicatorBorder, - this.indicatorBoxShadow = const [], - this.boxShadow = const [], this.minTouchTargetSize = 48.0, this.textDirection, this.defaultCursor = SystemMouseCursors.click, @@ -1001,13 +998,16 @@ class AnimatedToggleSwitch extends StatelessWidget { ThemeData theme = Theme.of(context); BorderRadiusGeometry defaultBorderRadius = BorderRadius.circular(height / 2); - final colors = ToggleStyle._( + final style = ToggleStyle._( indicatorColor: theme.colorScheme.secondary, backgroundColor: theme.scaffoldBackgroundColor, backgroundGradient: null, borderColor: theme.colorScheme.secondary, borderRadius: defaultBorderRadius, indicatorBorderRadius: defaultBorderRadius, + indicatorBorder: null, + indicatorBoxShadow: null, + boxShadow: null, )._merge(this.style); return CustomAnimatedToggleSwitch( @@ -1044,11 +1044,11 @@ class AnimatedToggleSwitch extends StatelessWidget { backgroundIndicatorBuilder: foregroundIndicatorIconBuilder != null ? null : (context, properties) => - _indicatorBuilder(context, properties, colors), + _indicatorBuilder(context, properties, style), foregroundIndicatorBuilder: foregroundIndicatorIconBuilder == null ? null : (context, properties) => - _indicatorBuilder(context, properties, colors), + _indicatorBuilder(context, properties, style), iconBuilder: (context, local, global) => _animatedOpacityIcon( _animatedSizeIcon(context, local, global), local.value == current), padding: EdgeInsets.all(borderWidth), @@ -1057,14 +1057,14 @@ class AnimatedToggleSwitch extends StatelessWidget { return _animationTypeBuilder( context, styleAnimationType, - (local) => colors._merge(_styleBuilder(context, local, global)), + (local) => style._merge(_styleBuilder(context, local, global)), ToggleStyle._lerp, - (colors) => DecoratedBox( + (style) => DecoratedBox( decoration: BoxDecoration( - color: colors.backgroundColor, - gradient: colors.backgroundGradient, - borderRadius: colors.borderRadius, - boxShadow: boxShadow, + color: style.backgroundColor, + gradient: style.backgroundGradient, + borderRadius: style.borderRadius, + boxShadow: style.boxShadow, ), child: DecoratedBox( position: DecorationPosition.foreground, @@ -1072,13 +1072,13 @@ class AnimatedToggleSwitch extends StatelessWidget { border: borderWidth <= 0.0 ? null : Border.all( - color: colors.borderColor!, + color: style.borderColor!, width: borderWidth, ), - borderRadius: colors.borderRadius, + borderRadius: style.borderRadius, ), child: ClipRRect( - borderRadius: colors.borderRadius, + borderRadius: style.borderRadius, child: child, ), ), @@ -1121,20 +1121,14 @@ class AnimatedToggleSwitch extends StatelessWidget { } Widget _indicatorBuilder(BuildContext context, - DetailedGlobalToggleProperties properties, ToggleStyle colors) { + DetailedGlobalToggleProperties properties, ToggleStyle style) { final child = foregroundIndicatorIconBuilder?.call(context, properties); return _animationTypeBuilder( context, indicatorAnimationType, - (local) => colors._merge(_styleBuilder(context, local, properties)), + (local) => style._merge(_styleBuilder(context, local, properties)), ToggleStyle._lerp, - (colors) => _customIndicatorBuilder( - context, - colors.indicatorColor!, - colors.indicatorBorderRadius!, - child, - properties, - ), + (style) => _customIndicatorBuilder(context, style, child, properties), properties, ); } @@ -1197,19 +1191,15 @@ class AnimatedToggleSwitch extends StatelessWidget { ); } - Widget _customIndicatorBuilder( - BuildContext context, - Color color, - BorderRadiusGeometry borderRadius, - Widget? child, - DetailedGlobalToggleProperties global) { + Widget _customIndicatorBuilder(BuildContext context, ToggleStyle style, + Widget? child, DetailedGlobalToggleProperties global) { final loadingValue = global.loadingAnimationValue; return DecoratedBox( decoration: BoxDecoration( - color: color, - borderRadius: borderRadius, - border: indicatorBorder, - boxShadow: indicatorBoxShadow, + color: style.indicatorColor, + borderRadius: style.indicatorBorderRadius, + border: style.indicatorBorder, + boxShadow: style.indicatorBoxShadow, ), child: Center( child: Stack( From 1ad0f586ec0bc838791052a6e93515d79c7730d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 16:04:21 +0200 Subject: [PATCH 25/36] fixes example --- example/lib/main.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 381310f..96c6206 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -245,9 +245,9 @@ class _MyHomePageState extends State { colors: [green, Colors.red[800]!], stops: [ global.position - - (1 - 2 * (global.position - 0.5).abs()) * 0.2, + (1 - 2 * max(0, global.position - 0.5)) * 0.2, global.position + - (1 - 2 * (global.position - 0.5).abs()) * 0.2, + max(0, 2 * (global.position - 0.5)) * 0.2, ], )); }, From b60a236e1f0495c327be08d970142c7fe08c6fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 16:27:58 +0200 Subject: [PATCH 26/36] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cdaf79c..ac17564 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,9 @@ For a slider with a similar look, you can check out [action_slider](https://pub. ![animated_toggle_switch_example_borderradius_builder](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/985492df-82a4-4225-a0ad-500970ad692d) Switch inspired by [lite_rolling_switch](https://pub.dev/packages/lite_rolling_switch) (made with `AnimatedToggleSwitch.dual()`) -![animated_toggle_switch_example_lite_rolling_switch_highres](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/357c31b5-710f-47e9-9132-07779371c1a6) +![animated_toggle_switch_example_lite_rolling](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/f1bec7cd-4885-4e8d-ac13-176d6b46245a) +![animated_toggle_switch_example_lite_rolling_gradient](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/6cd5d2d3-b4bd-4020-8568-354c71221e40) + Switch inspired by [toggle_switch](https://pub.dev/packages/toggle_switch) (made with `AnimatedToggleSwitch.size()`) ![animated_toggle_switch_example_toggle_switch](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/4812efdc-fe9a-4c34-808a-0983de65d2a4) @@ -39,7 +41,8 @@ Switch inspired by [toggle_switch](https://pub.dev/packages/toggle_switch) (made ![animated_toggle_switch_example_1](https://user-images.githubusercontent.com/43761463/161432579-9fe81c57-6463-45c3-a48f-75db666a3a22.gif) ![animated_toggle_switch_example_2](https://user-images.githubusercontent.com/43761463/161432589-d76f61f6-cb97-42e2-b1fd-8c5203a965fa.gif) ![animated_toggle_switch_example_gradient](https://user-images.githubusercontent.com/43761463/209117203-90a41ddc-db1c-41be-8375-5304317d1352.gif) -![animated_toggle_switch_example_borderradius_builder_2](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/e9a6328e-fc6a-4080-9868-1f0eaf60f6db) +![animated_toggle_switch_example_borderradius_builder_2](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/e9a6328e-fc6a-4080-9868-1f0eaf60f6db) +![animated_toggle_switch_example_rolling_separator](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/562fa54d-6a03-4099-a61b-0bc386d22adb) You can build any other switch with `CustomAnimatedToggleSwitch()` From 39e8be99ac35117f1cfffcf9cf33cb8d8d70e6a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 16:29:35 +0200 Subject: [PATCH 27/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac17564..5dc7598 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ For a slider with a similar look, you can check out [action_slider](https://pub. `AnimatedToggleSwitch.dual()` ![animated_toggle_switch_example_dual](https://user-images.githubusercontent.com/43761463/161432631-e6dd3d16-7b64-410b-a9fa-c956d3442598.gif) ![animated_toggle_switch_example_borderradius_builder](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/985492df-82a4-4225-a0ad-500970ad692d) +![animated_toggle_switch_example_lite_rolling_gradient](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/6cd5d2d3-b4bd-4020-8568-354c71221e40) Switch inspired by [lite_rolling_switch](https://pub.dev/packages/lite_rolling_switch) (made with `AnimatedToggleSwitch.dual()`) ![animated_toggle_switch_example_lite_rolling](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/f1bec7cd-4885-4e8d-ac13-176d6b46245a) -![animated_toggle_switch_example_lite_rolling_gradient](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/6cd5d2d3-b4bd-4020-8568-354c71221e40) Switch inspired by [toggle_switch](https://pub.dev/packages/toggle_switch) (made with `AnimatedToggleSwitch.size()`) From e2a50c2e3432148f2502491098ffb336750ab69f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 16:30:32 +0200 Subject: [PATCH 28/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5dc7598..0b3da6b 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ If you need additional parameters, you can use `customStyleBuilder`. AnimatedToggleSwitch.rolling( ... style: ToggleStyle(backgroundColor: Colors.red), // backgroundColor is set independently of the current selection - styleBuilder: ToggleStyle(indicatorColor: (value) => value.isEven ? Colors.yellow : Colors.green)), // indicatorColor changes and animates its value with the selection + styleBuilder: (value) => ToggleStyle(indicatorColor: value.isEven ? Colors.yellow : Colors.green)), // indicatorColor changes and animates its value with the selection ... ) ``` From f46ecd02340a05964e26aa4fe5d45eb95b10a506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 16:34:21 +0200 Subject: [PATCH 29/36] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0b3da6b..239b209 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,10 @@ Most builder arguments of `AnimatedToggleSwitch` have a standard and a custom ve For a slider with a similar look, you can check out [action_slider](https://pub.dev/packages/action_slider). -### Example Usage +## Example Usage ![usage](https://user-images.githubusercontent.com/43761463/114942384-c1200d00-9e44-11eb-9904-3cb1d7296da4.gif) -### Examples +## Examples `AnimatedToggleSwitch.dual()` ![animated_toggle_switch_example_dual](https://user-images.githubusercontent.com/43761463/161432631-e6dd3d16-7b64-410b-a9fa-c956d3442598.gif) ![animated_toggle_switch_example_borderradius_builder](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/985492df-82a4-4225-a0ad-500970ad692d) From e2df123a54eed43a9ce7b395a55afad5d70967c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 16:42:25 +0200 Subject: [PATCH 30/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 239b209..512c363 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Fully customizable, draggable and animated switch with multiple choices and [smo [Switches without an (initial) selection](#simple-rolling-animation-with-nullable-selection) are also possible. Most builder arguments of `AnimatedToggleSwitch` have a standard and a custom version. This ensures that you can get started easily and still customize a lot if necessary. -For a slider with a similar look, you can check out [action_slider](https://pub.dev/packages/action_slider). +For a slider with a similar look you can check out [action_slider](https://pub.dev/packages/action_slider). ## Example Usage ![usage](https://user-images.githubusercontent.com/43761463/114942384-c1200d00-9e44-11eb-9904-3cb1d7296da4.gif) From 966da54bc1dc5f953e2a485cef51ffd18d612edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 16:45:40 +0200 Subject: [PATCH 31/36] fixes documentation --- lib/src/widgets/animated_toggle_switch.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart index 9dfc403..2140462 100644 --- a/lib/src/widgets/animated_toggle_switch.dart +++ b/lib/src/widgets/animated_toggle_switch.dart @@ -264,7 +264,8 @@ class AnimatedToggleSwitch extends StatelessWidget { /// The [AnimationType] for [styleBuilder]. /// - /// The [AnimationType] for [ToggleStyle.indicatorColor] and [ToggleStyle.indicatorBorderRadius] + /// The [AnimationType] for [ToggleStyle.indicatorColor], [ToggleStyle.indicatorBorderRadius], + /// [ToggleStyle.indicatorBorder] and [ToggleStyle.indicatorBoxShadow]. /// is managed separately with [indicatorAnimationType]. final AnimationType styleAnimationType; From 046cb3fab71b2dc861bd26682e42c6091492439b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 16:47:10 +0200 Subject: [PATCH 32/36] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89d96b5..cbb7531 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ - `indicatorBoxShadow` - `boxShadow` - BREAKING: merges `borderColorBuilder` with `styleBuilder` -- BREAKING: `indicatorAnimationType` handles `ToggleStyle.indicatorColor` and `ToggleStyle.indicatorBorderRadius` now +- BREAKING: `indicatorAnimationType` handles `ToggleStyle.indicatorColor`, `ToggleStyle.indicatorBorderRadius`, `ToggleStyle.indicatorBorder` and `ToggleStyle.indicatorBoxShadow` now - BREAKING: renames `foregroundBoxShadow` to `indicatorBoxShadow` ## 0.7.0 (2023-06-19) From c35b925608da2a965ec0d6c64451db765ef55c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 16:54:01 +0200 Subject: [PATCH 33/36] minor optimizations --- CHANGELOG.md | 3 +-- lib/src/widgets/animated_toggle_switch.dart | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbb7531..9096f7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,10 @@ - `indicatorBorderColor` - `indicatorBorder` - `indicatorBorder` - - `indicatorBoxShadow` + - `foregroundBoxShadow` (renamed to `indicatorBoxShadow`) - `boxShadow` - BREAKING: merges `borderColorBuilder` with `styleBuilder` - BREAKING: `indicatorAnimationType` handles `ToggleStyle.indicatorColor`, `ToggleStyle.indicatorBorderRadius`, `ToggleStyle.indicatorBorder` and `ToggleStyle.indicatorBoxShadow` now -- BREAKING: renames `foregroundBoxShadow` to `indicatorBoxShadow` ## 0.7.0 (2023-06-19) diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart index 2140462..2a64328 100644 --- a/lib/src/widgets/animated_toggle_switch.dart +++ b/lib/src/widgets/animated_toggle_switch.dart @@ -34,9 +34,9 @@ typedef IconBuilder = Widget Function( DetailedGlobalToggleProperties global, ); -typedef StyleBuilder = ToggleStyle? Function(T value); +typedef StyleBuilder = ToggleStyle Function(T value); -typedef CustomStyleBuilder = ToggleStyle? Function( +typedef CustomStyleBuilder = ToggleStyle Function( BuildContext context, StyledToggleProperties local, GlobalToggleProperties global, From c45ba7de5e4f77ab6a405c17850a38b8a9a3029b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 17:16:49 +0200 Subject: [PATCH 34/36] minor border radius fix --- lib/src/widgets/animated_toggle_switch.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart index 2a64328..94bfc04 100644 --- a/lib/src/widgets/animated_toggle_switch.dart +++ b/lib/src/widgets/animated_toggle_switch.dart @@ -108,8 +108,8 @@ class ToggleStyle { borderColor: other.borderColor ?? borderColor, borderRadius: other.borderRadius ?? borderRadius, indicatorBorderRadius: other.indicatorBorderRadius ?? - indicatorBorderRadius ?? other.borderRadius ?? + indicatorBorderRadius ?? borderRadius, indicatorBorder: other.indicatorBorder ?? indicatorBorder, indicatorBoxShadow: other.indicatorBoxShadow ?? indicatorBoxShadow, @@ -1005,7 +1005,7 @@ class AnimatedToggleSwitch extends StatelessWidget { backgroundGradient: null, borderColor: theme.colorScheme.secondary, borderRadius: defaultBorderRadius, - indicatorBorderRadius: defaultBorderRadius, + indicatorBorderRadius: null, indicatorBorder: null, indicatorBoxShadow: null, boxShadow: null, From 622c1504367c23b071269d4f4a1f3284db4dcdc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Fri, 28 Jul 2023 17:18:15 +0200 Subject: [PATCH 35/36] minor border radius fix --- lib/src/widgets/animated_toggle_switch.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart index 94bfc04..635db47 100644 --- a/lib/src/widgets/animated_toggle_switch.dart +++ b/lib/src/widgets/animated_toggle_switch.dart @@ -61,6 +61,8 @@ class ToggleStyle { final BorderRadiusGeometry? borderRadius; /// [BorderRadius] of the indicator. + /// + /// Defaults to [borderRadius]. final BorderRadiusGeometry? indicatorBorderRadius; /// [BorderRadius] of the indicator. @@ -1005,7 +1007,7 @@ class AnimatedToggleSwitch extends StatelessWidget { backgroundGradient: null, borderColor: theme.colorScheme.secondary, borderRadius: defaultBorderRadius, - indicatorBorderRadius: null, + indicatorBorderRadius: defaultBorderRadius, indicatorBorder: null, indicatorBoxShadow: null, boxShadow: null, From daae90fd80540160f738788cb6bdfeb1f3802bbe Mon Sep 17 00:00:00 2001 From: MaddinMade Date: Sat, 29 Jul 2023 08:37:04 +0200 Subject: [PATCH 36/36] changes version to 0.8.0-beta.0 --- CHANGELOG.md | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9096f7e..56198ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.8.0 +## 0.8.0-beta.0 - adds tests for all `AnimatedToggleSwitch` constructors - adds `separatorBuilder`, `customSeparatorBuilder`, `style` and `styleAnimationType` to `AnimatedToggleSwitch` - adds `separatorBuilder` to `CustomAnimatedToggleSwitch` diff --git a/pubspec.yaml b/pubspec.yaml index 06da78e..6626207 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: animated_toggle_switch description: Fully customizable, draggable and animated switch with multiple choices and smooth loading animation. It has prebuilt constructors for rolling and size animations. -version: 0.8.0 +version: 0.8.0-beta.0 repository: https://github.com/SplashByte/animated_toggle_switch issue_tracker: https://github.com/SplashByte/animated_toggle_switch/issues